浏览器/服务器CommonJS模块与依赖关系打包

比方说,我正在编写一个JavaScript模块,可以在浏览器和服务器上使用(与节点)。 让我们称之为模块 。 并且可以说,该模块将从另一个称为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'); 指令,更“维护”模块变得更麻烦。

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

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

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

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

添加这两个需求引发了像CommonJS这样的简单模块化框架的思想。

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

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

目前给出的两个答案都是非常有用的,并帮助我达到目前的解决scheme。 但是,根据我的意见,它们不能满足我对便携性和易用性(对于客户和模块维护者)的特殊要求。

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

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

所以…

以下是Modulepackage.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> 

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

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

 (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一样Dependency

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

在RequireJS中,这被称为简体CommonJS包装器: http ://requirejs.org/docs/api.html#cjsmodule

因为在你的代码开始处有一个require语句,所以它会被作为一个依赖项进行匹配,因此它会被延迟加载,或者如果你优化它 – 在早期标记为依赖项(它将转换define(definition)define(['Dependency'], definition)自动)。

这里唯一的问题是保持相同的文件path。 请记住,嵌套的要求(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) { } } 

这对我来说是完美的。 对于所有这些path来说都有点棘手,但是在一天结束的时候,你会得到两个单独的模块,这些模块可以在两端使用而不需要任何检查或者黑客 – 它们的所有依赖都像魅力:)

看看webpack打包器 。 您可以编写模块并通过模块导出将其导出。 然后你可以在服务器上使用你有module.export和webpack的浏览器。 configuration文件的使用将是最好的select

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

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

它可以作为commonJS模块输出,打开这个function

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

看看外部标志。 如果你想对某些依赖关系有特殊的行为,就使用它。 例如,你正在创build反应组件,并在你想要的模块,但不是当你捆绑networking,因为它已经在那里。

希望这是你在找什么。