Browserify - 如何在浏览器中调用通过Browserify生成的文件中打包的函数

112

我是nodejs和browserify的新手。我从这个链接开始学习。

我有一个main.js文件,其中包含以下代码:

var unique = require('uniq');

var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];

this.LogData =function(){
console.log(unique(data));
};

现在我要使用npm安装uniq模块:
 npm install uniq

然后,我使用browserify命令将从main.js开始的所有所需模块打包成一个名为bundle.js的单个文件:

browserify main.js -o bundle.js

生成的文件如下所示:
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var unique = require('uniq');

var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];

this.LogData =function(){
console.log(unique(data));
};

},{"uniq":2}],2:[function(require,module,exports){
"use strict"

function unique_pred(list, compare) {
  var ptr = 1
    , len = list.length
    , a=list[0], b=list[0]
  for(var i=1; i<len; ++i) {
    b = a
    a = list[i]
    if(compare(a, b)) {
      if(i === ptr) {
        ptr++
        continue
      }
      list[ptr++] = a
    }
  }
  list.length = ptr
  return list
}

function unique_eq(list) {
  var ptr = 1
    , len = list.length
    , a=list[0], b = list[0]
  for(var i=1; i<len; ++i, b=a) {
    b = a
    a = list[i]
    if(a !== b) {
      if(i === ptr) {
        ptr++
        continue
      }
      list[ptr++] = a
    }
  }
  list.length = ptr
  return list
}

function unique(list, compare, sorted) {
  if(list.length === 0) {
    return []
  }
  if(compare) {
    if(!sorted) {
      list.sort(compare)
    }
    return unique_pred(list, compare)
  }
  if(!sorted) {
    list.sort()
  }
  return unique_eq(list)
}

module.exports = unique
},{}]},{},[1])

在将bundle.js文件包含到我的index.htm页面后,我该如何调用logData函数?

你想在哪里调用它?为什么要调用它? - artur grzesiak
2
@arturgrzesiak:我想在我的另一个项目中利用这个函数,该项目将在浏览器中运行。 - SharpCoder
12个回答

114

使用Browserify捆绑独立模块的关键部分是--s选项。它将你从模块使用node的module.exports导出的任何内容作为全局变量暴露出来。然后可以在<script>标签中包含该文件。

只有在某些情况下需要暴露该全局变量时才需要执行此操作。在我的情况下,客户端需要一个独立模块,可以在网页中包含,而不需要担心这个Browserify业务。

以下是一个示例,其中我们使用带有module参数的--s选项:

browserify index.js --s module > dist/module.js

这将把我们的模块作为全局变量命名为module
来源

更新: 感谢@fotinakis。请确保传递--standalone your-module-name。如果您忘记了--standalone需要一个参数,那么Browserify可能会生成一个空模块,因为找不到它。

希望这节省了您一些时间。


2
我正在尝试将经过Babel转换的ES6代码进行Browserify。但是当我在浏览器中控制台输出时,独立对象为空。没有任何模块的简单ES6代码在独立模式下正常工作。对此有什么指针吗? - John
1
@jackyrudetsky 请确保你传递了 --standalone your-module-name。如果你忘记了 --standalone 需要一个参数,browserify 可能会悄悄地生成一个空模块,因为它找不到它。 - fotinakis
1
@fotinakis 这实际上是 Browserify 的一个问题 https://github.com/substack/node-browserify/issues/1537 - John
3
我认为这应该成为被接受的答案。如果您正在使用全局函数,最好拥有自己的命名空间,而不是将每个函数都挂在window上。 - VictorB
1
@VictorB 在Javascript中,所有的全局变量都是window对象的属性,因此这两种方法都可以实现同样的功能(将全局变量添加到window对象中)。 - David Lopez
显示剩余2条评论

92

默认情况下,browserify 不允许您从浏览器化的代码之外访问模块 - 如果您想调用浏览器化的模块中的代码,则应该将您的代码与该模块一起 browserify。请参见 http://browserify.org/ 上的示例。

当然,您也可以像这样明确地使您的方法能够从外部访问:

window.LogData =function(){
  console.log(unique(data));
};

然后,您可以从页面上的任何其他位置调用LogData()


1
谢谢。这个可行。这是否意味着,在创建函数时,我应该写window.functionName而不是this.functionName?我们有其他解决方法吗?使用window.functionName的原因是什么? - SharpCoder
28
"你应该将模块与代码一起进行Browserify" - 哎呀,如果我想做类似于 onclick="someFunction()" 的东西怎么办。你不可能认为这是一个罕见的用例,对吧?! - BlueRaja - Danny Pflughoeft
64
针对初学者如何在客户端实际使用 Browserify,目前缺乏详细的文档说明。 - Oliver Dixon
1
是的,文档应该明确指出这是一个需要避免的设计决策,但同时提供一条清晰的路径,以使其在没有替代方案时能够工作(在我的情况下,使用模板中的数据填充JS对象)...感谢@thejh指出了一个简单的解决方案! ;) - Alexandre Martini
1
我甚至无法想象在哪种情况下您不想将主要函数在模块外部提供。这不是默认行为吗?什么样的Web应用程序不调用函数呢? - Cybernetic
显示剩余4条评论

56

@Matas Vaitkevicius的答案使用Browserify的standalone选项是正确的,(@thejh的答案利用window全局变量也可以正常工作,但正如其他人所指出的,它会污染全局命名空间,因此不是理想的选择)。我想补充一些有关如何使用standalone选项的细节。

在您要捆绑的源脚本中,请确保通过module.exports公开您想要调用的函数。在客户端脚本中,您可以通过<bundle-name> .<func-name>调用这些公开的函数。以下是一个示例:

我的源文件src/script.js将包含以下内容:
module.exports = {myFunc: func};

我的browserify命令将类似于以下内容:
browserify src/script.js --standalone myBundle > dist/bundle.js

我的客户端脚本dist/client.js将加载捆绑的脚本
<script src="bundle.js"></script>
然后像这样调用公开的函数:
<script>myBundle.myFunc();</script>


在调用公开的函数之前,不需要在客户端脚本中要求捆绑包名称,例如:<script src="bundle.js"></script><script>var bundled = require("myBundle"); bundled.myFunc();</script>不是必要的也行不通。

实际上,与没有独立模式的browserify捆绑的所有函数一样,require函数在捆绑脚本外部不可用。Browserify允许您在客户端使用一些Node函数,但是仅限于捆绑脚本本身;它不适用于创建可以在任何地方客户端导入和使用的独立模块,这就是为什么我们需要额外麻烦才能在捆绑上下文之外调用单个函数的原因。


4
哇!终于有一个实际的例子了。 - N73k
1
好的例子,但是关于“它污染了全局命名空间,因此不理想”的说法并不是自动成立的,如果只有一个函数,这种方式可能是可以接受的;只是花招,即使myBundle被附加到窗口对象上,也应该使用window.myBundle.myFunc()而不是window.myFunc() - joedotnot
2
给予提供端到端示例的人额外加分。 - Sharud
这就是文档应该编写的方式。 - Ellery Leung
在我的电脑上无法工作,这是我的帖子 - Antonio Ooi

8
我刚刚阅读了答案,似乎没有人提到全局变量作用域的使用?如果你想在 Node.js 和浏览器中使用相同的代码,则这是很有用的。
class Test
{
  constructor()
  {
  }
}
global.TestClass = Test;

那么您就可以在任何地方访问TestClass

<script src="bundle.js"></script>
<script>
var test = new TestClass(); // Enjoy!
</script>

注意: TestClass 然后在任何地方都可用。这与使用 window 变量相同。

此外,您可以创建一个装饰器,将类公开到全局作用域中。这真的很好,但很难跟踪变量定义的位置。


正如你自己所说,将函数添加到global与添加到window产生的效果相同,这已经被thejh涵盖了。这个答案没有添加任何新信息。 - Galen Long
只想指出,这里的悬挂括号对于JavaScript来说是非常糟糕的实践,例如:将此模式应用于return关键字并准备哭泣。例如 return {} 但是将左花括号放到下一行。 - Sgnl
@Sgnl,抱歉我不明白你在说什么。你能详细说明一下吗? - Playdome.io
1
@Azarus 我创建了一个演示用的fiddle - https://jsfiddle.net/cubaksot/1/ - Sgnl
@Azarus,如何“创建一个公开类的装饰器”?根据您上次回答的最后一部分。 - joedotnot
显示剩余8条评论

6

最小可运行示例

这基本上与https://dev59.com/sWAg5IYBdhLWcg3ws8ro#43215928相同,但有具体的文件让你可以轻松运行和重现。

此代码也可在https://github.com/cirosantilli/browserify-hello-world获得。

index.js

const uniq = require('uniq');

function myfunc() {
  return uniq([1, 2, 2, 3]).join(' ');
}
exports.myfunc = myfunc;

index.html

<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>Browserify hello world</title>
</head>
<body>
<div id="container">
</body>
</div>
<script src="out.js"></script>
<script>
document.getElementById('container').innerHTML = browserify_hello_world.myfunc();
</script>
</html>

Node.js 的使用:

#!/usr/bin/env node

const browserify_hello_world = require('./index.js');

console.log(browserify_hello_world.myfunc());

为浏览器生成out.js

npx browserify --outfile out.js --standalone browserify_hello_world index.js

浏览器和命令行均显示了预期的输出:

1 2 3

测试环境为Browserify 16.5.0、Node.js v10.15.1、Chromium 78、Ubuntu 19.10。


2
这段代码中的 exports.myfunc = myfunc 部分非常关键,其他答案中可能会遗漏。 - parttimeturtle

5

阅读有关--standalone参数的browserify的README.md文件,或者搜索“browserify umd”


21
这更像是一个提示,告诉你在哪里能找到答案,而不是直接给出答案。 - user2314737
这让我找了两天的解决方案(如何在require.js环境中使用browserify输出)。谢谢! - Flion

3

整个概念是关于封装的。

1.) 替代方案 - 对象"this"

为此,我将假设您有"仅用于整个应用程序的1个脚本{{app_name}}"和"1个函数{{function_name}}"

添加函数{{function_name}}

function {{function_name}}(param) { ... }

this 转化为对象

this.{{function_name}} = function(param) { ... }

那么你需要给该对象命名才能使用 - 你可以像其他人建议的一样添加参数 "standalone with name"

所以如果你使用"watchify" with "browserify",请使用以下内容

var b = browserify({
    ...
    standalone: '{{app_name}}'
});

或者命令行

browserify index.js --standalone {{app_name}} > index-bundle.js

那么你可以直接调用这个函数。
{{app_name}}.{{function_name}}(param);
window.{{app_name}}.{{function_name}}(param);

2.) 替代方案 - 对象"window"

添加函数{{function_name}}

function {{function_name}}(param) { ... }

window 对象转换为对象。

window.{{function_name}} = function(param) { ... }

那么您可以直接调用该函数。
{{function_name}}(param);
window.{{function_name}}(param);

3
为了使您的函数可以在HTML和服务器端Node中都可用:
main.js:
var unique = require('uniq');

function myFunction() {
    var data = [1, 2, 2, 4, 3];
    return unique(data).toString();
}
console.log ( myFunction() );

// When browserified - we can't call myFunction() from the HTML, so we'll externalize myExtFunction()
// On the server-side "window" is undef. so we hide it.
if (typeof window !== 'undefined') {
    window.myExtFunction = function() {
        return myFunction();
    }
}

main.html:

<html>
    <head>
        <script type='text/javascript' src="bundle.js"></script>
    <head>
    <body>
        Result: <span id="demo"></span>
        <script>document.getElementById("demo").innerHTML = myExtFunction();</script>
    </body>
</html>

运行:

npm install uniq
browserify main.js > bundle.js

当你在浏览器中打开 main.html 时,应该得到与运行时相同的结果。

node main.js

1

您有几个选项:

  1. 让插件 browserify-bridge 自动将模块导出到生成的入口模块中。这对于 SDK 项目或不需要手动跟踪导出内容的情况非常有用。

  2. 按照伪命名空间模式进行卷起暴露:

首先,像这样安排您的库,利用文件夹上的索引查找:

/src
--entry.js
--/helpers
--- index.js
--- someHelper.js
--/providers
--- index.js
--- someProvider.js
...

使用这个模式,您可以像这样定义条目:

exports.Helpers = require('./helpers');
exports.Providers = require('./providers');
...

请注意,require命令会自动加载各个子文件夹中的index.js文件。
在您的子文件夹中,您只需要包含一个类似的清单,列出该上下文中可用的模块:
exports.SomeHelper = require('./someHelper');

这种模式具有良好的扩展性,并允许上下文(按文件夹)跟踪要包含在汇总API中的内容。

0

你也可以像这样从html文件中调用你的函数:

main.js:(将包含在bundle.js中)

window.onload = function () {
    document.getElementById('build-file')
        .addEventListener('click', buildFile)

}
function buildFile() {
 ...
}

index.html:

<button id="build-file"">Build file</button>

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