2016 年 5 月 29 日
前端模块化杂谈
Teambition是一家追求卓越技术的公司,我们工程师都很Geek,我们使用了很多新潮的,开源的技术。同时我们也贡献了很多开源的项目。我们希望能够把一些技术经验分享给大家。于是有了这个「创作随笔」。废话休说,「创作随笔」第一弹,来自我们的前端工程师寸志,谈一谈他在前端模块化开发方面的一些感想。
在模块化方面,Node.js 就显得游刃有余。
作为用户,我们把代码放到一个个 JavaScript 文件中,然后 Node.js 就有一套规则帮我们把这些代码组织起来,Node.js 还有包的概念,于是我们就可以使用 npm 将代码放到一个个包中,放到这里那里的 node_modules 中使用。很方便不是?感谢Node.js。
可在浏览器端,模块化这事就没那么简单了。
前端的模块
前端的一个模块包含很多东西,JavaScript、CSS、图片字体等等。甚至,可能根据业务需要,还包含国际化的文件。一个模块如果包含以上这些东西,复杂度就上了几个数量级。
怎么复杂了?和高大上的 iOS 开发比起来,人家有 SDK,代码随便往项目里扔,图片扔,国际化有成熟的解决方案,最后构建一下一个可运行的应用就出来了。
前端缺乏 SDK,没有成熟统一的开发方案,集成方案,前端模块化之路很崎岖。开发时,我们需要一种方式来组织,加载代码,发布时,我们还需要另外一种方式将代码、资源合并到一起发布。
眼前的现状
TJ 给出了自己解决方案——Component。可以份文件开发,然后再把 JavaScript、CSS 和模板文件合并到一起。我只能说,理想很丰满,现实很骨感,Component 无法适应各种奇葩的应用场景。
或者我们自由一点——
依赖的第三方模块,我们有 Bower,好爽,运行个命令,依赖就安装好了。
但是 Bower 不是银弹,Bower 只解决了模块依赖,安装依赖的问题。Bower中的模块没有任何标准和规则,有的只有 JavaScript,有的支持 AMD,有的可能只有 CSS。文件结构,入口文件完全不一样。并不是使用 Bower 安装的模块我们就可以使用同样的方式使用任何一个模块,使用某种工具将这些模块打包发布!
AMD 作为事实上的前端 JavaScript 模块化标准,或可以出来解救我们。很多 Bower 模块都是支持 AMD 规范的。而且 AMD 还提供了打包工具,总算有点解脱了。好景不长……
每个模块中的 HTML 怎么办,如果我们使用的框架是 Backbone,注定我们要将 HTML 模板转换成 JavaScript 模块,或者使用模块加载器的插件来实现。如果我们使用 AngularJS,那倒是可以交由 AngularJS。发布时 Backbone 中的模块使用 AMD 打包,AngularJS 可以使用 Grunt 内联到页面中。
HTML 模块也没有固定的模式,没有固定的 SDK 来解脱我们。我们只能组合现有的工具!
CSS 就更不用说了,分开写,使用 LESS、SASS 来组织?再一次没有固定的模式没有 SDK。
还有图片呢,字体呢?
拼凑的方案
前端如果想做模块化开发,首先需要针对每一种资源(JavaScript、CSS、模板等)寻找一种模块与集成方案,然后需要根据情况的不同选用不同的工具框架拼凑出来。
JavaScript
目前比较拿的出手的,也就是 JavaScript 的模块化,比如 AMD 或者 CMD 等等,分别可以使用 RequireJS 和 SeaJS。
去年在研究基于 Backbone 的框架 Marionette 时,想与 Sea.js 结合使用。现在来看这种组合是完全没有必要的。Marionette 提供了模块化的组织方案,反而生拉硬扯在一起,冲突得很难受。其实把 JavaScript 文件一列放在 HTML 中也没什么问题。
究竟使用什么来实现 JavaScript,往往与选择的 JavaScript 框架有关,选 Backbone可以 AMD,也可以 CMD。选 AngularJS 直接引用就行。
CSS
CSS 模块化应该是两方面的问题——
一是模块必须有一套基础样式。与 JavaScript 相比,CSS 冲突简直是家常便饭,Shadow DOM 还没成熟,原生的 CSS 样式隔离还在路上。所以必须有一套基础样式来规定这一套模块化组件的样式。我们可以选 Bootstrap,如果闲它太重,也可以自己写。
其次,每个组件必须有命名空间,避免组件间样式冲突。如果选择使用 LESS、SASS 等,那就比较好办,它们的缩进语法避免写很多重复的 CSS 代码。
HTML 模板
如果使用 AngularJS,那 AngularJS 已经帮您解决了模板模块化的问题,AngularJS可以根据模块代码中的引用加载对应的 HTML。如果使用 Backbone,可以选择各种各样的模板引擎,Mustache?不过比起 AngularJS 就低端些,我们必须自己处理模板文件,要么通过模块加载器通过 XHR 请求,然后动态编译;或者将 Mustache 编译成 JS,在当做模块加载。
图片、字体?
放在使用他们的模块中,该怎么引用就怎么引用。
国际化文件?这些就不多说了,总之,每种文件需要选定一种开发的组织方式。
模块分发
模块采用统一的模式来开发之后,只需选定一种包的分发方式就行了——Bower。npm 不适合这样的场景,npm 的版本管理太过细致了。如果同一个项目中允许出现同一模块的不同版本,事情就做的太过复杂了。
发布上线
发布上线必须一个以终为始的过程。如果你不追求网站或者应用的速度,那就把那些开发文件直接丢到生产服务器上去吧。别说,我还真见过这样的商用的网站。
如果讲究一些方案,资源合并成数个文件,代码线上组合和运行方式完全可以与本地开发不一样。只需要功能在就行。
JavaScript 代码打成一个文件,或者两个?都行。如果使用 RequireJS,那 RequireJS 提供了打包的工具,打包成几个文件,什么粒度,都行。如果是 Sea.js 也有对应的工具。就算文件都是一个一个列出来,我们也总是可以打出来你想要的文件。
CSS 等等其他资源都是如此,就算开发时各自不同的结构模式,打包时都是可以打的。
至于上线 CDN,版本号缓存什么的并不在本文的讨论范围之内。
总结
前端模块没有什么特效药。唯一要遵守的原则就是,比起 Node.js 来讲,每种资源必须要有一种自己的开发和上线组织方式(Node.js 开发和上线都是一致的),开发和上线完全可以是两种方式,大可不必人云亦云,只要适合可以随意组合;CSS 在 CSS Scoped Style 正式使用之前,应该有一套基础样式和沙盒(模块样式命名空间)。