webpack 4 code splitting and var injection - javascript

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!

Related

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

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

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.

Webpack exclude entries for CommonsChunkPlugin

I am trying to set up webpack production configuration. All looks well. However, I realized that while using the commons chunk plugin, it covers all the files in common as expected. What I want to do is, separation of common library modules and common application modules. My config file is :
module.exports = {
entry: {
lib: ["react", "react-dom"],
app: "./ui-v2/app/app.js",
app2: "./ui-v2/app/app2.js"
},
output: {
path: path.join(__dirname, "target/ui/v2"),
filename: "/app/[name].[chunkhash].min.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.(png|jpg|svg)/,
loader: "file-loader?name=img/[name].[hash].[ext]"
// loaders: ["url", "image-webpack"]
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!sass-loader", {
publicPath: __dirname
})
},
{
test: /\.(woff|woff2|ttf|eot)$/,
loader: "file-loader?name=fonts/[name].[hash].[ext]"
}
]
},
plugins: [
clean,
new webpack.optimize.CommonsChunkPlugin("common", "app/common.[chunkhash].js"),
new webpack.ProvidePlugin({
React: "react",
ReactDOM: "react-dom",
$: "jquery",
_: "lodash"
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
sourceMap: true
},
mangle: {
except: ["exports", "import", "$", "_", "require", "React", "ReactDOM"]
}
}),
new ExtractTextPlugin("styles/[name].[contenthash].css"),
new Manifest()
]
}
Basically I have 3 modules in the app; app.js, app2.js and a common component user.js.
What I want to achieve is to bundle all library related files like react, react-dom, lodash, etc in a lib bundle, and common application components like user.js in a common bundle. In order to do this, I thought there might be an option to exclude the files that I don't want them to go to "common" file. If I use this output, what is the point for long term caching files for library bundles because whenever I get a common component in my project, they will go into the common bundle and the content hash will be different, but nothing changes in this library files like react, jquery, lodash, etc.
Anyway, what I have at the end of build process is everything still goes into the common bundle and lib has nothing and the file sizes are :
app.<hash>.min.js -> 3.05KB
app2.<hash>.min.js -> 3.05KB
lib.<hash>.min.js -> 165 Bytes (has almost nothing!)
common.<hash>.js -> 678 KB
Is there any way to achieve what I want or what would be the best approach to a production build in similar cases? Thank you!
Its because the first parameter for CommonsChunkPlugin is "common" where it should be "lib". The plugin picks up the entry with a name matching with the value of its first parameter.
A simple example config picked from webpack's wiki -
var webpack = require("webpack");
module.exports = {
entry: {
app: "./app.js",
vendor: ["jquery", "underscore", ...],
},
output: {
filename: "bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
]
};
Note that the "vendor" entry is again specified in CommonsChunkPlugin
You should check out Webpack's DLL Plugin.
https://github.com/webpack/webpack/blob/cb3d8e2097503c7245c5dda5b7a6e9d63421a72b/examples/dll/README.md
With this plugin you bundle up common 3rd party vendor dependencies such as React and friends in a DLL, which is essentially just a JSON Manifest that goes along with your requires wrapped in webpack context and cached to disk.
In your project code, you would have your shared components which depend on React and friends, and you would have your application code which depend on your shared components as well as react and friends.
Your project would incorporate the DLL Reference plugin as you can see here:
https://github.com/webpack/webpack/blob/cb3d8e2097503c7245c5dda5b7a6e9d63421a72b/examples/dll-user/README.md
This will see to it that your shared components and your application code pull React and other 3rd party modules from the same DLL bundle. This can help improve build times and the performance of the dev server and hot module reloading.

Categories

Resources