Webpack:如何使角度自动检测 jQuery 并将其用作 angular.element 而不是 jqLit​​e?

Posted

技术标签:

【中文标题】Webpack:如何使角度自动检测 jQuery 并将其用作 angular.element 而不是 jqLit​​e?【英文标题】:Webpack: how to make angular auto-detect jQuery and use it as angular.element instead of jqLite? 【发布时间】:2016-07-04 02:50:56 【问题描述】:

我正在使用 Webpack 构建一个 Angular 1.4 项目。该项目使用了几个 jQuery 插件,这些插件被包装在 angular 指令中。这些指令在内部使用angular.element,可能暗示 angular.element 是真正的 jQuery,而不是 jqLit​​e。

我希望 Angular 自动检测 jQuery 并使用它来代替 jqLit​​e。我尝试在我的入口点模块 app.js:require('jquery') 中本地要求 jquery,并使用 require(expose?$!expose?jQuery!jquery) 全局公开 jQuery。

不过,不管我做什么,angular.element 指的是 jqLit​​e。


我的研究得出了几个发现:

    即使作为 CommonJS 模块导入时,Angular 也会将自己分配给全局变量 window.angular,因此我不需要使用 Webpack Does Angular assign itself to `window.angular` globally, when loaded as CommonJS module? expose。 ProviderPlugin 似乎不能解决问题:它不会将 jQuery 暴露给全局命名空间;相反,对于依赖于全局名称 jQuery 的每个模块,它会在其中插入 require('jquery')。我不是 100% 确定,但看起来 Angular 没有直接从全局命名空间访问 jQuery,而是尝试在 bindJQuery 函数中访问 window.jQuery,所以这种方法没有帮助:Expose jQuery to real Window object with Webpack . 出于与 ProviderPlugin 相同的原因,imports-loader 似乎不合适:Angular 想要 window.jQuery,而不仅仅是 jQuery使用expose-loader,jquery 使其成为窗口对象。 我的问题是 Babel 在生成的代码中将其所有导入提升到模块的顶部。因此,虽然require(expose?jquery!jquery) 在源文件中位于import angular from "angular" 之前,但在bundle 中require("angular") 位于文件顶部,在jquery 之前,因此在导入Angular 时,jquery 尚不可用。我想知道,如何使用带有 ECMA6 导入语法的 Webpack 加载器。 有人建议在 jquery 中使用 import 语法而不是 require 语法:import "jquery"import $ from "jquery",而不是 require(jquery):(Petr Averyanov:How to use Webpack loaders syntax ( imports/exports/expose) with ECMAScript 6 imports?)。 jquery 源代码是wrapped with a special wrapper,它标识了 jquery 是如何被需要的(使用 AMD/require、CommonJS 或全局使用 <script> 语句)。基于此,它为 jquery fabric 设置了一个特殊的参数noGlobal,并根据noGlobal 的值创建或不创建window.jQuery。从 jquery 2.2.4 开始,不会创建 import "jquery" noGlobal === truewindow.jQuery。 IIRC,一些旧版本的 jquery 没有将 import 识别为 CommonJS 导入并将 imported jquery 添加到全局命名空间,这允许 angular 使用它。

详情:这是我的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"
      );
);

【问题讨论】:

如何将 Angular 和 jQuery 加载到应用程序中?它们是捆绑在一起的还是从 CDN 加载的? @HamletHakobyan 它们都在 webpack 包中。 你用的是什么角度版本? @maioman Angular 1.4. 如果你在 Angular 之前加载 jquery。 angular 足够聪明,可以在 jqlite 上使用 jquery 【参考方案1】:

从john-reilly得到这个答案:The mysterious case of webpack angular and jquery

bob-sponge 的回答不太正确 - 提供插件实际上是在它处理的模块上进行文本替换,所以我们需要提供 window.jQuery(这是 angular 正在寻找的)而不仅仅是 @987654326 @。

在您的webpack.config.js 中,您需要添加 插件的以下条目:

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

这使用 webpack ProvidePlugin 并且,在点 webpackification (© 2016 John Reilly) 代码中的所有引用 window.jQuery 将被替换为对 webpack 模块的引用 包含 jQuery。因此,当您查看捆绑的文件时,您会看到 检查window 对象的jQuery 的代码已变为 这个:

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

