本文原文地址:http://trock.lofter.com/post/117023_1208040 。

引言: 

在前端开发领域,相信大家对AMD/CMD规范一定不会陌生,尤其对requireJS、seaJS等模块加载器更是耳熟能详,不少网站目前也正在使用。requireJS、seaJS的出现,极大程度的促进了前端模块化编程方式的发展,前端的代码风格趋近标准化。而两者的共同特点都是运行在浏览器端的模块加载器,可以智能解析模块依赖关系,并自动加载相关模块。而我们今天介绍的是服务端的AMD/CMD模块加载器— styleCombine,一个为前端模块化开发及性能提升而诞生的前端开发神器。

解决的问题:

相信大家对服务端加载器的概念比较陌生,为方便理解,不如我们先看一个最终的效果示例:

 

某位前端同学使用 AMD 的开发模式写了如下一个html页面:

<html>

<body>
<script src=”http://www.1688.com/mod/a.js" ></script>
<script data-sc-amd=”true” src=”http://www.1688.com/mod/b.js" ></script>
</body>
</html>

其中 a.js 是一个普通的 JS 文件,非 AMD 模块。

而 b.js 是一个 AMD 模块,它的内容是:

define(“mod/b”, [“mod/c”], function(c){
console.log(“I am module b”);
})

mod/c.js 也是一个 AMD 模块,它的内容是:

define(“mod/b”, [“mod/c”], function(c){
console.log(“I am module b”);
})

显然,模块 b 依赖于模块 c ,而模块 c 又依赖了模块 d 。这样的一个页面,在页面初始加载时,使用requireJS将会异步加载子模块,算上非 AMD 模块 a.js,一共将产生4个JS加载请求。

而若有了 styleComine,事情将会变得很不一样。前端同学写的 html 源码经由 styleCombine 处理后,自动的转换成了如下格式:

<html>

<body>
<script src=”http://www.1688.com/mod/??a.js,b.js,c,js,d.js?_v=9c6b7bc035092416” ></script>
</body>
</html>

很明显,原本需要4个请求才能加载的AMD JS模块,现在全都 combo 在一个url中一次请求完成,性能的提升那是非常自然的事情。

从上面的示例你至少可以认识到:styleCombine 是一个藏身在服务器端的家伙,因为他能直接改变html的输出源码;另外就是它能够自动分析出模块 a 的多层级依赖关系,并自动拼接成 combo URL的形式,使得多个模块的请求被合并为一个。正由于styleCombine有以上两个特性,所以我们称之为服务端的AMD/CMD模块加载器

styleCombine 拥有的功能:

  1. 将 HTML 页面上的多个 js/css 请求自动地合并成一个请求,发送给combo服务器

  2. 对于入口的 AMD/CMD 模块,能够自动解析出模块的深层依赖关系,并将所依赖文件及页面上的其它 js 文件合并为一个请求发送。

  3. 对 HTML 页面中每个 js/css 链接都会根据文件内容自动地添加版本号后缀,js/css 内容更新将触发版本号的实时更新,使得浏览器端缓存或 CDN 缓存能够强制失效。

有了这样一个神器,想象一下你可以做什么

  • 模块化、组件化的开发模式可以更彻底(尤其是对内容型页面而言):一个页面的各模板可以任意地拆分,相关的 CSS、JS 文件可以内嵌引用到这个模板中,而当各个模块最终组装为一个页面时,所有的 CSS、JS  都将被自动合并成一个请求。模块化开发爽了,而又不损耗性能!
  • 无需进行苦逼的线下打包配置等工作,开发时我们就使用浏览器端的加载器进行调试,而上线了就让 styleCombine 来自动做服务端加载,整个过程对开发者完全透明。
  • 静态资源部署 CDN 将成为一件惬意的事,智能更新的时间戳解决了文件缓存问题,妈妈再也不用担心我手工修改时间戳清缓存的苦逼生活了!

实现原理:

styleCombine 的使用可以说对前端开发者完全透明,不必对现有的前后端应用架构做任何修改即可快速享用到 styleCombine 带来的强大功能! 那么它是如何做到的呢?

styleCombine 是由以下三部分构成:

