使用Webpack构建BEM项目

本文将重点介绍如何使用Webpack捆绑程序组装BEM项目 。 我将展示一个配置示例,其中不会向阅读器加载不必要的实体。


该材料适用于刚开始接触BEM的人员。 首先,我们将探讨方法论的理论方面,在“实践”部分中,我将展示如何应用它们。


一点理论


如果这是您第一次了解BEM,并且想自己了解,请保存文档


BEM是一种用于组织任何规模项目的方法。 Yandex开发了它,起初仅在其服务工作中使用它,但后来在公共领域发布。


BEM代表“块,元素,修饰符”。


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

来自项目存根公共存储库的代码。


对于刚开始使用BEM的人来说,ENB配置代码显然会很复杂。


该文档包含收集器的现成设置 ,可以使用它们而无需深入研究组件的详细信息。 但是,如果您像我一样,想要对项目在构建过程中发生的事情有一个完整的了解,该怎么办?


BEM文档从理论上很好地解释了组装过程,但是,很少有实际示例,并且它们并不总是适合于清楚地了解该过程。 为了解决这个问题,我将尝试使用Webpack构建一个基本的BEM项目。


练习


在此之前,我提到过代码分离和汇编组织简化了该项目的工作。 在下面的示例中,我们将使用BEM提供代码分离,并使用Webpack进行汇编。


我们想要获得最简单的配置,组装逻辑应该是线性且直观的。 让我们将一个带有一个BEM块的页面放在一起,它将具有两种技术:CSS和JS。


您可以使用带有“ block”类的一个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


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,看看会发生什么。 从./dist文件夹中打开index.html


页面截图


块样式已加载,javascript成功运行。 现在我们可以正确地在项目中添加珍贵的字母“ BEM”。


首先,创建BEM是为了处理大型项目。 假设我们的设计师尝试过,现在页面上的字符不是100个,而是100个。 按照前面的场景,我们将使用require()手动连接每个块的技术。 也就是说,index.js中将至少出现一百行代码。


可以避免的多余代码行是不好的。 未使用的代码更糟。 如果在我们的页面上只有10个可用块,或者20个或53个块,该怎么办? 开发人员将进行其他工作:他将必须专注于页面上使用了哪些块,并手动连接和断开它们,以免最终捆绑包中不必要的代码。


幸运的是,这项工作可以委托给Webpack。


使该过程自动化的最佳动作算法:


  1. 从现有的HTML代码中选择与BEM命名相对应的类;
  2. 根据这些类,获取页面上使用的BEM实体列表;
  3. 检查在重新定义级别上是否有使用过的块,元素和修饰符的目录;
  4. 通过添加适当的require()表达式,将这些实体的技术连接到项目。

首先,我决定检查是否有用于此任务的现成的引导程序。 我没有找到一个可以在一瓶中提供所有必要功能的模块。 但是我遇到了bemdecl-to-fs-loader ,它将BEM声明转换为require()表达式。 它基于项目文件结构中可用的重新定义级别和技术。


BEM声明 -页面上使用的BEM实体列表。 在文档中阅读有关它们的更多信息。

缺少一个链接-将HTML转换为一组BEM实体。 此任务由html2bemjson模块解决。


bemjson-反映未来页面结构的数据。 通常,bem-xjst模板引擎使用它们来形成页面。 bemjson的语法与声明的语法相似,但是声明仅包含所用实体的列表,而bemjson也反映了它们的顺序。

bemjson不是声明,因此我们首先将其转换为decl格式,以传输到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上下载了该模块。


让我们在项目中安装bootloader并更改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引导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()连接require()并使用连接技术创建适当的目录。


因此,我们拥有一个与BEM方法相对应的文件结构,以及一种自动连接块技术的机制。


从方法的机制和实体中抽象出来,我们创建了一个非常简单但有效的Webpack配置。 我希望这个例子可以帮助每个刚接触BEM的人更好地理解构建BEM项目的基本原理。


有用的链接


Source: https://habr.com/ru/post/zh-CN447560/


All Articles