Création d'un projet BEM à l'aide de Webpack

L'article se concentrera sur l'assemblage de projets BEM à l'aide du bundle Webpack. Je vais montrer un exemple de configuration sans charger de lecteurs avec des entités inutiles.


Le matĂ©riel convient Ă  ceux qui commencent tout juste Ă  se familiariser avec BEM. Tout d'abord, nous aborderons les aspects thĂ©oriques de la mĂ©thodologie, et dans la section «Pratique», je montrerai comment ils peuvent ĂȘtre appliquĂ©s.


Un peu de théorie


Si c'est la premiĂšre fois que vous entendez parler de BEM et que vous voulez en savoir plus vous-mĂȘme, conservez la documentation .


Le BEM est une méthodologie utilisée pour organiser des projets de toute envergure. Yandex l'a développé et l'a d'abord utilisé uniquement dans le travail de ses services, mais plus tard, il a été publié dans le domaine public.


BEM signifie «Block, Element, Modifier».


Block est une entitĂ© avec une architecture autonome qui peut ĂȘtre rĂ©utilisĂ©e. Un bloc peut contenir ses propres Ă©lĂ©ments.


Un Ă©lĂ©ment fait partie intĂ©grante d'un bloc. Un Ă©lĂ©ment ne peut ĂȘtre utilisĂ© qu'Ă  l'intĂ©rieur du bloc parent.


Un modificateur est une entité qui modifie l'affichage, l'état ou le comportement d'un bloc.


Ces éléments sous-tendent la méthodologie. Ils offrent une beauté et une séparation de code pratique. Plus de détails sur leur appareil sont écrits dans la documentation .


La documentation BEM est largement Ă©crite. Cependant, il y a un «mais»: un seuil Ă©levĂ© pour entrer dans le matĂ©riau. Si les bases de la composition peuvent ĂȘtre comprises en lisant une page de la documentation, alors la question de l'assemblage du projet est plus compliquĂ©e.


Pourquoi s'agissait-il de monter le projet? Lorsque l'on travaille sur un projet Ă  grande Ă©chelle, tout le monde est confrontĂ© au problĂšme de l'organisation du code. Il n'est pas pratique de stocker tout le code d'un grand projet dans un seul fichier. Diviser le code en plusieurs fichiers, puis le compiler manuellement n'est pas non plus une bonne solution. Pour rĂ©soudre ce problĂšme, des collecteurs ou des bundlers sont utilisĂ©s qui automatisent la conversion du code source du projet en un code prĂȘt Ă  ĂȘtre envoyĂ© en production.


Permettez-moi de vous rappeler: il est en outre supposé que les lecteurs possÚdent les compétences de base de Webpack. Si vous n'avez jamais travaillé avec lui auparavant, je vous recommande de vous familiariser avec cet outil.


La documentation BEM fournit des recommandations pour l'assemblage des projets. Seules deux options sont proposées à titre d'exemples: l'assemblage utilisant ENB et Gulp.


ENB est un utilitaire conçu spĂ©cifiquement pour la construction de projets BEM. Elle est capable de travailler avec des entitĂ©s BEM hors de la boĂźte. Mais jetez un Ɠil au code. À premiĂšre vue, il peut dĂ©motiver un dĂ©veloppeur non prĂ©parĂ©:


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

Code du référentiel public project-stub .


Le code de configuration ENB sera évidemment compliqué pour ceux qui commencent tout juste à utiliser BEM.


La documentation contient des paramĂštres prĂȘts Ă  l'emploi pour le collecteur , et ils peuvent ĂȘtre utilisĂ©s sans plonger dans les dĂ©tails de l'assemblage. Mais que se passe-t-il si vous, comme moi, souhaitez avoir une image complĂšte de ce qui se passe avec le projet pendant la construction?


La documentation BEM explique bien le processus d'assemblage en théorie, cependant, il existe peu d'exemples pratiques et ils ne sont pas toujours adaptés à une compréhension claire du processus. Pour résoudre ce problÚme, je vais essayer de construire un projet BEM élémentaire en utilisant Webpack.


Pratique


Avant cela, j'ai mentionné que la séparation du code et l'organisation de l'assemblage simplifiaient le travail avec le projet. Dans l'exemple ci-dessous, nous fournirons la séparation du code à l'aide de BEM et son assemblage à l'aide de Webpack.


Nous voulons obtenir la configuration la plus simple, la logique d'assemblage doit ĂȘtre linĂ©aire et intuitive. Mettons ensemble une page avec un bloc BEM, qui aura deux technologies: CSS et JS.


Vous pouvez écrire du code HTML avec un DIV avec la classe "block" et connecter manuellement toutes ses technologies. En utilisant la dénomination de classe BEM et la structure de fichiers correspondante, nous ne violons pas les principes de la méthodologie.


J'ai obtenu l'arborescence de projet suivante:


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

La premiÚre ligne fait référence au niveau prioritaire de «bureau». Dans la terminologie BEM, les niveaux de redéfinition sont des répertoires qui contiennent leurs propres implémentations de blocs. Lors de l'assemblage d'un projet, les implémentations de tous les niveaux de redéfinition dans un certain ordre tombent dans le bundle final.


Par exemple, nous avons un niveau de redĂ©finition de "bureau" dans lequel les implĂ©mentations de blocs pour les appareils de bureau sont stockĂ©es. Si nous devons complĂ©ter le projet avec une mise en page pour les appareils mobiles, il nous suffira de crĂ©er un nouveau niveau de redĂ©finition du "mobile" et de le remplir avec de nouvelles implĂ©mentations des mĂȘmes blocs. La commoditĂ© de cette approche est qu'Ă  un nouveau niveau de redĂ©finition, nous n'aurons pas besoin de dupliquer le code qui existe dĂ©jĂ  dans "desktop", car il se connectera automatiquement.