当用户访问我们的页面时,apache/nginx 模块会负责将应用端(比如Java程序)输出的 html 先进行一次处理,匹配所有的 <script>及<link>标签,对于 AMD/CMD JS 模块则取出子模块的依赖列表。将匹配到的所有<script>或<link>标签,按照标准的 combo service格式,拼接为相应的 combo URL,并加上 md5 时间戳。

上面流程中的获取 AMD/CMD JS 模块依赖子模块列表的工作显然是不能在用户访问时进行(性能考虑),而是由 NodeJS 服务在线下计算的。那如何计算呢? 只需要遍历代码分支中的每个AMD/CMD JS模块,并让requireJS、seaJS运行在Node环境中,那么这个文件所有被依赖的模块都会被递归的找到,最终形成一个映射表(示意图):

md5 时间戳也是由 NodeJS 服务线下根据每个静态资源文件的内容算出文件自己对于的 md5 值,并最终根据合并的所有文件算一个总的 md5 值来得到的。 任何一个文件内容发生变化,那么 md5 值就会自动更新。也就是说还有一个 url —> md5 码的映射表存在。

apache/nginx 模块就是根据这两张映射表来完成合并逻辑,并最终在服务端完成了AMD/CMD 模块的加载。

在线实例:

目前 styleCombine 已在阿里巴巴中文站各核心业务页面上广泛使用,经历过多次大促的考验,属于比较成熟的解决方案,推广成本很低。

可以研究下这个正在使用 styleCombine 的在线页面:打开 http://quan.1688.com/  查看下页面的源码,你会发现 JS、CSS 已经是被combo过的。 而若是你打开这个链接再看页面源码http://quan.1688.com/?_debugMode_=1   就会发现原来在开发状态下页面完全是模块化的,并且 AMD 模块的子模块也被合并进来了。

方案优劣分析:

  1. 部署成本:styleCombine 系统由三部分组成,需要相互配合方可正常工作。对 nginx 服务器、nodejs 比较熟悉的同学,整体的部署成本不会高,我们后期也会提供更加自动化的部署脚本来简化这一流程。在部署完毕的后续开发流程中,styleCombine 系统对开发者、应用架构是完全透明的,不增加后期维护成本。
  2. 与浏览器端加载器的优劣比较:实际上两者不是对立的关系,而是相互的配合,服务端的加载器能够完成浏览器端加载器不能完成的一些功能,比如对多层依赖关系的解析并一次性 combo,比如对 JS、CSS 文件内容变化自动更新时间戳的支持等。它更适用于大型网站对性能有着极致化要求的应用场景,并有利于网站长久地保持高性能

    但并非有了服务端的加载器就不需要浏览器端的加载器,比如一些需要异步加载的模块,服务端是无法处理到 html 源码的,所以就需要浏览器端的加载器配合进行,seaJS、requireJS 也都实现了动态 combo 功能(但无法做到多层依赖的一次 combo )。

  3. 系统目前更适用于多页面应用的场景,对单页应用,由于异步加载的模块较多,目前只能发挥出对页面初始加载时的优化功能。PS:对于这个问题 @前端农民工 提供了一种解决思路,可以把文件依赖列表通过 apache/nginx 模块注入到页面上,这样单页应用也可以在异步加载时一次 combo 获取依赖文件。

结束语:

styleCombine 以创新性的思维实现了前端代码的服务器端加载,对开发者透明,对应用架构几乎无改造成本,而带来的是更彻底的模块化开发方式及应用性能的自动化保障!

