适用于浏览器JavaScript的Node风格的require?

104

有没有适用于浏览器端JavaScript的库,其提供与Node.js的require相同的灵活性、模块化和易用性?

更详细地说,之所以require如此出色是因为它:

  1. 允许从其他位置动态加载代码(在我看来样式更好,而不是将所有代码链接到HTML中)
  2. 提供了构建模块的一致接口
  3. 易于模块依赖其他模块(因此我可以编写一个需要jQuery的API,以便使用jQuery.ajax()
  4. 加载的JavaScript是封闭作用域的,这意味着我可以使用var dsp = require("dsp.js");进行加载,并且我能够访问 dsp.FFT,这不会干扰我的本地变量var FFT

我尚未找到有效实现这一点的库。 我经常使用的解决方法是:

  • coffeescript-concat -- 很容易要求其他js,但你必须编译它,这意味着它不太适合快速开发(例如在测试中构建API)

  • RequireJS -- 它很受欢迎,简单明了,并解决了1-3的问题,但缺乏作用域是一个真正的破坏者(我相信head.js也类似于缺少作用域,虽然我从未有过任何使用它的场合。同样,LABjs可以加载和.wait()可以缓解依赖问题,但仍然没有做到作用域)

据我所知,似乎有许多动态和/或异步加载javascript的解决方案,但它们往往具有与从HTML加载js相同的作用域问题。最重要的是,我希望有一种加载javascript的方式,完全不污染全局命名空间,但仍允许我加载和使用库(就像node的require一样)。

2020年更新:模块现在是ES6的标准,在2020年中期,大多数浏览器已经原生支持了。模块支持同步和异步(使用Promise)加载。我目前的建议是,大多数新项目应该使用ES6模块,并使用转换器回退到单个JS文件以适应旧版浏览器。

总体原则是,今天的带宽通常比我最初提出这个问题时要宽得多。因此,实际上,您可能会合理地选择始终使用ES6模块的转换器,并将精力集中在代码效率而不是网络上。

以前的编辑(或者如果您不喜欢ES6模块):自从写下这篇文章以来,我广泛使用了RequireJS(现在有更清晰的文档)。在我看来,RequireJS确实是正确的选择。我想澄清一下系统如何工作,以便像我一样困惑的人能够理解:

您可以在日常开发中使用require。模块可以是由函数返回的任何内容(通常是对象或函数),并且作为参数进行作用域限制。您还可以使用r.js将项目编译为单个文件以进行部署(实际上,这几乎总是更快的,即使require可以并行加载脚本)。

RequireJS和类似browserify(tjameson建议的很酷的项目)使用的node-style require之间的主要区别在于模块的设计和要求方式:

  • RequireJS 使用 AMD(异步模块定义)。在 AMD 中,require 接受需要加载的模块(JavaScript 文件)列表和回调函数。当它加载完每个模块后,它会将每个模块作为参数传递给回调函数。因此,它是真正的异步,并且非常适合于 Web。
  • Node 使用 CommonJS。在 CommonJS 中,require 是一个阻塞调用,它会加载一个模块并将其作为对象返回。这对 Node 来说很好用,因为文件可以快速从文件系统中读取,但在 Web 上效果不佳,因为同步加载文件可能需要更长的时间。

实际上,许多开发人员在看到 AMD 之前就已经使用了 Node(因此使用了 CommonJS)。此外,许多库/模块是针对 CommonJS 编写的(通过向 exports 对象添加内容),而不是针对 AMD 编写的(通过从 define 函数返回模块)。因此,许多从 Node 转到 Web 的开发人员希望在 Web 上使用 CommonJS 库。这是可能的,因为从 <script> 标签加载是阻塞的。像 browserify 这样的解决方案可以将 CommonJS(Node)模块包装起来,以便您可以使用 script 标签包含它们。

因此,如果您正在为Web开发自己的多文件项目,我强烈推荐使用RequireJS,因为它真正是Web的模块系统(尽管公平披露,我认为AMD比CommonJS更自然)。最近,这种区别变得不那么重要了,因为RequireJS现在允许您基本上使用CommonJS语法。此外,RequireJS可以用于在Node中加载AMD模块(尽管我更喜欢node-amd-loader)。

1
请注意,RequireJS实际上支持模块化和作用域。自从询问以来,我已经更广泛地使用它。在我看来,它功能丰富,但需要大量的文档阅读才能有效使用,并且在完美之前需要某种形式的一流同步加载。 - Alex Churchill
1
异步的区别是否有意义?每当我需要代码时,我基本上被阻止了,因为它定义了函数和其他我需要做任何事情的内容... - Michael
这个问题的答案应该被挑选出来并放置为一个实际的答案。请参考 Can I answer my own question? - Liam
7个回答

30
我知道可能有一些初学者想要组织他们的代码。现在是2022年,如果你考虑使用模块化JS应用程序,那么你应该立即开始使用npm和Webpack。
以下是几个简单的步骤:
1. 在项目根目录下运行 `npm init -y` 来初始化一个npm项目。 2. 下载Webpack模块打包工具:`npm install webpack webpack-cli`。 3. 创建一个index.html文件。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>App</title>
</head>
<body>
    
    <script src="_bundle.js"></script>
</body>
</html>

请特别注意_bundle.js文件 - 这是由webpack生成的最终JS文件,您不会直接修改它(继续阅读)。
4. 创建一个<project-root>/app.js文件,在其中导入其他模块:
const printHello = require('./print-hello');

printHello();

创建一个示例的print-hello.js模块:
module.exports = function() {
    console.log('Hello World!');
}
  1. 创建一个<project-root>/webpack.config.js文件,并复制粘贴以下内容:
var path = require('path');

module.exports = {
  entry: './app.js',
  output: {
    path: path.resolve(__dirname),
    filename: '_bundle.js'
  }
};

在上面的代码中,有2个要点:
  • 入口点app.js是您将编写JS代码的位置。它将导入其他模块,如上所示。
  • 输出_bundle.js是由webpack生成的最终捆绑包。这就是您的html在最后看到的内容。
  1. 打开您的package.json,并将scripts替换为以下命令:
  "scripts": {
    "start": "webpack --mode production -w"
  },
  1. 最后,运行脚本 watch app.js 并通过运行 npm start 生成 _bundle.js 文件。
  2. 享受编码的乐趣吧!

我按照您的步骤操作,已经在本地使项目正常运行,但是当我将其部署到GitHub页面时,我遇到了以下问题: _bundle.js:1 Failed to load resource: the server responded with a status of 404 () 这是从我的GitHub页面上显示的项目控制台中输出的。 请问有什么想法或提示吗?非常感谢! - JeromeCode
有时候我想分享一些示例代码,只想复制/粘贴并在即时通讯中发送它 - 我不想设置一个git仓库来分享代码片段... - neubert

22

请看Ender,它可以实现很多功能。

此外,browserify也不错。我用过require-kiss¹,它也能够胜任。还可能有其他选择。

至于RequireJS,我不太确定。它和node的不太一样。你可能会遇到从其他位置加载的问题,但它可能也能够工作。只要有一个叫做provide的方法或其他可调用的方法即可。

简而言之——我推荐使用browserify或require-kiss。


更新:

1:require-kiss已经停止更新,并被作者删除了。我后来使用RequireJS而没有遇到问题。require-kiss的作者编写了pakmanagerpakman。完全公开透明,我与开发者共事。

个人喜欢RequireJS更多。它更容易调试(你可以在开发中有单独的文件,在生产中只需一个文件),而且建立在坚实的“标准”基础上。


好像 require-kiss 的链接失效了。简单的搜索并没有找到任何信息 - 它去哪里了? - Joel Purra
@JoelPurra - require-kiss已被移除并被pakmanager替代。我现在推荐require-js。我已更新答案。 - beatgammit
这里的回答很好,朋友 : ),不介意看看我刚刚提出的类似但又不同的问题吗?http://stackoverflow.com/questions/43237875/javascript-require-in-browser-console - Webeng

