简单介绍下modJS

ygfb 8年前
 

近期把团队的项目构建迁移到fis3,同时把模块加载器也由之前的requirejs切换到了modJS。

有些同学可能不了解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一样的开发体验。

那么,我们为什么要使用modJS呢?

有以下几点:

  1. 加载器更小

上面已经提到,200+行和2000+行的差异

  1. 配合构建工具,开发体验更好

之前开发时,需要将每一个模块的代码单独放在define内部,并且需要申明每一个依赖。 而现在,只需要使用类似nodejs的方式编写代码,需要使用某个依赖模块时,直接require('id')即可。 发布编译时,由构建工具统一添加define包裹,自动添加模块id(默认根据路径生成,也可以在fis3的配置中声明格式)。整体的开发体验更好一些。

此外,js文件打包及异步依赖的问题,也可以通过生成resourceMap来解决。

一般情况下,modJS配合fis3已经可以满足大部分需求,并且官方还提供了完整支持amd规范的esl-mod.js。

如果不够用,也可以在modJS的基础上定制一些功能来满足需求,因为源码只有200行,代码也很清晰简单。

我们的项目在切换到modJS时,由于项目比较复杂(2000+文件),一些特殊的需求最终也是通过定制modJS得到解决,这里分享几点:

遇到哪些问题

  • 同步(直接require)的依赖需要打包,异步依赖使用require.async加载

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中。

  • 不支持直接引用远程url,包括同步和异步方式都不支持。

由于历史原因,项目中存在一些如下形式的代码:

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。

  1. 异步加载方法require.async([deps],callback)中的callback触发

modJS加载异步模块时,将callback回调加入一个队列,然后将依赖模块的script标签打入html的head。 但是callback并不是通过script的onload事件触发,而是通过依赖模块的define方法触发。

当我们只是想用require.async异步加载一个非amd规范的脚本时(例如一些第三方的组件、jquery插件等),由于该脚本并没有被define方法包裹,所以无法触发回调的逻辑。

虽然理论上不应该有这个问题,一个项目的模块化方案应该统一,每一个模块都应该使用define包裹,但现实就这样。

  1. 同时异步加载多个模块时,脚本的加载顺序问题

modJS异步加载时,会将多个script脚本同时插入到head,并不会维护脚本的加载顺序。

这个问题,怎么说,理论上模块的依赖关系都应该由加载器来管理,而不应该出现脚本的加载依赖问题。但现实就这样。

当然,如果你愿意的话也可以将源码改成这样的形式:

require.async(moduleA,function(){    require.async(moduleB,function(){      ...do something      ...囧      ...do something    });  });