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プロジェクト構築の基本原則をよりよく理解するのに役立つことを願っています。







便利なリンク






All Articles