Reputation: 23732
I have a project using Webpack 4 and want to enable code splitting. I have this config to enable split chunks.
optimization: {
splitChunks: {
chunks: 'all'
},
}
When I run the build I get some files name:
about-d434910cfbfb3b1f4f52.js
billing-d434910cfbfb3b1f4f52.js
login-d434910cfbfb3b1f4f52.js
vendors~about~billing~login~~97fa390a-d434910cfbfb3b1f4f52.js
about.js
, billing.js
, and login.js
are my entry points. The vendor file contains jQuery which is used on these 3 pages. My understanding is that I need to write 2 script tags in my template like:
<script type="text/javascript" src="http://0.0.0.0:8000/assets/bundles/vendors~about~billing~login~~97fa390a-d434910cfbfb3b1f4f52.js" ></script>
<script type="text/javascript" src="http://0.0.0.0:8000/assets/bundles/about-d434910cfbfb3b1f4f52.js" ></script>
This makes sense but it's not clear to me how I'm supposed to know how to connect which vendor files with which entry point file. In this case there is only one vendor file, but my project has many more dependencies so in reality there may be 3 more. Not to mention these will change as code is changed.
In Require.js you would do this:
define(['jquery', 'my-module'], function($, myModule) {
...
});
and jQuery would be fetched from the server. In Webpack 4 this would be:
import $ from 'jquery';
import MyModule from 'my-module';
But when you do this in Webpack you must list the dependency bundle as a script tag. It will not go and download jQuery when imported.
I know Webpack has dynamic import which returns a promise:
import('lodash').then(_ => {
...
});
This is similar to Require.js, but is meant for segregating application functionality. This is not what I want to do. Perhaps it could work if import()
could take multiple module names and I just used that to import everything, but that definitely is not the way this feature is meant to be used according to the docs.
1) How are you supposed to know programmatically which files depend on which so you can render correct script tags?
2) Is there a way to use import
to get the behavior of downloading the module that AMD does when the module is not linked in the page already?
Upvotes: 8
Views: 2029
Reputation: 2059
If for some reason you can't use HtmlWebpackPlugin, for example in my case I'm working with PHP and generating the HTML completely on the backend, you can use the webpack-manifest-plugin and read the generated manifest to programmatically link only the needed files.
Please note that you will only load some initial JavaScript (in my case the one generated by the entrypoint), but then it's Webpack that will take care of linking the files generated by dynamic imports.
In order to generate a Manifest file with the entry points you can use the webpack configuration currently used in Create React App: https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/config/webpack.config.js#L672, by the way they also give some hints about this specific use case in the comments:
// Generate an asset manifest file with the following content: // - "files" key: Mapping of all asset filenames to their corresponding // output file so that tools can pick it up without having to parse // `index.html` // - "entrypoints" key: Array of files which are included in `index.html`, // can be used to reconstruct the HTML if necessary
Here is the configuration in case it will change in the future:
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
and then in the plugins
array:
new WebpackManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
Be careful that the entrypoints.main
part will not work if your entrypoint has a different name, for example in my case I had to change it to entrypoints.app
.
The generated manifest file will have the following structure:
{
"files": {
"app.js": "/js/app.9cdce93ada164d829885.js",
// [more generated files, including the ones generated by dynamic imports]
},
"entrypoints": [
"css/7741.bdbd58131904790cf1ce.css",
"js/app.9cdce93ada164d829885.js"
// [other files, for example the runtime file and/or the vendor file]
]
}
You will then need to link the assets with the language that you're using. You will probably need to separate the css files from the js files in order to link them in the correct way.
In my case I've created an app_assets()
method in PHP, that takes the extension as a parameter and returns the assets, so for example in my blade template I can do something like this for CSS:
@foreach(app_assets('css') as $link)
<link rel="stylesheet" href="{{secure_asset($link)}}" type="text/css" />
@endforeach
and this for JavaScript:
@foreach(app_assets('js') as $link)
<script src="{{secure_asset($link)}}" type="module"></script>
@endforeach
Upvotes: 1