Voici la configuration 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") } ]) ] } 

Ici, nous /pages/index.js le fichier /pages/index.js comme point d'entrée, ajoutons des chargeurs pour les styles CSS et copions /pages/index.html dans /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!]" 

L'exemple utilise un niveau de remplacement et un bloc. La tùche consiste à assembler la page pour que les technologies (css, js) de notre bloc y soient connectées.


Pour connecter les technologies, nous utilisons require() :


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

Lancez Webpack et voyez ce qui se passe. Ouvrez index.html partir du dossier ./dist :


Capture d'écran de la page


Les styles de bloc ont été chargés, javascript a fonctionné avec succÚs. Maintenant, nous pouvons légitimement ajouter les lettres précieuses "BEM" à notre projet.


Tout d'abord, BEM a été créé pour travailler avec de grands projets. Imaginons que notre designer ait essayé et que la page ne soit plus un bloc, mais une centaine. Suivant le scénario précédent, nous connecterons manuellement les technologies de chaque bloc à l'aide de require() . Autrement dit, au moins cent lignes de code supplémentaires apparaßtront dans index.js.


Les lignes de code supplĂ©mentaires qui auraient pu ĂȘtre Ă©vitĂ©es sont mauvaises. Le code inutilisĂ© est encore pire. Que faire si sur notre page il n'y aura que 10 des blocs disponibles, ou 20, ou 53? Le dĂ©veloppeur aura du travail supplĂ©mentaire: il devra se concentrer exactement sur les blocs utilisĂ©s sur la page, ainsi que les connecter et les dĂ©connecter manuellement pour Ă©viter le code inutile dans le bundle final.


Heureusement, ce travail peut ĂȘtre confiĂ© Ă  Webpack.


L'algorithme optimal d'actions pour automatiser ce processus:


  1. Sélectionnez les classes correspondant à la dénomination BEM à partir du code HTML existant;
  2. En fonction des classes, obtenez la liste des entités BEM utilisées sur la page;
  3. Vérifiez s'il existe des répertoires des blocs, éléments et modificateurs utilisés aux niveaux de redéfinition;
  4. Connectez la technologie de ces entités au projet en ajoutant les expressions require() appropriées.

Pour commencer, j'ai dĂ©cidĂ© de vĂ©rifier s'il existe des chargeurs de dĂ©marrage prĂȘts Ă  l'emploi pour cette tĂąche. Je n'ai pas trouvĂ© de module qui fournirait toutes les fonctionnalitĂ©s nĂ©cessaires dans une seule bouteille. Mais je suis tombĂ© sur bemdecl-to-fs-loader , qui convertit les dĂ©clarations BEM en expressions require() . Il est basĂ© sur les niveaux de redĂ©finition et les technologies disponibles dans la structure du fichier projet.


Déclaration BEM - une liste des entités BEM utilisées sur la page. En savoir plus à leur sujet dans la documentation .

Un lien est manquant: la conversion du HTML en un tableau d'entités BEM. Cette tùche est résolue par le module html2bemjson .


bemjson - des données qui reflÚtent la structure de la future page. Habituellement, ils sont utilisés par le moteur de modÚle bem-xjst pour former des pages. La syntaxe de bemjson est similaire à la syntaxe des déclarations, mais la déclaration ne contient qu'une liste d'entités utilisées, tandis que bemjson reflÚte également leur ordre.

bemjson n'est pas une déclaration, nous le convertissons donc d'abord au format décl pour la transmission à bemdecl-to-fs-loader. Pour cette tùche, utilisez le module du SDK: bemjson-to-decl . Comme ce sont des modules NodeJS normaux, et non des chargeurs Webpack, vous devez créer un chargeur wrapper. AprÚs cela, nous pouvons les utiliser pour convertir en Webpack.


Nous obtenons le code du chargeur de démarrage suivant:


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

Pour simplifier l'installation du chargeur de démarrage et gagner du temps à l'avenir, j'ai téléchargé le module sur NPM .


Installons le chargeur de démarrage dans notre projet et apportons des modifications à la configuration de 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") } ]) ] } 

Le paramÚtre des levels bemdecl-to-fs-loader démarrage bemdecl-to-fs-loader spécifie les niveaux de substitution à utiliser et dans quel ordre. Les extensions donnent les extensions de technologie de fichier utilisées dans notre projet.


Par conséquent, au lieu de connecter les technologies manuellement, nous incluons uniquement un fichier HTML. Toutes les conversions nécessaires seront effectuées automatiquement.


Remplaçons le contenu de index.js par la ligne:


 require('./index.html'); 

Maintenant, lancez Webpack. Lors de l'assemblage, la ligne s'affiche:


 [ BemEntityName { block: 'block' } ] 

Cela signifie que la formation de la déclaration a réussi. Nous regardons directement la sortie de 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 

Capture d'écran de la page


Nous avons obtenu un résultat identique au précédent, à la différence prÚs que toutes les technologies de blocs étaient connectées automatiquement. Pour l'instant, il nous suffit d'ajouter une classe nommée BEM au HTML, de connecter ce HTML avec require() et de créer le répertoire approprié avec les technologies de connexion.


Nous avons donc une structure de fichiers qui correspond à la méthodologie BEM, ainsi qu'un mécanisme de connexion automatique des technologies de blocs.


Abstraction faite des mĂ©canismes et des entitĂ©s de la mĂ©thodologie, nous avons créé une configuration Webpack extrĂȘmement simple mais efficace. J'espĂšre que cet exemple aidera tous ceux qui commencent Ă  se familiariser avec BEM Ă  mieux comprendre les principes de base de la construction de projets BEM.


Liens utiles


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


All Articles