一,優化背景
上一篇關於webpack優化的文章webpack + react 優化:縮小js包體積談到如何縮小webpack打包後的js代碼體積,來減少網絡請求數據量,這次嘗試將第三方庫(React,ajax等)從業務代碼中分離出來,並且將分離出來的第三方庫緩存在LocalStorage中。
該次優化的出發點有下面兩點:
①每次更改業務代碼都會打包成新的
bundle.js
,前端需要拋棄以前的HTTP緩存重新下載249K左右的bundle.js
,即使改變的業務代碼很少。
②嘗試使用LocalStorage(移動端上兼容很好)
第一點是痛點,第二點是作爲前端工程師學習研究用的,希望足夠嚴謹不會給用戶帶來副作用。
優化結果
①webpack分離出業務代碼和第三方庫
分離前:
分離後:bundle.js
爲業務代碼,vendor.js
爲第三方庫代碼(幾乎不更新)
②使用LocalStorage緩存第三方庫到本地
緩存前:
緩存後:(少下了198KB的libs.js,注:這裏的app對應上面的bundle.js,libs對應vendor.js)
下面上相關代碼。
二,優化思路
①將公用庫分出來
這一部給出webpack配置文件即可,注意看註釋部分:
var webpack = require('webpack');
var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
//業務代碼
bundle: path.resolve(__dirname, 'src/main.jsx'),
//第三方庫
vendor: ["react","react-dom","react-router","@fdaciuk/ajax"]
},
//輸出路徑
output: {
path: __dirname + '/server/public',
publicPath: '/',
filename: './[name].js'
},
module: {
loaders:[
{ test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") },
{ test: /\.scss$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader") },
{ test: /\.(jpg|png)$/, loader: "url" },
{ test: /\.js[x]?$/, include: path.resolve(__dirname, 'src'), exclude:/node_modules/, loader: 'babel-loader' },
{ test: /\.woff$/, loader: "url-loader?prefix=font/&limit=5000&mimetype=application/font-woff" },
{ test: /\.ttf$/, loader: "file-loader?prefix=font/" },
{ test: /\.svg$/, loader: "file-loader?prefix=font/" }
]
},
resolve: {
extensions: ['', '.web.js', '.js', '.json', '.jsx'],
modulesDirectories: ['node_modules', path.join(__dirname, '../node_modules')],
},
plugins: [
new webpack.optimize.DedupePlugin(),
new ExtractTextPlugin("bundle.css"),
//將第三方庫打包到vendor.js
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }),
new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV), }, }),
new webpack.optimize.UglifyJsPlugin({ output: { comments: false, }, compress: { warnings: false } }),
]
};
關於這部分深入的理解和學習可以看webpack的示例.
②將公用庫緩存在LoaclStorage中
這一步比較好玩,涉及到的東西也比較多,先上學習參考資料:
- MDN:LocalStorage
- 知乎:靜態資源(JS/CSS)存儲在localStorage有什麼缺點?爲什麼沒有被廣泛應用?
- Dynamic script addition should be ordered?(動態添加的script標籤會按順序執行嗎?)
關於這一部分,有兩個要點:
- LocalStorage緩存
- LocalStorage緩存版本控制
按照慣例:上源碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<title>Examples</title>
<meta name="description" content="">
<meta name="keywords" content="">
<!-- 利用noscript, 使script標籤不加載,不執行 -->
<noscript>
<script src="vendor.js"></script>
<script src="bundle.js"></script>
</noscript>
<script>
//這則匹配獲取兩個script的src
var script_info = document.head.querySelector('noscript').innerText.split(/\s/).join('').match(/src="(.+)".*src="(.+)"/);
var vendor_src = script_info[1], bundle_src = script_info[2];
var vendor_script = document.createElement('script'),
bundle_script = document.createElement('script'); vendor_script.defer = true;
bundle_script.defer = true, bundle_script.src = bundle_src;
if (window.localStorage) {
var cur_version = vendor_src, vendor = null;
if (window.localStorage.getItem("vendor_version") == cur_version && window.localStorage.getItem("vendor")) {
//命中緩存
vendor = window.localStorage.getItem("vendor");
vendor_script.innerText = vendor;
document.head.appendChild(vendor_script);
document.head.appendChild(bundle_script);
} else {
//獲取js代碼
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function(){
if (httpRequest.readyState === XMLHttpRequest.DONE && httpRequest.status === 200 || httpRequest.status === 304) {
vendor = httpRequest.responseText;
vendor_script.innerText = vendor;
document.head.appendChild(vendor_script);
document.head.appendChild(bundle_script);
//以當前vendor的src鏈接作爲key用於版本控制
window.localStorage.setItem("vendor_version", cur_version);
//存儲實際代碼
window.localStorage.setItem("vendor", vendor);
}
//should handle 500
}
httpRequest.open('GET', vendor_src, true);
httpRequest.send(null);
}
} else {
//不支持localstorage時的回退方案,直接將script標籤添加到head
vendor_script.src = vendor_script;
document.head.appendChild(vendor_script);
document.head.appendChild(bundle_script);
}
</script>
<link href="bundle.css" rel="stylesheet">
</head>
<body>
DEMO
</body>
</html>
代碼沒有什麼高深的地方(畢竟水平有限,但是簡單易懂有用的代碼也挺好的),應該看完就知道大概了,可能你會疑惑:
爲什麼用了
<noscript>
標籤?
這裏回答一下,也是今早折騰了一小下的小亮點。
我的web後臺使用了rails,我在後臺給靜態資源文件名加上了指紋作爲緩存的依據,實際如下:
rails 後臺模板:會自動找到加上自問的靜態資源。
生成的HTML:
可見一旦文件內容發生變化,對應的文件名也發生了改變(HTTP緩存失效),因此可以用文件名來做LocalStorage緩存的版本。也正是因爲文件名不確定,因此只能通過去找到相應的script標籤的src才能獲取資源。
那麼問題就來,一開始我把vendor和bundle的script標籤放在控制緩存的腳本後面,但是執行到緩存腳本時,頁面還沒解析到後面兩個script標籤,因此無法獲取script的src。而把vendor和bundle的script標籤放在控制緩存的腳本前面時,在執行緩存腳本前vendor和bundle腳本就已經開始下載了(通過設置type=”text”或其他可以使script不執行),不夠優雅。
解決思路如下:
頁面還沒解析到後面兩個script標籤時能獲取後面的信息嗎?(個人覺得不合常理,如果有方法,請分享,萬分感謝)- 有什麼辦法能讓前面的script不下載? =>
<noscript>
包裹即可,well done.
三,優化結果 && more
優化結果其實前面已經提及了,無非是首次加載web應用後就不需要再加載不常改變的libs.js
(200k左右),值得注意的是,前面緩存前後的比較其實不嚴謹,因爲都把HTTP cache關了,實際場景中設置了HTTP cache,所以其實LocalStorage優化不大,但是節省了用戶流量是實實在在的。
優化完之後還有些疑惑:
- 爲什麼vendor.js體積比所有庫(
["react","react-dom","react-router","@fdaciuk/ajax"]
)加起來大?vendor.js
裏面肯定包含了webpack實現模塊化的一些代碼,但佔這麼大應該不科學,抽空應該好好看看webpack的實現原理。 - LocalStorage是否比HTTP cache更加可靠?
四,一些想法
除了將第三方庫緩存下來之外,是不是也可以將一些萬年不變的圖片緩存下來?例如各個分類都有個大圖,很耗流量,現在的做法是減少尺寸,在清晰度上犧牲了一些。
最近也在看事件循環,zepto.js的event.js和touch.js,好多可以學習深入的地方,興奮之餘也在提醒自己,要踏踏實實攻城拔寨。
人生是一場馬拉松,今日的領先和落後都不值一提,唯一值得關注的是你自身的不斷成長。
自勉。