Erstellen eines BEM-Projekts mit Webpack

Der Artikel konzentriert sich auf die Zusammenstellung von BEM-Projekten mit dem Webpack-Bundler. Ich werde ein Beispiel für eine Konfiguration zeigen, ohne Leser mit unnötigen Entitäten zu laden.


Das Material ist für diejenigen geeignet, die gerade erst anfangen, sich mit BEM vertraut zu machen. Zunächst werden wir die theoretischen Aspekte der Methodik ansprechen und im Abschnitt „Praxis“ zeigen, wie sie angewendet werden können.


Ein bisschen Theorie


Wenn Sie zum ersten Mal von BEM hören und es selbst kennenlernen möchten, bewahren Sie die Dokumentation auf .


BEM ist eine Methode, mit der Projekte jeder Größenordnung organisiert werden. Yandex entwickelte es und verwendete es zunächst nur für die Arbeit seiner Dienste, später wurde es jedoch öffentlich veröffentlicht.


BEM steht fĂĽr "Block, Element, Modifier".


Block ist eine Entität mit einer autonomen Architektur, die wiederverwendet werden kann. Ein Block kann seine eigenen Elemente enthalten.


Ein Element ist ein integraler Bestandteil eines Blocks. Ein Element kann nur innerhalb des ĂĽbergeordneten Blocks verwendet werden.


Ein Modifikator ist eine Entität, die die Anzeige, den Status oder das Verhalten eines Blocks ändert.


Diese Komponenten liegen der Methodik zugrunde. Sie bieten Schönheit und bequeme Codetrennung. Weitere Details zu ihrem Gerät finden Sie in der Dokumentation .


Die BEM-Dokumentation ist ausführlich geschrieben. Es gibt jedoch ein „Aber“: eine hohe Schwelle für den Materialeintritt. Wenn die Grundlagen des Layouts durch Lesen einer Seite der Dokumentation herausgefunden werden können, ist das Problem beim Zusammenstellen des Projekts komplizierter.


Warum ging es darum, das Projekt zusammenzustellen? Bei der Arbeit an einem Großprojekt steht jeder vor dem Problem, Code zu organisieren. Es ist unpraktisch, den gesamten Code eines großen Projekts in einer Datei zu speichern. Das Aufteilen von Code in mehrere Dateien und das anschließende manuelle Kompilieren ist ebenfalls kein guter Ausweg. Um dieses Problem zu lösen, werden Kollektoren oder Bündler verwendet, die die Konvertierung des Projektquellcodes in Code automatisieren, der bereit ist, an die Produktion gesendet zu werden.


Ich möchte Sie daran erinnern: Es wird weiterhin davon ausgegangen, dass die Leser über grundlegende Webpack-Kenntnisse verfügen. Wenn Sie noch nicht mit ihm gearbeitet haben, empfehle ich Ihnen, sich zuerst mit diesem Tool vertraut zu machen.


Die BEM-Dokumentation enthält Empfehlungen für die Montage von Projekten. Als Beispiele werden nur zwei Optionen angeboten: Montage mit ENB und Gulp.


ENB ist ein Dienstprogramm, das speziell für die Erstellung von BEM-Projekten entwickelt wurde. Sie ist in der Lage, sofort mit BEM-Entitäten zu arbeiten. Aber schauen Sie sich den Code an. Auf den ersten Blick kann er einen unvorbereiteten Entwickler demotivieren:


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 aus dem öffentlichen Repository von project-stub .


Der ENB-Konfigurationscode wird offensichtlich fĂĽr diejenigen kompliziert sein, die gerade erst anfangen, BEM zu verwenden.


Die Dokumentation enthält vorgefertigte Einstellungen für den Kollektor . Sie können verwendet werden, ohne auf die Details der Baugruppe einzugehen. Aber was ist, wenn Sie wie ich ein vollständiges Bild davon haben möchten, was mit dem Projekt während des Builds passiert?


Die BEM-Dokumentation erklärt den Montageprozess in der Theorie gut, es gibt jedoch nur wenige praktische Beispiele und sie sind nicht immer für ein klares Verständnis des Prozesses geeignet. Um dieses Problem zu lösen, werde ich versuchen, ein elementares BEM-Projekt mit Webpack zu erstellen.


