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

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?

Related

What is the difference between resolve.plugins and plugins field in webpack configuration?

From webpack documentation I found, that plugins can be connected either with plugins field
module.exports = {
...
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
...
};
or in resolve.plugins:
...
var DirectoryNamedWebpackPlugin = require("directory-named-webpack-plugin");
resolve: {
plugins: [
new DirectoryNamedWebpackPlugin()
]
}
...
What is the difference between between these plugins, so they have to be connected differently?
The difference between resolve.plugins and plugins is that the plugins configured in resolve.plugins work within the module's resolution rules. plugins are configured to extend webpack and are combined with loaders for maximum control
resolve.plugins
Plugins

Code Splitting loads all bundles into html

How does code splitting know which chunks go with which entry? For example I have two pages, on page uses AgGrid and another page uses ReactTable for example.
I code split and get vendor.1.bundle.js (contains AgGrid) and vendor.2.bundle.js( contains ReactTable)
I set up my entry:
entry: {
index: './src/index.jsx',
review: './src/review.jsx'
},
optimization: {
splitChunks: {
chunks: 'all'
},
// plugins
new HtmlWebpackPlugin({
filename: 'index.html',
inject: true,
excludeChunks: ['review'],
template: 'src/pages/index.html'
}),
new HtmlWebpackPlugin({
filename: 'review.html',
inject: true,
excludeChunks: ['index'],
template: 'src/pages/review.html'
}),
What I end up with is two html files with both vendor files instead of one vendor file containing the deps needed per page.
Shouldn't index which uses AgGrid only load vendor.1.bundle.js and review.html load only vendor.1.html? I thought that was the point of code splitting. Otherwise why not just make 1 huge shared bundle between both pages, I think that would defeat the point. Not sure what I'm doing wrong in this setup however.

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!

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