Managing jQuery plugin dependency in webpack
You've mixed different approaches how to include legacy vendor modules. This is how I'd tackle it:
1. Prefer unminified CommonJS/AMD over 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.
2. Use the ProvidePlugin
to inject implicit globals
Most 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"
})
]
3. Use the imports-loader to configure 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.
4. Use the imports-loader to disable AMD
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"
}
]
}
5. Use the script-loader (no longer mantained) to globally import scripts
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.
6. Use noParse
to include large dists
When 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$/
]
}
How to import jquery in webpack
So there are few themes in your webpack.config.js
, some of which conflict. Just going to list them so I can better understand what I think you are trying to achieve.
Theme 1
You have an entry called vendor
that is clearly referencing a minified jQuery library you have presumably downloaded and placed in the directory specified.
Theme 2
You also have an expose-loader
that is essential exposing the jquery
library from its node_modules
probably listed in the dependencies
of your package.json
.
This makes the jquery
in the node_modules
available as $
and jQuery
in the global scope of the page where your bundle is included.
Theme 3
You also have included the ProvidePlugin
with configuration for jQuery.
The ProvidePlugin
is supposed to inject dependencies into the scope of your module code, meaning you do not need to have import $ from 'jquery'
instead $
and jQuery
will already be available in all of your modules.
Conclusion
From what I have gathered I think you're trying to bundle jQuery from the static file at ./src/main/webapp/js/vendor/jquery-3.3.1.min.js
in a vendor bundle.
You are then trying to expose the libraries in the vendor bundle to the global scope (jQuery).
Then also have your application code able to import jQuery from what is made available by the vendor bundle in the global scope.
Answer
So if that is what you are doing you need to do the following things.
Firstly, check in your package.json
files dependencies
for jquery
. If its there you want to remove it, there's no need for it if you're going to use your jquery-3.3.1.min.js
file instead to provide jQuery to your application.
Secondly, change your test
of the expose-loader
to trigger when it sees your jquery-3.3.1.min.js
file in your entries, not resolve it from the jquery
dependency from your node_modules
.
This regex pattern should do the trick.
{
test: /jquery.+\.js$/,
use: [{
loader: 'expose-loader',
options: 'jQuery'
},{
loader: 'expose-loader',
options: '$'
}]
}
Thirdly, remove the ProvidePlugin
if you're going to import the library explicitly with import $ from 'jquery'
you do not need it.
Lastly, you need to tell webpack when it sees an import for jquery
it can resolve this from window.jQuery
in the global scope. You can do this with the externals configuration you already referenced.
externals: {
jquery: 'jQuery'
}
With all these changes you should end up with a webpack.config.js
file that looks like this.
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
entry: {
vendor: [
'./src/main/webapp/js/vendor/jquery-3.3.1.min.js',
// './src/main/webapp/js/vendor/fs.js',
'./src/main/webapp/js/vendor/google-adsense.js',
'./src/main/webapp/js/vendor/jquery.menu-aim.min.js',
'./src/main/webapp/js/vendor/jquery.touchSwipe.min.js',
],
app: './src/main/assets/js/desktop/app.js',
mobile: './src/main/assets/js/mobile/app.js',
touch: './src/main/assets/js/touch/app.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /jquery.+\.js$/,
use: [{
loader: 'expose-loader',
options: 'jQuery'
},{
loader: 'expose-loader',
options: '$'
}]
}
],
},
plugins: [
// new CleanWebpackPlugin(['src/main/webapp/assets']),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // Specify the common bundle's name.
}),
new UglifyJsPlugin({
test: /\.js$/,
sourceMap: process.env.NODE_ENV === "development"
})
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, './src/main/webapp/js')
},
externals: {
jquery: 'jQuery'
}
};
I hope that does not just give you the answer but enough explanation as to where you were going wrong.
Including additional jQuery plugins globally with Webpack 4
I've gone a few rounds with this type of issue and had the most success with the expose-loader. In your webpack config you should set up a section for jQuery using the following expose loader configuration:
{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: 'jQuery'
}, {
loader: 'expose-loader',
options: '$'
}]
},
There is a similar SO posts here:
How to import jquery in webpack (their regex pattern did not work for me)
Expose jQuery to real Window object with Webpack
Webpack 2 loading, exposing, and bundling jquery and bootstrap
You should be able to find several other articles/posts using this configuration, it is the only one that I have successfully been able to get to work to date.
Also of note, bootstrap 4 seems to also load or do a require on jQuery internally, so if you include an import or require after your jQuery import/require and plugins, it will reinit jQuery and cause your plugins to lose scope.
How to configure and use jQuery with webpack
You can use webpack.ProvidePlugin
to resolve the jQuery as a global identifier. When you use ProvidePlugin
you dont want to import jQuery
into the modules since it would be available as global variable.
something like
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
Related Topics
React: "This" Is Undefined Inside a Component Function
How to Use Setinterval and Clearinterval
How to Run Multiple Npm Scripts in Parallel
How to Fetch the Value of a Non-Standard CSS Property via JavaScript
Flex Items Not Shrinking When Window Gets Smaller
Dynamically Expand Height of Input Type "Text" Based on Number of Characters Typed into Field
Npm Not Installing Returns Error Consistently
Window Is Not Defined in Next.Js React App
How to Have a Default Option in Angular.Js Select Box
How to Add Hours to a Date Object
Last Segment of Url with JavaScript
Why Don't Logical Operators (&& and ||) Always Return a Boolean Result
Sort Array Elements (String with Numbers), Natural Sort
Does JavaScript Have "Short-Circuit" Evaluation
Is It Spread "Syntax" or the Spread "Operator"