封装浏览器/服务器具有依赖性的CommonJS模块

时间:2016-04-06 14:53:18

标签: javascript node.js module browserify commonjs

让我们说我正在用JavaScript编写一个模块,可以在浏览器和服务器上使用(使用Node)。让我们称之为模块。并且假设模块将受益于另一个名为 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 可以直接在浏览器中使用。但如果模块中包含var dependancy = require('dependency');指令,则“维护”模块会变得更加麻烦。

我知道我可以对模块中的依赖进行全局检查,如下所示:

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

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

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

添加这两个要求会引发像CommonJS这样易于使用的模块化框架。

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

有没有其他方法可以做到这一点,我没有想过?

3 个答案:

答案 0 :(得分:4)

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

我最终找到的是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>

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

答案 1 :(得分:1)

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

(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语句,它将作为依赖项进行匹配,因此它将被延迟加载或者如果你优化它 - 在早期标记为依赖项(它将自动将define(definition)转换为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) { }
}

这对我来说非常合适。所有这些路径都有点棘手,但在一天结束时,你可以将两个独立的模块放在不同的文件中,可以在两端使用,不用任何类型的检查或黑客 - 他们有他们所有的依赖都像魅力一样工作:)

答案 2 :(得分:1)

看看webpack bundler。 您可以编写模块并通过模块导出将其导出。然后,您可以在服务器中使用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

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

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

希望这就是你要找的东西。