Webpack Code Splitting: Does it do anything? Seems like no effect - javascript

new to webpack and react here. I followed this medium article to create code splitting in react router. It seems like it has no effect though because my app still has to load the whole bundle.js file synchronously on the initial page load. Any tips for reducing this load time? bundle.js is 2.2mb in dev but prod is about 400kb at the moment after uglifying it.
Simulating a regular 3G connection on network tab
router.js
export default [
{
path: '/',
component: App,
childRoutes: [
{
path: 'signup',
getComponent(location, cb) {
System.import('./modules/App/components/Authentication/Login.js')
.then(loadRoute(cb))
.catch(errorLoading);
}
}
]
}
]

Try these plugins to reduce your duplicate codes
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin()
],
Deduce plugin will find duplicate files and codes and merge them into single unit.
Uglify plugin will uglify your code in production.

So I went through the webpack docs and used several plugins. Managed to get the file sizes down
from 2.2mb to 92kb and speed up the loading times by 10x.
Here is my webpack.config file now.
module.exports = {
entry: {
js: [ './src/index.js' ],
vendor: [
'react', 'react-router', 'react-redux', 'toastr', 'lodash'
]
},
output: {
path: path.join(__dirname, '/dist/prod'),
publicPath: '/dist/prod',
filename: 'bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.js',
minChunks: Infinity,
}),
new ExtractTextPlugin("styles.css"),
new webpack.optimize.DedupePlugin(),
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.js$|\.html$/,
threshold: 10240,
minRatio: 0.8
}),
new webpack.optimize.UglifyJsPlugin(),
],
module: {
rules: ...
}
}
EDIT: After moving fonts from google to local fonts folder, removing duplicate font calls from libraries like simple-grid and using #font-face, managed to really cut down the load times.
Now 5.5s on regular 3G compared to 27s before. 80+% reduction in load-times.

Related

Migration from webpack4 -> webpack5, bundled file will have a faulty url when it gets requested?

I'm currently working on a migration from webpack4 to webpack5. I have problems with the URL to the bundled JS (in my case client.js).
This request should be two different ones. Here is an image from how it was with webpack4:
This is the entry for the client:
// ...
name: 'client',
target: 'web',
devtool: 'source-map',
entry: {
client: ['#babel/polyfill', './src/client.js'],
},
// ...
This is the output:
// ...
output: {
path: resolvePath(BUILD_DIR, 'public/assets'),
publicPath: '/assets/',
pathinfo: isVerbose,
filename: isDebug ? '[name].js' : '[name].[chunkhash:8].js',
chunkFilename: isDebug ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js',
},
// ...
I have also set organization.splitChunks.cacheGroups as follows:
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: 'initial',
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
},
},
},
},
Since I was not the person who initially did this config file it's kind of hard to debug, I'm shooting wild here. And the file is fairly large, I've just pasted the bits I believe are important. If you think the issue could be because of another configuration property, feel free to share.
Anyone know why this is happening and why this happens?
Thanks in advance!
#AKX (Migration from webpack4 -> webpack5, bundled file will have a faulty url when it gets requested?)
You certainly guided me to the problem here. After fixing this error I accidentally created a nested array in chunk-manifest.json. The content should be:
{
"client": [
"/assets/vendors.chunk.js",
"/assets/client.js"
]
}
Instead I screwed up while converting the Set to an array (to be able to run filter on it) so the content was instead:
{
"client": [
[
"/assets/vendors.chunk.js",
"/assets/client.js"
]
]
}
The nested array resulted in this concatenation so the URL went with a comma instead as two different sources.

webpack 4 code splitting and var injection

