Membangun proyek BEM menggunakan Webpack

Artikel ini akan fokus pada perakitan proyek BEM menggunakan bundler Webpack. Saya akan menunjukkan satu contoh konfigurasi tanpa memuat pembaca dengan entitas yang tidak perlu.


Materi ini cocok untuk mereka yang baru mulai berkenalan dengan BEM. Pertama, kita akan menyentuh pada aspek teoritis dari metodologi, dan di bagian "Praktek" saya akan menunjukkan bagaimana mereka dapat diterapkan.


Sedikit teori


Jika ini adalah pertama kalinya Anda mendengar tentang BEM dan ingin mengetahuinya sendiri, simpan dokumentasi .


BEM adalah metodologi yang digunakan untuk mengatur proyek dari skala apa pun. Yandex mengembangkannya dan pada awalnya menggunakannya hanya dalam pekerjaan layanannya, tetapi kemudian diterbitkan dalam domain publik.


BEM adalah singkatan dari "Block, Element, Modifier".


Block adalah entitas dengan arsitektur otonom yang dapat digunakan kembali. Blok mungkin mengandung elemennya sendiri.


Elemen adalah bagian integral dari sebuah blok. Item hanya dapat digunakan di dalam blok induk.


Pengubah adalah entitas yang mengubah tampilan, status, atau perilaku blok.


Komponen-komponen ini mendasari metodologi. Mereka memberikan keindahan dan pemisahan kode yang nyaman. Rincian lebih lanjut tentang perangkat mereka ditulis dalam dokumentasi .


Dokumentasi BEM ditulis secara luas. Namun, ada satu "tetapi": ambang batas tinggi untuk memasukkan materi. Jika dasar-dasar tata letak dapat dipecahkan dengan membaca satu halaman dokumentasi, maka masalah pemasangan proyek lebih rumit.


Mengapa itu tentang merakit proyek? Saat mengerjakan proyek skala besar, semua orang dihadapkan pada masalah pengorganisasian kode. Sangat tidak nyaman untuk menyimpan semua kode proyek besar dalam satu file. Memisahkan kode menjadi beberapa file, lalu mengompilasinya secara manual juga bukan jalan keluar yang baik. Untuk mengatasi masalah ini, pengumpul, atau bundler , digunakan yang mengotomatiskan konversi kode sumber proyek menjadi kode yang siap dikirim ke produksi.


Biarkan saya mengingatkan Anda: diasumsikan lebih lanjut bahwa pembaca memiliki keterampilan Webpack dasar. Jika Anda belum pernah bekerja dengannya sebelumnya, saya sarankan Anda untuk lebih dulu mengenal alat ini.


Dokumentasi BEM memberikan rekomendasi untuk perakitan proyek. Hanya dua opsi yang ditawarkan sebagai contoh: perakitan menggunakan ENB dan Gulp.


ENB adalah utilitas yang dirancang khusus untuk membangun proyek BEM. Dia dapat bekerja dengan entitas BEM di luar kotak. Tapi lihat kodenya. Sekilas, ia dapat mendemotivasi pengembang yang tidak siap:


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']); }); }; 

Kode dari repositori publik stub-proyek .


Kode konfigurasi ENB jelas akan rumit bagi mereka yang baru mulai menggunakan BEM.


Dokumentasi berisi pengaturan yang sudah jadi untuk kolektor , dan mereka dapat digunakan tanpa mempelajari detail perakitan. Tetapi bagaimana jika Anda, seperti saya, ingin memiliki gambaran lengkap tentang apa yang terjadi dengan proyek selama pembangunan?


Dokumentasi BEM dengan baik menjelaskan proses perakitan dalam teori, namun, ada beberapa contoh praktis dan mereka tidak selalu cocok untuk pemahaman yang jelas tentang proses tersebut. Untuk mengatasi masalah ini, saya akan mencoba membangun proyek BEM dasar menggunakan Webpack.


Berlatih


Sebelum itu, saya menyebutkan bahwa organisasi pemisah kode dan perakitan menyederhanakan pekerjaan dengan proyek. Pada contoh di bawah ini, kami akan menyediakan pemisahan kode menggunakan BEM dan perakitannya menggunakan Webpack.


Kami ingin mendapatkan konfigurasi yang paling sederhana, logika perakitan harus linier dan intuitif. Mari kita buat satu halaman dengan satu blok BEM, yang akan memiliki dua teknologi: CSS dan JS.


Anda dapat menulis kode HTML dengan satu DIV dengan kelas "blok" dan menghubungkan semua teknologinya secara manual. Menggunakan penamaan kelas BEM dan struktur file yang sesuai, kami tidak melanggar prinsip-prinsip metodologi.


Saya mendapat pohon proyek berikut:


 ├── 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 

Baris pertama mengacu pada level override "desktop". Dalam terminologi BEM, level redefinisi adalah direktori yang berisi implementasi blok mereka sendiri. Saat merakit sebuah proyek, implementasi dari semua level redefinisi dalam urutan tertentu termasuk dalam bundel terakhir.


Misalnya, kami memiliki tingkat definisi ulang "desktop" di mana implementasi blok untuk perangkat desktop disimpan. Jika kita perlu melengkapi proyek dengan tata letak untuk perangkat seluler, itu akan cukup bagi kita untuk membuat tingkat redefinisi baru "seluler" dan mengisinya dengan implementasi baru dari blok yang sama. Kenyamanan dari pendekatan ini adalah bahwa pada level redefinisi baru, kita tidak perlu menduplikasi kode yang sudah ada di "desktop", karena akan terhubung secara otomatis.


Berikut adalah konfigurasi 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") } ]) ] } 

