بناء مشروع BEM باستخدام Webpack

ستركز المقالة على تجميع مشاريع BEM باستخدام حزمة Webpack. سأعرض مثالًا واحدًا على التهيئة دون تحميل القراء بكيانات غير ضرورية.


المواد مناسبة لأولئك الذين بدأوا للتو التعرف على BEM. أولاً ، سنتطرق إلى الجوانب النظرية للمنهجية ، وفي قسم "الممارسة" ، سأبيّن كيف يمكن تطبيقها.


قليلا من الناحية النظرية


إذا كانت هذه هي المرة الأولى التي تسمع فيها عن BEM وترغب في التعرف عليها بنفسك ، فاحتفظ بالوثائق .


BEM هي منهجية تُستخدم لتنظيم المشاريع على أي نطاق. قامت Yandex بتطويره واستخدمته في البداية فقط في عمل خدماتها ، ولكن تم نشره لاحقًا في المجال العام.


BEM لتقف على "Block ، Element ، Modifier".


Block عبارة عن كيان له بنية مستقلة يمكن إعادة استخدامها. كتلة قد تحتوي على عناصرها الخاصة.


عنصر هو جزء لا يتجزأ من كتلة. يمكن استخدام عنصر فقط داخل الكتلة الأصل.


المعدل هو كيان يغير عرض أو حالة أو سلوك كتلة.


هذه المكونات تكمن وراء المنهجية. أنها توفر الجمال والفصل رمز مريحة. مزيد من التفاصيل حول أجهزتهم مكتوبة في الوثائق .


وثائق BEM مكتوبة على نطاق واسع. ومع ذلك ، هناك واحد "لكن": عتبة عالية لإدخال المواد. إذا تمكنت من معرفة أساسيات التخطيط من خلال قراءة صفحة واحدة من الوثائق ، فإن مسألة تجميع المشروع تكون أكثر تعقيدًا.


لماذا كان حول تجميع المشروع؟ عند العمل في مشروع واسع النطاق ، يواجه الجميع مشكلة تنظيم التعليمات البرمجية. من غير المناسب تخزين كل رمز مشروع كبير في ملف واحد. تقسيم الشفرة إلى ملفات متعددة ، ثم تجميعها يدويًا ليس طريقة جيدة أيضًا. لحل هذه المشكلة ، يتم استخدام أدوات التجميع أو أدوات التجميع التي تعمل تلقائيًا على تحويل رمز مصدر المشروع إلى رمز جاهز لإرساله إلى الإنتاج.


اسمحوا لي أن أذكرك: من المفترض كذلك أن القراء لديهم مهارات Webpack الأساسية. إذا لم تكن قد عملت معه من قبل ، فإنني أوصيك أولاً بالتعرف على هذه الأداة.


تقدم وثائق BEM توصيات لتجميع المشاريع. يتم تقديم خيارين فقط كأمثلة: التجميع باستخدام ENB و Gulp.


ENB هي أداة مساعدة مصممة خصيصًا لبناء مشاريع BEM. هي قادرة على العمل مع كيانات BEM خارج الصندوق. لكن ألقِ نظرة على الكود. للوهلة الأولى ، يمكنه إلغاء تنشيط مطور غير مستعد:


