Webpack - Merging splitted bundles - javascript

I have one big bundled JS file which contains scripts that can run at the end of page loading.
In other words - I wanted to reduce the size of the first downloaded JS file by multiple entry points
So I've created two entry points :
module.exports = {
entry: {
a: "./scripts/entry.js",
b: "./scripts/windowEvents.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("common.js")
],
output: {
path: path.join(__dirname, '/scripts/bundle'),
filename: "[name].entry.js"
},
module: {
loaders: [
]
}
};
So now I have :
a.entry.js
b.entry.js
common.js.entry.js
And my HTML file looks like :
<script src="/scripts/bundle/common.js.entry.js"></script>
<script src="/scripts/bundle/a.entry.js"></script>
//Html renders fast
//bottom page script
<script src="/scripts/bundle/b.entry.js"></script>
But here is the problem :
The first two scripts sections makes 2 requests to the server. I don't want that
Question:
How can I merge the first two bundles into one bundle there will be only one request ? In other words I want to merge a.entry.js & common.js.
Something like that :
<script src="/scripts/bundle/Common_AND_Entry_a.js"></script>
//Html renders fast
//bottom page script
<script src="/scripts/bundle/b.entry.js"></script>

You can give the CommonsChunkPlugin the name of an existing chunk and it will add it to that instead of creating a new file. So in your case it would get the name a (as shown in Explicit vendor chunk):
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "a"
})
],
If you want to change the output filename as well, you can do that with the filename option, which accepts the same placeholders as output.filename. The following produces common_and_a.js and b.entry.js:
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "a",
filename: "common_and_[name].js"
})
],
For all available options see CommonsChunkPlugin - Options

Recently, I ran into this same issue, but time has passed and CommonsChunkPlugin has been deprecated in favour of SplitChunksPlugin.
Currently there is no way to achieve the intended behaviour by using only Webpack's included functionalities, so I wrote a plugin that solves this problem and I'm glad to share it with who may need it!
You can find it HERE.

Related

How to correct "preload key requests" performance problem on lighthouse with vue-cli and vue.config.js

I use Vue-CLI to build my vue.js project.
When I use lighthouse, I see big opportunities of performance: "Preload Key requests" with these warnings:
A preload was found for ".../js/chunk-vendors.505a9ffc.js" but was not used by the browser. Check that you are using the crossorigin attribute properly.
A preload was found for ".../js/app.a1661204.js" but was not used by the browser. Check that you are using the crossorigin attribute properly.
A preload was found for ".../css/chunk-vendors.89b73702.css" but was not used by the browser. Check that you are using the crossorigin attribute properly.
A preload was found for ".../css/app.9ea691b0.css" but was not used by the browser. Check that you are using the crossorigin attribute properly.
Does anyone have a solution to fix it with Vue-CLI and vue.config.js to modify the webpack config?
And can you explain the problem?
You should be able to configure crossOriginLoading in webpack config file.
module.exports = {
//...
output: {
crossOriginLoading: 'anonymous'
}
};
Not sure if you have a separate webpack config file or not, but if you are using default vue.config.js file, you can use configureWebpack option for the same:
module.exports = {
configureWebpack: {
output: {
crossOriginLoading: 'anonymous'
},
...
}
}
I've used preload-webpack-plugin (https://www.npmjs.com/package/preload-webpack-plugin)
const PreloadWebpackPlugin = require('preload-webpack-plugin');
// adds <style> tag with preload
module.exports = {
...,
plugins: [
...,
new PreloadWebpackPlugin({
rel: 'preload',
as: 'style',
include: ['main', 'profile', 'search'], // can be 'allChunks' or 'initial' or see more on npm page
fileBlacklist: [/\.map|.js/], // here may be chunks that you don't want to have preloaded
})
]
}

How to Find the Chunks for a Webpack Entry

