Using CommonsChunkPlugin without always needing to define webpackJsonp? - javascript

I have a project that makes use of a large bundle (dexie.js not that it's important to this question), that I'd like to 'split out' into it's own bundle that I can include manually, because it's only needed in a few of my entry point scripts.
Just so that you know: my webpack config uses multiple modules, with multiple (6) entry points, so this is a cut-down sample of my webpack.config.js:
{
context: path.join(__dirname, 'src'),
entry: {
'js/contentscript.js' : './js/contentscript.js',
'js/background.js' : './js/background.js',
'js/popup.js' : './js/popup.js',
'js/options.js' : './js/options.js',
},
output: {
path: path.join(__dirname, 'dist'),
filename: "[name]"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "dexie",
filename: "js/dexie.js",
minChunks: function (module) {
// this assumes your vendor imports exist in the node_modules directory
return module.context && module.context.includes("node_modules/dexie");
}
}),
... // other non relevant plugins here
]
}
The problem is that I have no typical 'vendor' nor 'common' requirement, like many other projects to. It's only in a few cases that I want to include the js/dexie.js bundle.
eg. in options.html, I have this:
<script src="js/dexie.js"></script>
<script src="js/options.js"></script>
...but I do not want it to be used for popup.html, which I want to keep as this:
<script src="js/popup.js"></script>
And since this is also a WebExtensions project, I definitely don't want my content scripts to need it!
The problem is, that when I open popup.js, I get the error: Uncaught ReferenceError: webpackJsonp is not defined.
Is there any way -- without splitting this into more webpack modules -- so that the CommonsChunkPlugin will play nice with the entry points so that only the ones I want are affected by it?
The only solution I can think of, is to make another CommonsChunkPlugin that acts in the way that 'vendor' and 'common' is typically used. That is:
new webpack.optimize.CommonsChunkPlugin({
name: "dexie",
filename: "js/dexie.js",
minChunks: function (module) {
// this assumes your vendor imports exist in the node_modules directory
return module.context && module.context.includes("node_modules/dexie");
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
filename: "js/manifest.js",
minChunks: Infinity
}),
Unfortunately, this means I need to include js/manifest.js in all my scripts, and since this is a WebExtension, then that means I have to inject it into each content page...IMHO this is a terrible solution.
Any ideas on how to improve this? Am I using CommonsChunkPlugin incorrectly? Is there a better module I should be using? (I'm actually still coming to grips with webpack!)

First of all, using 'js/xxx.js' as an entry name is not a good way.
If your options.js need dexie.js and popup.js do not need it.
You can try to change files like below.
webpack.config.js
entry: {
'vendor' : ['dexie'],
'contentscript' : './js/contentscript.js',
'background' : './js/background.js',
'popup' : './js/popup.js',
'options' : './js/options.js',
},
resolve: {
alias: {
'dexie':'...'
}
},
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity
}),
Like I said before, CommonChunksPlugin will automatically help u extract common dependencies, in this case you dont need write a function of minChunks to indicate dependencies.
options.html
<script src="js/vendor.js"></script>
<script src="js/options.js"></script>
popup.html
<script src="js/popup.js"></script>

I stumbled across this excellent answer by #prograhammer: https://stackoverflow.com/a/40416826/125525
In it he made reference to the Externals plugin, which I'd not heard of before, and it neatly solves my problems. This is the description from the webpack site:
For example, to include jQuery from a CDN instead of bundling it:
index.html
<script
src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous">
</script>
webpack.config.js
externals: {
jquery: 'jQuery'
}
This leaves any dependent modules unchanged, i.e. the code shown below
will still work:
import $ from 'jquery';
$('.my-element').animate(...);

Related

Webpack bundles my files in the wrong order (CommonsChunkPlugin)