18

我写了一个小脚本,可以异步和同步加载JavaScript文件,在这里可能会有所帮助。它没有依赖项,并且与Node.js & CommonJS兼容。安装非常容易:

$ npm install --save @tarp/require

接下来只需将以下代码添加到您的HTML中以加载主模块:

<script src="/node_modules/@tarp/require/require.min.js"></script>
<script>Tarp.require({main: "./scripts/main"});</script>

在你的主模块(以及任何子模块)中,你可以像在CommonJS/NodeJS中一样使用require()函数。完整的文档和代码可以在GitHub上找到。


如何使用位于main.js中的函数?例如,main.js有一个简单的myFunction函数来弹出“hello”。我该如何调用main.myFunction()?这样不会弹出警报吗? - Brian Wiley
你必须使用 Tarp.require({ expose: true }); 才能使它工作吗?就像你的测试中一样? - Brian Wiley
不一定需要。选项 expose: true 只有在你想要在主模块之外使用 require() 时才需要。例如直接在 HTML 文件中。但在你的情况下,最好将 myFunction() 添加到全局 window 对象中。你可以在主模块内定义它,如 window.myFunction = function() { ... }。这样 myFunction() 就可以从任何地方访问。 - Torben

16

一种变体的Ilya Kharlamov优秀回答,增加了一些代码以使其与Chrome开发者工具兼容。

