原文连接:http://fex.baidu.com/blog/2014/03/fis-optimize/javascript
每个參与过开发企业级 web 应用的前端project师也许都曾思考过前端性能优化方面的问题。咱们有雅虎 14 条性能优化原则。还有两本很是经典的性能优化指导书:《高性能站点建设指南》、《高性能站点建设指南》。经验丰富的project师对于前端性能优化方法耳濡目染。基本都能一一列举出来。这些性能优化原则大概是在 7 年前提出的。对于 web 性能优化至今都有很重要的指导意义。
php
然而,对于构建大型 web 应用的团队来讲,要坚持贯彻这些优化原则并不是一件十分easy的事。因为优化原则中很是多要求与project管理相违背。比方“把 css 放在头部”和“把 js 放在尾部”这两条原则,咱们不能让整个团队的project师在写样式和脚本引用的时候都去改动同一份的页面文件。css
这会严重影响团队成员间并行开发的效率,尤为是在团队有版本号管理的状况下。天天要花大量的时间进行代码改动合并。这项成本是难以接受的。html
所以在前端project界,总会看到周期性的性能优化工做,辛勤的前端project师们每到月圆之夜就会倾巢出动依据优化原则作一次最佳实践。前端
本文从一个全新的视角来思考 web 性能优化与前端project之间的关系。经过解读百度前端集成解决方式小组(F.I.S)在打造高性能前端架构并统一百度 40 多条前端产品线的过程当中所经历的技术尝试。揭示前端性能优化在前端架构及开发工具设计层面的实现思路。java
性能优化原则及分类python
笔者先若是本文的读者是有前端开发经验的project师,并对企业级 web 应用开发及性能优化有必定的思考。所以我不会反复介绍雅虎 14 条性能优化原则,若是您没有这些前续知识的,请移步这里来学习。jquery
首先,咱们把雅虎 14 条优化原则。《高性能站点建设指南》以及《高性能站点建设进阶指南》中提到的优化点作一次梳理,假设依照优化方向分类可以获得这样一张表格: git
优化方向 | 优化手段 |
---|---|
请求数量 | 合并脚本和样式表,CSS Sprites,拆分初始化负载。划分主域 |
请求带宽 | 开启 GZip。精简 JavaScript,移除反复脚本,图像优化 |
缓存利用 | 使用 CDN。使用外部 JavaScript 和 CSS。加入 Expires 头,下降 DNS 查找。配置 ETag,使 AjaX 可缓存 |
页面结构 | 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 |
代码校验 | 避免 CSS 表达式。避免重定向 |
眼下大多数前端团队可以利用 yui compressor 或者 google closure compiler 等压缩工具很是easy作到“精简 javascript ”这条原则。相同的,也可以使用图片压缩工具对图像进行压缩,实现“图像优化”原则,这两条原则是对单个资源的处理,所以不会引发不论什么project方面的问题。很是多团队也经过引入代码校验流程来确保实现“避免 css 表达式”和“避免重定向”原则。眼下绝大多数互联网公司也已经开启了服务端的 Gzip 压缩,并使用 CDN 实现静态资源的缓存和高速訪问。一些技术实力雄厚的前端团队甚至研发出了本身主动 CSS Sprites 工具。攻克了 CSS Sprites 在project维护方面的难题。使用“查找 - 替换”思路,咱们彷佛也可以很是好的实现“划分主域”原则。github
咱们把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些尚未很是好实现的优化原则,再来回想一下以前的性能优化分类:
优化方向 | 优化手段 |
---|---|
请求数量 | 合并脚本和样式表。拆分初始化负载 |
请求带宽 | 移除反复脚本 |
缓存利用 | 加入 Expires 头。配置 ETag,使 Ajax 可缓存 |
页面结构 | 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 |
诚然,不能否认现在有很是多顶尖的前端团队可以将上述还剩下的优化原则也都一一解决,但业界大多数团队都还没能很是好的解决这些问题。所以接下来本文将就这些原则的解决方式作进一步的分析与解说,从而为那些尚未进入前端工业化开发的团队提供一些基础技术建设意见,也借此机会与业界顶尖的前端团队在工业化project化方向上交流一下彼此的心得。
静态资源版本号更新与缓存
如表格 2 所看到的,在“缓存利用”分类中保留了“加入 Expires 头”和“配置 ETag ”两项,也许有些人会质疑,明明这两项仅仅要配置了server的相关选项就可以实现。为何说它们难以解决呢?确实,开启这两项很是easy,但开启了缓存后,咱们的项目就開始面临还有一个挑战:怎样更新这些缓存。
相信大多数团队也找到了相似的答案。它和《高性能站点建设指南》关于“加入 Expires 头”所说的原则同样——修订文件名称。即:
思路没错。但要怎么改变连接呢?变成什么样的连接才干有效更新缓存。又能最大限度避免那些没有改动过的文件缓存不失效呢?
先来看看现在通常前端团队的作法:
<script type="text/javascript" src="a.js?t=20130825"></script>
或者
<script type="text/javascript" src="a.js?v=1.0.0"></script>
你们会採用加入 query 的形式改动连接。这样作是比較直观的解决方式。但在訪问量较大的站点,这么作可能将面临一些新的问题。
一般一个大型的 web 应用差点儿天天都会有迭代和更新,公布新版本号也就是公布新的静态资源和页面的过程。以上述代码为例。若是现在线上执行着 index.html 文件。并且使用了线上的 a.js 资源。index.html 的内容为:
<script type="text/javascript" src="a.js?v=1.0.0"></script>
此次咱们更新了页面中的一些内容。获得一个 index.html 文件,并开发了新的与之匹配的 a.js 资源来完毕页面交互。新的 index.html 文件的内容所以而变成了:
<script type="text/javascript" src="a.js?v=1.0.1"></script>
好了,现在要開始将两份新的文件公布到线上去。
可以看到,a.html 和 a.js 的资源其实是要覆盖线上的同名文件的。
不管如何。在公布的过程当中,index.html 和 a.js 总有一个前后的顺序,从而中间出现一段或大或小的时间间隔。
对于一个大型互联网应用来讲即便在一个很是小的时间间隔内,都有可能出现新用户訪问,而在这个时间间隔中訪问了站点的用户会发生什么状况呢:
这就是为何大型 web 应用在版本号上线的过程当中经常会较集中的出现前端报错日志的缘由。也是一些互联网公司选择加班到半夜等待訪问低峰期再上线的缘由之中的一个。此外,由于静态资源文件版本号更新是“覆盖式”的,而页面需要经过改动 query 来更新,对于使用 CDN 缓存的 web 产品来讲。还可能面临 CDN 缓存攻击的问题。
咱们再来观察一下前面说的版本号更新手段:
<script type="text/javascript" src="a.js?v=1.0.0"></script>
咱们不难预測,a.js 的下一个版本号是“ 1.0.1 ”。那么就可以刻意构造一串这种请求“ a.js?
v=1.0.1 ”、“ a.js?v=1.0.2 ”、……让 CDN 将当前的资源缓存为“将来的版本号”。这样当这个页面所用的资源有更新时。即便更改了连接地址。也会因为 CDN 的缘由返回给用户旧版本号的静态资源,从而形成页面错误。即使不是刻意制造的攻击,在上线间隙出现訪问也可能致使区域性的 CDN 缓存错误。
此外。当版本号有更新时,改动所有引用连接也是一件与project管理相悖的事,至少咱们需要一个可以“查找 - 替换”的工具来本身主动化的解决版本号号改动的问题。
对付这个问题,眼下来讲最优方案就是基于文件内容的 hash 版本号冗余机制 了。也就是说,咱们但愿project师源代码是这么写的:
<script type="text/javascript" src="a.js"></script>
但是线上代码是这种:
<script type="text/javascript" src="a_8244e91.js"></script>
当中”_82244e91 ”这串字符是依据 a.js 的文件内容进行 hash 运算获得的。仅仅有文件内容发生变化了才会有更改。由于版本号序列是与文件名称写在一块儿的。而不是同名文件覆盖,所以不会出现上述说的那些问题。那么这么作都有哪些优势呢?
尽管这种方案是相比之下最完美的解决方式,但它没法经过手工的形式来维护,因为要依靠手工的形式来计算和替换 hash 值并生成对应的文件将是一项很繁琐且easy出错的工做。
所以。咱们需要借助工具。有了这种思路,咱们如下就来了解一下 fis 是怎样完毕这项工做的。
首先。之因此有这样的工具需求,全然是因为 web 应用执行的根本机制决定的:web 应用所需的资源是以字面的形式通知浏览器下载而聚合在一块儿执行的。
这样的资源载入策略使得 web 应用从本质上差异于传统桌面应用的版本号更新方式。也是大型 web 应用需要工具处理的最根本缘由。
为了实现资源定位的字面量替换操做。前端构建工具理论上需要识别所有资源定位的标记。当中包含:
<script src=” path ”>
、<link href=” path ”>
、<img src=” path ”>
、已经 embed、audio、video、object 等具备资源载入功能的标签。为了project上的维护方便。咱们但愿project师在源代码中写的是相对路径。而工具可以将其替换为线上的绝对路径,从而避免相对路径定位错误的问题(比方 js 中需要定位图片路径时不能使用相对路径的状况)。
fis 有一个很棒的资源定位系统,它是依据用户本身的配置来指定资源公布后的地址。而后由 fis 的资源定位系统识别文件里的定位标记,计算内容 hash,并依据配置替换为上线后的绝对 url 路径。
要想实现具有 hash 版本号生成功能的构建工具不是“查找 - 替换”这么简单的,咱们考虑这样一种状况:
由于咱们的资源版本是经过对文件内容进行 hash 运算获得,如上图所看到的,index.html 中引用的 a.css 文件的内容事实上也包括了 a.png 的 hash 运算结果。所以咱们在改动 index.html 中 a.css 的引用时。不能直接计算 a.css 的内容 hash,而是要先计算出 a.png 的内容 hash。替换 a.css 中的引用。获得了 a.css 的终于内容,再作 hash 运算,最后替换 index.html 中的引用。
这意味着构建工具需要具有“递归编译”的能力,这也是为何 fis 团队不得不放弃 gruntjs 等 task-based 系统的根本缘由。
针对前端项目的构建工具必须是具有递归处理能力的。此外,由于文件之间的交叉引用等缘由,fis 构建工具还实现了构建缓存等机制。以提高构建速度。
在攻克了基于内容 hash 的版本号更新问题以后,咱们可以将所有前端静态资源开启永久强缓存。每次版本号公布都可以首先让静态资源全量上线,再进一步上线模板或者页面文件,不再用操心各类缓存和时间间隙的问题了!
静态资源管理与模板框架
让咱们再来看看前面的优化原则表还剩些什么:
优化方向 | 优化手段 |
---|---|
请求数量 | 合并脚本和样式表,拆分初始化负载 |
请求带宽 | 移除反复脚本 |
缓存利用 | 使 Ajax 可缓存 |
页面结构 | 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 |
很是不幸。剩下的优化原则都不是使用工具就能很是好实现的。也许有人会辩驳:“我用某某工具可以实现脚本和样式表合并”。
嗯,必须认可。使用工具进行资源合并并替换引用也许是一个不错的办法。但在大型 web 应用,这样的方式有一些很是严重的缺陷,来看一个很是熟悉的样例:
某个 web 产品页面有 A、B、C 三个资源
project师依据“下降 HTTP 请求”的优化原则合并了资源
产品经理要求 C 模块按需出现,此时 C 资源已出现多余的可能
C 模块再也不需要了,凝视掉吧!但 C 资源一般不敢轻易剔除
不知不觉中。性能优化变成了性能恶化……
其实,使用工具在线下进行静态资源合并是没法解决资源按需载入的问题的。
假设解决不了按需载入。则势必会致使资源的冗余。此外,线下经过工具实现的资源合并通常会使得资源载入和使用的分离,比方在页面头部或配置文件里写资源引用及合并信息,而用到这些资源的 html 组件写在了页面其它地方,这样的书写方式在project上很easy引发维护不一样步的问题,致使使用资源的代码删除了,引用资源的代码却还在的状况。所以。在工业上要实现资源合并至少要知足例如如下需求:
将以上要求综合考虑。不难发现,单纯依靠前端技术或者工具处理的是很是难达到这些理想要求的。现代大型 web 应用所展现的页面绝大多数都是使用服务端动态语言拼接生成的。有的产品使用模板引擎,比方 smarty、velocity,有的则干脆直接使用动态语言,比方 php、python。无论使用哪一种方式实现。前端project师开发的 html 绝大多数终于都不是以静态的 html 在线上执行的,接下来我会讲述一种新的模板架构设计,用以实现前面说到那些性能优化原则。同一时候知足project开发和维护的需要,这样的架构设计的核心思想就是:
考虑一段这种页面代码:
<html> <head> <title>hello world</title> <link rel="stylesheet" type="text/css" href="A.css"> <link rel="stylesheet" type="text/css" href="B.css"> <link rel="stylesheet" type="text/css" href="C.css"> </head> <body> <div>html of A</div> <div>html of B</div> <div>html of C</div> </body> </html>
依据资源合并需求中的第二项,咱们但愿资源引用与使用能尽可能靠近,这样未来维护起来会更easy一些。所以,理想的源代码是:
<html> <head> <title>hello world</title> </head> <body> <link rel="stylesheet" type="text/css" href="A.css"><div>html of A</div> <link rel="stylesheet" type="text/css" href="B.css"><div>html of B</div> <link rel="stylesheet" type="text/css" href="C.css"><div>html of C</div> </body> </html>
固然,把这种页面直接送达给浏览器用户是会有严重的页面闪烁问题的,因此咱们实际上仍然但愿终于页面输出的结果仍是如最開始的截图同样,将 css 放在头部输出。
这就意味着,页面结构需要有一些调整,并且有能力收集资源载入需求。那么咱们考虑一下这种源代码:
<html> <head> <title>hello world</title> <!--[CSS LINKS PLACEHOLDER]--> </head> <body> {require name="A.css"}<div>html of A</div> {require name="B.css"}<div>html of B</div> {require name="C.css"}<div>html of C</div> </body> </html>
在页面的头部插入一个 html 凝视“<!--[CSS LINKS PLACEHOLDER]-->
”做为占位,而将原来字面书写的资源引用改为模板接口(require)调用。该接口负责收集页面所需资源。require 接口实现很easy,就是准备一个数组,收集资源引用。并且可以去重。
最后在页面输出的前一刻,咱们将 require 在执行时收集到的“ A.css ”、“ B.css ”、“ C.css ”三个资源拼接成 html 标签。替换掉凝视占位“<!--[CSS LINKS PLACEHOLDER]-->
”,从而获得咱们需要的页面结构。
通过 fis 团队的总结,咱们发现模板层面仅仅要实现三个开发接口,既可以比較完美的实现眼下遗留的大部分性能优化原则,这三个接口各自是:
实现了这些接口以后,一个重构后的模板页面的源码可能看起来就是这种了:
<html> <head> <title>hello world</title> <!--[CSS LINKS PLACEHOLDER]--> {require name="jquery.js"} {require name="bootstrap.css"} </head> <body> {require name="A/A.css"}{widget name="A/A.tpl"} {script}console.log('A loaded'){/script} {require name="B/B.css"}{widget name="B/B.tpl"} {require name="C/C.css"}{widget name="C/C.tpl"} <!--[SCRIPTS PLACEHOLDER]--> </body> </html>
而终于在模板解析的过程当中,资源收集与去重、页面 script 收集、占位符替换操做。终于从服务端发送出来的 html 代码为:
<html> <head> <title>hello world</title> <link rel="stylesheet" type="text/css" href="bootstrap.css"> <link rel="stylesheet" type="text/css" href="A/A.css"> <link rel="stylesheet" type="text/css" href="B/B.css"> <link rel="stylesheet" type="text/css" href="C/C.css"> </head> <body> <div>html of A</div> <div>html of B</div> <div>html of C</div> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript">console.log('A loaded');</script> </body> </html>
不难看出。咱们眼下已经实现了“按需载入”。“将脚本放在底部”,“将样式表放在头部”三项优化原则。
前面讲到静态资源在上线后需要加入 hash 戳做为版本号标识,那么这样的使用模板语言来收集的静态资源该怎样实现这项功能呢?答案是:静态资源依赖关系表。
若是前面讲到的模板源码所相应的文件夹结构为下图所看到的:
那么咱们可以使用工具扫描整个 project 文件夹。而后建立一张资源表,同一时候记录每个资源的部署路径。可以获得这种一张表:
{
"res": {
"A/A.css": {
"uri": "/A/A_1688c82.css",
"type": "css"
},
"B/B.css": {
"uri": "/B/B_52923ed.css",
"type": "css"
},
"C/C.css": {
"uri": "/C/C_6dda653.css",
"type": "css"
},
"bootstrap.css": {
"uri": "bootstrap_08f2256.css",
"type": "css"
},
"jquery.js": {
"uri": "jquery_9155343.css",
"type": "js"
},
},
"pkg": {}
}
基于这张表。咱们就很是easy实现 {require name=” id ”} 这个模板接口了。
仅仅须查表就能够。比方运行{require name=” jquery.js ”},查表获得它的 url 是“/jquery_9151577.js ”,声明一个数组收集起来就行了。这样,整个页面运行完成以后。收集资源载入需求,并替换页面的占位符。就能够实现资源的 hash 定位,获得:
<html> <head> <title>hello world</title> <link rel="stylesheet" type="text/css" href="bootstrap_08f2256.css"> <link rel="stylesheet" type="text/css" href="A/A_1688c82.css"> <link rel="stylesheet" type="text/css" href="B/B_52923ed.css"> <link rel="stylesheet" type="text/css" href="C/C_6dda653.css"> </head> <body> <div>html of A</div> <div>html of B</div> <div>html of C</div> <script type="text/javascript" src="jquery_9155343.js"></script> <script type="text/javascript">console.log('A loaded');</script> </body> </html>
接下来。咱们讨论怎样在基于表的设计思想上是怎样实现静态资源合并的。也许有些团队使用过 combo 服务,也就是咱们在终于拼接生成页面资源引用的时候,并不是生成多个独立的 link 标签。而是将资源地址拼接成一个 url 路径。请求一种线上的动态资源合并服务,从而实现下降 HTTP 请求的需求,比方:
<html> <head> <title>hello world</title> <link rel="stylesheet" type="text/css" href="/combo?files=bootstrap_08f2256.css,A/A_1688c82.css,B/B_52923ed.css,C/C_6dda653.css"> </head> <body> <div>html of A</div> <div>html of B</div> <div>html of C</div> <script type="text/javascript" src="jquery_9155343.js"></script> <script type="text/javascript">console.log('A loaded');</script> </body> </html>
这个“/combo?files=file1,file2,file3,…”的 url 请求响应就是动态 combo 服务提供的,它的原理很是easy,就是依据 get 请求的 files 參数找到相应的多个文件,合并成一个文件来响应请求。并将其缓存,以加快訪问速度。
这样的方法很是巧妙。有些server甚至直接集成了这类模块来方便的开启此项服务,这样的作法也是大多数大型 web 应用的资源合并作法。
但它也存在一些缺陷:
对于上述第二条缺陷。可以举个样例来看说明:
files=a,b,e,f
很是明显。假设 combo 服务能聪明的知道 A 页面使用的资源引用为“/combo?files=a,b ”和“/combo?files=c,d ”,而 B 页面使用的资源引用为“/combo?files=a,b ”,“/combo?files=e,f ”就行了。
这样当用户在訪问 A 页面以后再訪问 B 页面时,仅仅需要下载 B 页面的第二个 combo 文件就能够。第一个文件已经在訪问 A 页面时缓存好了的。
基于这种思考。fis 在资源表上新增了一个字段。取名为“ pkg ”,就是资源合并生成的新资源,表的结构会变成:
{
"res": {
"A/A.css": {
"uri": "/A/A_1688c82.css",
"type": "css"
},
"B/B.css": {
"uri": "/B/B_52923ed.css",
"type": "css"
},
"C/C.css": {
"uri": "/C/C_6dda653.css",
"type": "css"
},
"bootstrap.css": {
"uri": "bootstrap_08f2256.css",
"type": "css"
},
"jquery.js": {
"uri": "jquery_9155343.css",
"type": "js"
},
},
"pkg": {
"p0": {
"uri": "/pkg/utils_b967346.css",
"type": "css",
"has": ["bootstrap.css", "A/A.css"]
},
"p1": {
"uri": "/pkg/others_0d4552a.css",
"type": "css",
"has": ["B/B.css", "C/C.css"]
}
}
}
相比以前的表,可以看到新表中多了一个 pkg 字段,并且记录了打包后的文件所包括的独立资源。
这样,咱们又一次设计一下{require name=” id ”}这个模板接口:在查表的时候,假设一个静态资源有 pkg 字段,那么就去载入 pkg 字段所指向的打包文件,不然载入资源自己。比方运行{require name=” bootstrap.css ”}。查表得知 bootstrap.css 被打包在了“ p0 ”中。所以取出 p0 包的 url “/pkg/utils_b967346.css ”,并且记录页面已载入了“ bootstrap.css ”和“ A/A.css ”两个资源。
这样一来,以前的模板代码运行以后获得的 html 就变成了:
<html> <head> <title>hello world</title> <link rel="stylesheet" type="text/css" href="pkg/utils_b967346.css"> <link rel="stylesheet" type="text/css" href="pkg/others_0d4552a.css"> </head> <body> <div>html of A</div> <div>html of B</div> <div>html of C</div> <script type="text/javascript" src="jquery_9155343.js"></script> <script type="text/javascript">console.log('A loaded');</script> </body> </html>
css 资源请求数由原来的 4 个下降为 2 个。这种打包结果是怎么来的呢?答案是配置获得的。
咱们来看一下带有打包结果的资源表的 fis 配置:
fis.config.set('pack', {
'pkg/util.css': [ 'bootstrap.css', 'A/A.css'],
'pkg/other.css': [ '**.css' ]
});
咱们将“ bootstrap.css ”、“ A/A.css ”打包在一块儿。其它 css 另外打包。从而生成两个打包文件,当页面需要打包文件里的资源时,模块框架就会收集并计算出最优的资源载入结果。从而解决静态资源合并的问题。
这样作的缘由是为了弥补 combo 在前面讲到的两点技术上的不足而设计的。但也不难发现这样的打包策略是需要配置的。这就意味着维护成本的添加。
但好在它有两个优点可以必定程度上弥补这个问题:
关于第二点。fis 有这样辅助系统来支持自适应打包算法:
至此,咱们经过基于表的静态资源管理系统和三个模板接口实现了几个重要的性能优化原则,现在咱们再来回想一下前面的性能优化原则分类表,剔除掉已经作到了的。看看还剩下哪些没作到的:
优化方向 | 优化手段 |
---|---|
请求数量 | 拆分初始化负载 |
请求带宽 | 拆分初始化负载 |
缓存利用 | 使 Ajax 可缓存 |
页面结构 | 尽早刷新文档的输出 |
“拆分初始化负载”的目标是将页面一開始载入时不需要运行的资源从所有资源中分离出来,等到需要的时候再载入。project师一般没有耐心去区分资源的分类状况,但咱们可以利用组件化框架接口来帮助project师管理资源的使用。仍是从样例開始思考:
<html> <head> <title>hello world</title> {require name="jquery.js"} </head> <body> <button id="myBtn">Click Me</button> {script} $('#myBtn').click(function(){ var dialog = require('dialog/dialog.js'); dialog.alert('you catch me!'); }); {/script} <!--[SCRIPTS PLACEHOLDER]--> </body> </html>
在 fis 给百度内部团队开发的架构中,假设这样书写代码,页面终于的运行结果会变成:
<html> <head> <title>hello world</title> </head> <body> <button id="myBtn">Click Me</button> <script type="text/javascript" src="/jquery_9151577.js"></script> <script type="text/javascript" src="/dialog/dialog_ae8c228.js"></script> <script type="text/javascript"> $('#myBtn').click(function(){ var dialog = require('dialog/dialog.js'); dialog.alert('you catch me!'); }); </script> <!--[SCRIPTS PLACEHOLDER]--> </body> </html>
fis 系统会分析页面中 require(id)函数的调用,并将依赖关系记录到资源表相应资源的 deps 字段中,从而在页面渲染查表时可以载入依赖的资源。
但此时 dialog.js 是以 script 标签的形式同步载入的,这样会在页面初始化时出现资源的浪费。所以。fis 团队提供了 require.async 的接口。用于异步载入一些资源。源代码改动为:
<html> <head> <title>hello world</title> {require name="jquery.js"} </head> <body> <button id="myBtn">Click Me</button> {script} $('#myBtn').click(function() { require.async('dialog/dialog.js', function( dialog ) { dialog.alert('you catch me!'); }); }); {/script} <!--[SCRIPTS PLACEHOLDER]--> </body> </html>
这样书写以后。fis 系统会在表里以 async 字段来标准资源依赖关系是异步的。fis 提供的静态资源管理系统会将页面输出的结果改动为:
<html> <head> <title>hello world</title> </head> <body> <button id="myBtn">Click Me</button> <script type="text/javascript" src="/jquery_9151577.js"></script> <script type="text/javascript" src="/dialog/dialog_ae8c228.js"></script> <script type="text/javascript"> $('#myBtn').click(function() { require.async('dialog/dialog.js', function( dialog ) { dialog.alert('you catch me!'); }); }); </script> <!--[SCRIPTS PLACEHOLDER]--> </body> </html>
dialog.js 不会在页面以 script src 的形式输出。而是变成了资源注冊。这样,当页面点击button触发 require.async 运行的时候,async 函数才会查表找到资源的 url 并载入它,载入完成后触发回调函数。
到眼下为止,咱们又以架构的形式实现了一项优化原则(拆分初始化负载),回想咱们的优化分类表,现在仅有两项没能作到了:
优化方向 | 优化手段 |
---|---|
缓存利用 | 使 Ajax 可缓存 |
页面结构 | 尽早刷新文档的输出 |
剩下的两项优化原则要作到并不easy。真正可缓存的 Ajax 在现实开发中比較少见。而尽早刷新文档的输出的状况 facebook 在 2010 年的 velocity 上提到过。就是 BigPipe 技术。
当时 facebook 团队还讲到了 Quickling 和 PageCache 两项技术,当中的 PageCache 算是比較完全的实现 Ajax 可缓存的优化原则了。fis 团队也曾与某产品线合做基于静态资源表、模板组件化等技术实现了页面的 PipeLine 输出、以及 Quickling 和 PageCache 功能。但终于效果没有达到理想的性能优化预期,所以这两个方向尚在探索中,相信在不久的未来会有新的突破。
总结
事实上在前端开发project管理领域还有很是多细节值得探索和挖掘,提高前端团队生产力水平并不是一句空话,它需要咱们能对前端开发及代码执行有更深入的认识。对性能优化原则有更仔细的分析与研究。fis 团队一直致力于从架构而非经验的角度实现性能优化原则;解决前端project师开发、调试、部署中遇到的project问题。提供组件化框架,提升代码复用率;提供开发工具集,提高project师的开发效率。
在前端工业化开发的所有环节均有可节省的人力成本,这些成本很是可观。相信现在很是多大型互联网公司也都有了这种共识。本文仅仅是将这个领域中很是小的一部分知识的展开讨论,抛砖引玉。但愿能为业界相关领域的工做者提供一些不同的思路。欢迎关注fis项目,对本文有不论什么意见或建议都可以在 fis 开源项目中进行反馈和讨论。