What I want is to bundle my JavaScript vendor files in a specific order via CommonsChunkPlugin from Webpack.
I'm using the CommonsChunkPlugin for Webpack. The usage from the official documentation is straight forward and easy. It works as intended but I believe the plugin is bundling my files in alphabetical order (could be wrong). There are no options for the plugin to specify the order they should be bundled.
Note: For those who are not familiar with Bootstrap 4, it currently
requires a JavaScript library dependency called Tether.
Tether must be loaded before Bootstrap.
webpack.config.js
module.exports = {
entry: {
app: './app.jsx',
vendor: ['jquery', 'tether', 'bootstrap', 'wowjs'],
},
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin(),
],
};
Two things are happening here:
vendor.bundle.js contains bootstrap, jquery, tether,
wowjs
bundle.js contains the rest of my application
Bundling order:
correct: jquery, tether, bootstrap, wowjs
incorrect: bootstrap, jquery, tether, wowjs
Notice in my webpack.config.js I ordered them exactly as they should but they are bundled in the incorrect order. It doesn't matter if I rearrange them randomly the result is the same.
After I use Webpack to build my application, the vendor.bundle.js shows me the incorrect order.
I know they're bundled incorrectly cause Chrome Dev. Tools tell me there are dependency issues. When I view the file through the tool and my IDE, it is bundled in the incorrect order.
My other approach also resulted in the same issue
I also tried import and require in my entry file (in this case, app.jsx) without the use of the CommonChunkPlugin and that also loads my JavaScript libraries in alphabetical order for some reason.
webpack.config.js
module.exports = {
entry: './app.jsx',
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
app.jsx (entry)
import './node_modules/jquery/dist/jquery.min';
import './node_modules/tether/dist/js/tether.min';
import './node_modules/bootstrap/dist/js/bootstrap.min';
import './node_modules/wowjs/dist/wow.min';
or
require('./node_modules/jquery/dist/jquery.min');
require('./node_modules/tether/dist/js/tether.min');
require('./node_modules/bootstrap/dist/js/bootstrap.min');
require('./node_modules/wowjs/dist/wow.min');
The result?
Bootstrap > jQuery > Tether > wowjs
How do I load my vendor files in the correct order?
Success!
webpack.config.js
module.exports = {
entry: {
app: './app.jsx',
vendor: [
"script-loader!uglify-loader!jquery",
"script-loader!uglify-loader!tether",
"script-loader!uglify-loader!bootstrap",
"script-loader!uglify-loader!wowjs",
]
},
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin(),
],
};
What magic is happening here?
Webpack creates vendor.bundle.js by minifying & bundling my vendor
files which now execute in the global context.
Webpack creates bundle.js with all of its application code
entry file (app.jsx in this case)
import './script';
This script is just custom JavaScript that uses jQuery, Bootstrap, Tether and wowjs. It executes after vendor.bundle.js, allowing it to run successfully.
A mistake I made trying to execute my script.js was that I thought it had to be in the global context. So I imported it with script-loader like this: import './script-loader!script';. In the end, you don't need to because if you're importing through your entry file it will end up in the bundle file regardless.
Everything is all good.
Thanks #Ivan for the script-loader suggestion. I also noticed that the CommonsChunkPlugin was pulling the non-minified vendor versions so I chained uglify-loader into the process.
Although, I do believe some .min.js are created differently to get rid of extra bloat. Though that is for me to figure out. Thanks!
You can try https://webpack.js.org/guides/shimming/#script-loader - it looks like it will execute scripts in order and in global context.
Worked with htmlWebpackPlugin from official tutorials and switched the order form entry key. ( vendor then app )
In webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
vendor: [
'angular'
],
app: [
'./src/index.js',
'./src/users/users.controller.js',
'./src/users/users.directive.js',
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: './src/index-dev.html'
}),
new webpack.NamedModulesPlugin()
...
}
Now in the generated index.html file I have the correct order
<script src='vendor.bundle.js'></script>
<script src='app.bundle.js'></scrip
This worked for me https://www.npmjs.com/package/webpack-cascade-optimizer-plugin
const CascadeOptimizer = require('webpack-cascade-optimizer-plugin');
module.exports = {
entry: {
app: './app.jsx',
vendor: ['jquery', 'tether', 'bootstrap', 'wowjs'],
},
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin(),
new CascadeOptimizer({
fileOrder: ['jquery', 'tether', 'bootstrap', 'wowjs']
})
],
};

