使用Browserify-shim配置通用的jQuery插件?

41

我正在使用browserify-shim,想使用通用的jQuery插件。我已经多次查看了Browserify-shim文档,但是似乎无法理解它的工作原理,或者它如何知道放置插件、连接到jQuery对象等等。下面是我的package.json文件内容:

"browser": {
  "jquery": "./src/js/vendor/jquery.js",
  "caret": "./src/js/vendor/jquery.caret.js"
},

"browserify-shim": {
  "caret": {
     "depends": ["jquery:$"]
  }
}

根据browserify-shim文档上给出的例子,我不想指定exports,因为这个插件(以及大多数,如果不是全部的jQuery插件)会附加到jQuery对象上。除非我在使用时出了什么问题,否则我不明白为什么它不起作用(我收到一个错误告诉我函数未定义)。请见下文:

$('#contenteditable').caret(5);  // Uncaught TypeError: undefined is not a function

所以我的问题是,如何使用browserify和browserify-shim配置一个通用的jQuery插件(它会附加到jQuery对象上)?

4个回答

98

经过重新审视和尝试了一些方法后,我最终理解了browserify-shim是做什么以及如何使用它。对我来说,在我最终理解如何使用browserify-shim之前,有一个关键原则我必须掌握。基本上有两种使用browserify-shim的方式:暴露和shimming,它们适用于不同的用例。

背景

假设您只想在标记中放置一个脚本标签(出于测试或性能原因,如缓存、CDN等)。通过在标记中包含脚本标签,浏览器将命中该脚本,运行它,并很可能在window对象上附加一个属性(也称为JS中的全局变量)。当然,可以通过执行myGlobalwindow.myGlobal来访问它。但是这两种语法都存在问题。它们不遵循CommonJS规范,这意味着如果模块开始支持CommonJS语法(require()),您将无法利用它。

解决方案

Browserify-shim允许您指定一个希望通过CommonJS require()语法“公开”的全局变量。请记住,您可以执行var whatever = global;var whatever = window.global;,但是您不能执行var whatever = require('global')并期望它给您提供正确的库/模块。不要对变量名称感到困惑。它可以是任意的任意命名。实际上,您将全局变量变为一个局部变量。听起来很愚蠢,但这是JS在浏览器中处于悲惨状态的表现形式。再次强调,希望一旦库支持CommonJS语法,它就永远不会通过window对象上的全局变量自身附加自己。这意味着您必须使用require()语法将其分配给本地变量,然后在需要时使用它。

注意:我发现browserify-shim文档/示例中变量命名略微令人困惑。请记住,关键是您希望包含一个库,并像一个行为正常的CommonJS模块一样使用。因此,您实际上告诉browserify,当您需要myGlobal require('myGlobal')时,您实际上只想得到window对象上的全局属性window.myGlobal

实际上,如果您对require函数的实际操作感到好奇,那么它非常简单。以下是其背后发生的事情:

var whatever = require('mygGlobal');

变成...

var whatever = window.mygGlobal;

暴露模块

有了这个背景,让我们看看如何在browserify-shim配置中暴露一个模块/库。基本上,你需要告诉browserify-shim两件事情。当你调用require()时想要访问的名称和它应该在window对象上找到的全局变量。这就是global:* 语法发挥作用的地方。让我们看一个例子。我想将jquery作为脚本标记放入index.html中,以获得更好的性能。这就是我在配置文件中需要做的事情(这将在package.json或外部配置JS文件中):

"browserify-shim": {
  "jquery": "global:$"
}
所以这就是它的意思。我已经在其他地方包含了jQuery(记住,browserify-shim不知道我们放置了哪个标签,但它不需要知道),但我想要的只是当我使用字符串参数“jquery”请求模块时,在window对象上给出$属性。为了进一步说明。我也可以这样做:
"browserify-shim": {
  "thingy": "global:$"
}
在这种情况下,我需要将“thingy”作为参数传递给require函数,以便获取jQuery对象的实例(它只是从window.$获取jQuery):
var $ = require('thingy');

是的,变量名可以是任何东西,$与jQuery库使用的全局属性$没有什么特别之处。尽管使用相同的名称很有意义,可以避免混淆。这最终会引用在package.json中的browserify-shim对象中选择的global:$值来选定window对象上的$属性。

Shimming

好的,那基本上涵盖了曝光。 browserify-shim 的另一个主要功能是 shimming。那是什么? Shimming 本质上与 exposing 做的事情相同,但不同的是,您告诉 browserify-shim 在哪里本地抓取 JS 文件,而不是像使用 script 标记一样在 HTML 标记中包含 lib 或模块。无需使用 global:* 语法。因此,让我们回到我们的 jQuery 示例,但这次假设我们不是从 CDN 加载 jQuery,而只是将其与所有 JS 文件捆绑在一起。所以配置看起来像:

"browser": {
  "jquery": "./src/js/vendor/jquery.js", // Path to the local JS file relative to package.json or an external shim JS file
},
"browserify-shim": {
  "jquery": "$"
},

这个配置告诉browserify-shim从指定的本地路径加载jQuery,然后从window对象中获取$属性,并在需要使用require函数的字符串参数来要求jQuery时返回它。同样举例说明,您也可以将此重命名为任何其他名称。

"browser": {
  "thingy": "./src/js/vendor/jquery.js", // Path to the local JS file relative to package.json or an external shim JS file
},
"browserify-shim": {
  "thingy": "$"
},

这可能需要:

var whatever = require('thingy');

我建议查看browserify-shim文档,了解使用exports属性和depends属性的长写法的更多信息。其中depends属性允许您告诉browserify-shim一个库是否依赖于另一个库/模块。我在这里解释的适用于两者。

匿名Shimming

匿名shimming是browserify-shim 的替代方案,它可以让您使用browserify的--standalone选项将类库(如jQuery)转换为UMD模块。

$ browserify ./src/js/vendor/jquery.js -s thingy > ../dist/jquery-UMD.js
如果将它放在脚本标签中,该模块会将jQuery添加到窗口对象中作为thingy。当然也可以是$或其他你喜欢的名称。
然而,如果它是在您的browserify应用程序包中通过require引入的,就像这样:var $ = require("./dist/jquery-UMD.js");,则可以在应用程序中使用jQuery而无需将其添加到窗口对象中。
这种方法不需要使用browserify-shim,并利用了jQuery对CommonJS的支持,其中它寻找一个模块对象并将noGlobal标志传递给其工厂,告诉它不要将自己附加到窗口对象上。

1
不,你是对的。它只是项目根目录中的 package.json 文件(其中定义了所有 NPM 依赖项)。 - Glen Selle
1
谢谢你的信息 :) 尽管你在回答中实际上并没有提到插件... :/ - Jamie Hutber
25
感谢你的完整解释,但最终你是如何集成那个jQuery插件的? - David
1
这是一个非常好的答案,比文档更详细地介绍了browserify-shim,但即使有这个解释,我仍然无法让一个jquery插件正常工作。你真的不应该假设你的答案提供了足够的信息来帮助用户理解接下来该怎么做。 - jamis
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - jennas
显示剩余13条评论

13

以下是一个附加到jQuery/$对象的jQuery插件的package.jsonapp.js文件的示例,例如:$('div').expose()。当我需要它时,我不希望jQuery成为全局变量(window.jQuery),这就是为什么将jQuery设置为'exports': null的原因。但是,由于插件期望全局jQuery对象,以便可以将自己附加到该对象上,因此您必须在文件名后指定其依赖项:./jquery-2.1.3.js:jQuery。此外,在使用插件时,您需要实际导出jQuery全局变量,即使您不想这样做,因为否则插件将无法正常工作(至少是这个特定的插件)。

package.json

{
  "name": "test",
  "version": "0.1.0",
  "description": "test",
  "browserify-shim": {
    "./jquery-2.1.3.js": { "exports": null },
    "./jquery.expose.js": { "exports": "jQuery", "depends": [ "./jquery-2.1.3.js:jQuery" ] }
  },
  "browserify": {
    "transform": [
      "browserify-shim"
    ]
  }
}

app.js

翻译为:

app.js

// copy and delete any previously defined jQuery objects
if (window.jQuery) {
  window.original_jQuery = window.jQuery;
  delete window.jQuery;

  if (typeof window.$.fn.jquery === 'string') {
    window.original_$ = window.$;
    delete window.$;
  }
}

// exposes the jQuery global
require('./jquery.expose.js');
// copy it to another variable of my choosing and delete the global one
var my_jQuery = jQuery;
delete window.jQuery;

// re-setting the original jQuery object (if any)
if (window.original_jQuery) { window.jQuery = window.original_jQuery; delete window.original_jQuery; }
if (window.original_$) { window.$ = window.original_$; delete window.original_$; }

my_jQuery(document).ready(function() {
  my_jQuery('button').click(function(){
    my_jQuery(this).expose();
  });
});

