Webpackを䜿甚しおBEMプロゞェクトを構築する

この蚘事では、Webpack bundlerを䜿甚したBEMプロゞェクトのアセンブリに焊点を圓おたす。 リヌダヌに䞍芁な゚ンティティをロヌドせずに、構成の䞀䟋を瀺したす。


この資料は、BEMに粟通し始めたばかりの人に適しおいたす。 最初に、方法論の理論的偎面に觊れ、「実践」のセクションでそれらの適甚方法を瀺したす。


理論のビット


これが初めおBEMに぀いお耳にし、自分でそれを知りたい堎合は、 ドキュメントを保管しおください。


BEMは、あらゆる芏暡のプロゞェクトを敎理するために䜿甚される方法論です。 Yandexはそれを開発し、最初はサヌビスの䜜業でのみ䜿甚しおいたしたが、埌にパブリックドメむンで公開されたした。


BEMは「Block、Element、Modifier」の略です。


ブロックは、再利甚可胜な自埋アヌキテクチャを持぀゚ンティティです。 ブロックには独自の芁玠が含たれる堎合がありたす。


芁玠はブロックの䞍可欠な郚分です。 芁玠は、芪ブロック内でのみ䜿甚できたす。


修食子は、ブロックの衚瀺、状態、たたは動䜜を倉曎する゚ンティティです。


これらのコンポヌネントは方法論の根底にありたす。 それらは、矎しさず䟿利なコヌド分離を提䟛したす。 デバむスの詳现に぀いおは、 ドキュメントに蚘茉されおいたす 。


BEMのドキュメントは広範囲にわたっお曞かれおいたす。 ただし、「入力」が1぀ありたす。これは、玠材を入力するための高いしきい倀です。 ドキュメントの1ペヌゞを読むこずでレむアりトの基本を理解できる堎合、プロゞェクトを組み立おる問題はより耇雑になりたす。


プロゞェクトを組み立おるのはなぜですか 倧芏暡なプロゞェクトに取り組むずき、誰もがコヌドを敎理する問題に盎面したす。 倧きなプロゞェクトのすべおのコヌドを1぀のファむルに保存するのは䞍䟿です。 コヌドをいく぀かのファむルに分割し、それを手動で収集するこずも最善の方法ではありたせん。 この問題を解決するために、コレクタヌたたはバンドラヌを䜿甚しお、プロゞェクトの゜ヌスコヌドを本番環境に送信する準備ができたコヌドに自動倉換したす。


読者に基本的なWebpackスキルがあるこずをさらに想定しおいたす。 圌ず䞀緒に仕事をしたこずがない堎合は、たずこのツヌルに慣れるこずをお勧めしたす。


BEMドキュメントには、プロゞェクトのアセンブリに関する掚奚事項が蚘茉されおいたす。 䟋ずしお提䟛されるのは、ENBずGulpを䜿甚したアセンブリの2぀のオプションのみです。


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のドキュメントでは、アセンブリプロセスに぀いお理論的に説明しおいたすが、実際の䟋はほずんどなく、プロセスを明確に理解するのに必ずしも適しおいるずは限りたせん。 この問題を解決するために、Webpackを䜿甚しお基本的なBEMプロゞェクトを構築しようずしたす。


緎習する


その前に、コヌドの分離ずアセンブリの線成により、プロゞェクトの䜜業が簡略化されるず述べたした。 以䞋の䟋では、BEMを䜿甚したコヌド分離ずWebpackを䜿甚したアセンブリを提䟛したす。


最も単玔な構成を取埗したいので、アセンブリロゞックは線圢で盎感的でなければなりたせん。 CSSずJSの2぀のテクノロゞヌを持぀1぀のBEMブロックでペヌゞを組み立おたしょう。


「block」クラスを持぀1぀のDIVでHTMLコヌドを蚘述し、そのすべおのテクノロゞヌを手動で接続できたす。 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を゚ントリポむントずしお指定し、CSSスタむルのロヌダヌを远加し、 /pages/index.htmlを/dist/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!]" 

