一直酝酿着写一篇关于模块化框架的文章,由于模块化框架是前端工程中的 最为核心的部分
。原本又想长篇大论的写一篇完整且严肃的paper,但看了 @糖饼 在 DIV.IO 的一篇文章 《再谈 SeaJS 与 RequireJS 的差别》以为能够借着这篇继续谈一下,加上最近spm3发布,在seajs的官网上又引来了一场 口水战 ,我并不想参与到这场论战中,各有所爱的事情很差评论什么,但我想从工程的角度来阐述一下已知的模块化框架相关的问题,并给出一些新的思路,其实也不新啦,都实践了2多年了。javascript
前端模块化框架肩负着
模块管理
、资源加载
两项重要的功能,这两项功能与工具、性能、业务、部署等工程环节都有着很是紧密的联系。所以,模块化框架的设计应该最高优先级考虑工程须要。html
基于 @糖饼 的文章 《再谈 SeaJS 与 RequireJS 的差别》,我这里还要补充一些模块化框架在工程方面的缺点:前端
合并请求
和 按需加载
带来了实现上的矛盾:
//AMD for SPA require(['page/index', 'page/detail'], function(index, detail){ //在执行回调以前,index和detail模块的factory均执行过了 switch(location.hash){ case '#index': index(); break; case '#detail': detail(); break; } });
在执行回调以前,已经同时执行了index和detail模块的factory,而CMD只有执行到require才会调用对应模块的factory。这种差异带来的不只仅是性能上的差别,也可能为开发增长一点小麻烦,好比不方便实现换肤功能,factory注意不要直接操做dom等。固然,咱们能够多层嵌套require来解决这个问题,但又会引发模块请求串行的问题。java
结论:以纯前端方式实现模块化框架 不能 同时知足
按需加载
,请求合并
和依赖管理
三个需求。node
致使这个问题的根本缘由是 纯前端方式只能在运行时分析依赖关系
。git
因为根本问题出在 运行时分析依赖
,所以新思路的策略很简单:不在运行时分析依赖。这就要借助 构建工具
作线下分析了,其基本原理就是:github
利用构建工具在线下进行
模块依赖分析
,而后把依赖关系数据写入到构建结果中,并调用模块化框架的依赖关系声明接口
,实现模块管理、请求合并以及按需加载等功能。json
举个例子,假设咱们有一个这样的工程:数据结构
project ├ lib │ └ xmd.js #模块化框架 ├ mods #模块目录 │ ├ a.js │ ├ b.js │ ├ c.js │ ├ d.js │ └ e.js └ index.html #入口页面
工程中,index.html
的源码内容为:框架
<!doctype html> ... <script src="lib/xmd.js"></script> <!-- 模块化框架 --> <script> //等待构建工具生成数据替换 `__FRAMEWORK_CONFIG__' 变量 require.config(__FRAMEWORK_CONFIG__); </script> <script> //用户代码,异步加载模块 require.async(['a', 'e'], function(a, e){ //do something with a and e. }); </script> ...
工程中,mods/a.js
的源码内容为(采用相似CMD的书写规范):
define('a', function(require, exports, module){ console.log('a.init'); var b = require('b'); var c = require('c'); exports.run = function(){ //do something with b and c. console.log('a.run'); }; });
{ "a" : [ "b", "c" ], "b" : [ "d" ] }
index.html
的构建结果为:<!doctype html> ... <script src="lib/xmd.js"></script> <!-- 模块化框架 --> <script> //构建工具生成的依赖数据 require.config({ "deps" : { "a" : [ "b", "c" ], "b" : [ "d" ] } }); </script> <script> //用户代码,异步加载模块 require.async(['a', 'e'], function(a, e){ //do something with a and e. }); </script>
模块化框架根据依赖表加载资源,好比上述例子,入口须要加载a、e两个模块,查表得知完整依赖关系,配合combo服务,能够发起一个合并后的请求:
依赖分析完成后能够压缩掉require关键字
require.config({...})
相关的数据也是能够的。对于小项目,文件所有合并的状况,更加不须要deps表了,只要在入口的require.async调用以前加载全部模块化的文件,依赖关系无需额外维护请求合并
,而不用等到一级模块加载完成才能知道后续的依赖关系。因为采用require函数做为依赖标记,所以若是须要变量方式require,须要额外声明,这个时候能够实现兼容AMD规范写法,好比
define('a', ['b', 'c'], function(require, exports, module){ console.log('a.init'); var name = isIE ? 'b' : 'c'; var mod = require(name); exports.run = function(){ //do something with mod. console.log('a.run'); }; })
只要工具把define函数中的 deps
参数,或者factory内的require都做为依赖声明标记来识别,这样工程性就比较完备了。
但无论怎样, 线下分析始终依靠了字面量信息
,因此开发上可能会有必定的局限性,但总的来讲瑕不掩瑜。