没错; webpack 还在为 Angular 提供 jQuery jQuery 变量放在window 上。整齐吧?

【讨论】:

我爱你,因此我花了 2 天时间试图找出疯狂的错误。【参考方案2】:

!!更新

显然你仍然需要在 ES6 示例中使用 commonJs 对 angular 的要求。

import $ from "jquery"

window.$ = $;
window.jQuery = $;

var angular = require("angular");

以下是原答案



我想要一个更简单的解决方案。只需将 jQuery 设为全局窗口,以便 Angular 可以识别它:

var $ = require("jquery")

window.$ = $;
window.jQuery = $;

var angular = require("angular");

或者在你的情况下(OUT DATED):

import $ from "jquery"

window.$ = $;
window.jQuery = $;

import angular from "angular";

我希望这会有所帮助:)

【讨论】:

您的解决方案很好。此外,我发现您可以只使用import jquery; import angular 并且 angular 应该检测到 jquery(请参阅问题的更新,#5)。你能测试一下,如果可行吗? 嗯,我自己测试过。使用 jquery 2.2.4 noGlobal=true 和 import $ from "jquery"import "jquery" 你的结果是什么? 啊,对不起,结果是否定的——它不起作用。 jquery 的包装器识别出它是作为 CommonJS 的风格导入的,将 noGlobal 设置为 true 并且不设置 window.$,因此 angular 不会检测到它。 你能使用我的解决方案吗? noGlobal=false。【参考方案3】:

在您的情况下,最好使用ProvidePlugin。只需将此行添加到插件部分的 webpack 配置中,jquery 就会在您的应用程序中可用:

    new webpack.ProvidePlugin(
         "$": "jquery",
         "jQuery": "jquery"
    )

【讨论】:

嗨,鲍勃·海绵!这没有帮助:angular.element -> function JQLite()。虽然,我不明白,为什么 angular 被导出到全局命名空间 - 我已经注释掉了 require("expose?angular!angular"); 行。 事实上,我在 app.js 中导入了两次 angular - 本地和全局 - 这可能会引起混淆。我想删除本地导入,但我不能。没有本地的import angular from "angular"; 浏览器会诅咒webpack:///./bower_components/angular-animate/angular-animate.js?:9 Uncaught TypeError: Cannot read property 'noop' of undefined。全局 require("expose?angular!angular"); 用于调试目的,将在生产中删除。 确保您使用的是 jQuery 2.1+ 并且window.jQuery 不是undefined 终于解决了。这是由于 Babel 将导入置于需求之前。它重新排列了我的代码,以便在需要 jquery 之前始终导入 angular。该死。 :) @invincibleDudess 我没有完全解决它 - 我只是用 CommonJS requires 替换了所有的 ECMA6 imports,因为 Webpack 中没有办法在 ECMA6 @987654336 中说 require("expose?$!expose?jQuery!jquery"); @ 句法。虽然,还有另一种解决方案。原来jquerylodashangular,当imported 在ECMA6 中只是将自己添加到window 对象并成为全局可用的,所以对于他们你不需要expose 加载器。所以你可以在任何地方使用 ECMA6 imports,除非你的一些小库需要暴露。以防我到目前为止坚持使用require【参考方案4】:

有这篇日文文章I want to use the jQuery not jQLite in webpack + AngularJS 似乎在谈论同样的问题(顺便说一句,我还不懂日文)。我用谷歌翻译成英文,感谢 cither 得到这个不错的答案。

他提供了四种方法来解决这个问题:

    直接分配给窗口(不是很酷)

    window.jQuery = require('jquery');
    var angular = require('angular');
    console.log(angular.element('body'));
    //[body, prevObject: jQuery.fn.init[1], context: document, selector: "body"]
    

    使用expose-loader(好的,但没那么酷)

    npm install --saveDev expose-loader
    

    webpack.config.js

    module.exports = 
        entry: "./index",
        output: 
            path: __dirname,
            filename: "bundle.js"
        ,
        module: 
            loaders: [
                test: /\/jquery.js$/,
                loader: "expose?jQuery"
            ]
        
    ;
    

    用法:

    require('jquery');
    var angular = require('angular');
    console.log(angular.element('body'));
    //[body, prevObject: jQuery.fn.init[1], context: document, selector: "body"]
    

    使用expose-loader(更好)

    npm install --saveDev expose-loader
    

    webpack.config.js

        module.exports = 
        entry: "./index",
        output: 
            path: __dirname,
            filename: "bundle.js"
        ,
        module: 
            loaders: [
                test: /\/angular\.js$/,
                loader: "imports?jQuery=jquery"
            , 
                test: /\/jquery.js$/,
                loader: "expose?jQuery"
            ]
        
    ;
    

    用法:

    var angular = require('angular');
    console.log(angular.element('body'));
    //[body, prevObject: jQuery.fn.init[1], context: document, selector: "body"]
    

    使用ProvidePlugin(最佳解决方案)

    这里其实和studds's accepted answer是一样的

    module.exports = 
        entry: "./index",
        output: 
            path: __dirname,
            filename: "bundle.js"
        ,
        plugins: [
            new webpack.ProvidePlugin(
                "window.jQuery": "jquery"
            )
        ],
    ;
    

    用法:

    var angular = require('angular');
    console.log(angular.element('body'));
    //[body, prevObject: jQuery.fn.init[1], context: document, selector: "body"]
    

我想我会在这里分享这个,因为我们遇到了完全相同的问题。我们在项目中成功使用了expose-loader 解决方案。我想直接在window中注入jquery的ProvidePlugin也是个好主意。

【讨论】:

【参考方案5】:

所以,我给出了我的解决方案,它实际上是@BobSponge 答案和@Bob 的提示/cmets 的混搭。所以没有什么原创的,只是展示了对我有用的东西(在一个不使用 Babel / ES6,顺便说一句的项目中)并试图解释它为什么有效......

(最终)技巧确实是使用expose loader。

正如他们页面中所解释的,我们必须输入module.loaders

 test: require.resolve("jquery"), loader: "expose?$!expose?jQuery" ,

此外,在plugins 列表中,我有:

        new webpack.ProvidePlugin(
        
            $: 'jquery',
            jQuery: 'jquery',
            _: 'lodash',
            // [...] some other libraries that put themselves in the global scope.
            //angular: 'angular', // No, I prefer to require it everywhere explicitly.
        ),

它实际上在代码中找到这些变量的全局出现,并将它们放入每个模块的本地变量中。就像我一样,它简化了现有项目(从 RequireJS 到 Webpack)的迁移...... 如果您希望在导入中明确说明,我认为我们可以不使用此插件。

而且,重要的是(我认为),在应用程序的入口点,我需要它们按照我想要的顺序。就我而言,我制作了一个供应商捆绑包,所以这就是这个捆绑包中的顺序。

require('jquery');

require('lodash');
// [...]

var angular = require('angular');
// Use the angular variable to declare the app module, etc.

Webpack 将按照它看到requires 的顺序将库添加到相关包中(除非您使用重新排序它们的插件/加载器)。 但是导入是隔离的(没有全局泄漏),因此 Angular 无法看到 jQuery 库。因此需要expose。 (我尝试了window.jQuery = require('jquery');,但没有成功,也许为时已晚。)

【讨论】:

in the vendor bundle, as I made one 是什么意思?我的意思是......你把定义顺序的代码放在哪里? 我不得不重新阅读自己几次才能理解我的意思... :-) 我希望在供应商捆绑包中订购这些库。 requires 以正确的顺序放入应用程序包中,即。当然是在应用代码中。

以上是关于Webpack:如何使角度自动检测 jQuery 并将其用作 angular.element 而不是 jqLit​​e?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 webpack typescript 响应 webpack-dev-server 配置以自动构建和重新加载页面

如何使用 CdkScrollable 检测角度材料 2 自动完成列表上的滚动事件

JQuery没有检测到角度指令中的DOM元素

使用 jQuery 检测表单输入的自动完成

JQuery下focus()无法自动获取焦点的处理方法 jquery如何使文本框获得焦点

检测 jQuery UI 自动完成