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

(完)

原文:http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html

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

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

    就在这个周末,npm 超过了 cpan ,成为地球上最大的软件模块仓库. npm 的模块都是 JavaScript 语言写的,但浏览器用不了,因为不支持 CommonJS 格式.要想让浏览器用上这些模 ...

  2. SpringBoot开发 - 什么是热部署和热加载?devtool的原理是什么?

    在SpringBoot开发调试中,如果我每行代码的修改都需要重启启动再调试,可能比较费时间:SpringBoot团队针对此问题提供了spring-boot-devtools(简称devtools)插件 ...

  3. 浏览器加载和渲染html的顺序(html/css/js)

    最近在学习前端的技术,把html.js.css的基础知识看了看.感觉越看越觉得前端并不比后端容易,技术含量还是相当大的.今天突然想弄明白浏览器到底是怎么加载和渲染html的?html中的DOM.js文 ...

  4. 『心善渊』Selenium3.0基础 — 22、使用浏览器加载项配置实现用户免登陆

    目录 1.浏览器的加载项配置 2.加载Firefox配置 3.加载Chrome配置 1.浏览器的加载项配置 在很多情况下,我们在登录网站的时候,浏览器都会弹出一个是否保存登录账号的信息.如果我们选择保 ...

  5. Angular中懒加载一个模块并动态创建显示该模块下声明的组件

    angular中支持可以通过路由来懒加载某些页面模块已达到减少首屏尺寸, 提高首屏加载速度的目的. 但是这种通过路由的方式有时候是无法满足需求的. 比如, 点击一个按钮后显示一行工具栏, 这个工具栏组 ...

  6. 浏览器加载和渲染HTML的过程(标准定义的过程以及现代浏览器的优化)

    先看一下标准定义的浏览器渲染过程(网上找的): 浏览器打开网页的过程 用户第一次访问网址,浏览器向服务器发出请求,服务器返回html文件: 浏览器开始载入html代码,发现 head 标签内有一个 l ...

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

    如果loop模块没有编译进内核就要先加载loop模块 modprobe loop 然后更改/etc/modprobe.d/modprobe.conf(有些文章写是在/etc/modprobe.conf ...

  8. Java提高篇——JVM加载class文件的原理机制

    在面试java工程师的时候,这道题经常被问到,故需特别注意. 1.JVM 简介 JVM 是我们Javaer 的最基本功底了,刚开始学Java 的时候,一般都是从“Hello World ”开始的,然后 ...

  9. Angular.JS + Require.JS + angular-async-loader 来实现异步加载 angular 模块

    传统的 angular 应用不支持异步加载模块,必须在 module 启动的时候,所有模块必须预加载进来. 通过使用 angular-async-loader 库,我们可以使用 requirejs 等 ...

随机推荐

  1. AUTOSAR的前期开源实现Arctic Core

    AUTOSAR (AUTomotive Open System ARchitecture) is a worldwide development partnership of vehicle manu ...

  2. 《T-SQL查询》读书笔记Part 2.执行计划

    一.关于执行计划 执行计划是优化器生成的用于确定如何处理一个给定查询的“工作计划”.一个计划包含一组运算符,通常按照特定的顺序来应用这些运算符.此外,一些运算符可以在它们之前的运算符还在处理时被应用( ...

  3. C语言 > 数组和指针

    C语言 数组和指针 const: 关于指针和const需要注意一些规则.首先,把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的. 然而,只能把非const数据的地 ...

  4. PAT1107:Sum of Number Segments

    1104. Sum of Number Segments (20) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CAO, Pen ...

  5. Redis的九大应用场景

    毫无疑问,Redis开创了一种新的数据存储思路,使用Redis,我们不用在面对功能单调的数据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用Redis灵活多变的数据结构和数据操作,为不同的大象 ...

  6. EXCEL解析之终极方法WorkbookFactory

    Selenium做自动化测试当然不能避免和Excel打交道. 由于Excel版本的关系,文件扩展名分xls和xlsx, 以往的经验都是使用HSSFWorkbook和XSSFWorkbook来分别处理. ...

  7. 在Windows下同时安装Python2.x和Python3.x

    前言: Python现在是两个版本共存,Python2.x和Python3.x都同时在更新.但是Python2.x和Python3.x的区别还是很多的(以后我可能会写一篇文章列举一下Python2.x ...

  8. Spring Cloud实践:降级、限流、滚动、灰度、AB、金丝雀的实现思路

    端口:8888,方便起见直接读取配置文件,生产环境可以读取git.application-dev.properties为全局配置.先启动配置中心,所有服务的配置(包括注册中心的地址)均从配置中心读取. ...

  9. 按钮组,导航条选中其中一个后添加Class突出元素

    $(document).on("click",".modalnavtop",function(e){ $(".modalnavtop").e ...

  10. 更改eclipse的编辑器样式风格

    这里提供两种方法: 1.从网站http://eclipsecolorthemes.org/ 下载,选好自己喜欢的风格后下载epf文件...再使用eclipse的import->preferenc ...