【问题标题】:Packaging-up Browser/Server CommonJS modules with dependancies拾取具有依赖关系的浏览器/服务器 CommonJS 模块
【发布时间】:2016-07-27 01:43:31
【问题描述】:

假设我正在用 JavaScript 编写一个模块,它可以在浏览器和服务器(使用 Node)上使用。我们称之为模块。假设 Module 将受益于另一个名为 Dependancy 的模块中的方法。这两个模块都被编写为供浏览器和服务器使用,就像 CommonJS 风格:

module.js

if (typeof module !== 'undefined' && module.exports)
  module.exports = Module; /* server */
else
  this.Module = Module; /* browser */

dependancy.js

if (typeof module !== 'undefined' && module.exports)
  module.exports = Dependancy; /* server */
else
  this.Dependancy = Dependancy; /* browser */

显然,Dependancy 可以直接在浏览器中使用。但是,如果 Module 中包含 var dependancy = require('dependency'); 指令,那么“维护”模块就会变得更加麻烦。

我知道我可以在 Module 中对 Dependancy 执行全局检查,如下所示:

var dependancy = this.Dependancy || require('dependancy');

但这意味着我的模块对浏览器安装有两个附加要求:

  • 用户必须将 dependency.js 文件作为<script> 包含在他们的文档中
  • 并且用户必须确保在 module.js 之前加载此脚本

添加这两个要求引发了一个像 CommonJS 这样简单易用的模块化框架的想法。

我的另一个选择是在我的 Module 包中包含第二个已编译脚本,其中 dependency.js 使用 browserify 捆绑。然后我指示在浏览器中使用该脚本的用户包含此脚本,而服务器端用户使用package.json 中概述的非捆绑条目脚本。这比第一种方式更可取,但它需要一个预编译过程,我每次更改库时都必须运行该过程(例如,在上传到 GitHub 之前)。

还有其他我没有想到的方法吗?

【问题讨论】:

  • 这是一个糟糕的想法,出于安全原因,服务器和浏览器不应该共享同一个库,这就是为什么我们首先需要两者。想象一下,您的依赖项之一会暴露您的用户电子邮件或密码或类似的东西。这将是灾难性的。
  • @Val 有许多通用库可以在服务器端和浏览器端使用,但不涉及安全性。例如,async 库。此外,在这种情况下,Module和Dependency都是我自己编写的。

标签: javascript node.js module browserify commonjs


【解决方案1】:

当然,您可以在双方都使用相同的模块 with 依赖项。您只需要更好地指定它。这是我使用的方式:

(function (name, definition){
    if (typeof define === 'function'){ // AMD
        define(definition);
    } else if (typeof module !== 'undefined' && module.exports) { // Node.js
        module.exports = definition();
    } else { // Browser
        var theModule = definition(), global = this, old = global[name];
        theModule.noConflict = function () {
            global[name] = old;
            return theModule;
        };

        global[name] = theModule;
    }
})('Dependency', function () {
    // return the module's API
    return {
        'key': 'value'
    };
});

这只是一个非常基本的示例——您可以返回函数、实例化函数或做任何您喜欢的事情。就我而言,我正在返回一个对象。

现在假设这是Dependency 类。您的 Module 类应该看起来几乎相同,但它应该依赖于 Dependency,例如:

function (require, exports, module) {
    var dependency = require('Dependency');
}

在 RequireJS 中,这称为 Simplified CommonJS Wrapper:http://requirejs.org/docs/api.html#cjsmodule

因为在您的代码开头有一个require 语句,它将被匹配为依赖项,因此它将被延迟加载或者如果您对其进行优化 - 尽早标记为依赖项(它将转换@ 987654332@ 到 define(['Dependency'], definition) 自动)。

这里唯一的问题是保持文件的相同路径。请记住,嵌套需求(if-else)在 Require 中不起作用(阅读文档),因此我必须执行以下操作:

var dependency;
try {
    dependency = require('./Dependency'); // node module in the same folder
} catch(err) { // it's not node
    try {
        dependency = require('Dependency'); // requirejs
    } catch(err) { }
}

这对我来说非常有效。所有这些路径都有些棘手,但归根结底,您可以将两个单独的模块放在不同的文件中,这些文件可以在两端使用无需任何类型的检查或破解 - 他们有他们所有的依赖都像魅力一样工作:)

