Еще один способ использования Webpack 4 и разделение кода

от автора

Предыстория

Ни для кого не секрет, что с выходом Webpack 4 стратегия разделения кода сильно поменялась. Тут даже лучше сказать, что она была заново придумана, т.к. старый подход просто перестал работать, а новый не понятно как использовать.

Для тех, кто все еще не в курсе, плагина webpack.optimize.CommonsChunkPlugin больше нет. Совсем. Вместо этого предлагается в конфиге писать следующее:

module.exports = {   // ...   optimization: {     splitChunks: {       chunks: "all"     }   }   // ... }

Это должно работать как магия. Т.е. теперь не мы говорим webpack’у что сделать общим чанком, а он сам все сделает, да еще может даже и лучше нас.

И наступит счастье. Шутка. На самом деле нет…

Базовые приготовления

Вот пример из документации:

module.exports = {   mode: 'development',   entry: {     index: './src/index.js',     another: './src/another-module.js'   },   output: {     filename: '[name].bundle.js',     path: path.resolve(__dirname, 'dist')   },   optimization: {     splitChunks: {       chunks: 'all'     }   } };

Результатом сборки будут 3 файла: another.bundle.js, index.bundle.js, vendors~another~index.bundle.js

Hash: ac2ac6042ebb4f20ee54 Version: webpack 4.7.0 Time: 316ms                           Asset      Size                 Chunks             Chunk Names               another.bundle.js  5.95 KiB                another  [emitted]  another                 index.bundle.js  5.89 KiB                  index  [emitted]  index vendors~another~index.bundle.js   547 KiB  vendors~another~index  [emitted]  vendors~another~index Entrypoint index = vendors~another~index.bundle.js index.bundle.js Entrypoint another = vendors~another~index.bundle.js another.bundle.js [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 489 bytes {vendors~another~index} [built] [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {vendors~another~index} [built] [./src/another-module.js] 88 bytes {another} [built] [./src/index.js] 86 bytes {index} [built]     + 1 hidden module

Теперь, для того, чтобы запустить наши веб приложения, мы, в одном случае, должны подключить vendors~another~index.bundle.js и index.bundle.js, а во втором vendors~another~index.bundle.js и another.bundle.js.

В чем проблема?

Проблема в имени vendors~another~index.bundle.js. Пока у нас меньше трех точек входа, ничего страшного не происходит. Здесь все кажется логичным — бандл содержит npm модули (они же vendors) и общие модули для index и another. На каждую из страниц мы подключаем 2 файла и не имеем проблем.

Однако если у нас три и более точки входа, то новых бандлов (они же чанки) может быть куда больше и мы уже не знаем ни их количества, ни имен. Все становится еще веселее, если мы еще и css извлекаем в отдельные файлы. И это проблема.

Как решить эту проблему?

После завершения работы webpack у нас нет никаких файлов, которые содержали бы в себе информацию о том, какие именно бандлы на той или иной странице надо подключать. И в какой последовательности.

Однако в output’е мы можем найти вот такие строки:

Entrypoint index = vendors~another~index.bundle.js index.bundle.js Entrypoint another = vendors~another~index.bundle.js another.bundle.js

На самом деле это почти то, что нам надо. Т.е. webpack прекрасно знает какие бандлы нужны для каждой точки входа, но почему-то сам не хочет этой информацией с нами делиться.

Манифест нам здесь не помогает. Да, мы знаем что такой (vendors~another~index.bundle.js) бандл есть. Мы знаем где он лежит. Но кому он нужен не знаем. Т.е. манифест бесполезен.

Тогда я решил что раз webpack знает нужную информацию, то ее возможно получится достать с помощью плагинов. Готовых я не нашел и решил написать свой. И, только ради демонстрации этого плагина, я и пишу эту статью.

import * as webpack from "webpack";  export interface IChunkDescription {     readonly id: string | number;     readonly name: string;     readonly files: string[]; }  export interface IEntrypointsPluginOptions {     readonly filename: string;     readonly replacer?: (key: string, value: any) => any;     readonly space?: string | number;     readonly filter?: (chunk: IChunkDescription) => boolean; }  export default class EntrypointsPlugin {     private readonly options: IEntrypointsPluginOptions;      public constructor(options: IEntrypointsPluginOptions) {         this.options = Object.assign<IEntrypointsPluginOptions, IEntrypointsPluginOptions>({             filename: "entrypoints.json",             replacer: null,             space: null,             filter: null         }, options);     }      public apply(compiler: webpack.Compiler): void {         compiler.hooks.emit.tap("entrypoints", (compilation: webpack.compilation.Compilation) => {             let data = {};             let entrypoints = {};              const filter = this.options.filter;             const publicPath = compilation.compiler.options.output.publicPath;              for (let [key, value] of compilation.entrypoints.entries()) {                 const chunks: IChunkDescription[] = value.chunks.map(data => {                     const chunk: IChunkDescription = {                         id: data.id,                         name: data.name,                         files: data.files                     };                     return filter == null || filter(chunk) ? chunk : null;                 });                  const files = ([] as string[]).concat(...chunks.filter(c => c != null)                   .map(c => c.files.map(f => publicPath + f)));                 const js = files.filter(f => /.js/.test(f) && !/.js.map/.test(f));                 const css = files.filter(f => /.css/.test(f) && !/.css.map/.test(f));                  let entrypoint = {};                 if (js.length) entrypoint["js"] = js;                 if (css.length) entrypoint["css"] = css;                  data[key] = entrypoint;             }             const json = JSON.stringify(data, this.options.replacer, this.options.space);             compilation.assets[this.options.filename] = {                 source: () => json,                 size: () => json.length             };         });     } }

В файле webpack.config.(ts|js) добавим новый плагин:

plugins: [   new EntrypointsPlugin({     filename: "entrypoints.json",     space: 2   }) ]

и дождемся результата. Результатом будет файл entrypoints.json с вот таким содержанием:

{   "index": {     "js": ["vendors~another~index.bundle.js", "index.bundle.js"]   },   "another": {     "js": ["vendors~another~index.bundle.js", "another.bundle.js"]   } }

Если используется extract-css, то кроме секции js будет еще и css.

Последнее, что нам остается, при формировании HTML страницы, это прочитать файл entrypoints.json, найти нужную точку входа, подключить js и css файлы из соответствующих списков.

Проблема решена

Как-то так.


ссылка на оригинал статьи https://habr.com/post/423693/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *