Modules 模块化开发

MODULE

模块化也是一直在讨论的问题。

模块化开发规范:

  • AMD
  • CMD
  • UMD
  • CommonJS
  • ES6 Harmony | modules

2016/4/20 更新 随着ES6的支持越来越好,现在谈谈ES6的模块化。

The Asynchronous Module Definition

AMD WIKI:
https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88)
Javascript模块化编程(二):AMD规范 :
http://www.ruanyifeng.com/blog/2012/10/asynchronousmoduledefinition.html

The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.

相关:require.js

Common Module Definition

CMD
https://github.com/seajs/seajs/issues/242
https://github.com/cmdjs/specification

相关:sea.js

AMD VS CMD

https://www.zhihu.com/question/20351507
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
类似的还有 CommonJS Modules/2.0 规范,是 BravoJS 在推广过程中对模块定义的规范化产出。 还有不少⋯⋯

这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。 目前这些规范的实现都能达成浏览器端模块化开发的目的。

区别:

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

  2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:

// CMD
define(function(require, exports, module) {  
var a = require('./a')  
a.doSomething()  
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写  
b.doSomething()  
// ... 
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好  
a.doSomething()  
// 此处略去 100 行
b.doSomething()  
...
}) 

虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。

3.AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。

CommonJS

http://wiki.commonjs.org/wiki/CommonJS WIKI 的介绍
http://wiki.commonjs.org/wiki/Modules
http://javascript.ruanyifeng.com/nodejs/module.html API 实际使用,定义与使用模块

define modules:

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

use modules:

var example = require('./example.js');

console.log(example.x); // 5  
console.log(example.addX(1)); // 6  

只导出一个函数

// define 
var ale = function() {  
    console.log('commonjs')
}
module.exports = ale;  
// use module
var ale = require('common.js')  
ale()  // 'commonjs'  

发展历史

https://github.com/Huxpro/js-module-7day
https://github.com/seajs/seajs/issues/588
通过上面两个链接: 可以知道它们的关系。

AMD 和 CMD 都是由 CommonJS发展而来。

大概 09 年 - 10 年期间,CommonJS 社区大牛云集。CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践。

09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端,于是将社区改名叫 CommonJS,同时激烈争论 Modules 的下一版规范。分歧和冲突由此诞生,逐步形成了三大流派:

  • Modules/1.x 流派。这个观点觉得 1.x 规范已经够用,只要移植到浏览器端就好。要做的是新增 Modules/Transport 规范,即在浏览器上运行前,先通过转换工具将模块转换为符合 Transport 规范的代码。主流代表是服务端的开发人员。现在值得关注的有两个实现:越来越火的 component 和走在前沿的 es6 module transpiler。

  • Modules/Async 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范。这个观点下的典型代表是 AMD 规范及其实现 RequireJS。这个稍后再细说。

  • Modules/2.0 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范,但应该尽可能与 Modules/1.x 规范保持一致。这个观点下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者对 CommonJS 的社区的贡献很大,这份 Modules/2.0-draft 规范花了很多心思。FlyScript 的作者提出了 Modules/Wrappings 规范,这规范是 CMD 规范的前身。可惜的是 BravoJS 太学院派,FlyScript 后来做了自我阉割,将整个网站(flyscript.org)下线了。这个故事有点悲壮,下文细说。

 CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

QAQ

编写符合规范的代码,导出模块 , 使用模块

前端模块化 JS模块化 CSS模块化 具体是在谈,区别在哪里。

前端模块化 与 组件化的区别:

AMD API

AMD(异步模块定义)是为浏览器环境设计的,因为 CommonJS 模块系统是同步加载的,当前浏览器环境还没有准备好同步加载模块的条件。

AMD 定义了一套 JavaScript 模块依赖异步加载标准,来解决同步加载的问题。

模块通过 define 函数定义在闭包中,格式如下:

define(id?: String, dependencies?: String[], factory: Function|Object);  
  • id 是模块的名字,它是可选的参数。

  • dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数一次传入 factory 中。如果没有指定 dependencies,那么它的默认值是 ["require", "exports", "module"]。

  • define(function(require, exports, module) {}) factory 是最后一个参数,它包裹了模块的具体实现,它是一个函数或者对象。如果是函数,那么它的返回值就是模块的输出接口或值。

一些用例:

定义一个名为 myModule 的模块,它依赖 jQuery 模块:

// 定义 定义一个模块也会有依赖其他模块
define('myModule', ['jquery'], function($) {  
    // $ 是 jquery 模块的输出
    $('body').text('hello world');
});
// 使用
define(['myModule'], function(myModule) {});  

注意:在 webpack 中,模块名只有局部作用域,在 Require.js 中模块名是全局作用域,可以在全局引用。

定义一个没有 id 值的匿名模块,通常作为应用的启动函数:

define(['jquery'], function($) {  
    $('body').text('hello world');
});

依赖多个模块的定义:

define(['jquery', './math.js'], function($, math) {  
    // $ 和 math 一次传入 factory
    $('body').text('hello world');
});

模块输出:

define(['jquery'], function($) {

    var HelloWorldize = function(selector){
        $(selector).text('hello world');
    };

    // HelloWorldize 是该模块输出的对外接口
    return HelloWorldize;
});

在模块定义内部引用依赖:

define(function(require) {  
    var $ = require('jquery');
    $('body').text('hello world');
});

CommonJS

CommonJS 是以在浏览器环境之外构建 JavaScript 生态系统为目标而产生的项目,比如在服务器和桌面环境中。

CommonJS 规范是为了解决 JavaScript 的作用域问题而定义的模块形式,可以使每个模块它自身的命名空间中执行。该规范的主要内容是,模块必须通过 module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。

一个直观的例子:

// moduleA.js   定义
module.exports = function( value ){  
    return value * 2;
}
// moduleB.js   使用
var multiplyBy2 = require('./moduleA');  
var result = multiplyBy2(4);  

CommonJS 是同步加载模块,但其实也有浏览器端的实现,其原理是现将所有模块都定义好并通过 id 索引,这样就可以方便的在浏览器环境中解析了,可以参考 require1k 和 tiny-browser-require 的源码来理解其解析(resolve)的过程。

简单的实现: https://github.com/Stuk/require1k
https://github.com/ruanyf/tiny-browser-require

更多关于 CommonJS 规范的内容请查看 http://wiki.commonjs.org/wiki/CommonJS。

UMD

通用模块规范:兼容AMD和CommonJS

http://web.jobbole.com/82238/
http://webpack.toobug.net/zh-cn/chapter2/umd.html
https://github.com/umdjs/umd

以我写的TOC插件来说明:

(function(window, factory) {
    if (typeof exports === 'object') {
        module.exports = factory;
    } else if (typeof define === 'function' && define.amd) {
        define(factory);
    } else {
        window.toc = factory;
    }
})(this,toc);

 function toc(get, n, put) {
        var node = document.getElementById(get);
        var nodes = node.querySelectorAll(n || 'h1,h2,h3,h4,h5');
        var out = '<ul><li>' + '<a href=#h' + '0' + '>' + nodes[0].innerHTML + '</a>';
        nodes[0].setAttribute('id', 'h0');
        for (var i = 1; i < nodes.length; i++) {
            var a = nodes[i].nodeName.charAt(1) - nodes[i - 1].nodeName.charAt(1);
            nodes[i].setAttribute('id', 'h' + i);
            for (var j = 1; j < a; a--) {
                out += '<ul><li>';
            }
            for (var k = -1; k > a; a++) {
                out += '</li></ul>';
            }
            if (a === 0) {
                out += '</li><li>' + '<a href=#h' + i + ' >' + nodes[i].innerHTML + '</a>';
            } else if (a === -1) {
                out += '</li></ul><li>' + '<a href=#h' + i + ' >' + nodes[i].innerHTML + '</a>';
            } else if (a === 1) {
                out += '<ul><li>' + '<a href=#h' + i + ' >' + nodes[i].innerHTML + '</a>';
            }
        }
        out += '</li></ul>';
        if (put) {
            var position = document.getElementById(put);
            position.innerHTML = out;
        }
        return out;

}