构建服务端的AMD/CMD模块加载器的更多相关文章

  1. JavaScript AMD 模块加载器原理与实现

    关于前端模块化,玉伯在其博文 前端模块化开发的价值 中有论述,有兴趣的同学可以去阅读一下. 1. 模块加载器 模块加载器目前比较流行的有 Requirejs 和 Seajs.前者遵循 AMD规范,后者 ...

  2. 一个简单的AMD模块加载器

    一个简单的AMD模块加载器 参考 https://github.com/JsAaron/NodeJs-Demo/tree/master/require PS Aaron大大的比我的完整 PS 这不是一 ...

  3. SeaJS:一个适用于 Web 浏览器端的模块加载器

    什么是SeaJS?SeaJS是一款适用于Web浏览器端的模块加载器,它同时又与Node兼容.在SeaJS的世界里,一个文件就是一个模块,所有模块都遵循CMD(Common Module Definit ...

  4. js模块化/js模块加载器/js模块打包器

    之前对这几个概念一直记得很模糊,也无法用自己的语言表达出来,今天看了大神的文章,尝试根据自己的理解总结一下,算是一篇读后感. 大神的文章:http://www.css88.com/archives/7 ...

  5. JS模块加载器加载原理是怎么样的?

    路人一: 原理一:id即路径 原则.通常我们的入口是这样的: require( [ 'a', 'b' ], callback ) .这里的 'a'.'b' 都是 ModuleId.通过 id 和路径的 ...

  6. 小矮人Javascript模块加载器

    https://github.com/miniflycn/webkit-dwarf 短小精悍的webkit浏览器Javascript模块加载器 Why 我们有许多仅基于webkit浏览器开发的应用 无 ...

  7. 使用RequireJS并实现一个自己的模块加载器 (一)

    RequireJS & SeaJS 在 模块化开发 开发以前,都是直接在页面上引入 script 标签来引用脚本的,当项目变得比较复杂,就会带来很多问题. JS项目中的依赖只有通过引入JS的顺 ...

  8. 【模块化编程】理解requireJS-实现一个简单的模块加载器

    在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题: ① 解决单文件变量命名冲突问题 ② 解决前端多人协作问题 ③ 解决文件依赖问题 ④ 按需加载(这个说法其实很假了) ⑤ ..... ...

  9. js 简易模块加载器 示例分析

    前端模块化 关注前端技术发展的各位亲们,肯定对模块化开发这个名词不陌生.随着前端工程越来越复杂,代码越来越多,模块化成了必不可免的趋势. 各种标准 由于javascript本身并没有制定相关标准(当然 ...

随机推荐

  1. nginx1.8+php5.6.10 服务器编译安装备忘2015-06

    又要重新装一台阿里云服务器.开始想用脚本,但发现脚本的程序版本都比较低  还是手动编译最新版本 开始前 更新服务器到最新版本 #yum makecache #yum update //分区挂数据盘 # ...

  2. github克隆项目中的子模块submodule时遇到的问题

    GitHub真是个开源大宝库,不只能学习代码,还能学习git的使用! 最近在研究Off-the-Record-iOS项目(https://github.com/chrisballinger/Off-t ...

  3. 【转】app后端如何选择合适的数据库产品

    转自:http://blog.csdn.net/newjueqi/article/details/44003503 app后端的开发中,经常要面临的一个问题是:数据放在哪里? mysql ?redis ...

  4. 转:.NET中使用Redis (一)

    原文来自于:http://blog.jobbole.com/83821/ 原文出处: 寒江独钓   欢迎分享原创到伯乐头条 Redis是一个用的比较广泛的Key/Value的内存数据库,新浪微博.Gi ...

  5. PowerShell使用SMTP发送邮件

    $smtpServer = "smtp.exmail.qq.com" $smtpUser = "xxxxx@qq.com" $smtpPassword = &q ...

  6. core dump

    Core Dump?! 整理:Wilbur Lang 何谓 core? 在使用半导体作为内存的材料前,人类是利用线圈当作内存的材料(发明 者为王安),线圈就叫作 core ,用线圈做的内存就叫作 co ...

  7. Strust2的json插件

    以下这段摘自网上: Json是一种轻量级的数据交换格式,JSon插件提供了一种名为json的ActionResultType .一旦为Action指定了该结果处理类型,JSON插件就会自动将Actio ...

  8. 查看Redis信息和状态

    原文转自:http://redisdoc.com/server/info.html INFO [section] 以一种易于解释(parse)且易于阅读的格式,返回关于 Redis 服务器的各种信息和 ...

  9. UVA-10735 - Euler Circuit(混合欧拉回路输出)

    题意:给你一个图,有N个点,M条边,这M条边有的是单向的,有的是双向的. 问你能否找出一条欧拉回路,使得每条边都只经过一次! 分析: 下面转自别人的题解: 把该图的无向边随便定向,然后计算每个点的入度 ...

  10. 【Fiddler】手机抓包

    Fiddler (四) 实现手机的抓包 手机配置了后,Fiddler不开起来就什么也连接不了