前端模块化开发

在JavaScript发展的初期是为了实现简单的页面交互逻辑, 就这么一句话.

如今, 浏览器性能得到极大的提高, 很多页面逻辑迁移到了客户端(表单验证等), 随着Web 2.0时代的到来, ajax技术得到广泛使用, jQuery, angularjs, vuejs等前端库也层出不穷, 前端代码越来越庞大.

这时JavaScript作为嵌入式的脚本语言的定位动摇了, JavaScript却没有为组织代码提供任何明显的帮助, 甚至没有类的概念(当然, 你也可以利用原型链来模拟面向对向编程), 更不用说模块了, JavaScript极其简单的代码组织规范不足以驾驭如此庞大规模的代码.

  • 前端模块化的价值
    ==========

引用下Sea.js中关于前端模块化的价值一文.详细请点击此处

  • 前端模块化规范标准
    ============
    目前的主要有三种:
    1. CommonJS(Node.js)
    2. AMD(Require.js)
    3. 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就是同步的.

  • 模块化开发上线部署

    1. 压缩
    2. 合并
    3. 更新版本

不能直接压缩:
因为模块加载器在分析模块的依赖时, 会先将模块的工厂函数factory.toString(),
然后通过正则匹配require局部变量, 这样意味着不能直接通过压缩工具进行压缩,
若require这个变量被替换, 加载器与自动化工具将无法获取模块的依赖.

不能直接合并
我们在开发时, 通常是省略模块的ID的, 如果多个模块直接合并成一个文件, 这样加载器无法区分不同的模块.

所以采用模块化开发部署时, 压缩前通常通过工具先提取依赖, 这样require应该可以当做普通变量直接压缩了, 同时也不再需要加载器分析提取依赖, 对于提升性能也是蛮有好处的. 合并前同样也需要借助工具先提取各个模块的ID, 然后才能按照合并配置进行合并.整个过程如下:

image.png

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. 线上本地调试功能...

本文部份内容引用至: 前端模块化开发方案小对比

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容