I'm using webpack to modernize a legacy multiple-page ASP.NET Web Forms application. I've had pretty good success with it up till I tried using the SplitChunksPlugin to de-dupe my bundles using its chunks: 'all' option. This unfortunately makes a handful of extra JS bundles that all need to be included in script tags along with the original entry bundle. No surprise, the above linked doc states as much:
By default [the plugin] only affects on-demand chunks, because changing initial chunks would affect the script tags the HTML file should include to run the project.
But I would very much like to have those initial entry chunks split up, so I'm trying to find a way of getting all those extra chunks included in script tags. It seems the standard advice here is to use the HtmlWebpackPlugin to generate an HTML page with all the script tags included, but this doesn't work for me (at least in its default configuration) for at least two reasons:
This is a Web Forms project. One does not simply tamper with aspx files.
Even if I did find a way to generate some valid aspx files every time I ran webpack (I suppose that's doable, but here is the main difficulty); it seems HtmlWebpackPlugin only generates script tags for all the chunks, or a manually selected subset of them (using the chunks: [] option).
To elaborate on that second point, and get to my question -- I could do some manual analysis of the split chunks to build a dependency graph and manually include each one in the aspx, but that's obviously not a maintainable approach. I was hoping HtmlWebpackPlugin could offer some way of at least indicating that this chunk is ultimately used by this entry, or this entry uses these chunks, etc., but I have not found any such relationships present in its output.
Is there any way without going through hack-hoops to automatically determine which of the split chunks are dependencies of a given entry chunk?
Install this plugin with option to your webpack config:
npm install webpack-manifest-plugin --save-dev
// webpack.config.js
const ManifestPlugin = require('webpack-manifest-plugin')
concatMerge(configuration, {
// ...
plugins: [
new ManifestPlugin({
fileName: 'prod.manifest.json',
generate: (seed, files) => {
const entrypoints = new Set()
files.forEach(
(file) => ((file.chunk || {})._groups || []).forEach(
(group) => entrypoints.add(group)
)
)
const entries = [...entrypoints]
const entryArrayManifest = entries.reduce((acc, entry) => {
const name = (entry.options || {}).name
|| (entry.runtimeChunk || {}).name
const files = [].concat(
...(entry.chunks || []).map((chunk) => chunk.files)
).filter(Boolean)
return name ? {...acc, [name]: files} : acc
}, seed)
return entryArrayManifest
}
}),
],
}
It will generate a prod.manifest.json contain chunks for every entry or routes:
{
"entryOne": [
"main.common.d7791ce7a1e7ba394.css",
"main.common.d7791ce7a1e7ba394.js",
"main.entryOne.eb614be915641d465.js"
],
"a-route": [
"main.common.d7791ce7a1e7ba394.css",
"main.a-routes.14b91be915641d465.js"
]
// ...
}

Webpack's CommonChunksPlugin configuration with lazy loaded modules?

I'm using Webpack with CommonChunksPlugin .
My problem is with lazy loaded modules.
When I load lazy modules ( 0.js and 1.js in image) , Where each uses the Http Module , it seems that Http is embedded/repeated in EACH module :
The entries in my file are :
entry: {
bundle: aot ? "./main.aot.ts" : "./main.ts",
vendor: "./vendor",
},
With CommonsChunkPlugin configured as :
new webpack.optimize.CommonsChunkPlugin({
name: ["vendor"],
}),
— I get this result where you can see multiple http.js both in bundle & 1.js& 0.js.
With CommonsChunkPlugin configured with async ( as written in here) :
new webpack.optimize.CommonsChunkPlugin({
async: true,
children: true,
}),
— I get this result where you can see tha 0.js and 1.js are OK but the bundle.js now contains dups as vendor(?!?!)
Question:
I've read the docs But as you can see , I'm trying to do something without success.
How can I fix the config so that common chunks will be extracted and not be repeated ?
webpack.config
Was having the same issue as you are/were facing.
In my case, I've removed the name parameter from the configuration and set async to true and children to true as well.
Also, make sure that the CommonChunksPlugin is the last plugin in the array of plugins.

Webpack: How to load the same asset without knowing the querystring?

I have an entry array in my webpack config:
entry: {
'main': [
'webpack-hot-middleware/client?path=some-query'
'my-module/my-file',
]
Inside of my code (node_modules/my-module/my-file.js) I attempt to require that initial third party file.
var client = require('webpack-hot-middleware/client');
Because I don't require it with the same querystring, webpack treats it as a separate asset/module, and inlines webpack-hot-middleware/client twice in the output bundle. This means I'm working with a new instance of the code, while I want to access the original instance. I don't have access to the third party code so I need to do it in my own library.
Currently the only solution I have is to duplicate the query string:
entry: {
'main': [
'webpack-hot-middleware/client?path=some-query'
'my-module/my-file?path=some-query',
]
And then require it using the __resourceQuery exposed to every Webpack file:
var client = require('webpack-hot-middleware/client' + __resourceQuery);
This requires me to duplicate the query string into my module, which is undesired, especially because my module won't use the querystring params (and might want to use its own, which isn't allowed here).
You should be able to make this work with a webpack resolver alias: https://webpack.github.io/docs/configuration.html#resolve-alias

Webpack with two common chunks: one exported, one local

I would like to use Webpack in a multi-page application in such a way that some pre-determined dependencies are bundled into a "vendor" chunk and the rest of the dependencies is bundled into a "commons" chunk.
For example, assuming two entry points (effectively representing a different page each), pageA.js and pageB.js contain both this code (in EC6, processed via Babel), followed by their own code:
import $ from 'jquery';
require('bootstrap/dist/css/bootstrap.css');
import angular from 'angular';
import uitree from 'angular-ui-tree';
I'd like jQuery and Bootstrap to be bundled into a "vendor" chunk, and the rest (whatever that is) to be bundled into a "commons" chunk.
The objectives are:
I would like to be able to have another separate build that would be able to rely on that same vendor chunk, without it needing to re-include the vendor libraries (I would explicitly declare that set of vendor libraries, to make it available to any sub-build that needs it).
I would also like not to have to re-process the vendor chunk every time I make a change to a page's script.
Here is the configuration I've tried:
module.exports = {
entry : {
"vendor" : [ "jquery", "bootstrap" ],
"pageA" : "./src/pageA.js",
"pageB" : "./src/pageB.js"
},
output : {
path : path.join(__dirname, "./dest"),
filename : "[name].chunk.js"
},
module : {
loaders : [ {
test : /bootstrap\/js\//,
loader : 'imports?jQuery=jquery'
},
/* ... Some modules for angular and CSS processing ... */
{
test : /\.js?$/,
include : [ path.resolve(__dirname, "./src") ],
loader : 'babel',
query : {
presets : [ 'es2015' ]
}
}
/* ... Some other settings for the fonts ... */ ]
},
plugins : [
new webpack.ProvidePlugin({
$ : "jquery",
jQuery : "jquery"
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap : false,
mangle : false,
compress : false
}),
new CommonsChunkPlugin({
name : "vendor",
minChunks : Infinity
}),
new CommonsChunkPlugin({
name : "commons",
minChunks : 2
})
]
};
With the configuration above, I get jQuery and Bootstrap in vendor.chunk.js, as required, but the commons.chunk.js file is almost empty, all the rest of what's commonly used by pageA.js and pageB.js is then put into pageA.chunk.js and pageB.chunk.js (effectively duplicating that code).
If I swap the order of the last two plugins, commons.chunk.js now contains almost everything (unless what's actually specific to pageA.js and pageB.js), and vendor.chunk.js is almost empty:
plugins : [
// ...,
new CommonsChunkPlugin({
name : "commons",
minChunks : 2
}),
new CommonsChunkPlugin({
name : "vendor",
minChunks : Infinity
})
]
Is there a way to bundle a pre-defined list of libraries (e.g. [ "jquery", "jquery-ui", "bootstrap" ] into one particular chunk (in such a way that it can be used by completely independent scripts) and also have another common chunk for whatever else is in commonly used between the entry points?
The aim of all this would be to be able to build a completely separate piece of code for another page later, and tell it it doesn't need to re-bundle those pre-defined libraries.
Here is a diagram representing what I'm trying to achieve:
I would then use the generated scripts as follows on page A:
<script src="vendor.js"></script>
<script src="common.js"></script>
<script src="pageA.chunk.js"></script>
And on page C (built completely independently from pages A and B):
<script src="vendor.js"></script>
<script src="common2.js"></script>
<script src="pageC.chunk.js"></script>
(I am using Webpack 1.12.14.)
I have tried the solution suggested in the only answer so far. While this makes it indeed possible to separate the vendor chunk from the commons chunk, the vendor chunks (with the same definition) made from two separate builds generally cannot be swapped between each other. This does not make it possible to use only one of those vendor chunks across two builds (even though they are virtually the same).
I was working on something similar. I observed the behaviour you desire by having this configuration:
const entryPath = path.resolve(__dirname, "src", "modules"),
entryPoints = {
vendor: ["babel-polyfill", "react", "react-dom", "react-pure-render", "react-simpletabs", "react-redux", "redux", "redux-thunk"],
a: entryPath + "/a.js",
b: entryPath + "/b.js",
c: entryPath + "/c.js",
d: entryPath + "/d.js",
...
}, plugins = [
new CommonsChunkPlugin({
name: "commons",
filename: "commons.bundle.js",
minChunks: 2,
chunks: Object.keys(entryPoints).filter(key => key !== "vendor")
}),
new CommonsChunkPlugin("vendor", "vendor.bundle.js"),
new ExtractTextPlugin("[name].bundle.css", {
//allChunks: true
})
];
so this code above left in b.bundle.js still some import of React and other React libraries. after I added "fixed-data-table" to the vendor list, that disappeared and the only react source code there was was in vendor.bundle.js I assume that is what you were looking for? (in the end each vendor not listed in vendor list ended up in each own module.bundle.js or in commons.bundle.js if it was re-used in multiple bundles)
Regards
Jonas
As far as your request to be able to use the vendor chunk created in one webpack build with bundles from another build, the only way I have found to do this is through a combination of expose-loader and externals.
In my case, I successfully used this to bundle jQuery (and Bootstrap) into a "common.js" in one build, and then use that jQuery in modules belonging to another build, as follows:
1. Expose jQuery from build A chunk
module: {
loaders: [
{
// export jQuery globals for other modules to use as externals
test: require.resolve('jquery'),
loader: 'expose?$!expose?jQuery'
}
]
}
2. Consume jQuery in build B modules
externals: {
'jquery': 'jQuery'
}
Of course, the drawback to this is that you're bridging between the two sets of bundles by using manually-managed global variables, which you probably started using webpack to avoid :-)
However, I don't know of any other way at this point, given that every webpack build creates its own namespace of "private" IDs internally to reference modules, with no guarantee of stability or global unique-ness.

Categories