模块化

称职的作家会把他的书分章节和段落;好的程序员会把他的代码分成模块。

  • 无模块化
  • CommonJS规范
  • AMD
  • CMD
  • UMD
  • ES6 modules

无模块化

&emsp;&emsp;<script src="jquery.js"></script>
&emsp;&emsp;<script src="main.js"></script>
&emsp;&emsp;<script src="a.js"></script>
&emsp;&emsp;<script src="b.js"></script>
&emsp;&emsp;<script src="c.js"></script>
复制代码

缺点:

  • 污染全局作用域。容易有命名冲突问题
  • 开发和后期维护成本较高
  • 依赖关系不明显,不利于维护

CommonJS规范

  • 不是一个js类库,而是规范
  • Node.js是commonJS规范的主要实践者
  • 它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global

用法

通过 require方法来同步加载所要依赖的其他模块,然后通过 exports 或者 module.exports 来导出需要暴露的接口

// a.js
var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

// b.js
vara = require('./a.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
复制代码

优点:

CommonJS规范完成了JavaScript的模块化,解决了依赖、全局变量污染的问题

缺点:

commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。这就是AMD规范诞生的背景。

AMD规范

  • 异步加载模块:这里异步指的是不堵塞浏览器其他任务(dom构建,css渲染等),而加载内部是同步的(加载完模块后立即执行回调)
  • require.js是AMD规范的实现

用法

define()定义模块,用require()加载模块

  • define(id, [depends], callback)
  • require([module], callback)
require(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
    a.test()
    if (false) {
       b.foo()
    }
})

复制代码

优点:

适合在浏览器环境中异步加载模块。 可以并行加载多个模块。

缺点:

不能按需加载,而是必须提前加载所有的依赖。

CMD规范

  • 阿里的玉伯提出
  • sea.js是对CMD规范的实现
  • 按需加载,依赖就近,延迟执行

用法

define(function(require, exports, module) {
    var a = require('./a'); //在需要时申明
    a.doSomething();
    if (false) {
        var b = require('./b');
        b.doSomething();
    }
})
复制代码

优点:

同样实现了浏览器端的模块化加载。 可以按需加载,依赖就近。

缺点: 依赖SPM打包,模块的加载逻辑偏重。

UMD

  • 不是一种规范
  • 是结合 AMD 和 CommonJS 的一种更为通用的 JS 模块解决方案

打包时配置:

output: {
  path: path.resolve(__dirname, '../dist'),
  filename: 'vue.js',
  library: 'Vue',
  libraryTarget: 'umd'
}
复制代码

表示打包出来的模块为 umd 模块,既能在服务端(node)运行,又能在浏览器端运行。我们来看 vue 打包后的源码 vue.js

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) : 
  (global.Vue = factory());
}(this, (function () { 'use strict';
// ...
})))
复制代码
  • 首先判断是否为 node 环境:exports 为一个对象,并且 module 存在
  • 如果是 node 环境就用 module.exports = factory() 把 vue 导出 (通过 require(‘vue’) 进行引用)
  • 如果不是 node 环境判断是否支持 AMD:define 为 function 并且 define.amd 存在
  • 如果支持 AMD 就使用 define 定义模块,(通过 require([‘vue’]) 引用)
  • 否则的话直接将 vue 绑定在全局变量上(通过 window.vue 引用)

ES6 module

  • 之前的几种模块化方案都是前端社区自己实现的,只是得到了大家的认可和广泛使用
  • ES6 在语言标准的层面上,实现了模块功能,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
  • 由于ES6目前在部分浏览器无法执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require

支持情况

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };

// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}
复制代码

模块打包

打包CommonJS

可以使用Browserify

var myDependency = require('myDependency');
var myGrades = [93, 95, 88, 0, 91];
var myAverageGrade = myDependency.average(myGrades);
复制代码
browserify main.js -o bundle.js
复制代码

Browserify 首先会通过抽象语法树(AST)来解析代码中的每一个 require 语句,在分析完所有模块的依赖和结构之后,就会把所有的代码合并到一个文件中。然后你在HTML文件里引入一个bundle.js就够了

打包 AMD

开发者选择用RequireJS optimizer, r.js一类的构建工具来合并和压缩AMD的模块。

由于AMD是异步加载模块的。这也就意味着你不是必须把所有的代码打包到一个文件里,模块加载不影响后续语句执行,逐步加载的的模块也不会导致页面阻塞无法响应。

所以,在开发过程中,采用AMD的应用直到正式上线发布之前都不需要构建。

Webpack

Webpack 是新推出的构建工具里最受欢迎的。它兼容CommonJS, AMD, ES6各类规范

参考