近期把团队的项目构建迁移到fis3,同时把模块加载器也由之前的requirejs切换到了modJS。
有些同学可能不了解modJS,这里做个简单的介绍。
简单的说,modJS是百度fex-team提供的一个轻量级的模块加载器,类似requirejs。
但modJS并不完全兼容规范amd/cmd,事实上,只支持非常简单的全局方法define(id,factory)。
另外factory提供了3个参数require/exports/module,用于引用和导出模块。
modJS源码只有200行左右,相比之下requirejs的源码达到了2000+行。
除了体量非常小之外,modJS配合fis3的fis3-hook-commonjs插件,可以在纯前端项目中实现类似nodejs一样的开发体验。
有以下几点:
上面已经提到,200+行和2000+行的差异
之前开发时,需要将每一个模块的代码单独放在define内部,并且需要申明每一个依赖。 而现在,只需要使用类似nodejs的方式编写代码,需要使用某个依赖模块时,直接require('id')即可。 发布编译时,由构建工具统一添加define包裹,自动添加模块id(默认根据路径生成,也可以在fis3的配置中声明格式)。整体的开发体验更好一些。
此外,js文件打包及异步依赖的问题,也可以通过生成resourceMap来解决。
一般情况下,modJS配合fis3已经可以满足大部分需求,并且官方还提供了完整支持amd规范的esl-mod.js。
如果不够用,也可以在modJS的基础上定制一些功能来满足需求,因为源码只有200行,代码也很清晰简单。
我们的项目在切换到modJS时,由于项目比较复杂(2000+文件),一些特殊的需求最终也是通过定制modJS得到解决,这里分享几点:
requirejs将所有依赖模块在define方法的参数中声明,所以可以保证先异步加载需要的模块,最后再执行factory。
而modJS设计之初,便考虑到稍微大型点的前端项目都会打包模块js减少请求优化性能,依赖的模块其实早已合并打包,并不需要在define中声明后再异步加载。
所以需要将所有异步的模块以require.async方法来加载。
以我们的项目为例,首次加载时,会加载3个打包的js文件,分别是基础库(modjs、jquery、badjs)、base.js(其他打包的通用模块,初始化一些变量)、mod.main.js(当前页面用到的逻辑打包)。 当用户点击其他页面(非刷新)时,再异步加载该页面用到的mod.main.js(其他页面打包合并后的js),这部分js的模块id和url由构建工具统一打到resourceMap中。
由于历史原因,项目中存在一些如下形式的代码:
define(id,['http://a','http://b'],function(a,b){ a.xxx(b) ... })
通过脚本统一替换为commonJS规范后,旧代码变成了这样:
var a = require('http://a'); var b = require('http://b'); a.xxx(b) ...
这种同步方式的代码目前没什么好的解决办法,最后重构代码解决。
异步的方式稍微好一些,可以通过构建将远程url打入到resourceMap。
modJS加载异步模块时,将callback回调加入一个队列,然后将依赖模块的script标签打入html的head。 但是callback并不是通过script的onload事件触发,而是通过依赖模块的define方法触发。
当我们只是想用require.async异步加载一个非amd规范的脚本时(例如一些第三方的组件、jquery插件等),由于该脚本并没有被define方法包裹,所以无法触发回调的逻辑。
虽然理论上不应该有这个问题,一个项目的模块化方案应该统一,每一个模块都应该使用define包裹,但现实就这样。
modJS异步加载时,会将多个script脚本同时插入到head,并不会维护脚本的加载顺序。
这个问题,怎么说,理论上模块的依赖关系都应该由加载器来管理,而不应该出现脚本的加载依赖问题。但现实就这样。
当然,如果你愿意的话也可以将源码改成这样的形式:
require.async(moduleA,function(){ require.async(moduleB,function(){ ...do something ...囧 ...do something }); });