基于amd规范的自动化版本及智能依赖构建系统实验报告

2011-09-07 00:08

基于amd规范的自动化版本及智能依赖构建系统实验报告

by army

at 2011-09-06 16:08:36

original http://army8735.org/2011/09/06/1006.html

实验前提是了解amd规范:http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition,国内很多社区或个人或公司或开源项目都已有一些具体实现,不再赘述。

另外,这里只有概念,不涉及具体算法和代码。

版本控制

土豆网现有的js版本控制逻辑是为每个更新发布的js文件自动更新版本号,它以下划线+版本号数字为文件名结尾(不包括后缀名):

而在amd中,我们自己实现的异步加载模块机制,譬如说是个use方法,需要使用a.js这个模块。那么不可能每更新一次a.js,所有使用到它的地方都手动改为use(‘a_2.js’)

实际情况是:源代码里一直写的是use(‘a.js’),至于版本控制,它是在服务器端自动追加的:

红色字体部分需要特殊解释一下,它并不是正则替换掉所有use(‘a.js’)的地方,而是做了一个映射或者说路由。以a.js为key、a_2.js为value存入一个HashMap中,然后use(‘a.js’)方法执行时读取到映射关系,将key转换为value后再加载。

这样做的好处是既不影响规范的id语义,又屏蔽了版本号对开发人员的细节可见度,甚至当迫不得已时想使用某个古老版本亦可:use(‘a_1.js’)——因为匹配不到映射关系,所以会直接加载a_1.js这个模块。

智能依赖构建

有时候一个页面上的js要使用到多个模块,为了节省请求数量,我们希望将多个模块合并到这一个js中。为此引入了@import语法,它以注释形式出现在文件头部,服务器端构建工具会自动将导入的js文件合并到一起。这个过程是递归的,也就是说会导入可能所有需要导入的子孙文件。

事实上,这个方法早已屡见不鲜,很多构建工具都是这样做的,只是大部分都是客户端构建,由开发人员手动打包文件后发布。将这一过程移植到服务器端可省却许多不必要的麻烦。

值得一提的是,打包构建会影响到版本映射时的逻辑。譬如我们use(‘a.js’),但a.js已经被导入到当前文件中了,因此映射逻辑会忽视掉它——这个id的模块已经存在,不会再去异步加载了,映射还有什么用呢?

更进一步

手动导入其实不仅仅限于导入依赖或者导入模块,导入其它不相关的东西都行。有时候,在使用一个模块时,这个模块有依赖、依赖的模块还会继续依赖……我们希望一下子全部导入所有依赖到这个文件,而不想手动@import。较好的做法是增加一个关键字比如@build,它会递归将所有依赖打包到一个文件中。

这个步骤我还没有做。

是否需要final

经过讨论有这样一个观点:a.js被b.js和c.js所依赖,a.js更新后原本b和c都会自动追加版本映射(如果是导入则自动更新导入),但是c并不想随之自动更新,c想等自己文件有更新后才去更新导入和依赖的东西,因此需要增加一个@final关键字指定。

经过思考我觉得它带来的好处和带来的坏处一样多:构建和版本映射系统将变得复杂; 阅读维护上增加歧义;等c下次更新时你可能已经忘记了依赖的哪些模块上次没有自动更新。所以这个指令还是不要的好,每次更新一个模块后,所有依赖和导入的都随之自动更新,由测试人员在测试环境根据文件列表全部测试到位。

实际上我们也应该这样做,一个底层的被大量依赖的模块必须经过严格的测试,它被设计成被大量依赖的说明它是全局性的,即使不存在于amd中也是被写成全局代码,在更新时一样要测试到位。

允许并存

开发人员手动合并还存在一种冲突现象:这个页面用到了a.js和b.js,一个人改了a另外一个人改了b,他俩在发布测试环境时会互相覆盖对方的结果。服务器端智能构建可以很好的解决这一问题:一个人发a的源文件而另外一个人发b的源文件,同时在测试环境测试;a测试完成后a发布上线即可,不会发布到b,反之亦然。

甚至某个模块想要设计时也可以是“平滑过渡”的。试想由于某些原因,底层模块a要升级了,但并不是所有地方都兼容新版本,此时我们可以新建个底层模块a2作为新版本,兼容的地方手动替换a2,不兼容的继续使用a。这得益于模块化的松耦合特性。

单元测试

有了模块化之后,单元测试便有了前提。具体怎么测试就不探讨了,这是属于测试人员的范畴。至于为什么说有了前提,那是因为所有模块文件都是异步加载的,在线上环境可能被合并构建成了一个文件,但是只要将全部js代理成源代码(源代码里只写了类似@import的东西,并没有合并,合并是在服务器端最后发布做的),所有导入都变成异步加载了,这有利于分离调试并定位错误。