I'm using Webpack in my application, in which I create two entry points - bundle.js for all my JavaScript files/codes, and vendors.js for all libraries like jQuery and React. What do I do in order to use plugins which have jQuery as their dependencies and I want to have them also in vendors.js? What if those plugins have multiple dependencies?
Currently I'm trying to use this jQuery plugin here - https://github.com/mbklein/jquery-elastic. The Webpack documentation mentions providePlugin and imports-loader. I used providePlugin, but still the jQuery object is not available. Here is how my webpack.config.js looks like-
var webpack = require('webpack');
var bower_dir = __dirname + '/bower_components';
var node_dir = __dirname + '/node_modules';
var lib_dir = __dirname + '/public/js/libs';
var config = {
addVendor: function (name, path) {
this.resolve.alias[name] = path;
this.module.noParse.push(new RegExp(path));
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jquery: "jQuery",
"window.jQuery": "jquery"
}),
new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js', Infinity)
],
entry: {
app: ['./public/js/main.js'],
vendors: ['react','jquery']
},
resolve: {
alias: {
'jquery': node_dir + '/jquery/dist/jquery.js',
'jquery.elastic': lib_dir + '/jquery.elastic.source.js'
}
},
output: {
path: './public/js',
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: 'jsx-loader' },
{ test: /\.jquery.elastic.js$/, loader: 'imports-loader' }
]
}
};
config.addVendor('react', bower_dir + '/react/react.min.js');
config.addVendor('jquery', node_dir + '/jquery/dist/jquery.js');
config.addVendor('jquery.elastic', lib_dir +'/jquery.elastic.source.js');
module.exports = config;
But in spite of this, it still throws an error in the browser console:
Uncaught ReferenceError: jQuery is not defined
Similarly, when I use the imports-loader, it throws an error,
require is not defined'
in this line:
var jQuery = require("jquery")
However, I could use the same plugin when I don't add it to my vendors.js file and instead required it in the normal AMD way as how I include my other JavaScript code files, like-
define(
[
'jquery',
'react',
'../../common-functions',
'../../libs/jquery.elastic.source'
],function($,React,commonFunctions){
$("#myInput").elastic() //It works
});
But this is not what I want to do, as this would mean that jquery.elastic.source.js is bundled along with my JavaScript code in bundle.js, and I want all my jQuery plugins to be in the vendors.js bundle. So how do I go about achieving this?
You've mixed different approaches how to include legacy vendor modules. This is how I'd tackle it:
dist
Most modules link the dist
version in the main
field of their package.json
. While this is useful for most developers, for webpack it is better to alias the src
version because this way webpack is able to optimize dependencies better (e.g. when using the DedupePlugin
).
// webpack.config.js
module.exports = {
...
resolve: {
alias: {
jquery: "jquery/src/jquery"
}
}
};
However, in most cases the dist
version works just fine as well.
ProvidePlugin
to inject implicit globalsMost legacy modules rely on the presence of specific globals, like jQuery plugins do on $
or jQuery
. In this scenario you can configure webpack, to prepend var $ = require("jquery")
everytime it encounters the global $
identifier.
var webpack = require("webpack");
...
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
this
Some legacy modules rely on this
being the window
object. This becomes a problem when the module is executed in a CommonJS context where this
equals module.exports
. In this case you can override this
with the imports-loader.
Run npm i imports-loader --save-dev
and then
module: {
loaders: [
{
test: /[\/\\]node_modules[\/\\]some-module[\/\\]index\.js$/,
loader: "imports-loader?this=>window"
}
]
}
The imports-loader can also be used to manually inject variables of all kinds. But most of the time the ProvidePlugin
is more useful when it comes to implicit globals.
There are modules that support different module styles, like AMD, CommonJS and legacy. However, most of the time they first check for define
and then use some quirky code to export properties. In these cases, it could help to force the CommonJS path by setting define = false
.
module: {
loaders: [
{
test: /[\/\\]node_modules[\/\\]some-module[\/\\]index\.js$/,
loader: "imports-loader?define=>false"
}
]
}
If you don't care about global variables and just want legacy scripts to work, you can also use the script-loader. It executes the module in a global context, just as if you had included them via the <script>
tag.
noParse
to include large distsWhen there is no AMD/CommonJS version of the module and you want to include the dist
, you can flag this module as noParse
. Then webpack will just include the module without parsing it, which can be used to improve the build time. This means that any feature requiring the AST, like the ProvidePlugin
, will not work.
module: {
noParse: [
/[\/\\]node_modules[\/\\]angular[\/\\]angular\.js$/
]
}