make.js
const techs = { // essential fileProvider: require('enb/techs/file-provider'), fileMerge: require('enb/techs/file-merge'), // optimization borschik: require('enb-borschik/techs/borschik'), // css postcss: require('enb-postcss/techs/enb-postcss'), postcssPlugins: [ require('postcss-import')(), require('postcss-each'), require('postcss-for'), require('postcss-simple-vars')(), require('postcss-calc')(), require('postcss-nested'), require('rebem-css'), require('postcss-url')({ url: 'rebase' }), require('autoprefixer')(), require('postcss-reporter')() ], // js browserJs: require('enb-js/techs/browser-js'), // bemtree // bemtree: require('enb-bemxjst/techs/bemtree'), // bemhtml bemhtml: require('enb-bemxjst/techs/bemhtml'), bemjsonToHtml: require('enb-bemxjst/techs/bemjson-to-html') }, enbBemTechs = require('enb-bem-techs'), levels = [ { path: 'node_modules/bem-core/common.blocks', check: false }, { path: 'node_modules/bem-core/desktop.blocks', check: false }, { path: 'node_modules/bem-components/common.blocks', check: false }, { path: 'node_modules/bem-components/desktop.blocks', check: false }, { path: 'node_modules/bem-components/design/common.blocks', check: false }, { path: 'node_modules/bem-components/design/desktop.blocks', check: false }, 'common.blocks', 'desktop.blocks' ]; module.exports = function(config) { const isProd = process.env.YENV === 'production'; config.nodes('*.bundles/*', function(nodeConfig) { nodeConfig.addTechs([ // essential [enbBemTechs.levels, { levels: levels }], [techs.fileProvider, { target: '?.bemjson.js' }], [enbBemTechs.bemjsonToBemdecl], [enbBemTechs.deps], [enbBemTechs.files], // css [techs.postcss, { target: '?.css', oneOfSourceSuffixes: ['post.css', 'css'], plugins: techs.postcssPlugins }], // bemtree // [techs.bemtree, { sourceSuffixes: ['bemtree', 'bemtree.js'] }], // bemhtml [techs.bemhtml, { sourceSuffixes: ['bemhtml', 'bemhtml.js'], forceBaseTemplates: true, engineOptions : { elemJsInstances : true } }], // html [techs.bemjsonToHtml], // client bemhtml [enbBemTechs.depsByTechToBemdecl, { target: '?.bemhtml.bemdecl.js', sourceTech: 'js', destTech: 'bemhtml' }], [enbBemTechs.deps, { target: '?.bemhtml.deps.js', bemdeclFile: '?.bemhtml.bemdecl.js' }], [enbBemTechs.files, { depsFile: '?.bemhtml.deps.js', filesTarget: '?.bemhtml.files', dirsTarget: '?.bemhtml.dirs' }], [techs.bemhtml, { target: '?.browser.bemhtml.js', filesTarget: '?.bemhtml.files', sourceSuffixes: ['bemhtml', 'bemhtml.js'], engineOptions : { elemJsInstances : true } }], // js [techs.browserJs, { includeYM: true }], [techs.fileMerge, { target: '?.js', sources: ['?.browser.js', '?.browser.bemhtml.js'] }], // borschik [techs.borschik, { source: '?.js', target: '?.min.js', minify: isProd }], [techs.borschik, { source: '?.css', target: '?.min.css', minify: isProd }] ]); nodeConfig.addTargets([/* '?.bemtree.js', */ '?.html', '?.min.css', '?.min.js']); }); }; 

رمز من مستودع المشروع كعب الروتين .


من الواضح أن رمز التكوين ENB سيكون معقدًا لأولئك الذين بدأوا للتو في استخدام BEM.


تحتوي الوثائق على إعدادات جاهزة لهواة الجمع ، ويمكن استخدامها دون الخوض في تفاصيل التجميع. ولكن ماذا لو كنت ، مثلي ، ترغب في الحصول على صورة كاملة لما يحدث في المشروع أثناء البناء؟


تشرح وثائق BEM جيدًا عملية التجميع من الناحية النظرية ، ومع ذلك ، فهناك أمثلة عملية قليلة وهي ليست مناسبة دائمًا لفهم واضح للعملية. لحل هذه المشكلة ، سأحاول إنشاء مشروع BEM أولي باستخدام Webpack.


ممارسة


قبل ذلك ، ذكرت أن الفصل بين الكود وتنظيم التجميع يبسطان العمل مع المشروع. في المثال أدناه ، سنوفر فصل الكود باستخدام BEM وتجميعه باستخدام Webpack.


نريد الحصول على أبسط أشكال التهيئة ، يجب أن يكون منطق التجميع خطيًا وبديهيًا. دعنا نجمع صفحة مع كتلة BEM واحدة ، والتي سيكون لها تقنيتان: CSS و JS.


يمكنك كتابة تعليمات HTML البرمجية مع DIV واحد مع الفئة "block" وتوصيل جميع التقنيات الخاصة بها يدويًا. باستخدام تسمية فئة BEM وبنية الملفات المقابلة ، لا ننتهك مبادئ المنهجية.


حصلت على شجرة المشروع التالية:


 ├── desktop #   "desktop" │ └── block #  "block" │ ├── block.css # CSS-  "block" │ └── block.js # JS-  "block" ├── dist # ,      ├── pages # ,       JS- │ ├── index.html # ,     │ └── index.js #      index.html └── webpack.config.js # - Webpack 

يشير السطر الأول إلى مستوى تجاوز "سطح المكتب". في مصطلحات BEM ، تعد مستويات إعادة التعريف بمثابة أدلة تحتوي على تطبيقات الكتلة الخاصة بها. عند تجميع المشروع ، تندرج التطبيقات من جميع مستويات إعادة التعريف بترتيب معين في الحزمة النهائية.


على سبيل المثال ، لدينا مستوى لإعادة تعريف "سطح المكتب" يتم فيه تخزين تطبيقات الكتلة لأجهزة سطح المكتب. إذا كنا بحاجة إلى تكملة المشروع بتخطيط للأجهزة المحمولة ، فسيكون ذلك كافياً لإنشاء مستوى جديد من إعادة تعريف "الجوال" وملءه بتطبيقات جديدة لنفس الكتل. راحة هذا النهج هي أنه في مستوى جديد من إعادة التعريف ، لن نحتاج إلى تكرار الكود الموجود بالفعل في "سطح المكتب" ، لأنه سيتم الاتصال تلقائيًا.


هنا هو التكوين Webpack:


 // webpack.config.js //    const path = require('path'); const opy = require('copy-webpack-plugin'); module.exports = { //  entry  output -       entry: path.resolve(__dirname, "pages", "index.js"), output: { filename: 'index.js', path: path.join(__dirname, 'dist') }, module: { rules: [ //    CSS- { test: /\.css$/, loader: 'style-loader!css-loader' } ] }, plugins: [ new opy([ //  HTML-      { from: path.join(__dirname, 'pages'), test: /\.html$/, to: path.join(__dirname, "dist") } ]) ] } 

نحدد هنا /pages/index.js الملف /pages/index.js إدخال ، ونضيف برامج تحميل لأنماط CSS ونسخ / /pages/index.html إلى /dist/index.html .


index.html و
 <html> <body> <div class="block">Hello, World!</div> <script src="index.js"></script> </body> </html> 

block.css
 .block { color: red; font-size: 24px; text-align: center; } 

block.js
 document.getElementsByClassName('block')[0].innerHTML += " [This text is added by block.js!]" 

يستخدم المثال مستوى تجاوز واحد وكتلة واحدة. تتمثل المهمة في تجميع الصفحة بحيث يتم ربط التقنيات (css، js) من الكتلة الخاصة بنا بها.


لتوصيل التقنيات ، نستخدم require() :


 // index.js require('../desktop/block/block.js'); require('../desktop/block/block.css'); 

قم بتشغيل Webpack وشاهد ما يحدث. افتح index.html من المجلد ./dist :


لقطة شاشة للصفحة


تم تحميل أنماط البلوك ، وعملت جافا سكريبت بنجاح. الآن يمكننا بحق إضافة الحروف العزيزة "BEM" إلى مشروعنا.


بادئ ذي بدء ، تم إنشاء BEM للعمل مع المشاريع الكبيرة. دعونا نتخيل أن مصممنا حاول وعلى الصفحة الآن ليس كتلة واحدة ، بل مائة. باتباع السيناريو السابق ، سنقوم يدويًا بتوصيل تقنيات كل كتلة باستخدام require() . بمعنى ، سيظهر ما لا يقل عن مائة سطر إضافي من التعليمات البرمجية في index.js.


الأسطر الإضافية من التعليمات البرمجية التي كان يمكن تجنبها سيئة. رمز غير المستخدمة هو أسوأ. ماذا لو كان في صفحتنا سيكون هناك 10 فقط من القطع المتاحة ، أو 20 ، أو 53؟ سيكون لدى المطور عمل إضافي: سيتعين عليه التركيز على الكتل التي يتم استخدامها بالضبط على الصفحة ، بالإضافة إلى توصيلها وفصلها يدويًا لتجنب الكود غير الضروري في الحزمة النهائية.


لحسن الحظ ، يمكن أن يعهد هذا العمل إلى Webpack.


الخوارزمية المثلى لإجراءات أتمتة هذه العملية:


  1. حدد الفئات المقابلة لتسمية BEM من كود HTML الحالي ؛
  2. بناءً على الفئات ، احصل على قائمة كيانات BEM المستخدمة في الصفحة ؛
  3. تحقق مما إذا كانت هناك أدلة من الكتل المستخدمة والعناصر والمعدلات في مستويات إعادة التعريف ؛
  4. قم بتوصيل تقنية هذه الكيانات بالمشروع عن طريق إضافة التعبيرات require() .

بادئ ذي بدء ، قررت التحقق مما إذا كانت هناك أية محمّلات تمهيد جاهزة لهذه المهمة. لم أجد وحدة من شأنها أن توفر جميع الوظائف اللازمة في زجاجة واحدة. لكنني واجهت bemdecl-to-fs-loader ، والذي يحول إعلانات BEM require() تعبيرات require() . يعتمد على مستويات إعادة التعريف والتقنيات المتوفرة في بنية ملف المشروع.


إعلان BEM - قائمة بكيانات BEM المستخدمة في الصفحة. اقرأ المزيد عنها في الوثائق .

هناك رابط واحد مفقود - تحويل HTML إلى صفيف من كيانات BEM. يتم حل هذه المهمة من خلال وحدة html2bemjson .


bemjson - البيانات التي تعكس هيكل الصفحة المستقبلية. عادةً ما يتم استخدامها بواسطة محرك القالب bem-xjst لتكوين صفحات. يشبه بناء جملة bemjson بناء جملة الإعلانات ، لكن الإعلان لا يحتوي إلا على قائمة بالكيانات المستخدمة ، بينما يعكس bemjson أيضًا ترتيبها.

إن bemjson ليس إعلانًا ، لذلك نقوم أولاً بتحويله إلى تنسيق dec للإرسال إلى bemdecl-to-fs-loader. لهذه المهمة ، استخدم الوحدة النمطية من SDK: bemjson-to-decl . نظرًا لأن هذه الوحدات النمطية NodeJS عادية وليست Webpack ، يجب عليك إنشاء مُلحق مجمّع. بعد ذلك ، يمكننا استخدامها لتحويل إلى Webpack.


نحصل على رمز أداة تحميل التشغيل التالية:


 let html2bemjson = require("html2bemjson"); let bemjson2decl = require("bemjson-to-decl"); module.exports = function( content ){ if (content == null && content == "") callback("html2bemdecl requires a valid HTML."); let callback = this.async(); let bemjson = html2bemjson.convert( content ); let decl = bemjson2decl.convert( bemjson ); console.log(decl); //     callback(null, decl); } 

لتبسيط تثبيت محمل الإقلاع وتوفير الوقت في المستقبل ، قمت بتنزيل الوحدة على NPM .


لنقم بتثبيت أداة تحميل التشغيل في مشروعنا وإجراء تغييرات على تكوين Webpack:


 const webpack = require('webpack'); const path = require('path'); const opy = require('copy-webpack-plugin'); module.exports = { entry: path.resolve(__dirname, "pages", "index.js"), output: { filename: 'index.js', path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /\.html$/, use: [ { //    bemdecl-to-fs-loader loader: 'bemdecl-to-fs-loader', //       options: { levels: ['desktop'], extensions: ['css', 'js'] } }, //      html2bemdecl-loader { loader: 'html2bemdecl-loader' } ] }, { test: /\.css$/, loader: 'style-loader!css-loader' } ] }, plugins: [ new opy([ { from: path.resolve(__dirname, 'pages'), test: /\.html$/, to: path.resolve(__dirname, "dist") } ]) ] } 

تحدد معلمة levels محمل الإقلاع bemdecl-to-fs-loader levels التي يتم تجاوزها وترتيبها. تمنح extensions تكنولوجيا الملفات المستخدمة في مشروعنا.


نتيجةً لذلك ، بدلاً من توصيل التقنيات يدويًا ، فإننا نقوم فقط بتضمين ملف HTML. سيتم تنفيذ جميع التحويلات اللازمة تلقائيًا.


دعنا نستبدل محتويات index.js بالسطر:


 require('./index.html'); 

الآن قم بتشغيل Webpack. عند التجميع ، يتم عرض الخط:


 [ BemEntityName { block: 'block' } ] 

وهذا يعني أن تشكيل الإعلان كان ناجحًا. نحن ننظر مباشرة إلى إخراج Webpack:


  Entrypoint main = index.js [0] ./pages/index.js 24 bytes {0} [built] [1] ./pages/index.html 74 bytes {0} [built] [2] ./desktop/block/block.css 1.07 KiB {0} [built] [3] ./node_modules/css-loader/dist/cjs.js!./desktop/block/block.css 217 bytes {0} [built] [7] ./desktop/block/block.js 93 bytes {0} [built] + 3 hidden modules 

لقطة شاشة للصفحة


لقد حصلنا على نتيجة مماثلة للنتيجة السابقة ، مع اختلاف أن جميع تقنيات الكتلة كانت متصلة تلقائيًا. في الوقت الحالي ، يكفي أن نضيف فئة تسمى BEM إلى HTML ، وربط هذا HTML مع require() وإنشاء الدليل المناسب مع تقنيات الاتصال.


لذلك ، لدينا هيكل ملف يتوافق مع منهجية BEM ، وكذلك آلية لربط تقنيات الكتلة تلقائيًا.


مستخلصاً من آليات وكيانات المنهجية ، قمنا بإنشاء تهيئة Webpack بسيطة للغاية ولكنها فعالة. آمل أن يساعد هذا المثال كل شخص يبدأ معرفته بـ BEM على فهم المبادئ الأساسية لبناء مشاريع BEM بشكل أفضل.


روابط مفيدة


Source: https://habr.com/ru/post/ar447560/


All Articles