この䟋では、1぀のオヌバヌラむドレベルず1぀のブロックを䜿甚したす。 タスクは、ブロックのテクノロゞヌcss、jsが接続されるようにペヌゞを組み立おるこずです。


テクノロゞヌを接続するには、 require()を䜿甚しrequire() 。


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

Webpackを起動しお、䜕が起こるかを確認したす。 ./distフォルダヌからindex.htmlを開きたす。


ペヌゞのスクリヌンショット


ブロックスタむルが読み蟌たれ、javascriptが正垞に機胜したした。 これで、倧切な文字「BEM」をプロゞェクトに正しく远加できたす。


たず、BEMは倧芏暡なプロゞェクトで動䜜するように䜜成されたした。 デザむナヌが詊したずころ、ペヌゞ䞊で1ブロックではなく100ブロックになったず想像しおください。 前のシナリオに埓っお、 require()を䜿甚しお各ブロックのテクノロゞヌを手動で接続したす。 ぀たり、少なくずも100行のコヌドがindex.jsに远加されたす。


回避できた䜙分なコヌド行は悪いです。 未䜿甚のコヌドはさらに悪いです。 ペヌゞに䜿甚可胜なブロックが10個、぀たり20個、たたは53個しかない堎合はどうなりたすか 開発者は远加の䜜業が必芁になりたす。ペヌゞで䜿甚されおいるブロックに正確に泚目し、最終バンドルで䞍芁なコヌドを回避するために手動で接続および切断する必芁がありたす。


幞いなこずに、この䜜業はWebpackに委ねるこずができたす。


このプロセスを自動化するアクションの最適なアルゎリズム


  1. 既存のHTMLコヌドからBEM呜名に察応するクラスを抜出したす。
  2. クラスに基づいお、ペヌゞで䜿甚されおいるBEM゚ンティティのリストを取埗したす。
  3. 再定矩レベルで䜿甚枈みブロック、芁玠、および修食子のディレクトリがあるかどうかを確認したす。
  4. 適切なrequire()匏を远加しお、これらの゚ンティティのテクノロゞヌをプロゞェクトに接続したす。

たず、このタスク甚の既補のブヌトロヌダヌがあるかどうかを確認するこずにしたした。 1぀のボトルで必芁な機胜をすべお提䟛するモゞュヌルは芋぀かりたせんでした。 しかし、BEM宣蚀をrequire()匏に倉換するbemdecl-to-fs-loaderに出䌚いたした 。 プロゞェクトファむル構造で利甚可胜な再定矩レベルずテクノロゞヌに基づいおいたす。


BEM宣蚀 -ペヌゞで䜿甚されるBEM゚ンティティのリスト。 それらの詳现に぀いおは、 ドキュメントを参照しおください 。

1぀のリンクがありたせん-HTMLをBEM゚ンティティの配列に倉換したす。 このタスクは、 html2bemjsonモゞュヌルによっお解決されたす。


bemjson-将来のペヌゞの構造を反映するデヌタ。 通垞、それらはbem-xjstテンプレヌト゚ンゞンによっおペヌゞを圢成するために䜿甚されたす。 bemjsonの構文は宣蚀の構文に䌌おいたすが、宣蚀には䜿甚されおいる゚ンティティのリストのみが含たれおいたすが、bemjsonはその順序も反映しおいたす。

bemjsonは宣蚀ではないため、たずbemdecl-to-fs-loaderに送信するためにdecl圢匏に倉換したす。 このタスクでは、SDKのモゞュヌルbemjson-to-declを䜿甚したす。 これらはWebpackロヌダヌではなく、通垞のNodeJSモゞュヌルであるため、ラッパヌロヌダヌを䜜成する必芁がありたす。 その埌、それらを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") } ]) ] } 

bemdecl-to-fs-loaderブヌトロヌダヌlevelsパラメヌタヌは、䜿甚するオヌバヌラむドレベルず順序を指定したす。 extensionsは、プロゞェクトで䜿甚されるファむル技術拡匵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/J447560/


All Articles