How can I build a webpack bundle that imports a module from another entry point bundle?

I'm trying to generate a second webpack bundle that's dependent on another bundle. Every page needs bundle-one, but only some pages need bundle-two.
For instance, let's say I have the following entry point scripts (these are trivial examples, just using them to get the point across):
bundle-one.js
import $ from 'jquery';
$(document).data('key', 'value');
bundle-two.js
import $ from 'jquery';
const value = $(document).data('key');
I know that I can use CommonsChunkPlugin to generate a commons.js file containing the WebPack loader and jQuery, but that would require loading both commons.js and bundle-one.js on every page, even when I don't need to load bundle-two.js.
So, basically: is there a way to build bundle-one.js as the "main" JavaScript bundle for all my pages, and then have bundle-two.js setup to load jQuery from bundle-one.js?
Option 1
Have two separate Webpack configs, one for each bundle. In your first bundle, expose jQuery as a global variable when you first require it using the expose-loader:
loaders: [
{ test: require.resolve('jquery'), loader: 'expose?jQuery!expose?$' }
]
Then, in your second bundle config, tell Webpack that jQuery is "external" and shouldn't be bundled in with the rest of the code:
{
externals: {
// require("jquery") is external and available
// on the global var jQuery
"jquery": "jQuery"
}
}
This way the first bundle exposes jQuery as a global variable, then the second bundle looks for that global variable instead of including the source.
Option 2
I'm not sure if this will work, but the CommonsChunkPlugin documentation says you can specify the name config option as an existing chunk. You try setting the name to your bundle1 entry point chunk name, and in theory jQuery (and other libs required across all chunks) will be built into bundle 1, and not bundle 2.
You can do this in the following way using the provide plugin -
//webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: '[name].js'
},
externals: {
jquery: 'jQuery'
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]
};
This can be useful for refactoring legacy code with a lot of different files referencing $.

Webpack build error

Having some issues with webpack. It builds fine but when I open my site, i get: Getting error: "Uncaught ReferenceError: webpackJsonp is not defined"
I believe my CommonsChunkPlugin is running before my app is bundled?
It may help to know I have my config in src/config/webpack.config.js which is building to dist/js/.
Have read https://medium.com/#MarkEwersDev/note-to-self-if-you-ever-get-this-uncaught-referenceerror-webpackjsonp-is-not-defined-message-and-d354f5c4d335#.9cysuil5p and https://github.com/webpack/webpack/issues/368 but neither seem to help unless I am missing something.
devtool: 'source-map',
entry: {
vendor: [
'react', 'react-dom', 'react-router', 'react-helmet', 'react-redux', 'moment-timezone', 'cookies-js', 'superagent', 'classnames', 'es6-promise'
],
app: [
'./src/client/entry',
'./scss/main.scss',
]
}
output:{
path: __dirname + '../../dist/js',
filename: 'app.js'
}
plugins:[
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'),
new ExtractTextPlugin('../css/app.css', {
allChunks: true
}),
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: true
},
output: {
comments: false
}
}),
...persistentPlugins
],
The webpackJsonp function is defined by the commons chunk, so you must always put the commons chunk (vendor.js in your case) first when writing <script> tags. You also can't use <script async>.
Another possibility is that your vendor chunk is being overwritten by the entry chunk as you set output.filename to a constant string. Try naming it [name].js instead of app.js.
I'm facing the same problem on my production environment. I don't know why, but when I upload just the bundle + vendors, built with the production webpack config, I get the same error. I just solved this error restarting the node application. But I know that it's not a good approach.
I'm using exactly the same plugins on my webpack production config as you, and I'm quite sure that the reason is one of the webpack optimization plugins.
My suggestion is:
Try to comment one by one of those optimization plugins (AggressiveMergingPlugin, DedupePlugin and UglifyJsPlugin), and you might find out which one is causing the problem.
I can't test it right now because it just happens on my production environment and I can't stop/test it at this time. But, if it works for you, please let me know :)

