📜 ⬆️ ⬇️

Another way to use Webpack 4 and code sharing

Prehistory


It's no secret that with the release of Webpack 4, the code separation strategy has changed a lot. It is even better to say that it was re-invented, because The old approach just stopped working, and the new one is not clear how to use.


For those who are still not up to date, the webpack.optimize.CommonsChunkPlugin plugin is no longer available. Totally. Instead, it is suggested in the config to write the following :


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

It should work like magic. Those. now it's not we who tell the webpack what to do with a common chunk, but he will do everything himself, and maybe even better than us.


And happiness will come. Joke. Not really...


Basic preparations


Here is an example from the documentation:


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

The build will result in 3 files: 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 

Now, in order to run our web applications, we must, in one case, connect vendors ~ another ~ index.bundle.js and index.bundle.js, and in the second, vendors ~ another ~ index.bundle.js and another. bundle.js.


What is the problem?


The problem is in the name vendors ~ another ~ index .bundle.js. As long as we have less than three entry points, nothing terrible happens. Here everything seems logical - the bundle contains npm modules (they are the same vendors) and general modules for index and another. On each of the pages we connect 2 files and have no problems.


However, if we have three or more entry points, then new bundles (they are chunks) may be much more and we no longer know their numbers or names. Everything becomes even more fun if we also extract css into separate files. And this is a problem.


How to solve this problem?


After the completion of the webpack, we do not have any files that would contain information about which bundles on this or that page should be connected. And in what sequence.


However, in the output we can find the following lines:


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

In fact, this is almost what we need. Those. webpack knows what bundles are needed for each entry point, but for some reason he does not want to share this information with us.


Manifesto does not help us here. Yes, we know what such (vendors ~ another ~ index.bundle.js) bundle is. We know where he lies. But who needs it we do not know. Those. manifest is useless.


Then I decided that once the webpack knows the necessary information, then it may be possible to get it with the help of plug-ins. I did not find the finished ones and decided to write my own. And, just for the sake of demonstrating this plugin, I am writing this article.


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

In the file webpack.config. (Ts | js) we will add a new plugin:


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

and wait for the result. The result will be an entrypoints.json file with this content:


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

If extract-css is used, then besides the js section there will also be css.


The last thing that remains for us, when creating the HTML page, is to read the entrypoints.json file, find the desired entry point, connect the js and css files from the corresponding lists.


Problem solved


Something like this.


')

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


All Articles