Хабрахабр

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

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

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

module.exports = } // ...
}

Т.е. Это должно работать как магия. теперь не мы говорим 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. На каждую из страниц мы подключаем 2 файла и не имеем проблем. Здесь все кажется логичным — бандл содержит npm модули (они же vendors) и общие модули для index и another.

Все становится еще веселее, если мы еще и 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 файлы из соответствующих списков.

Как-то так.

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть