Managing jQuery plugin dependency in webpack
Asked Answered
A

12

502

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?

Andie answered 10/3, 2015 at 17:6 Comment(0)
N
830

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$/
    ]
}
Nth answered 11/3, 2015 at 14:36 Comment(26)
Thank you. Your answer was very descriptive. So does this mean that I can use both providePlugin and imports-loader for different files? And would it make sense to have a separate bundle only for JQuery plugins (not including JQuery)?Andie
The ProvidePlugin is applied on all occurrences of the given identifiers in all files. The imports-loader can be applied on specific files only, but you should not use both for the same variables/dependencies.Nth
You second question depends on your use-case, but personally I'd pack all jquery plugins + jquery itself into one bundle.Nth
This helped me, thanks. I figured that I was going wrong by using noParse and the ProvidePlugin together. It works now. Thanks again for the detailed answerAndie
I'm confused by this. Where is the jquery actually coming from? Locally or a CDN? Everything after 3. its unclear whether that is necessary. What are the actual steps to integrate jquery into your project using webpack. Does it bypass the version that is available via CDN?Reich
Everything from a CDN is out of scope for webpack, so this refers to a local jquery installation. Jquery can just be required, there are no special steps necessary to integrate it. This question is about how to integrate jquery plugins that depend on the global $ variable.Nth
@Reich You just need to add the aliases of your library/plugin and then add them to the entry point (in your vendors list). Then you can include them in your project files.Andie
@ashwinator Vendors list? What is this?Reich
@Reich It's nothing but the external libraries that you wish to use in your app, for e.g jQuery, jQueryUI, underscore, bootstrap etc. you add those dependencies to the 'vendors' array (or call it whatever you want, but basically it's a different list than your JS code files)Andie
I had to use option 2 to get a module working. When I combine it with option 1, the jquery modules don't load in the browser with jQuery undefined.Argive
@jhnns Regarding Point 1, the src version throws an error when I use JQuery from bower instead of npm. Could you explain why?Andie
I would like to use noParse and assume jquery will be there from a CDN, but what options do I then need for libraries that do import/require jquery? i've been getting redefinition of '$' from jshint everywhere for thoseArchibaldo
I think window.jQuery: "jquery" is not working anymore, it should be "window.jQuery": "jquery"Karakul
Did all this, still not working; Then added: "module": { "loaders": [ { test: require.resolve("jquery"), loader: "expose?$!expose?jQuery" }, and it worked fine.Fount
I really think you should add some word about where jquery come from, the method 2 is actually a broken one for newbie in fact.Scrapple
What do you mean with "where jquery come from"? You mean, you have to install it via npm? And why does method 2 not work for you?Nth
@Fount real nice, this resolved my issue... this should be added to the awesome article information above, cant you like provide this information to the webpack documentation since this seems to be really good explinations of how you should think,with webpackBod
Prefer unminified CommonJS/AMD over dist is this meant to all dependencies or just jquery? Cuz when I used src version for one of the plugins, didn't work.Resound
Do all this, to just include a jquery package ?, what a pain. I'm going back to my gulp buildCamara
Just a note: the jquery: "jquery/src/jquery" alias path is not very safe. Some jQuery plugins require jQuery as a dependency and the above path matches the jQuery within the plugin/module directory which can be a different version. You can end up with few jQuery versions in your bundle and a lot of missing hair. This did the trick for us: jquery: path.resolve(path.join(__dirname, '../../node_modules', 'jquery')) which returns something like User/name/projects/app/node_modules/jquery`Kieffer
Regarding Webpack v3: webpack.optimize.DedupePlugin isn't needed anymore. Remove it from your configuration. seeGiggle
Hi bro can you please help me out on How to update multiple bundled js files , here is my thread with 100 Bounty #60031006Aekerly
FYI it looks like "loaders" is now "rules" in webpack 4: stackoverflow.com/a/49376468Donation
Can you update the part with import-loader for Webpack 5?Fortification
good lord this is horribleTripoli
Webpack 5 tells me that the syntax loader: "imports-loader?this=>window" is no longer supported. And, as @Donation points out, "loaders" is now "rules". How would your 4th point be translated for Webpack 5?Sure
A
97

For global access to jquery then several options exist. In my most recent webpack project, I wanted global access to jquery so I added the following to my plugins declarations:

 plugins: [
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery"
    })
  ]

This then means that jquery is accessible from within the JavaScript source code via global references $ and jQuery.

Of course, you need to have also installed jquery via npm:

$ npm i jquery --save

For a working example of this approach please feel free to fork my app on github

Antihero answered 18/12, 2015 at 11:11 Comment(9)
Please can you leave a comment if you downgrade a vote explaining the reason. The above information is accurate. Please see my working app at: github.com/arcseldon/react-babel-webpack-starter-app using the above approach.Antihero
I'm not the downvoter, but maybe this is because the ProvidePlugin solution you suggest was already proposed?Reentry
@LéoLam - thanks for your comment. appreciate your point - i had deduced the answer via separate enquires, and just shared the snippet here I thought was likely most relevant to others wishing to emulate what I had done. You're right though, the official answer does cover this option.Antihero
It's working but I get 2 warnings: ./~/jQuery/dist/jquery.js There is another module with an equal name when case is ignored. and the same one with ./~/jquery/dist/jquery.jsSeftton
I needed to add 'window.jQuery': 'jquery' to that list to make the ms-signalr-client npm package load. I also put in 'window.$': 'jquery' for good measure :)Coy
I dont understand where to put the plugins:[...] snippet.Matteo
@Matteo - please see github.com/arcseldon/react-babel-webpack-starter-app/blob/… (linked in the answer above too)Antihero
I had to restart my entire server to get this working. Thanks for posting.Foliation
This is the best answer. Thank you!Digress
D
62

I don't know if I understand very well what you are trying to do, but I had to use jQuery plugins that required jQuery to be in the global context (window) and I put the following in my entry.js:

var $ = require('jquery');
window.jQuery = $;
window.$ = $;

The I just have to require wherever i want the jqueryplugin.min.js and window.$ is extended with the plugin as expected.

Divulgate answered 19/5, 2015 at 17:11 Comment(2)
Basically I was making the mistake of using the ProvidePlugin along with the noParse condition. Plugins like ProvidePlugin do not work if we do NoParse, as stated by in point number 6 of the answer. You can see that mistake in the codeAndie
yeah, I've found that works more consistently across plugins than the provide plugins etc... esp if you're bringing in angular 1.x via the script-loader.Tithe
S
34

I got things working nicely while exposing $ and jQuery as global variables with Webpack 3.8.1 and the following.

Install jQuery as a project dependency. You can omit @3.2.1 to install the latest version or specify another version.

npm install --save [email protected]

Install expose-loader as a development dependency if not installed already.

npm install expose-loader --save-dev

Configure Webpack to load and expose jQuery for us.

// webpack.config.js
const webpack = require('webpack')

module.exports = {
  entry: [
    // entry bits
  ],
  output: {
    // output bits
  },
  module: {
    rules: [
      // any other rules
      {
        // Exposes jQuery for use outside Webpack build
        test: require.resolve('jquery'),
        use: [{
          loader: 'expose-loader',
          options: 'jQuery'
        },{
          loader: 'expose-loader',
          options: '$'
        }]
      }
    ]
  },
  plugins: [
    // Provides jQuery for other JS bundled with Webpack
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    })
  ]
}
Sublunary answered 25/10, 2017 at 16:39 Comment(3)
You forgot to mention that you need to install expose-loader in order to it to work properly npm install expose-loader --save-devHowie
The expose-loaded did it for me. I didn't get an error at compile time, but in Chrome developer tools. That's solved now.Barrier
For me it worked using a slightly different syntax: ` test: require.resolve("jquery"), loader: "expose-loader", options: { exposes: ["$", "jQuery"], }, }, `Wally
B
33

In your webpack.config.js file add below:

 var webpack = require("webpack");
 plugins: [
    new webpack.ProvidePlugin({
        $: "jquery",
        jQuery: "jquery"
    })
 ],

Install jQuery using npm:

$ npm i jquery --save

In app.js file add below lines:

import $ from 'jquery';
window.jQuery = $;
window.$ = $;

This worked for me. :)

Bigwig answered 19/5, 2018 at 15:53 Comment(2)
How will this act if you have for ex. app.js, and jquery-module.js, that both require jquery?, for me i get jquery13981378127,and jquery12389723198 as instances on the window?Bod
Take care of case sensitivity on "jquery". In my webpack.config externals, I had "jquery: 'jQuery'". I had "import $ from 'jQuery'" in my script. Changing to "import $ from 'jquery'" solved my problem :)Liris
T
18

Add this to your plugins array in webpack.config.js

new webpack.ProvidePlugin({
    'window.jQuery': 'jquery',
    'window.$': 'jquery',
})

then require jquery normally

require('jquery');

If pain persists getting other scripts to see it, try explicitly placing it in the global context via (in the entry js)

window.$ = jQuery;
Thriftless answered 25/8, 2016 at 15:33 Comment(0)
I
11

I tried some of the supplied answers but none of them seemed to work. Then I tried this:

new webpack.ProvidePlugin({
    'window.jQuery'    : 'jquery',
    'window.$'         : 'jquery',
    'jQuery'           : 'jquery',
    '$'                : 'jquery'
});

Seems to work no matter which version I'm using

Illume answered 31/8, 2017 at 0:26 Comment(2)
+1 seems to be a misunderstanding that globals added here will automatically be set against the window, doesn't seem to work like that you need to be explicit.Britisher
Correct, this does not add it to the window object. It creates a window alias inside of webpack. So if you try to use $ outside of a webpack required file, it will be undefined.Illume
O
7

This works in webpack 3:

in the webpack.config.babel.js file:

resolve: {
    alias: {
         jquery: "jquery/src/jquery"
    },
 ....
}

And use ProvidePlugin

new webpack.ProvidePlugin({
        '$': 'jquery',
        'jQuery': 'jquery',
    })
Obtund answered 30/7, 2017 at 21:12 Comment(2)
For others that are super new at webpack (like me!) "resolve" goes in your webpack.config.js under module.exports = {} just like entry, output, plugins, module, etc..Topical
And new webpack.ProvidePlugin goes inside the plugins array.Topical
P
3

Edit: Sometimes you want to use webpack simply as a module bundler for a simple web project - to keep your own code organized. The following solution is for those who just want an external library to work as expected inside their modules - without using a lot of time diving into webpack setups. (Edited after -1)

Quick and simple (es6) solution if you’re still struggling or want to avoid externals config / additional webpack plugin config:

<script src="cdn/jquery.js"></script>
<script src="cdn/underscore.js"></script>
<script src="etc.js"></script>
<script src="bundle.js"></script>

inside a module:

const { jQuery: $, Underscore: _, etc } = window;
Placebo answered 30/9, 2017 at 5:59 Comment(0)
S
2

The best solution I've found was:

https://github.com/angular/angular-cli/issues/5139#issuecomment-283634059

Basically, you need to include a dummy variable on typings.d.ts, remove any "import * as $ from 'jquery" from your code, and then manually add a tag to jQuery script to your SPA html. This way, webpack won't be in your way, and you should be able to access the same global jQuery variable in all your scripts.

Selfappointed answered 23/5, 2017 at 18:55 Comment(0)
A
2

This works for me on the webpack.config.js

    new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        'window.jQuery': 'jquery'
    }),

in another javascript or into HTML add:

global.jQuery = require('jquery');
Aphrodite answered 16/11, 2017 at 15:15 Comment(0)
M
0

Whoever is facing any issues after applying the good solutions found here, all you need is simply follow the clear instruction inside the webpack.config.js file:

// uncomment if you're having problems with a jQuery plugin
.autoProvidejQuery()

By un-commenting this line, you will get things work well!

Marita answered 5/1, 2023 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.