Bob Bob - 4 months ago 95
AngularJS Question

Webpack: how to make angular auto-detect jQuery and use it as angular.element instead of jqLite?

I'm using Webpack to build an Angular 1.4 project. The project makes use of several jQuery plugins, which are wrapped into angular directives. Those directives internally use

angular.element
, probably implying that angular.element is the real jQuery, not jqLite.

I want angular to auto-detect jQuery and use it instead of jqLite. I tried to require jquery locally in my entry point module
app.js
:
require('jquery')
and to expose jQuery globally with
require(expose?$!expose?jQuery!jquery)
.

Still, whatever I do,
angular.element
refers to jqLite.




My research resulted in several findings:


  1. Even when imported as a CommonJS module, Angular assigns itself to a global variable
    window.angular
    , so I don't need to
    expose
    it with Webpack: Does Angular assign itself to `window.angular` globally, when loaded as CommonJS module?.

  2. ProviderPlugin doesn't seem to do the trick: it doesn't expose jQuery to global namespace; instead, for every module that depends on global name jQuery, it inserts
    require('jquery')
    in it. I'm not 100% sure, but looks like Angular doesn't access
    jQuery
    from global namespace directly, instead, it tries to access
    window.jQuery
    in
    bindJQuery
    function, so this approach doesn't help: Expose jQuery to real Window object with Webpack.

  3. For the same reason as ProviderPlugin,
    imports-loader
    seems unfit: Angular wants
    window.jQuery
    , not just
    jQuery
    .

  4. With
    expose-loader
    , jquery makes it to the window object.
    My problem was that Babel hoists all of its imports to the top of module in the resulting code. Hence, although
    require(expose?jquery!jquery)
    was before
    import angular from "angular"
    in source files, in bundle
    require("angular")
    is at the top of the file, before jquery, so by the time Angular is imported, jquery is not yet available. I wonder, how to use Webpack loaders with ECMA6 import syntax.

  5. There was a suggestion to use
    import
    syntax instead of
    require
    syntax with jquery:
    import "jquery"
    or
    import $ from "jquery"
    , not
    require(jquery)
    : (Petr Averyanov: How to use Webpack loaders syntax ( imports/exports/expose) with ECMAScript 6 imports?). jquery source code is wrapped with a special wrapper, which idenitifies how jquery is required (with AMD/require, CommonJS or globally with
    <script>
    statement). Based on that it sets a special argument
    noGlobal
    for jquery fabric and either creates
    window.jQuery
    or not, based on the value of
    noGlobal
    . As of jquery 2.2.4, upon
    import "jquery"
    noGlobal === true
    and
    window.jQuery
    is not created. IIRC, some older versions of jquery didn't recognize
    import
    as CommonJS import and added
    import
    ed jquery to global namespace, which allowed angular to use it.






Details: here's my
app.js
:

'use strict';

require("expose?$!expose?jQuery!jquery");
require("metisMenu/dist/metisMenu");
require("expose?_!lodash");
require("expose?angular!angular");

import angular from "angular";
import "angular-animate";
import "angular-messages";
import "angular-resource";
import "angular-sanitize";
import "angular-ui-router";
import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";
import "angular-bootstrap";

require("../assets/styles/style.scss");
require("../assets/fonts/pe-icon-7-stroke/css/pe-icon-7-stroke.css");

// Import all html files to put them in $templateCache
// If you need to use lazy loading, you will probably need
// to remove these two lines and explicitly require htmls
const templates = require.context(__dirname, true, /\.html$/);
templates.keys().forEach(templates);

import HomeModule from "home/home.module";
import UniverseDirectives from "../components/directives";

angular.module("Universe", [
"ngAnimate",
"ngMessages",
"ngResource",
"ngSanitize",
"ui.router",
"ui.bootstrap",

HomeModule.name,
UniverseDirectives.name,
])
.config(function($urlRouterProvider, $locationProvider, $stateProvider){
// $urlRouterProvider.otherwise('/');

// $locationProvider.html5Mode(true);

$stateProvider
.state('test', {
url: "/test",
template: "This is a test"
});
});

Answer

Got this answer from http://stackoverflow.com/users/761388/john-reilly: http://blog.johnnyreilly.com/2016/05/the-mysterious-case-of-webpack-angular-and-jquery.html

http://stackoverflow.com/users/1554165/bob-sponge's answer is not quite right - the Provide plugin is actually doing a text replacement on modules it processes, so we need to provide window.jQuery (which is what angular is looking for) and not just jQuery.

You need the ProvidePlugin In your webpack.config.js you need to add the following entry to your plugins:

javascript new webpack.ProvidePlugin({ "window.jQuery": "jquery" }),

This uses the webpack ProvidePlugin and, at the point of webpackification (© 2016 John Reilly) all references in the code to window.jQuery will be replaced with a reference to the webpack module that contains jQuery. So when you look at the bundled file you'll see that the code that checks the window object for jQuery has become this:

javascript jQuery = isUndefined(jqName) ? __webpack_provided_window_dot_jQuery : // use jQuery (if present) !jqName ? undefined : // use jqLite window[jqName]; // use jQuery specified by `ngJq`

That's right; webpack is providing Angular with jQuery whilst still not placing a jQuery variable onto the window. Neat huh?