浏览器加载 CommonJS 模块的原理与实现 (阮一峰大哥的 http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html)

就在这个周末,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),就通过立即执行函数提供即可。

时间: 2024-10-03 18:56:23

浏览器加载 CommonJS 模块的原理与实现 (阮一峰大哥的 http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html)的相关文章

浏览器加载解析渲染网页原理

浏览器加载网页资源的原理 JS与CSS阻塞 重排与重绘 一.浏览器加载网页资源的原理 1.HTML支持的组要资源类型 在浏览器内核有一个管理资源的对象CachedResource类,在CachedResource类下有很多子类来分工不同的资源管理,这些资源管理子类分别是:  资源  资源管理类  HTML  MainResource ===> CachedRawResource  JavaScript  CachedScript  CSS  CachedCSStyleSheet  图片  Cac

浏览器加载js的阻塞与非阻塞

这几天因项目需求,要实现一系列的js文件加载的问题,于是,就按照常规思路写了一通,悲催的是测试后发现问题重重: 为什么会有这么多问题,仔细想来还是对浏览器加载js的原理理解不透,于是我翻阅了大量的资料和自己的一些实践,总结出来以下的内容: 1.js在浏览器中的阻塞 常规理解就是浏览器在加载js时候会阻塞浏览器的渲染操作,但页面性能决定不希望我们在加载js的时候影响页面的渲染,所以我们经常会把js文件放到body之前去加载.而不会把它放在head里去阻塞页面的渲染.那么问题来了,如果我们把js放在

浏览器加载渲染网页过程解析 (转)

浏览器的工作机制,一句话概括起来就是:web浏览器与web服务器之间通过HTTP协议进行通信的过程.所以,C/S之间握手的协议就是HTTP协议.浏览器接收完毕开始渲染之前大致过程如下: 从浏览器地址栏的请求链接开始,浏览器通过DNS解析查到域名映射的IP地址,成功之后浏览器端向此IP地址取得连接,成功连接之后,浏览器端将请 求头信息 通过HTTP协议向此IP地址所在服务器发起请求,服务器接受到请求之后等待处理,最后向浏览器端发回响应,此时在HTTP协议下,浏览器从服务器接收到 text/html

浏览器加载渲染网页过程解析

浏览器的工作机制,一句话概括起来就是:web浏览器与web服务器之间通过HTTP协议进行通信的过程.所以,C/S之间握手的协议就是HTTP协议.浏览器接收完毕开始渲染之前大致过程如下: 从浏览器地址栏的请求链接开始,浏览器通过DNS解析查到域名映射的IP地址,成功之后浏览器端向此IP地址取得连接,成功连接之后,浏览器端将请 求头信息 通过HTTP协议向此IP地址所在服务器发起请求,服务器接受到请求之后等待处理,最后向浏览器端发回响应,此时在HTTP协议下,浏览器从服务器接收到 text/html

浏览器加载渲染网页过程解析--备用

浏览器的工作机制,一句话概括起来就是:web浏览器与web服务器之间通过HTTP协议进行通信的过程.所以,C/S之间握手的协议就是HTTP协议.浏览器接收完毕开始渲染之前大致过程如下: 从浏览器地址栏的请求链接开始,浏览器通过DNS解析查到域名映射的IP地址,成功之后浏览器端向此IP地址取得连接,成功连接之后,浏览器端将请 求头信息 通过HTTP协议向此IP地址所在服务器发起请求,服务器接受到请求之后等待处理,最后向浏览器端发回响应,此时在HTTP协议下,浏览器从服务器接收到 text/html

浏览器加载渲染网页过程解析(二)

转自:http://blog.csdn.net/longeremmy/article/details/8030736 浏览器的工作机制,一句话概括起来就是:web浏览器与web服务器之间通过HTTP协议进行通信的过程.所以,C/S之间握手的协议就是HTTP协议.浏览器接收完毕开始渲染之前大致过程如下 : 从浏览器地址栏的请求链接开始,浏览器通过DNS解析查到域名映射的IP地址,成功之后浏览器端向此IP地址取得连接,成功连接之后,浏览器端将请 求头信息 通过HTTP协议向此IP地址所在服务器发起请

archlinux 加载loop模块,且设定loop设备个数

如果loop模块没有编译进内核就要先加载loop模块 modprobe loop 然后更改/etc/modprobe.d/modprobe.conf(有些文章写是在/etc/modprobe.conf,但是我试验是在此目录下) options loop max_loop=64 之后就可以重启系统,然后执行 modprobe loop 我们就可以看到loop设备增加为64个了.

NGINX 加载动态模块(NGINX 1.9.11开始增加加载动态模块支持)

NGINX 1.9.11开始增加加载动态模块支持,从此不再需要替换nginx文件即可增加第三方扩展.目前官方只有几个模块支持动态加载,第三方模块需要升级支持才可编译成模块. [email protected]:~/nginx-1.12.0$ ./configure --help | grep dynamic --with-http_xslt_module=dynamic enable dynamic ngx_http_xslt_module --with-http_image_filter_mo

浏览器加载、解析、渲染的过程

最近在学习性能优化,学习了雅虎军规 ,可是觉着有点云里雾里的,因为里面有些东西虽然自己也一直在使用,但是感觉不太明白所以然,比如减少DNS查询,css和js文件的顺序.所以就花了时间去了解浏览器的工作,有一篇经典的文章<how browsers work> ,讲的很详细,也有中文译本 .不过就是文章有点太长,也讲了一堆东西,还是自己总结一下. 为什么要了解浏览器加载.解析.渲染这个过程? 好,我们先说一下,为什么要了解这些呢?如果想写出一个最佳实践的页面,就要好好了解. 了解浏览器如何进行加载