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


我正在使用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文档中给出的示例,我不想指定导出,因为这个插件(以及大多数(如果不是全部)jQuery插件)将自己附加到jQuery对象。除非我上面做错了,否则当我使用它时,我不明白为什么它不起作用(我得到一个错误告诉我函数未定义)。见下文:

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

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


40
2017-07-19 01:41


起源




答案:


在重新审视并尝试了更多的东西之后,我终于围绕着浏览器shim正在做什么以及如何使用它。对我来说,在我最终了解如何使用browserify-shim之前,我必须掌握一个关键原则。对于两种不同的用例,有两种方法可以使用browserify-shim:曝光和填充。

背景

假设您只想在标记中插入脚本标记(出于测试或性能原因,如缓存,CDN等)。通过在标记中包含脚本标记,浏览器将点击脚本,运行它,并且很可能在窗口对象上附加属性(在JS中也称为全局)。当然,这可以通过做任何事情来访问 myGlobal 要么 window.myGlobal。但是这两种语法都存在问题。它不遵循CommonJS规范,这意味着如果模块开始支持CommonJS语法(require()),你无法利用它。

解决方案

Browserify-shim允许您通过CommonJS指定您想要“暴露”的全局 require() 句法。记住,你可以做到 var whatever = global; 要么 var whatever = window.global; 但你做不到 var whatever = require('global') 并期望它为您提供正确的lib /模块。不要混淆变量的名称。它可能是任意的。您实际上是将全局变量设为局部变量。这听起来很愚蠢,但它是浏览器中JS的悲惨状态。同样,希望一旦lib支持CommonJS语法,它将永远不会通过窗口对象上的全局附加自身。这意味着你必须使用 require() 语法并将其分配给局部变量,然后在需要的地方使用它。

注意: 我发现变量命名在browserify-shim docs / examples中有点令人困惑。请记住,关键是您要包含lib 仿佛 它是一个表现良好的CommonJS模块。所以你最终要做的就是告诉浏览器当你需要myGlobal时 require('myGlobal') 你实际上只是希望在窗口对象上获得全局属性 window.myGlobal

事实上,如果你对require函数实际上做了什么感到好奇,那就很简单了。以下是引擎盖下发生的事情:

var whatever = require('mygGlobal');

成为...

var whatever = window.mygGlobal;

曝光

所以在这个背景下,让我们看看我们如何在browserify-shim配置中公开一个模块/ lib。基本上,你告诉browserify-shim两件事。您希望在呼叫时可以访问的名称 require() 它应该期望在窗口对象上找到全局。所以这就是那里 global:* 语法来了。让我们看一个例子。我想将jquery作为index.html中的脚本标记放入,以便我获得更好的性能。这是我在配置中需要做的事情(这将在package.json或外部配置JS文件中):

"browserify-shim": {
  "jquery": "global:$"
}

所以这就是这意味着什么。我在其他地方包含了jQuery(记住,browserify-shim不知道我们在哪里放置我们的标签,但它不需要知道),但我想要的只是给予它 $ 当我需要带有字符串参数“jquery”的模块时,窗口对象上的属性。进一步说明。我也可以这样做:

"browserify-shim": {
  "thingy": "global:$"
}

在这种情况下,我必须将“thingy”作为参数传递给require函数,以便获取jQuery对象的实例(它只是从jQuery获取jQuery) window.$):

var $ = require('thingy');

是的,同样,变量名可以是任何东西。没什么特别的 $ 与全球财产相同 $ 实际的jQuery库使用。虽然使用相同的名称来避免混淆是有意义的。这最终引用了 $ 窗口对象上的属性,由 global:$ 价值在 browserify-shim package.json中的对象。

匀场

好的,所以几乎涵盖暴露。 browserify-shim的另一个主要功能是shimming。那是什么? Shimming与暴露基本上是一样的,除了在HTML标记中包含lib或模块之类的东西,比如脚本标记,你告诉browserify-shim在哪里抓取JS文件。没有必要使用 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对象中获取$属性,当需要带有字符串参数的jQuery到“jquery”的require函数时返回该属性。同样,为了便于说明,您还可以将其重命名为其他任何内容。

"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 如果lib依赖于另一个lib /模块,则允许您告诉browserify-shim的属性。我在这里解释的内容适用于两者。我希望这有助于其他人努力了解browserify-shim实际上在做什么以及如何使用它。

匿名填充

匿名填充是browserify-shim的替代品,它允许您使用browserify将jQuery等库变换为UMD模块 --standalone选项。

$ browserify ./src/js/vendor/jquery.js -s thingy > ../dist/jquery-UMD.js

如果将其放入脚本标记中,则此模块会将jQuery添加到窗口对象中 thingy。当然也可以 $ 或者你喜欢什么。

但是,如果是的话 require加入您的browserify应用包, var $ = require("./dist/jquery-UMD.js");,您将在应用程序内部使用jQuery,而无需将其添加到窗口对象中。

这种方法不需要browserify-shim,并且在它寻找的时候利用jQuery的CommonJS意识 module 对象并传递一个 noGlobal 标志进入其工厂,告诉它不要将自己附加到窗口对象。


97
2017-08-30 18:59



不,你说得对。它只是项目根目录中的package.json文件(您可以在其中定义所有NPM依赖项) - Glen Selle
谢谢你的信息:)虽然你没有在你的答案中谈论插件......:/ - Jamie Hutber
谢谢你的整个解释,但你怎么去集成jquery插件呢? - David
感谢您的解释。您应该回复文档。这比官方文档要好。 - Evan Carroll
非常好的解释,谢谢你这个格伦!绕过圈子试图让垫子工作,并想知道为什么我的结果总是未定义。这一切都清楚:) - Michael Martin


对于每个人,谁在寻找一个具体的例子:

以下是一个例子 package.json 和 app.js 一个jQuery插件的文件,它附加到 jQuery/$ 对象,例如: $('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

// 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" ] }

希望能帮助一些正在寻找具体例子的人(就像我,当我发现这个问题时)。


12
2018-01-11 11:09



谢谢!我花了将近一天时间试图让我的浏览器工作。 - Kesha Antonov
我确定这个帖子会因感谢而关闭,但是谢谢你。整个匀场的概念在任何地方都没有得到很好的解释,也没有任何血腥的语法来使事情发挥作用。你的写作帮助我理解如何测试行为 - ekkis
更进一步,你可以把它扼杀在萌芽状态,避免污染 window 对象首先,但只有你可以添加一个简单的标题插件。为了完整起见,我在单独的答案中添加了该方法。 - Cool Blue


为了完整起见,这里有一个利用jQuery的CommonJS意识的方法,以避免担心污染 window 没有实际需要垫片的物体。

特征

  1. 包含在jQuery中的jQuery
  2. 捆绑包中包含的插件
  3. 没有污染了 window 目的

配置

./package.json,添加一个 browser 节点为资源位置创建别名。 这纯粹是为了方便,没有必要实际填充任何内容,因为模块和全局空间之间没有通信(脚本标签)

{
  "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感知的,它会感觉到它的存在 module 提供的对象 browserify 并返回一个实例,而不将其添加到 window 目的。
  • 在应用程序中, require jquery并将其添加到 module.exports 对象(以及需要共享的任何其他上下文)。
  • 在插件的开头添加一行,要求应用程序访问它创建的jQuery实例。
  • 在应用程序中,将jQuery实例复制到 $ 并使用jQuery与插件。
  • 使用默认选项浏览化应用程序,并将生成的包放入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使用的模式是用它来调用它的工厂 noGlobal 标志,如果它感知到CommonJS环境。它不会添加实例 window 对象并将一如既往地返回一个实例。

CommonJS上下文由。创建 browserify 默认。下面是显示jQuery模块结构的bundle的简化摘录。我删除了处理同构处理的代码 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;
    }) );
}, {}]

我找到的最好的方法是让事情在节点模块系统中运行,然后在浏览器化后每次都可以工作。
只是用 jsdom 垫了一下 window 对象,以便代码是同构的。然后,专注于让它在节点中工作。然后,在模块和全局空间之间填充任何流量,最后浏览它,它将在浏览器中工作。


1
2017-07-17 14:09



什么是 .cb 文件扩展名? - Glen Selle
@GlenSelle哦,对不起,这只是文件名的一部分。它实际上是app.cb.js.编辑使其更清晰。 - Cool Blue


我在使用wordpress。因此,我有点被迫使用wordpress核心的jQuery,可用于 window 目的。

它正在产生 slick() 当我尝试使用时,没有定义错误 slick() 来自npm的插件。添加 browserify-shim 没多大帮助。

我做了一些挖掘并发现了这一点 require('jquery') 始终不一致。

在我的主题javascript文件中,它调用了wordpress core的jquery。

但在 slick jquery插件它从节点模块调用最新的jquery。

最后,我能够解决它。所以,分享 package.json 和 gulpfile 组态。

的package.json:

"browserify": { "transform": [ "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})

做变换'browserify-shim'是至关重要的部分,我早些时候失踪了。没有它 browserify-shim 不一致。


0
2018-06-22 12:00