Ăśbe


Zuvor erwähnte ich, dass die Codetrennung und die Organisation der Assemblierung die Arbeit mit dem Projekt vereinfachten. Im folgenden Beispiel stellen wir die Codetrennung mithilfe von BEM und deren Zusammenstellung mithilfe von Webpack bereit.


Wir möchten die einfachste Konfiguration erhalten, die Montagelogik sollte linear und intuitiv sein. Lassen Sie uns eine Seite mit einem BEM-Block zusammenstellen, der zwei Technologien enthält: CSS und JS.


Sie können HTML-Code mit einem DIV mit der Klasse "block" schreiben und alle Technologien manuell verbinden. Bei Verwendung der BEM-Klassennamen und der entsprechenden Dateistruktur verstoßen wir nicht gegen die Prinzipien der Methodik.


Ich habe folgenden Projektbaum:


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

Die erste Zeile bezieht sich auf die Ăśberschreibungsebene von "Desktop". In der BEM-Terminologie sind Neudefinitionsebenen Verzeichnisse, die ihre eigenen Blockimplementierungen enthalten. Beim Zusammenstellen eines Projekts fallen Implementierungen aus allen Neudefinitionsebenen in einer bestimmten Reihenfolge in das endgĂĽltige Paket.


Zum Beispiel haben wir eine Neudefinitionsstufe von "Desktop", in der Blockimplementierungen für Desktop-Geräte gespeichert sind. Wenn wir das Projekt durch ein Layout für mobile Geräte ergänzen müssen, reicht es aus, eine neue Ebene der Neudefinition von "mobil" zu erstellen und es mit neuen Implementierungen derselben Blöcke zu füllen. Der Vorteil dieses Ansatzes besteht darin, dass wir auf einer neuen Ebene der Neudefinition den Code, der bereits auf "Desktop" vorhanden ist, nicht duplizieren müssen, da er automatisch eine Verbindung herstellt.


Hier ist die Webpack-Konfiguration:


 // 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") } ]) ] } 

Hier geben wir die Datei /pages/index.js als Einstiegspunkt an, fĂĽgen Loader fĂĽr CSS-Stile hinzu und kopieren /pages/index.html nach /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!]" 

Das Beispiel verwendet eine Ăśberschreibungsebene und einen Block. Die Aufgabe besteht darin, die Seite so zusammenzustellen, dass die Technologien (css, js) unseres Blocks damit verbunden sind.


Um Technologien zu verbinden, verwenden wir require() :


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

Starten Sie Webpack und sehen Sie, was passiert. Ă–ffnen Sie index.html aus dem Ordner ./dist :


Seite Screenshot


Blockstile wurden geladen, Javascript funktionierte erfolgreich. Jetzt können wir die geschätzten Buchstaben "BEM" zu Recht zu unserem Projekt hinzufügen.


Zunächst wurde BEM für die Arbeit mit großen Projekten entwickelt. Stellen wir uns vor, unser Designer hat es versucht und auf der Seite ist jetzt nicht ein Block, sondern hundert. Nach dem vorherigen Szenario verbinden wir die Technologien jedes Blocks manuell mit require() . Das heißt, in index.js werden mindestens hundert zusätzliche Codezeilen angezeigt.


Zusätzliche Codezeilen, die hätten vermieden werden können, sind schlecht. Nicht verwendeter Code ist noch schlimmer. Was ist, wenn auf unserer Seite nur 10 der verfügbaren Blöcke oder 20 oder 53 verfügbar sind? Der Entwickler muss zusätzliche Arbeit leisten: Er muss sich genau darauf konzentrieren, welche Blöcke auf der Seite verwendet werden, und sie manuell verbinden und trennen, um unnötigen Code im endgültigen Bundle zu vermeiden.


GlĂĽcklicherweise kann diese Arbeit Webpack anvertraut werden.


