浏览器加载 CommonJS 模块的原理与实现

就在这个周末,npm 超过了 cpan ,成为地球上最大的软件模块仓库。

npm 的模块都是 JavaScript 语言写的,但浏览器用不了,因为不支持 CommonJS 格式。要想让浏览器用上这些模块,必须转换格式。


本文介绍浏览器加载 CommonJS 的原理,并且给出一种非常简单的实现

一、原理

浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量。

  • module
  • exports
  • require
  • global

只要能够提供这四个变量,浏览器就能加载 CommonJS 模块。

下面是一个简单的示例。

 var module = {
 exports: {} }; (function(module, exports) {
 exports.multiply = function (n) { return n * 1000 }; }(module, module.exports)) var f = module.exports.multiply; f(5) // 5000

上面代码向一个立即执行函数提供 module 和 exports 两个外部变量,模块就放在这个立即执行函数里面。模块的输出值放在 module.exports 之中,这样就实现了模块的加载。

二、Browserify 的实现

知道了原理,就能做出工具了。Browserify 是目前最常用的 CommonJS 格式转换的工具。


请看一个例子,main.js 模块加载 foo.js 模块。


// foo.js
module.exports = function(x) {
 console.log(x); };
// main.js
var foo = require("./foo"); foo("Hi"); 

使用下面的命令,就能将main.js转为浏览器可用的格式。


$ browserify main.js > compiled.js

Browserify到底做了什么?安装一下browser-unpack,就能看清楚了。


$ npm install browser-unpack -g

然后,将前面生成的compile.js解包。


$ browser-unpack < compiled.js

