浏览器内javascript需要节点样式吗?

IT技术 javascript load require scoping
2021-02-27 10:10:49

是否有任何可提供与 Node 相同的灵活性/module化/易用性的浏览器内 JavaScript 库require

提供更多细节:原因require如此之好是它:

  1. 允许从其他位置动态加载代码(在我看来,这比在 HTML 中链接所有代码在风格上更好)
  2. 它为构建module提供了一致的接口
  3. module很容易依赖其他module(例如,我可以编写一个需要 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 的方法,它根本不会污染全局命名空间,但仍然允许我加载和使用库(就像节点的 require 那样)。

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

作为一般原则,今天的带宽通常也比我最初提出这个问题时宽得多。因此,在实践中,您可能会合理地选择始终使用带有 ES6 module的转译器,并将精力集中在代码效率而不是网络上。

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

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

RequireJS 和像 browserify(tjameson 推荐的一个很酷的项目)使用的 node-style require 之间的主要区别在于module的设计和需要的方式:

  • RequireJS 使用 AMD(异步module定义)。在 AMD 中,require需要加载一个module列表(javascript 文件)和一个回调函数。当它加载了每个module时,它会调用回调,并将每个module作为回调的参数。因此它是真正异步的,因此非常适合网络。
  • Node 使用 CommonJS。在 CommonJS 中,require是一个阻塞调用,它加载一个module并将其作为一个对象返回。这对 Node 很有效,因为文件是从文件系统读取的,这足够快,但在 Web 上效果不佳,因为同步加载文件可能需要更长的时间。

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

因此,如果您正在为 Web 开发自己的多文件项目,我强烈推荐 RequireJS,因为它确实是一个用于 Web 的module系统(尽管坦率地说,我发现 AMD 比 CommonJS 更自然)。最近,区别变得不那么重要了,因为 RequireJS 现在允许您基本上使用 CommonJS 语法。此外,RequireJS 可用于在 Node 中加载 AMD module(尽管我更喜欢node-amd-loader)。

6个回答

我意识到可能有初学者希望组织他们的代码。这是2021 年,如果您正在考虑module化 JS 应用程序,您应该立即开始使用npmWebpack

以下是一些简单的入门步骤:

  1. 在您的项目根目录中,运行npm init -y以初始化一个 npm 项目
  2. 下载 Webpack module打包器: 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文件,你不会直接修改它(继续阅读)。

  1. 创建一个<project-root>/app.js你将在其中导入其他module的:
const printHello = require('./print-hello');

printHello();
  1. 创建示例print-hello.jsmodule:
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点:

  • entryapp.js是您编写 JS 代码的地方。它将导入其他module,如上所示。
  • output_bundle.js是由 webpack 生成的最终包。这就是您的 html 将在最后看到的内容。
  1. 打开您的package.json, 并替换scripts为以下命令:
  "scripts": {
    "start": "webpack --mode production -w"
  },
  1. 最后运行脚本的手表app.js,并生成_bundle.js运行文件:npm start
  2. 享受编码!
这对于 webpack 项目来说是一个非常好的开始!谢谢 :)
2021-04-22 10:10:49

检查ender它做了很多这样的事情。

此外,browserify 非常好。我使用过require-kiss ¹ 并且它有效。可能还有其他人。

我不确定 RequireJS。它与节点不同。您可能会遇到从其他位置加载的问题,但它可能会起作用。只要有一个提供方法或可以调用的东西。

TL;DR - 我建议使用 browserify 或 require-kiss。


更新:

1:require-kiss现在已经死了,作者已经去掉了。从那以后,我一直在使用 RequireJS,没有任何问题。require-kiss 的作者写了pakmanagerpakman完全披露,我与开发商合作。

我个人更喜欢 RequireJS。调试要容易得多(您可以在开发中使用单独的文件,在生产中使用单个部署的文件)并且构建在可靠的“标准”之上。

@JoelPurra - require-kiss 已被删除并被 pakmanager 取代。我现在推荐 require-js。我已经更新了答案。
2021-04-27 10:10:49
require-kiss 的链接似乎已经失效。简单的(重新)搜索并没有带来任何结果——它去哪儿了?
2021-04-30 10:10:49
很好的答案,伙计:),介意检查一下我刚刚做的与这个相似的问题(但同时不同)吗?stackoverflow.com/questions/43237875/...
2021-05-11 10:10:49

我写了一个小脚本,它允许异步和同步加载 Javascript 文件,这可能在这里有用。它没有依赖项并且兼容 Node.js 和 CommonJS。安装非常简单:

$ npm install --save @tarp/require

然后只需将以下行添加到您的 HTML 以加载主module:

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

在你的主module(当然还有任何子module)中,你可以使用require()你从 CommonJS/NodeJS 知道的。完整的文档和代码可以在 GitHub 上找到

不必要。expose: true仅当您想require()在主module之外使用时才需要该选项例如直接在 HTML 文件中。但是在您的情况下,添加myFunction()到全局窗口对象可能会更好您可以像window.myFunction = function() { ... }在主module中一样定义它这种方式myFunction()可以从任何地方访问。
2021-04-16 10:10:49
你必须使用Tarp.require({ expose: true });它才能工作吗?就像在你的测试中一样?
2021-04-18 10:10:49
你如何使用 main.js 中的函数?例如 main.js 有简单myFunctionalert("hello"). 我打电话main.myFunction()吗?这不会做警报吗?
2021-04-20 10:10:49

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
(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() 是一个阻塞调用很好)。

@LucioM.Tato 我不确定,在Modules 1.1 standard中看不到 module.exports 的任何提及您可以随时调用 require(module.id) 来获取导出
2021-04-29 10:10:49
不应该是“exports:{}”“module”的属性吗?并且调用是 (R, module.exports, module)
2021-05-07 10:10:49
是的。你是对的,我在考虑 node.js module的实现。我在您非常好的答案中对代码添加了一些修改,以使其与 Chrome Dev Tools 配合使用(我将其用作调试时间 IDE)。我会发布代码作为这个问题的另一个答案,以防对其他人有用。
2021-05-11 10:10:49