译者水平有限, 如果有错误欢迎指正!
未来你会编写越来越多的 JS 和 CSS , 并且最终将找到一个合适的系统来管理它们.
这个是未雨绸缪专栏, 汇总了一些 web 工程化问题, 你应该在遇到它们之前就思考它们.
自动化处理依赖
在页面中添加静态资源最简单的办法, 就是在页面渲染之前, 在页面顶部一个个的引入它们, 看 Facebook 之前的方式:
- <?PHP
- require_js('js/base.js');
- require_js('js/utils.js');
- require_js('js/ajax.js');
- require_js('js/dialog.js');
- // ...
这样做在早期是可行的, 但从 2007 年开始情况变得不可控了. 因为你需要按正确的顺序逐条手动列出这些静态文件, 导致其他人需要把这一坨文件复制粘贴到各个页面. 这样页面需要加载大量的 JS, 影响了前端性能.
于是我们转向了一个被称作 Haste 的系统, 该系统使用类似 docblock 的头部声明 JS 依赖:
/**
* @provides dialog
* @requires utils Ajax base
*/
我们给每个文件手动添加注释, 虽然理论上可以用静态分析工具代替(我们没有真正这么做是因为我们的 JS 是非结构化的). 这样我们可以通过一次调用请求组件的所有依赖链:
require_static('dialog');
... 而不是复制粘贴那一大坨依赖文件.
按需加载
早期的方法带来的另一个问题是, 所有的静态资源都在页面顶部引入, 而不是在实际需要的时候引用. 这意味着 2 点:
你需要引入所有可能在页面上出现的资源;
如果你要在 2 个及以上的页面添加新内容, 你最好把它放到 base.JS 里.
这样每个页面都会有一坨蠢代码, 例如 CAPTCHA(因为某些工作流涉及未验证的用户, 理论上应该在所有页面上向所有用户展示 CAPTCHA), 以及其他人在 base.JS 里时不时添加的东西.
我们转用了一个系统, JS 和 CSS 标签在页面加载完之后被输出 (它们还是出现在页面顶部, 只是在输出到浏览器之前被预先准备好, 而不是被附加进去, 这里有些复杂, 超出本文讨论范围), 所以 require_static() 可能出现在代码的任何地方. 然后我们移动所有的 require_static()到调用的地方(只有对话框代码会引入其依赖的 CSS 和 JS, 因为并不是每个页面都有对话框), 并且把 base.JS 拆分成一系列更小的文件.
打包
大部分情况下最大的前端性能杀手是大量的 http 请求, 针对这个问题最有力的武器是把单独的 JS 和 CSS 打包成一个大文件, 这样就可以加载一个大的 JS 核心文件而不是加载一堆小文件. 一旦其他基本工作到位, 这是一个相对容易的更改. 我们从手动定义包开始, 最终转向基于生产数据的自动生成.
缓存与服务器内容
用最简单的方法引入静态资源, 比如用 src="/js/base.js" 写下一个原生 JS 标签. 随着站点规模的扩大, 这将带来灾难性的问题, 因为客户端使用的可能是老版本资源. 这会引起一系列微妙的问题(尤其是使用了 CDN 时), 最大的问题是当你 push/deploy 站点时用户正访问你的站点, 客户端不会为已有的缓存资源发起请求, 因此即使你的服务器能正确响应 If-None-Match (ETags) 和 If-Modified-Since (Expires), 当你正将静态资源变更 push 到站点时, 此时的浏览者将见不到这些变化后的静态资源.
解决这个问题最好的方法是给 URI 加版本号, 这样各版本资源文件对应一个独立的 URI, 如:
rsrc/af04d14/JS/base.JS
当你 push 站点时, 用户将访问引用新 URI 的页面, 浏览器会重新加载资源.
但是, 还有一个大问题, 一旦你有一堆前端页面:
当你 push 站点时, 用户可能会发出一个请求, 这个请求由运行新版本代码的服务器处理, 返回了一个包含新 URI 的页面. 然后浏览器发起了对新 URI 的请求, 却被分发到了还未安装新版本代码的服务器上, 服务器返回了旧版本的资源. 这样客户端就有了脏缓存: 新版本的 URI 下缓存了旧版本代码.
有很多巧妙的方法来解决这个问题, Facebook 解决的方法是用数据库而不是硬盘提供静态资源. 当一个 push 开始之前, 新的静态资源被写在数据库里, 这样每个服务器都可以同时处理新旧资源的请求.
这种方式也使一些处理流程变得相对容易(例如删除注释和空格), 只需要把压缩 / 加工的 CSS 和 JS 版本插入数据库即可.
参考实现: Celerity
这里讨论的一些想法在 Phabricator's 的 Celerity 系统中实现, 该系统本质上是 Facebook 使用的 Haste 系统简化版.
来源: https://juejin.im/post/5c88a651f265da2de04af973