I try to get code splitting working with webpack 4 (previous config with webpack 3 worked correctly)
I created a repository to easily reproduce my problem: https://github.com/iamdey/RD-webpack4-code-split
I generaly get stuck on the following error because I want code splitting and babel-polyfill in vendor bundle:
ReferenceError: regeneratorRuntime is not defined
The config
Here is my webpack config:
{
entry: {
app: ['./src/index.js'],
vendor: [
'babel-polyfill',
'moment',
],
},
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].[chunkhash].js',
},
plugins: [
new HtmlWebpackPlugin({
chunksSortMode: (chunk1, chunk2) => {
// make sure babel-polyfill in vendors is loaded before app
const order = ['manifest', 'vendor', 'app'];
const order1 = order.indexOf(chunk1.names[0]);
const order2 = order.indexOf(chunk2.names[0]);
return order1 - order2;
},
}),
],
optimization: {
runtimeChunk: {
name: 'manifest',
},
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true,
},
},
},
}
}
The problem
When debugging we see that vendor is loaded right after manifest and finally app.
But app is executed before vendor.
In case I remove the splitChunks options, just like before the manifest then the vendor is loaded but vendor is executed directly before loading app.
In this case the problem is the split has a bad effect: chunks are duplicated in vendor and app.
What to do ?
Here are the options I have:
put polyfill in app bundle instead of vendor bundle: I don't want that
leave webpack do the code splitting itself: I don't want that because in real world I want a very long caching even between releases on vendor and keep app as small as possible
the webpack config is incorrect: Please tell me :)
It probably is a bug: Cool, I'll open an issue asap
Let's start by having the polyfill loaded and executed as part of each entry point (as seen in the babel-polyfill doc) so that it looks like this
app: ['babel-polyfill', './src/index.js'],
vendor: ['moment'],
Here's the output after a npm run start2:
Asset Size Chunks Chunk Names
app.53452b510a24e3c11f03.js 419 KiB app [emitted] app
manifest.16093f9bf9de1cb39c92.js 5.31 KiB manifest [emitted] manifest
vendor.e3f405ffdcf40fa83b3a.js 558 KiB vendor [emitted] vendor
What I understand is setting babel-polyfill in entry.app tells webpack that the polyfill is required by the app. And package defined in vendor tells splitChunk plugin what packages to bundle in the cache group.
Given my understanding from the split-chunks-plugin doc
The test option controls which modules are selected by this cache group. Omitting it selects all modules.
So removing test from cacheGroup option will result that all code will be moved to vendor bundle.
Knowing that we have 2 solutions.
1/ Using a workaround and duplicate loaded script
{
entry: {
app: [
'babel-polyfill',
'./src/index.js',
],
vendor: [
'babel-polyfill',
'moment',
],
},
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true,
},
},
},
runtimeChunk: {
name: 'manifest',
},
},
}
So we tell webpack that babel-polyfill is required by app and we tell splitChunks that babel-polyfill and moment are vendors to use in cache group.
2/ Use import instead of script loader
In index.js
import 'babel-polyfill';
import moment from 'moment';
// ...
webpack.config.js
{
entry: {
app: [
'./src/index.js',
],
vendor: [
'babel-polyfill',
'moment',
],
},
// ...
This make sure the polyfill is required by the app.
Hope it helps!

Webpack 3 and CommonsChunkPlugin - Exclude specific entry point from requiring Vendor

I'm working on a large piece of business software which is slowly being migrated to use Webpack on all pages. I want to start incorporating some webpack-bundled helper typescript code into pages which haven't yet started using webpack - they include scripts the old-fashioned way.
Here are the relevant parts of the config:
entry: {
vendor: [
// All vendor scripts go here
],
// All entry points for 'webpack' pages...
// A special entry point that I want to inject into non-webpack pages
es5Compatibility: [
'Utility/ES5Compatibility.ts'
]
},
// ...
output: {
path: path.join(__dirname, 'Scripts/bundle'),
filename: '[name].[chunkhash].bundle.js',
chunkFilename: '[id].chunk.js'
},
plugins: [
// ...
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity
})
// This should NOT get injected into non-webpack pages, as all of the
// vendor dependencies already exist there
new HtmlWebpackPlugin({
chunks: ['vendor'],
template: 'bundleTemplate.ejs',
inject: false,
filename: '_VendorBundle.html'
}),
// This should get injected into non-webpack pages WITHOUT requiring
// the above bundle to be included
new HtmlWebpackPlugin({
chunks: ['es5Compatibility'],
template: 'bundleTemplate.ejs',
inject: false,
filename: '_ES5CompatibilityBundle.html'
}),
// ...
new webpack.HashedModuleIdsPlugin(),
]
The problem is, when the _ES5CompatibilityBundle.html is included in a page without the VendorBundle.html, I get the following Javascript error as Webpack expects that the vendor bundle was included:
Uncaught ReferenceError: webpackJsonp is not defined
How can I tell Webpack to bundle the ES5 compatibility bundle as a 'self-contained' bundle, while retaining the commons chunk functionality for the webpack pages?

