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

杨亚男      2022-02-13     701

关键词:

就在这个周末,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) {
  console.log(x);
};",
    "deps":{}
  },
  {
    "id":2,
    "source":"var foo = require("./foo");
foo("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),就通过立即执行函数提供即可。

模块化es6规范(代码片段)

...最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。ES6模块的设计思想是... 查看详情

使用browserify来实现commonjs的浏览器加载

... Nodejs的模块是基于CommonJS规范实现的,可不可以应用在浏览器环境中呢?varmath=require(‘math‘);math.add(2,3);  第二行math.add(2,3),在第一行require(‘math‘)之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整... 查看详情

第137篇:重学es6模块化(代码片段)

...起来。 CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。ES6模块的设计思想是... 查看详情

js模块化入门与commonjs解析与应用(代码片段)

JS模块化的基本原理commonjs规范commonjs在前端模块化中的基本使用commonjs的实现原理AMD与CMD规范剖析博客链接 一、JS模块化基本原理在JS没有提出来模块化的时候,开发JS项目比较简单,同时也比较杂乱,第一个问题就是全局变... 查看详情

es6特性之模块modules(代码片段)

...要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。同时还有CMD规范,为同步加载方案如seaJS。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS... 查看详情

es6模块的import和export用法

...要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。同时还有CMD规范,为同步加载方案如seaJS。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS... 查看详情

用react重构个人网站

...要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。同时还有CMD规范,为同步加载方案如seaJS。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS... 查看详情

flex布局教程:语法篇作者:阮一峰

...、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在 查看详情

commonjs和amd/cmd

...JS是CommonJS规范的实现,webpack也是以CommonJS的形式来书写.在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限,但在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程.node.js的模块系统,... 查看详情

想自学java的速来!docker原理阮一峰

二、我们先来看看这份笔记到底有什么1、先把kubernetes跑起来(先跑起来+创建kubernetes集群+部署应用+访问应用+Scale应用+滚动更新)2、重要概念3、部署kubernetesCluster(安装docker+安装kubelet.kubeadm和kubect... 查看详情

前端模块化commonjs(代码片段)

...服务器端,模块的加载是运行时同步加载的;在浏览器端& 查看详情

模块加载规范对比

...不用考虑非同步加载的方式,所以CommonJS规范比较适用。浏览器端一般采用AMD模块加载方式或者ES6的模块加载标准。模块导出方法(为防止混淆,建议一律采用module.exports而非ex 查看详情

记录scriptmodule(代码片段)

...最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。ES6模块的设计... 查看详情

一文彻底弄懂“commonjs”与“esmodule”区别(代码片段)

简介文章中就不具体解释什么是“CommonJs”与“EsModule”了,我们先简单的描述一下“CommonJs”与“EsModule”的区别。它们有三个重大差异。CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。CommonJS模块是运行... 查看详情

module的语法(代码片段)

...最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。ES6模块的设计思想是... 查看详情

wepack的模块化原理及配置方法

...化的规范,因为是同步的,所以只能在服务器端实现,而浏览器端因为缺少node的四个字段:moduleexportsrequireg 查看详情

[转帖]阮一峰:哈希碰撞与生日攻击(代码片段)

哈希碰撞与生日攻击分享按钮作者: 阮一峰日期: 2018年9月5日一、哈希碰撞是什么?所谓哈希(hash),就是将不同的输入映射成独一无二的、固定长度的值(又称"哈希值")。它是最常见的软件运算之一。如果不同的输... 查看详情

commonjs与es6模块化的具体使用方式

...所有暴露出来的函数和变量都存放在里面1.先写个6.js文件CommonJS规范规定,每个模块内部,module变量代表当前模板,这个变量是一个对象,他的exports属性(相当于module.exports)是对外的接口。这里详情请看我的另一篇文章:module... 查看详情