A annoying explanation where I try to be clear about what is going on here.
This documentation is for version 2.0.2.
This is the hypothetical configuration that is used for this documentation.
import WebpackMultiOutputPlugin from 'webpack-multi-output/plugin'
module.exports = {
entry: path.join(__dirname, 'src/index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.i18n$/,
loader: 'webpack-multi-output',
}
]
},
plugins: [
new WebpackMultiOutputPlugin({
values: ['en', 'fr', 'es'],
assets: {
filename: 'assets.json',
path: path.join(__dirname, 'dist'),
prettyPrint: true,
},
uglify: true,
})
]
}
The loader is very simple: it replaces any require for the given file extension in the configuration by a short code, containing the path to the file required. See the code
Input code:
const translations = require('hello.i18n')
Output:
exports.default = "WebpackMultiOutput-/path/to/file/hello.i18n-WebpackMultiOutput";
The plugin is a wee bit more complicated. Here is the basic idea of how the webpack compilation goes with the plugin and the loader:
- webpack resolves files and dependencies.
- when webpack loads a
.i18n
file, it receives the code showed above. - webpack wants to produce a
bundle.js
chunk. The plugin will duplicate this chunk to produce all the bundles we want:en.bundle.js
,fr.bundle.js
andes.bundle.js
. This is done in theoptimize-chunk-assets
phase of the compilation. See the code. - now for all bundles we've created, we need to replace the require we've transformed with the loader with the actual content of the file we want:
- in
en.bundle.js
we replaceexports.default = "WebpackMultiOutput-/path/to/file/hello.i18n-WebpackMultiOutput";
by the content of/path/to/file/en.i18n
- in
fr.bundle.js
we replaceexports.default = "WebpackMultiOutput-/path/to/file/hello.i18n-WebpackMultiOutput";
by the content of/path/to/file/fr.i18n
- in
es.bundle.js
we replaceexports.default = "WebpackMultiOutput-/path/to/file/hello.i18n-WebpackMultiOutput";
by the content of/path/to/file/es.i18n
- in
And voila! Now for more details:
- line 49 This part lets us iterate through all the chunks webpack is processing. This way, for each chunk (actually for each files of a chunk), we create all the versions we want.
- line 57 we check if we have something to replace in the chunk. if not, we don't produce multiple assets.
- line 64 we iterate through the values given in the configuration to create a new asset for each value.
- line 68 we process the newly created asset. This uses the processSource method, which will replace all content that we have to replace.
This (undocumented) hook for plugins is what is used to generate the code to lazy load chunks. It basically creates the script to create script tags. We are hacking into it so we can add the version of the asset to match the main bundle. To be more clear: let's say we load bundle.js
. If we use code-splitting, a 1.bundle.js
will be created. Whenever it's necessary, bundle.js
will create a new script tag to load 1.bundle.js
. Simple.
Now, we're not using bundle.js
but fr.bundle.js
we need to load fr.1.bundle.js
. To do that, we add a little function to add the correct prefix, and we also need to give the correct hash for the bundle (if we're using hashes in the filenames).
- line 117 The function we add in the script.
- line 191
__WEBPACK_MULTI_OUTPUT_VALUE__
is replaced by the value of the asset in theoptimize-chunk-assets
hook. - line 229
__WEBPACK_MULTI_OUTPUT_CHUNK_MAP__
is replaced by a map of the chunks and their hash in theoptimize-assets
hook.
- line 94 we iterate through the assets we've created to finish some stuff that we want to do once all plugins are done doing their stuff.
- line 95 replace the chunk map. See jsonp-script.
- line 102 we replace the hash for each assets so the hash are made from their actual content.
We used to use the assets-webpack-plugin to have a json file with our assets. Sadly, this plugin does not understand when there is multiple assets of the same extension so we had to replace it. This is done in the after-emit
hook.
- line 140 we iterate through the assets of the compilation, the create the asset file(s).
- line 150 if the assets filename in the configuration contains
[value]
, for example[value].assets.json
, we will create an asset file for each value. So in this case we'll haveen.assets.json
,fr.assets.json
andes.assets.json
. - line 158 if not, we'll have one asset file with all the files for all languages.
Random details and stuffs in the plugin or learned writing the plugin.
- line 38 here we set a simple property on the compilation object. This allows us to make sure the developers are not using the loader without the plugin, which would make no sense. The check is done here in the loader.
- line 102 the way we create the hash for the chunks is a bit lazy. The hash stuff in webpack actually has options to specify which algorithm and encoding you want to use, and the loader-utils package provides a method to get the hash from the content (used in the extract-text-webpack-plugin for example). But getting a simple hash just like that is enough (for now at least).
- line 225 since json is not modified with code minification, the function is pretty simple.
- If you write tests for a webpack loader of plugin, make sure to check for errors returned by webpack. Because even when throwing errors, webpack may produce your bundle. Never assume that because your bundles are here means everything went fine.