How to add a js file with webpack?

I was reading this webpack tutorial:
https://webpack.github.io/docs/usage.html
It says it bundles the src files and node_modules. If I want to add another .js file there, how can I do this? This is a thirdpartyjs file that is not part of the source and not part of the node_modules files. This is my current webpack.config.js:
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./app/app.js'
],
output: {
path: path.resolve(__dirname, "dist"),
publicPath: "/dist/",
filename: "dist.js",
sourceMapFilename: "dist.map"
},
devtool: 'source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
],
module: {
loaders: [{
loader: 'babel',
exclude: /node_modules/
}]
},
devServer: {
inline: true
},
node: {
fs: "empty"
},
watch: false
}
The start point for code is the entry field in config. In your config entry point is the list of files. Webpack gets all, resolve their dependencies and output in one file.
You have two options for adding third party script:
add the file path to entry list before app.js
require this file from app.js
In response to Dmitry's answer:
add the file path to entry list before app.js
This has the effect that you will get a bundled .js file for each entry point, which you might not want.
require this file from app.js
You might not have access to app.js if it is written dynamically, or for whatever reason you might not want to edit app.js.
Another option:
You can use webpack-inject-plugin to inject any JS code as string into the resulting .js bundle created by webpack. This way you can read the File you want to inject as a string (e.g. fs.readFile in nodejs) and inject it with the plugin.
Another solution but without using any extra plugins:
//Webpack.config.js
entry: {
main: './src/index',
/**
/* object is passed to load script at global scope and exec immediately
/* but if you don't need then simply do:
/* myCustomScriptEntry: './src/myCustomScript'
*/
myCustomScriptEntry: {
import: './src/myCustomScript',
library: {
name: 'myCustomScriptEntry',
type: 'var',
},
},
},
new HtmlWebpackPlugin({
template: './public/index.html',
excludeChunks: ['myCustomScriptEntry'], //exclude it from being autoreferenced in script tag
favicon: './public/favicon.svg',
title: 'Alida',
}),
and
//index.html
<script type="text/javascript" src="<%= compilation.namedChunks.get('myCustomScriptEntry').files[0] %>"></script>

How to polyfill fetch and promise with webpack 2?

How to polyfill fetch and promise for Webpack 2?
I have a lot of entry points, so Webpack 1-way to add them before each entry point is not desired solution.
Regardless of how many entry points you have, you should have a separate file for your vendor files, such as frameworks (react, angular, whatevs) and any libraries you always need but are rarely going to change. You want those as a separate bundle so you can cache it. That bundle should always be loaded. Anything you include in that bundle will always be available but never repeated in your chunks if you use it with the commonChunksPlugin.
Here's a sample from an app I've done (just showing relevant config options):
module.exports = {
entry: {
client: 'client',
vendor: [
'react',
'react-addons-shallow-compare',
'react-addons-transition-group',
'react-dom',
'whatwg-fetch'
]
},
output: {
path: `${__dirname}/dist`,
filename: '[name].js',
publicPath: '/build/'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
})
]
}
Maybe I'm not understanding correctly, but couldn't you just add babel-polyfill before the rest of your entry points in your webpack config?
module.exports = {
entry: ['babel-polyfill', './app/js', '/app/js/whatever']
};

Categories

Resources