//
///- REQUIRE FN
// equivalent to require from node.js
function require(url){
    if (url.toLowerCase().substr(-3)!=='.js') url+='.js'; // to allow loading without js suffix;
    if (!require.cache) require.cache=[]; //init cache
    var exports=require.cache[url]; //get from cache
    if (!exports) { //not cached
            try {
                exports={};
                var X=new XMLHttpRequest();
                X.open("GET", url, 0); // sync
                X.send();
                if (X.status && X.status !== 200)  throw new Error(X.statusText);
                var source = X.responseText;
                // fix (if saved form for Chrome Dev Tools)
                if (source.substr(0,10)==="(function("){ 
                    var moduleStart = source.indexOf('{');
                    var moduleEnd = source.lastIndexOf('})');
                    var CDTcomment = source.indexOf('//@ ');
                    if (CDTcomment>-1 && CDTcomment<moduleStart+6) moduleStart = source.indexOf('\n',CDTcomment);
                    source = source.slice(moduleStart+1,moduleEnd-1); 
                } 
                // fix, add comment to show source on Chrome Dev Tools
                source="//@ sourceURL="+window.location.origin+url+"\n" + source;
                //------
                var module = { id: url, uri: url, exports:exports }; //according to node.js modules 
                var anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module
                anonFn(require, exports, module); // call the Fn, Execute the module
                require.cache[url]  = exports = module.exports; //cache obj exported by module
            } catch (err) {
                throw new Error("Error loading module "+url+": "+err);
            }
    }
    return exports; //require returns object exported by module
}
///- END REQUIRE FN

6
(function () {
    // c is cache, the rest are the constants
    var c = {},s="status",t="Text",e="exports",E="Error",r="require",m="module",S=" ",w=window;
    w[r]=function R(url) {
        url+=/.js$/i.test(url) ? "" : ".js";// to allow loading without js suffix;
        var X=new XMLHttpRequest(),module = { id: url, uri: url }; //according to the modules 1.1 standard
        if (!c[url])
            try {
                X.open("GET", url, 0); // sync
                X.send();
                if (X[s] && X[s] != 200) 
                    throw X[s+t];
                Function(r, e, m, X['response'+t])(R, c[url]={}, module); // Execute the module
                module[e] && (c[url]=module[e]);
            } catch (x) {
                throw w[E](E+" in "+r+": Can't load "+m+S+url+":"+S+x);
            }
        return c[url];
    }
})();

最好不要在生产环境中使用,因为它会阻塞程序。(在node.js中,require()是一个阻塞调用)。

“exports:{}”不应该是“module”的一个属性吗?调用应该是(R,module.exports,module)。 - Lucio M. Tato
@LucioM.Tato 我不确定,在 Modules 1.1 standard 中没有看到 module.exports 的提及。 你可以随时调用 require(module.id) 来获取导出。 - Ilya Kharlamov
是的,你说得对,我在考虑node.js模块实现。我对你非常好的答案进行了一些修改,以便使其与Chrome Dev Tools(我正在使用它作为调试时IDE)兼容。如果有人需要,我会将代码作为另一个答案发布在这个问题下面。 - Lucio M. Tato

1

Require-stub提供了浏览器中符合node规范的require,可以解析模块和相对路径。使用类似于TKRequire(XMLHttpRequest)的技术。生成的代码完全可用于browserify,因为require-stub可以替代watchify


0

Webmake将Node风格的模块打包到浏览器中,请尝试一下。


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