Di sini kita menentukan file / /pages/index.js sebagai titik masuk, tambahkan loader untuk gaya CSS, dan salin / /pages/index.html ke /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!]" 

Contoh ini menggunakan satu level override dan satu blok. Tugasnya adalah merakit halaman sehingga teknologi (css, js) dari blok kami terhubung dengannya.


Untuk menghubungkan teknologi, kami menggunakan require() :


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

Luncurkan Webpack dan lihat apa yang terjadi. Buka index.html dari folder ./dist :


Tangkapan layar halaman


Gaya blok dimuat, javascript berhasil. Sekarang kita dapat menambahkan surat berharga "BEM" ke proyek kita.


Pertama-tama, BEM diciptakan untuk bekerja dengan proyek-proyek besar. Mari kita bayangkan bahwa perancang kami telah mencoba dan pada halaman tersebut sekarang bukan satu blok, tetapi seratus. Mengikuti skenario sebelumnya, kami akan secara manual menghubungkan teknologi dari setiap blok menggunakan require() . Artinya, setidaknya seratus baris kode tambahan akan muncul di index.js.


Baris-baris kode tambahan yang bisa dihindari itu buruk. Kode yang tidak digunakan bahkan lebih buruk. Bagaimana jika pada halaman kita hanya akan ada 10 blok yang tersedia, atau 20, atau 53? Pengembang akan memiliki pekerjaan tambahan: ia harus fokus pada blok mana yang digunakan pada halaman, dan juga menghubungkan dan memutuskannya secara manual untuk menghindari kode yang tidak perlu dalam bundel terakhir.


Untungnya, pekerjaan ini bisa dipercayakan ke Webpack.


Algoritma tindakan yang optimal untuk mengotomatisasi proses ini:


  1. Pilih kelas yang sesuai dengan penamaan BEM dari kode HTML yang ada;
  2. Berdasarkan kelas, dapatkan daftar entitas BEM yang digunakan pada halaman;
  3. Periksa apakah ada direktori blok bekas, elemen dan pengubah pada tingkat redefinisi;
  4. Hubungkan teknologi entitas-entitas ini dengan proyek dengan menambahkan ekspresi require() sesuai.

Untuk mulai dengan, saya memutuskan untuk memeriksa apakah ada bootloader yang siap pakai untuk tugas ini. Saya tidak menemukan modul yang akan menyediakan semua fungsi yang diperlukan dalam satu botol. Tapi saya menemukan bemdecl-to-fs-loader , yang mengubah pernyataan BEM menjadi require() ekspresi. Ini didasarkan pada tingkat redefinisi dan teknologi yang tersedia dalam struktur file proyek.


Deklarasi BEM - daftar entitas BEM yang digunakan pada halaman. Baca lebih lanjut tentang mereka dalam dokumentasi .

Satu tautan tidak ada - mengonversi HTML ke array entitas BEM. Tugas ini diselesaikan oleh modul html2bemjson .


bemjson - data yang mencerminkan struktur halaman masa depan. Biasanya mereka digunakan oleh mesin template bem-xjst untuk membentuk halaman. Sintaks bemjson mirip dengan sintaksis deklarasi, tetapi deklarasi hanya berisi daftar entitas yang digunakan, sementara bemjson juga mencerminkan urutannya.

bemjson bukan deklarasi, jadi pertama-tama kita mengubahnya untuk mendeklarasikan format untuk transmisi ke bemdecl-to-fs-loader. Untuk tugas ini, gunakan modul dari SDK: bemjson-to-decl . Karena ini adalah modul NodeJS biasa, bukan loader Webpack, Anda harus membuat loader wrapper. Setelah itu, kita bisa menggunakannya untuk mengonversi ke Webpack.


Kami mendapatkan kode bootloader berikut:


 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); } 

Untuk mempermudah instalasi bootloader dan menghemat waktu di masa depan, saya mengunduh modul pada NPM .


Mari kita instal bootloader di proyek kami dan buat perubahan pada konfigurasi 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") } ]) ] } 

Parameter levels bootloader bemdecl-to-fs-loader menentukan level override yang akan digunakan dan dalam urutan apa. extensions memberikan extensions teknologi file yang digunakan dalam proyek kami.


Akibatnya, alih-alih menghubungkan teknologi secara manual, kami hanya menyertakan file HTML. Semua konversi yang diperlukan akan dilakukan secara otomatis.


Mari kita ganti konten index.js dengan baris:


 require('./index.html'); 

Sekarang jalankan Webpack. Saat perakitan, garis ditampilkan:


 [ BemEntityName { block: 'block' } ] 

Ini berarti bahwa pembentukan deklarasi berhasil. Kami melihat langsung pada output 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 

Tangkapan layar halaman


Kami mendapat hasil yang identik dengan yang sebelumnya, dengan perbedaan bahwa semua teknologi blok terhubung secara otomatis. Untuk saat ini, cukup bagi kami untuk menambahkan kelas bernama BEM ke HTML, sambungkan HTML ini dengan require() dan buat direktori yang sesuai dengan teknologi untuk menghubungkan.


Jadi, kami memiliki struktur file yang sesuai dengan metodologi BEM, serta mekanisme untuk menghubungkan teknologi blok secara otomatis.


Mengabstraksi dari mekanisme dan entitas metodologi, kami telah menciptakan konfigurasi Webpack yang sangat sederhana namun efektif. Saya harap contoh ini akan membantu semua orang yang memulai perkenalan mereka dengan BEM untuk lebih memahami prinsip-prinsip dasar membangun proyek BEM.


Tautan yang bermanfaat


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


All Articles