How to bundle vendor scripts separately and require them as needed with Webpack?

I'm trying to do something that I believe should be possible, but I really can't understand how to do it just from the webpack documentation.
I am writing a JavaScript library with several modules that may or not depend on each other. On top of that, jQuery is used by all modules and some of them may need jQuery plugins. This library will then be used on several different websites which may require some or all modules.
Defining the dependencies between my modules was very easy, but defining their third-party dependencies seems to be harder then I expected.
What I would like to achieve: for each app I want to have two bundle files one with the necessary third-party dependencies and other with the necessary modules from my library.
Example:
Let's imagine that my library has the following modules:
a (requires: jquery, jquery.plugin1)
b (requires: jquery, a)
c (requires: jquery, jquery.ui, a, b)
d (requires: jquery, jquery.plugin2, a)
And I have an app (see it as a unique entry file) that requires modules a, b and c. Webpack for this case should generate the following files:
vendor bundle: with jquery, jquery.plugin1 and jquery.ui;
website bundle: with modules a, b and c;
In the end, I would prefer to have jQuery as a global so I don't need to require it on every single file (I could require it only on the main file, for example). And jQuery plugins would just extend the $ global in case they are required (it is not a problem if they are available to other modules that don't need them).
Assuming this is possible, what would be an example of a webpack configuration file for this case? I tried several combinations of loaders, externals, and plugins on my configuration file, but I don't really get what they are doing and which ones should I use. Thank you!
in my webpack.config.js (Version 1,2,3) file, I have
function isExternal(module) {
var context = module.context;
if (typeof context !== 'string') {
return false;
}
return context.indexOf('node_modules') !== -1;
}
in my plugins array
plugins: [
new CommonsChunkPlugin({
name: 'vendors',
minChunks: function(module) {
return isExternal(module);
}
}),
// Other plugins
]
Now I have a file that only adds 3rd party libs to one file as required.
If you want get more granular where you separate your vendors and entry point files:
plugins: [
new CommonsChunkPlugin({
name: 'common',
minChunks: function(module, count) {
return !isExternal(module) && count >= 2; // adjustable
}
}),
new CommonsChunkPlugin({
name: 'vendors',
chunks: ['common'],
// or if you have an key value object for your entries
// chunks: Object.keys(entry).concat('common')
minChunks: function(module) {
return isExternal(module);
}
})
]
Note that the order of the plugins matters a lot.
Also, this is going to change in version 4. When that's official, I update this answer.
Update: indexOf search change for windows users
I am not sure if I fully understand your problem but since I had similar issue recently I will try to help you out.
Vendor bundle.
You should use CommonsChunkPlugin for that. in the configuration you specify the name of the chunk (e.g. vendor), and file name that will be generated (vendor.js).
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity),
Now important part, you have to now specify what does it mean vendor library and you do that in an entry section. One one more item to entry list with the same name as the name of the newly declared chunk (i.e. 'vendor' in this case). The value of that entry should be the list of all the modules that you want to move to vendor bundle.
in your case it should look something like:
entry: {
app: 'entry.js',
vendor: ['jquery', 'jquery.plugin1']
}
JQuery as global
Had the same problem and solved it with ProvidePlugin. here you are not defining global object but kind of shurtcuts to modules. i.e. you can configure it like that:
new webpack.ProvidePlugin({
$: "jquery"
})
And now you can just use $ anywhere in your code - webpack will automatically convert that to
require('jquery')
I hope it helped. you can also look at my webpack configuration file that is here
I love webpack, but I agree that the documentation is not the nicest one in the world... but hey.. people were saying same thing about Angular documentation in the begining :)
Edit:
To have entrypoint-specific vendor chunks just use CommonsChunkPlugins multiple times:
new webpack.optimize.CommonsChunkPlugin("vendor-page1", "vendor-page1.js", Infinity),
new webpack.optimize.CommonsChunkPlugin("vendor-page2", "vendor-page2.js", Infinity),
and then declare different extenral libraries for different files:
entry: {
page1: ['entry.js'],
page2: ['entry2.js'],
"vendor-page1": [
'lodash'
],
"vendor-page2": [
'jquery'
]
},
If some libraries are overlapping (and for most of them) between entry points then you can extract them to common file using same plugin just with different configuration. See this example.
In case you're interested in bundling automatically your scripts separately from vendors ones:
var webpack = require('webpack'),
pkg = require('./package.json'), //loads npm config file
html = require('html-webpack-plugin');
module.exports = {
context : __dirname + '/app',
entry : {
app : __dirname + '/app/index.js',
vendor : Object.keys(pkg.dependencies) //get npm vendors deps from config
},
output : {
path : __dirname + '/dist',
filename : 'app.min-[hash:6].js'
},
plugins: [
//Finally add this line to bundle the vendor code separately
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.min-[hash:6].js'),
new html({template : __dirname + '/app/index.html'})
]
};
You can read more about this feature in official documentation.
Also not sure if I fully understand your case, but here is config snippet to create separate vendor chunks for each of your bundles:
entry: {
bundle1: './build/bundles/bundle1.js',
bundle2: './build/bundles/bundle2.js',
'vendor-bundle1': [
'react',
'react-router'
],
'vendor-bundle2': [
'react',
'react-router',
'flummox',
'immutable'
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle1',
chunks: ['bundle1'],
filename: 'vendor-bundle1.js',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle2',
chunks: ['bundle2'],
filename: 'vendor-bundle2-whatever.js',
minChunks: Infinity
}),
]
And link to CommonsChunkPlugin docs: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

Minimize only one Webpack chunk

I want to minimize my Javascript code for production. However I don't want to minimize the vendors' code because they already have a minimize version.
My current webpack.config.js splits the output code in two chunks.
module.exports = {
entry: {
vendor: ['jquery','angular'],
app: ['./Client/app.start.js']
},
output: {
filename: 'bundle.js',
path: __dirname
},
resolve : {
alias : {
'angular' : 'angular/angular.min.js',
'jquery' : 'jquery/dist/jquery.min.js'
}
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"),
new webpack.optimize.UglifyJsPlugin({minimize: true})
]
}
When I run >> webpack, both chunks ("bundle.js" and "vendor.bundle.js") are minimized. How can I configure Webpack to only minimize "bundle.js"?
Thanks
If, for some reason, you really want to minify only one bundle you could use the test option of the UglifyJsPlugin. Use the bundle name and don't test for individual modules with a regex because when the code is consumed by UglifyJsPlugin it's already bundled.
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
test: /(vendor.bundle.js\.js)+/i
})
See the docs: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
test, include, exclude (RegExp|Array): configure filtering of processed files (default: test: /.js($|\?)/i)
Usually, you would have different configs (one with the uglify and another without), for production and development, you would minimize only in production, if that's what you want.
You probably already know this, but is good to be thorough. What you may not know, is that webpack does the job better and it is recommended to use untouched code and let webpack do its thing. I don't believe that the uglifyJsPlugin is able to target chunks, maybe there is another plugin that could do it, but i am unaware.
As a side note, you don't have to worry about double minification, it adds a small effect, considering that it is a production environment and that it doesn't change by the minute.
This can be achieved via a hack as you can see in below code in webpack.config.js file:
`
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const PRODUCTION = process.env.NODE_ENV === 'production';
const plugins = [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', filename: './assets/js/vendor.min.js',
minChunks: Infinity
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}),
(...etc other plugins)
];
if (PRODUCTION) {
plugins.push(new UglifyJsPlugin({
include: /\.min\.js$/
}));
}
`
Check if you are creating production build.
Then, you can name the chunks you want to create as minified with".min.js" extension.
Rest unglifyjsplugin -> include filter will ensure that only these chuks will be minified.
Here, in this case, it'll only minify the vendor.min.js file.

Categories