[ { "id":1, "source":"module.exports = function(x) {\n console.log(x);\n};", "deps":{} }, { "id":2, "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");", "deps":{"./foo":1}, "entry":true } ] 

可以看到,browerify 将所有模块放入一个数组,id 属性是模块的编号,source 属性是模块的源码,deps 属性是模块的依赖。

因为 main.js 里面加载了 foo.js,所以 deps 属性就指定 ./foo 对应1号模块。执行的时候,浏览器遇到 require('./foo') 语句,就自动执行1号模块的 source 属性,并将执行后的 module.exports 属性值输出。

三、Tiny Browser Require

虽然 Browserify 很强大,但不能在浏览器里操作,有时就很不方便。

我根据 mocha 的内部实现,做了一个纯浏览器的 CommonJS 模块加载器 tiny-browser-require 。完全不需要命令行,直接放进浏览器即可,所有代码只有30多行。


它的逻辑非常简单,就是把模块读入数组,加载路径就是模块的id。

 function require(p){ var path = require.resolve(p); var mod = require.modules[path]; if (!mod) throw new Error('failed to require "' + p + '"'); if (!mod.exports) {
 mod.exports = {};
 mod.call(mod.exports, mod, mod.exports, require.relative(path)); } return mod.exports; }

require.modules = {};

require.resolve = function (path){ var orig = path; var reg = path + '.js'; var index = path + '/index.js'; return require.modules[reg] && reg
 || require.modules[index] && index
 || orig; };

require.register = function (path, fn){
 require.modules[path] = fn; };

require.relative = function (parent) { return function(p){ if ('.' != p.charAt(0)) return require(p); var path = parent.split('/'); var segs = p.split('/');
 path.pop(); for (var i = 0; i < segs.length; i++) { var seg = segs[i]; if ('..' == seg) path.pop(); else if ('.' != seg) path.push(seg); } return require(path.join('/')); }; }; 

使用的时候,先将上面的代码放入页面。然后,将模块放在如下的立即执行函数里面,就可以调用了。


<script src="require.js" />

<script>
require.register("moduleId", function(module, exports, require){
 // Module code goes here
});
var result = require("moduleId");
</script>

还是以前面的 main.js 加载 foo.js 为例。


require.register("./foo.js", function(module, exports, require){
 module.exports = function(x) {
 console.log(x); }; }); var foo = require("./foo.js"); foo("Hi"); 

注意,这个库只模拟了 require 、module 、exports 三个变量,如果模块还用到了 global 或者其他 Node 专有变量(比如 process),就通过立即执行函数提供即可。

(完)

时间: 2025-01-02 04:42:57

浏览器加载 CommonJS 模块的原理与实现的相关文章

浏览器加载服务器端JavaScript的CommonJS模块的原理与实现

npm 的模块都是 JavaScript 语言写的,但浏览器用不了,因为不支持 CommonJS 格式.要想让浏览器用上这些模块,必须转换格式. 本文介绍浏览器加载 CommonJS 的原理,并且给出一种非常简单的实现. 一.原理 浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量.         module        exports        require        global 只要能够提供这四个变量,浏览器就能加载 CommonJS 模块. 下面

使用RequireJS库加载JavaScript模块的实例教程_javascript类库

js通过script标签的默认加载方式是同步的,即第一个script标签内的js加载完成后,才开始加载第二个,以此类推,直至js文件全部加载完毕.且js的依赖关系必须通过script的顺序才能确保:而在js加载期间,浏览器将停止响应,这大大影响了用户体验,基于此,很多解决js以来和加载的方案出现,require js就是其中之一. requirejs加载的模块,一般为符合AMD标准的模块,即用define定义,用ruturn返回暴露方法.变量的模块:requirejs也可以加载飞AMD标准的模块

seajs学习之模块的依赖加载及模块API的导出_Seajs

前言 SeaJS非常强大,SeaJS可以加载任意 JavaScript 模块和css模块样式,SeaJS会保证你在使用一个模块时,已经将所依赖的其他模块载入到脚本运行环境中. 通过参照上文的demo,我们结合源码分析在简单的API调用的背后,到底使用了什么技巧来实现各个模块的依赖加载以及模块API的导出. 模块类和状态类 首先定义了一个Module类,对应与一个模块 function Module(uri, deps) { this.uri = uri this.dependencies = d

seajs模块之间依赖的加载以及模块的执行_Seajs

本文介绍的是seajs模块之间依赖的加载以及模块的执行,下面话不多说直接来看详细的介绍. 入口方法 每个程序都有个入口方法,类似于c的main函数,seajs也不例外.系列一的demo在首页使用了seajs.use() ,这便是入口方法.入口方法可以接受2个参数,第一个参数为模块名称,第二个为回调函数.入口方法定义了一个新的模块,这个新定义的模块依赖入参提供的模块.然后设置新模块的回调函数,用以在loaded状态之后调用.该回调函数主要是执行所有依赖模块的工厂函数,最后在执行入口方法提供的回调.

Win8如何管理和禁用IE10浏览器加载项

  Win8管理和禁用IE10浏览器加载项的方法如下: 1.在IE命令栏或右上角点击"工具"按钮,点击打开"管理加载项". 2.选中需要禁用或启用的选项,点击"禁用"或"启用"即可. 3.也可打开Internet选项,选择"程序"选项卡,点击"管理加载项"进行操作.

server-求助啊!SVN整合Apache后,apache不能启动了!提示不能加载svn模块!

问题描述 求助啊!SVN整合Apache后,apache不能启动了!提示不能加载svn模块! 错误提示! The Apache service named reported the following error: httpd.exe: Syntax error on line 103 of D:/Program Files/Apache Software Foundation/Apache2.4/conf/httpd.conf: Cannot load modules/mod_dav_svn.

jquery-IE浏览器加载多个easyui-combotree的问题

问题描述 IE浏览器加载多个easyui-combotree的问题 页面上有多个easyui-combotree文本框,IE加载进来只显示一个. 页面body中: JS中: $(".ccombotree").combotree({ method: "Get", url: contextPath + '/fa/getTreeData.do', multiple: false, required: false }).removeClass('ccombotree');

【转】浏览器加载渲染网页过程解析--总结(三)

1.浏览器加载和渲染html的顺序 1.IE下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的. 2.在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相关联的元素都已经下载完) 3.如果遇到语义解释性的标签嵌入文件(JS脚本,CSS样式),那么此时IE的下载过程会启用单独连接进行下载. 4.并且在下载后进行解析,解析过程中,停止页面所有往下元素的下载.阻塞加载 5.样式表在下载完成后,将和以前下载的所有样式表一起进行解析,解析完成后,将对此前所有元素(含以

html5封装的app怎么隐藏浏览器加载进度条和浏览器下底框

问题描述 html5封装的app怎么隐藏浏览器加载进度条和浏览器下底框 用html5写的网页,直接封装的: public class MainActivity extends Activity { private WebView webview; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //实例化WebView对象 webview = new W