在JavaScript发展的初期是为了实现简单的页面交互逻辑, 就这么一句话.
如今, 浏览器性能得到极大的提高, 很多页面逻辑迁移到了客户端(表单验证等), 随着Web 2.0时代的到来, ajax技术得到广泛使用, jQuery, angularjs, vuejs等前端库也层出不穷, 前端代码越来越庞大.
这时JavaScript作为嵌入式的脚本语言的定位动摇了, JavaScript却没有为组织代码提供任何明显的帮助, 甚至没有类的概念(当然, 你也可以利用原型链来模拟面向对向编程), 更不用说模块了, JavaScript极其简单的代码组织规范不足以驾驭如此庞大规模的代码.
- 前端模块化的价值
==========
引用下Sea.js中关于前端模块化的价值一文.详细请点击此处
- 前端模块化规范标准
============
目前的主要有三种:- CommonJS(Node.js)
- AMD(Require.js)
- CMD(Sea.js)
-
CommonJS
CommonJS是服务器模块的规范, Node.js采用了这个规范. 根据CommonJS规范, 一个单独的文件就是一个模块, 每一个模块都是一个单独的作用域, 在一个文件定义的变量(还包括函数和类), 都是私有的, 对其他文件是不可见的. CommonJS规范加载模块是同步的, 也就是说, 只有加载完成后, 才能执行后面的操作.
var x = 5;
var add = function(a, b){
return a + b;
};
module.exports.x = x; //对外提供的接口变量
module.exports.addX = add; // 对外提供的接口函数
-
AMD
由于Node.js主要用于服务器编程, 模块文件一般都已经存在于本地硬盘, 所以加载起来比较快, 不用考虑非同步加载的方式, 因此CommonJS规范比较适用. 但是, 如果是浏览器环境, 要从服务器端加载模块, 这时就必须采用非同步模式, 因此浏览器端一般采用AMD规范. 如下规范定义及一般写法.
// 规范
define(id?, dependencies?, factory);
define.amd = {};
// 写法1
define(function(require, exports, module){
var $ = require('jquery');
// 代码块
});
// 写法2
define(['jquery'], function($){
var btn = $('#btn1');
// 代码块
});
// 写法3
define(['require', 'jquery'], function(require){
var $ = require('jquery');
// 代码块
});
-
CMD
CMD规范和AMD类似, 都主要运行于浏览器端, 写法上看起来也很类似. 主要区别在于模块初始化时机, AMD中只要模块作为依赖时, 就会加载并初始化, 而CMD中, 模块作为依赖且被引用时才会初始化, 否则只会加载.
规范定义及一般写法如下:
// 规范
define(factory);
defind.cmd = {};
// 写法1
define(function(require, exports, modules){
var $ = require('jquery');
// 代码块
});
// 写法2
define(['jquery'], function(require, exports, module){
var $ = require('jquery');
// 代码块
});
-
兼容AMD, CMD及非模块化
很多时候, 如果我们引用第三方组件时, 并没有采用模块化开发, 通常我们自己需要包装一下或通过模块加载器的shim插件支持模块化引用依赖. 现在很多第三方库已经默认支持AMD规范的引用, 根据以上模块定义规范, 开放给第三方使用的组件能兼容不同的规范, 如下示例:
(function(root, factory){
if (typeof define === 'function' && (define.amd || define.cmd)){
define(function(require, exports, module){
return factory(root);
});
}else{
root.dialog = factory(root);
}
})(window, function(root){
// code here
return dialog;
});
-
AMD与CMD
对于一般使用者来说, require.js, sea.js都是不错的选择, 对外调用API上, CMD的API设计更简单, 职能更单一, 整体实现更轻量, 也更倾向于CommonJS的规范写法, 提供依赖就近声明. 前后端共享模块时, 只需要去掉define的包装头部就行了. 虽然AMD也支持CommonJS规范写法, 但不是强制的.
同时, 对于依赖的加载顺序, AMD是不保证按照书写的顺序按序初始化模块的, 而这点CMD也更接近CommonJS规范, 对于使用者来说require就是同步的.
-
模块化开发上线部署
- 压缩
- 合并
- 更新版本
不能直接压缩:
因为模块加载器在分析模块的依赖时, 会先将模块的工厂函数factory.toString(),
然后通过正则匹配require局部变量, 这样意味着不能直接通过压缩工具进行压缩,
若require这个变量被替换, 加载器与自动化工具将无法获取模块的依赖.
不能直接合并
我们在开发时, 通常是省略模块的ID的, 如果多个模块直接合并成一个文件, 这样加载器无法区分不同的模块.
所以采用模块化开发部署时, 压缩前通常通过工具先提取依赖, 这样require应该可以当做普通变量直接压缩了, 同时也不再需要加载器分析提取依赖, 对于提升性能也是蛮有好处的. 合并前同样也需要借助工具先提取各个模块的ID, 然后才能按照合并配置进行合并.整个过程如下:
Sea.js和Require.js官方都提供了构建工具, 如Seajs的spm, Requirejs的r.js, 当然也有很多grunt和glup插件可使用, 区别于普通压缩合并就是要有提取依赖及模块ID的能力.
对比构建工具使用感受, spm的整个上手并不是那么顺畅, 配置太复杂. r.js使用相对简单, 只需要配置好合并的配置文件即可, 其它grunt或glup提供的插件相对灵活, 可根据自身业务灵活配置.
完成压缩合并后, 最后一步就是更新版本号上线了, 通常是需要手动更新版本号, 或是修改控制版本号的配置参数. 这一步基本上还是需要人力手动干预一下. 当然如果合并是通过后端的combo服务就不需要了. 不管怎么说, 通过这些工具的组合使用, 整个开发上线流程基本实现自动化了, 比较方便.
-
FIS的集成解决方案
用过FIS的小伙伴们都知道, 采用FIS开发, 整体过程相当顺畅, 对于前端开发, 性能, 部署各方面的问题基本都考虑到了, 内置的小巧mod.js加载器, 就是一个特别轻量的模块加载器, 不需要做依赖分析, FIS强大的编译能力已经提前提取了依赖关系并生成jsmap.json, 已经前置依赖了, 一个轻量的加载器就足够了, 编译的同时自动修改新生成的版本号, 整个过程在FIS下轻松自动完成.
1. 语言扩展能力(less, coffee, jade)
2. 前端模板预编译
3. xss自动转义, 无需手动干预
4. 多域名支持, 动态切换
5. 编译后自动修改版本号(包括图片的引用)
6. 线上本地调试功能...
本文部份内容引用至: 前端模块化开发方案小对比