【讨论】:

  • 我看到RequireJS 使用AMD。这不会与 Node 使用 vanilla CommonJS 的服务器端冲突吗?我并不是暗示它会,但是根据您的经验,如果Browserify 也被用于将模块打包到某个地方,那么这两个模块系统是否可以很好地结合在一起?
  • 如果您跟踪代码,您会发现它会执行所有检查,因此 AMD 模块使用define,而节点声明方式使用module.exports。因此,它们开箱即用很好:) 很抱歉,我以某种方式决定您已经在客户端使用RequireJS,因为您尝试制作模块,这就是它的作用!我的错误在这里
  • 据说 browserify 使用 commonjs,所以它应该可以直接工作,希望 :)
  • 是的,Browserify 使用 CommonJS,但 RequireJS 使用 AMD,看起来像是为浏览器优化的 CommonJS。但我可以看到,只要 Browserify 配置得当,它们就有可能一起工作。不是我会使用 Browserify,而是其他人可能会使用它来打包模块。这周我会试一试,然后回来报告。谢谢。
  • github.com/substack/browserify-handbook#amd - 据我所知,两者之间不会有任何区别。希望有效。很抱歉错过了 requirejs 部分:)
【解决方案2】:

看看webpack 捆绑器。 您可以编写模块并通过模块导出将其导出。然后你可以在服务器中使用你有 module.export 的地方并使用 webpack 构建浏览器。配置文件使用将是最好的选择

module.exports = {
 entry: "./myModule",
 output: {
    path: "dist",
    filename: "myModule.js",
    library: "myModule",
    libraryTarget: "var"
 }
};

这将获取 myModule 并将其导出到 myModule.js 文件。内部模块将被分配给名为 myModule(库标志)的 var(libraryTarget 标志)。

可以导出为commonJS模块、war、this、function

由于捆绑是节点脚本,因此可以按语法设置此标志值。

看一下外部标志。如果您希望对某些依赖项具有特殊行为,则使用它。例如,您正在创建 React 组件,并且在您的模块中需要它,但在为 Web 捆绑时不需要它,因为它已经存在。

希望它是您正在寻找的。​​p>

【讨论】:

  • 感谢您的回答。 Webpack 看起来是一个有趣的项目。它看起来像是解决常见问题的一种很好的整体方法,但我敢说,对于我的特定用例来说,它似乎有点矫枉过正。
【解决方案3】:

目前给出的两个答案都非常有用,并且帮助我得出了当前的解决方案。但是,根据我的 cmets,它们并不能完全满足我对可移植性和易用性的特殊要求(对于客户端和模块维护者)。

最后,我发现browserify 命令行界面中的一个特定标志可以捆绑模块并将它们公开为全局变量并在 RequireJS 中使用(如果需要)。 Browserify(和其他人)将此称为通用模块定义(UMD)。更多关于 here.

通过在 browserify 命令中传递 --standalone 标志,我可以轻松地将我的模块设置为 UMD。

所以...

这是模块的package.js:

{
  "name": "module",
  "version": "0.0.1",
  "description": "My module that requires another module (dependancy)",
  "main": "index.js",
  "scripts": {
    "bundle": "browserify --standalone module index.js > module.js"
  },
  "author": "shennan",
  "devDependencies": {
    "dependancy": "*",
    "browserify": "*"
  }
}

所以,在我的模块的根目录下,我可以在命令行中运行它:

$ npm run-script bundle

它将依赖项捆绑到一个文件中,并按照 UMD 方法公开它们。这意味着我可以通过三种不同的方式引导模块:

NodeJS

var Module = require('module');
/* use Module */

浏览器原版

<script src="module.js"></script>
<script>
  var Module = module;
  /* use Module */
</script>

带有 RequireJS 的浏览器

<script src="require.js"></script>
<script>
  requirejs(['module.js'], function (Module) {
    /* use Module */
  });
</script>

再次感谢大家的意见。所有答案都是有效的,我鼓励每个人都尝试一下,因为不同的用例需要不同的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-08
    • 2011-11-26
    • 1970-01-01
    • 2015-12-28
    • 2011-07-12
    • 2016-07-21
    • 1970-01-01
    相关资源
    最近更新 更多