使用

toc('qw','h1,h2,h3,h4','er') // 可以直接 使用函数

var toc = require('test.js')  
toc()

define(['toc'], function(toc) {  
  toc()
});  

ES6 Module

http://es6.ruanyifeng.com/#docs/module

  • 严格模式
  • export命令
  • import命令
  • 模块的整体加载
  • export default命令
  • 模块的继承
  • ES6模块加载的实质
  • 循环加载
  • ES6模块的转码
// define 
function f() {}  
export {f};  
// use
import {f} from 'example.js'  // 与common不同的是,这里要使用与模块导出时一致的名字。  

模块加载器的实现

https://www.zhihu.com/question/21157540

Reference

https://github.com/Huxpro/js-module-7day 几个规范的历史
https://github.com/seajs/seajs/issues/588 规范历史与比较
https://www.zhihu.com/question/37011441 知乎对前端模块化的讨论
http://fex.baidu.com/blog/2014/03/fis-module/ 前端模块化与JS模块化的区分。但平常大家讨论的时候并没有详细区分。
https://www.zhihu.com/question/37649318 前端组件化 与 前端模块化的 区分。
https://github.com/seajs/seajs/issues/547 前端模块化的价值所在: 即模块化带来的好处和解决的问题。
https://addyosmani.com/writing-modular-js/ 国外大神
https://github.com/xufei/blog/issues/19 2015前端组件化框架之路 · xufei/blog
http://div.io/topic/371 前端优化:
http://tech.meituan.com/frontend-component-practice.html 前端组件化开发实践 - 美团
http://div.io/topic/831 开发一个完整的JavaScript组件 - Div.IO
http://www.ituring.com.cn/article/63549 图灵社区 : 阅读 : Web应用的组件化开发(一)——基本思路
http://zhaoda.net/webpack-handbook/reference.html 另一份参考连接

关于CSS模块化

随手补充一些关于 CSS 模块化的东西。CSS 的 Global Scope/Namespace 非常不 scale,虽然社区已经开始重视起来,但是目前仍然没有类似 CommonJS/ES6 于 JS 这样的事实标准或语言级别标准的解决方案。CSS 的模块化发展目前大约经历了这么几个历程:

  • CSS Framework (Bootstrap/Foundation)抽象了公共样式,解决了部分基本样式的复用问题。
  • CSS Preprocesser (Scss/less/stylus)通过支持 extend/mix-in 有效解决了开发时的“分治”问题,但仍无法解决运行时的全局问题
  • CSS Postprocesser (PostCSS,虽然官方认为自己并不只是个 Postprocesser)通过 AST 解决了比如 prefix 等部分样式自动化的问题,虽然目前在运行时并无非常成熟的方案,不过很有想象力
  • CSS Naming Convention (BEM/OOCSS/SUIT CSS)可以完全解决运行时命名空间的问题,不过在开发时对开发者造成的额外工作也很多
  • CSS in JS非常激进的做法,可以完全解决运行时命名空间的问题,虽然有一些相对可用的库比如 Radium/React-Style 不过也带来了很多冲击和很多新问题……
  • CSS Modules目前相对最为可用的方案,不过仍然有一些局限性,比如大量依赖 JS,继承需要使用 Compose,对工程师习惯仍然有一些冲击……另外,就算 CSS 4 来了,靠 Node 支撑无论向前向后兼容都比较轻松
  • Shadow DOMChrome/Polymer 推崇的浏览器级别 Web Components 的方案,听上去很合理,但是感觉设计有缺陷,并不看好。
  • CSS 4 Scoped CSS仍然在草案阶段,包括 @scope:scope ,最终方案如何仍然是未知,不过语言级别的方案一定是最靠谱的 —— 上述所有方案都可以很轻松的迁移过去