Der optimale Aktionsalgorithmus zur Automatisierung dieses Prozesses:


  1. Wählen Sie aus dem vorhandenen HTML-Code Klassen aus, die der BEM-Benennung entsprechen.
  2. Rufen Sie basierend auf den Klassen die Liste der auf der Seite verwendeten BEM-Entitäten ab.
  3. Überprüfen Sie, ob Verzeichnisse verwendeter Blöcke, Elemente und Modifikatoren auf Neudefinitionsebenen vorhanden sind.
  4. Verbinden Sie die Technologie dieser Entitäten mit dem Projekt, indem Sie die entsprechenden require() Ausdrücke hinzufügen.

Zunächst habe ich mich entschlossen zu prüfen, ob es für diese Aufgabe fertige Bootloader gibt. Ich habe kein Modul gefunden, das alle notwendigen Funktionen in einer Flasche bietet. Aber ich bin auf bemdecl-to-fs-loader gestoßen , der BEM-Deklarationen in require() -Ausdrücke konvertiert. Es basiert auf Neudefinitionsebenen und Technologien, die in der Projektdateistruktur verfügbar sind.


BEM-Deklaration - Eine Liste der auf der Seite verwendeten BEM-Entitäten. Lesen Sie mehr darüber in der Dokumentation .

Ein Link fehlt - Konvertieren von HTML in ein Array von BEM-Entitäten. Diese Aufgabe wird vom Modul html2bemjson gelöst.


bemjson - Daten, die die Struktur der zukünftigen Seite widerspiegeln. Normalerweise werden sie von der bem-xjst-Vorlagen-Engine zum Erstellen von Seiten verwendet. Die Syntax von bemjson ähnelt der Syntax von Deklarationen, aber die Deklaration enthält nur eine Liste der verwendeten Entitäten, während bemjson auch deren Reihenfolge widerspiegelt.

bemjson ist keine Deklaration, daher konvertieren wir sie zuerst in das Deklarationsformat für die Übertragung an bemdecl-to-fs-loader. Verwenden Sie für diese Aufgabe das Modul aus dem SDK: bemjson-to-decl . Da es sich um reguläre NodeJS-Module handelt, nicht um Webpack-Loader, müssen Sie einen Wrapper-Loader erstellen. Danach können wir sie zum Konvertieren in Webpack verwenden.


Wir erhalten den folgenden Bootloader-Code:


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

Um die Installation des Bootloaders zu vereinfachen und in Zukunft Zeit zu sparen, habe ich das Modul auf NPM heruntergeladen.


Lassen Sie uns den Bootloader in unserem Projekt installieren und Änderungen an der Webpack-Konfiguration vornehmen:


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

Der bemdecl-to-fs-loader bootloader levels gibt an, welche Ăśberschreibungsebenen in welcher Reihenfolge verwendet werden sollen. Die extensions geben die Dateitechnologie-Erweiterungen an, die in unserem Projekt verwendet werden.


Anstatt Technologien manuell zu verbinden, fĂĽgen wir daher nur eine HTML-Datei hinzu. Alle notwendigen Konvertierungen werden automatisch durchgefĂĽhrt.


Ersetzen wir den Inhalt von index.js durch die Zeile:


 require('./index.html'); 

FĂĽhren Sie nun Webpack aus. Beim Zusammenbau wird die Linie angezeigt:


 [ BemEntityName { block: 'block' } ] 

Dies bedeutet, dass die Bildung der Erklärung erfolgreich war. Wir sehen uns direkt die Ausgabe von Webpack an:


  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 

Seite Screenshot


Wir haben ein Ergebnis erhalten, das mit dem vorherigen identisch ist, mit dem Unterschied, dass alle Blocktechnologien automatisch verbunden wurden. Im Moment reicht es aus, dem HTML-Code eine BEM-benannte Klasse hinzuzufĂĽgen, diesen HTML-Code mit require() und das entsprechende Verzeichnis mit den Verbindungstechnologien zu erstellen.


Wir haben also eine Dateistruktur, die der BEM-Methodik entspricht, sowie einen Mechanismus zum automatischen Verbinden von Blocktechnologien.


Ausgehend von den Mechanismen und Entitäten der Methodik haben wir eine äußerst einfache, aber effektive Webpack-Konfiguration erstellt. Ich hoffe, dieses Beispiel hilft jedem, der mit BEM vertraut wird, die Grundprinzipien für die Erstellung von BEM-Projekten besser zu verstehen.


NĂĽtzliche Links


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


All Articles