在上面的例子中,我不希望我的代码设置任何全局变量,但是为了使插件工作,我暂时不得不这样做。如果你只需要 jQuery,你可以这样做,而不需要任何解决方法:var my_jQuery = require('./jquery-2.1.3.js')。如果您对您的 jQuery 作为全局变量公开感到满意,那么您可以像这样修改上面的 package.json 例子:

  "browserify-shim": {
    "./jquery-2.1.3.js": { "exports": "$" },
    "./jquery.expose.js": { "exports": null, "depends": [ "./jquery-2.1.3.js" ] }

希望这可以对一些需要具体示例的人有所帮助(就像我发现这个问题时一样)。


谢谢!我花了将近一天的时间来尝试让我的browserify工作。 - Kesha Antonov
我相信这个帖子会因为感谢而被关闭,但还是要谢谢你。整个 shim 的概念在任何地方都没有得到很好的解释,也没有血淋淋的语法来使事情正常运作。你的写作帮助我理解了如何测试行为。 - ekkis
更进一步,如果您愿意为插件添加一个简单的头文件,您实际上可以在一开始就避免污染window对象,但这只是可选的。为了完整起见,我在另一个答案中添加了该方法。 - Cool Blue

1

为了完整起见,这里有一种方法可以利用jQuery的CommonJS意识来避免担心污染window对象而不需要进行shim。

特点

  1. jQuery包含在捆绑包中
  2. 插件包含在捆绑包中
  3. 不会污染window对象

配置

./package.json中,添加一个browser节点以创建资源位置的别名。 这仅仅是为了方便,没有必要实际进行shim,因为模块和全局空间(脚本标签)之间没有通信

{
  "main": "app.cb.js",
  "scripts": {
    "build": "browserify ./app.cb.js > ./app.cb.bundle.js"
  },
  "browser": {
    "jquery": "./node_modules/jquery/dist/jquery.js",
    "expose": "./js/jquery.expose.js",
    "app": "./app.cb.js"
  },
  "author": "cool.blue",
  "license": "MIT",
  "dependencies": {
    "jquery": "^3.1.0"
  },
  "devDependencies": {
    "browserify": "^13.0.1",
    "browserify-shim": "^3.8.12"
  }
}

方法

  • 因为jQuery现在支持CommonJS,它会检测到browserify提供的module对象并返回一个实例,而不将其添加到window对象中。
  • 在应用程序中,使用require引入jquery并将其添加到module.exports对象中(以及需要共享的任何其他上下文)。
  • 在插件的开头添加一行代码来引入应用程序以访问它创建的jQuery实例。
  • 在应用程序中,将jQuery实例复制到$并使用插件中的jQuery。
  • 使用默认选项对应用程序进行Browserify处理,并将生成的捆绑包放入HTML中的脚本标签中。

代码

app.cb.js

var $ = module.exports.jQuery = require("jquery");
require('expose');

$(document).ready(function() {

    $('body').append(
        $('<button name="button" >Click me</button>')
            .css({"position": "relative",
                  "top": "100px", "left": "100px"})
            .click(function() {
                $(this).expose();
            })
    );
});

在插件的顶部
var jQuery = require("app").jQuery;

在HTML中。
<script type="text/javascript" src="app.cb.bundle.js"></script>

背景

jQuery使用的模式是,如果它检测到CommonJS环境,则调用其工厂函数时会带上noGlobal标志。它不会将实例添加到window对象中,并像往常一样返回一个实例。

通过默认设置,browserify会创建CommonJS上下文。下面是从打包文件中提取出的简化的jQuery模块结构。为了清晰起见,我删除了处理window对象的异构处理代码。

3: [function(require, module, exports) {

    ( function( global, factory ) {

        "use strict";

        if ( typeof module === "object" && typeof module.exports === "object" ) {
            module.exports = factory( global, true );
        } else {
            factory( global );
        }

    // Pass this if window is not defined yet
    } )( window, function( window, noGlobal ) {

    // ...

    if ( !noGlobal ) {
        window.jQuery = window.$ = jQuery;
    }

    return jQuery;
    }) );
}, {}]

我发现最好的方法是在node模块系统中使其工作,然后通过browserify进行打包后每次都能正常运行。
只需使用jsdom模拟window对象,使代码具有等同性。然后专注于在node中让它正常工作。接着,在模块和全局空间之间模拟任何流量,最后将其browserify并且它就可以在浏览器中正常工作了。

什么是 .cb 文件扩展名? - Glen Selle
@GlenSelle 哦,抱歉,那只是文件名的一部分。实际上它是 app.cb.js。我已经编辑过了,让它更清晰明了。 - Cool Blue

0

我在使用WordPress,因此被迫使用WordPress核心的jQuery,可在window对象中找到。

当我尝试使用npm中的slick()插件时,它生成了slick()未定义错误。添加browserify-shim并没有帮助太多。

我进行了一些挖掘,并发现require('jquery')并不总是一致的。

在我的主题JavaScript文件中,它调用WordPress核心的jQuery。

但是,在slick jQuery插件中,它会调用来自节点模块的最新版本jQuery。

最后,我解决了它。因此,分享package.jsongulpfile配置。

package.json:

"browserify": { "transform": [ "browserify-shim" ] }, "browserify-shim": { "jquery": "global:jQuery" },

“browserify”:{ “转换”:[ “browserify-shim” ] }, “browserify-shim”:{ “jquery”:“global:jQuery” },

gulpfile.babel.js:

browserify({entries: 'main.js', extensions: ['js'], debug: true}) .transform(babelify.configure({ presets: ["es2015"] })) .transform('browserify-shim', {global: true})

执行 transform 'browserify-shim' 是至关重要的一步,我之前忽略了它。没有它,browserify-shim 就不稳定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接