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

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

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

一、原理

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

  • module
  • exports
  • require
  • global

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

下面是一个简单的示例。


  1. var module = {
  2. exports: {}
  3. };
  4. (function(module, exports) {
  5. exports.multiply = function (n) { return n * 1000 };
  6. }(module, module.exports))
  7. var f = module.exports.multiply;
  8. f(5) // 5000

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

二、Browserify 的实现

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

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


  1. // foo.js
  2. module.exports = function(x) {
  3. console.log(x);
  4. };
  5. // main.js
  6. var foo = require("./foo");
  7. foo("Hi");

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


  1. $ browserify main.js > compiled.js

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


  1. $ npm install browser-unpack -g

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


  1. $ browser-unpack < compiled.js
  2. [
  3. {
  4. "id":1,
  5. "source":"module.exports = function(x) {\n console.log(x);\n};",
  6. "deps":{}
  7. },
  8. {
  9. "id":2,
  10. "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");",
  11. "deps":{"./foo":1},
  12. "entry":true
  13. }
  14. ]

可以看到,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。


  1. function require(p){
  2. var path = require.resolve(p);
  3. var mod = require.modules[path];
  4. if (!mod) throw new Error('failed to require "' + p + '"');
  5. if (!mod.exports) {
  6. mod.exports = {};
  7. mod.call(mod.exports, mod, mod.exports, require.relative(path));
  8. }
  9. return mod.exports;
  10. }
  11. require.modules = {};
  12. require.resolve = function (path){
  13. var orig = path;
  14. var reg = path + '.js';
  15. var index = path + '/index.js';
  16. return require.modules[reg] && reg
  17. || require.modules[index] && index
  18. || orig;
  19. };
  20. require.register = function (path, fn){
  21. require.modules[path] = fn;
  22. };
  23. require.relative = function (parent) {
  24. return function(p){
  25. if ('.' != p.charAt(0)) return require(p);
  26. var path = parent.split('/');
  27. var segs = p.split('/');
  28. path.pop();
  29. for (var i = 0; i < segs.length; i++) {
  30. var seg = segs[i];
  31. if ('..' == seg) path.pop();
  32. else if ('.' != seg) path.push(seg);
  33. }
  34. return require(path.join('/'));
  35. };
  36. };

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


  1. <script src="require.js" />
  2. <script>
  3. require.register("moduleId", function(module, exports, require){
  4. // Module code goes here
  5. });
  6. var result = require("moduleId");
  7. </script>

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


  1. require.register("./foo.js", function(module, exports, require){
  2. module.exports = function(x) {
  3. console.log(x);
  4. };
  5. });
  6. var foo = require("./foo.js");
  7. 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. 浅谈 RxAndroid + Retrofit + Databinding

    http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0131/3930.html 最近 RxAndroid .MVP.MVVM 一直是 And ...

  2. Python函数式实现单例特性

    传统的单例一般是基于类的特性实现,Python模块是天生的单例,下面来个简单的借助模块和函数实现单例特性: gdb = None def get_gdb(): global gdb if gdb is ...

  3. Django单元测试简明实践

    1.准备数据模式,Django空库测试需要所有相关数据模式必须在Django中定义,说白了,model不能有managed=Fasle,为了解决这个问题,你必须得有一个managed全部为True的S ...

  4. Java Code Style

    近期困惑于团队成员代码风格迥异,代码质量不可控,作为一名老司机,忧患于后期服务的可维护性,多次一对一的代码Review,耗时耗力不说,效果也不明显.痛定思痛,多次反思之后得出结论:无规矩不成方圆,可靠 ...

  5. Jmeter(二十六)_数据驱动测试

    花了一点时间做了一个通用的执行引擎,好处就是我不用再关注测试脚本的内容,而是用测试用例的数据去驱动我们执行的方向.(这个只适合单个接口的测试,具体运用到接口自动化时,还是要靠手动去编写脚本!) 首先我 ...

  6. Navicat永久激活步骤,激活工具,解决注册码无效的问题

    Navicat for MySQL是一套管理和开发MySQL或MariaDB的理想解决方案,支持单一程序,可同时连接到MySQL和MariaDB.这个功能齐备的前端软件为数据库管理.开发和维护提供了直 ...

  7. Postgresql中临时表(temporary table)的特性和用法

    熟悉Oracle的人,相比对临时表(temporary table)并不陌生,很多场景对解决问题起到不错的作用,开源库Postgresql中,也有临时表的概念,虽然和Oracle中临时表名字相同,使用 ...

  8. Web移动端页面 --响应式和动态REM

    响应式 什么是响应式页面呢? 顾名思义响应式页面就是能做出响应的页面,它的页面效果不是定死的,会随着用户的改变而改变. 如何着手响应式有以下几个思考的方向 找一份设计图 使用Media Query 隐 ...

  9. 使用Selenium对新浪微博模拟登录

    Selenium的配置 在项目中引入Selenium库 下载chromedriver.exe 在项目代码中加入chromedriver位置的配置 使用Selenium Selenim语法 智能等待 隐 ...

  10. Windows下Oracle的下载与安装

    一.Oracle下载 官网地址:http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html 百 ...