原文地址: secure.phabricator.com/book/phabfl…php
譯者水平有限,若是有錯誤歡迎指正!前端
將來你會編寫愈來愈多的 JS 和 CSS ,而且最終將找到一個合適的系統來管理它們。web
這個是未雨綢繆專欄,彙總了一些web工程化問題,你應該在遇到它們以前就思考它們。ajax
自動化處理依賴數據庫
在頁面中添加靜態資源最簡單的辦法,就是在頁面渲染以前,在頁面頂部一個個的引入它們,看 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 依賴:bash
/**
* @provides dialog
* @requires utils ajax base
*/
複製代碼
咱們給每一個文件手動添加註釋,雖然理論上能夠用靜態分析工具代替(咱們沒有真正這麼作是由於咱們的 JS 是非結構化的)。這樣咱們能夠經過一次調用請求組件的全部依賴鏈:服務器
require_static('dialog');
複製代碼
...而不是複製粘貼那一大坨依賴文件。前端性能
按需加載
早期的方法帶來的另外一個問題是,全部的靜態資源都在頁面頂部引入,而不是在實際須要的時候引用。這意味着2點:
這樣每一個頁面都會有一坨蠢代碼,例如 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 系統簡化版。