基於Redux架構的單頁應用開發總結(一)
寫在前面
“大學四年,細細回味。大一,面帶稚嫩的面龐,一腔傻傻的熱情。可愛帥氣的小涵妹,帶我認識時尚,好基友終生難忘。大二,踏上程式設計師之旅,曦點無緣,Smart不棄,恩師點撥學長提攜,滴水之恩湧泉報。大三,有了自己的團隊,樂雁老朱,程式設計遊戲我們都在一起。專案經驗,點點積累,低下小中探尋的是學以致用的真理。大四,杭州漂泊的一年,八愛到貝貝,小公司磨練,大公司學習,前端工程師之路,勇往直行!
振哥、超哥,帶我實踐和探索,彰顯、健芬,讓我開眼和提升自己。你們是我的良師益友,感謝每一段的指點。現在,新的團隊,可靠的後背,我們不畏懼任何艱辛,未來如何,樂意笑迎!”
好久沒更新部落格了,因為這段時間一直被專案進度和畢業的事情壓的透不過氣,公司學校之間來回奔波了許久。好在,順利完成了畢業任務,公司的專案最後也按時交付,總算可以緩下來寫寫文章了。接下來我會分幾篇把近期開發的基於Redux的單頁應用來一次技術剖析。
系統架構介紹
本專案開發基於 React
+ Redux
+ React-Route
框架,利用 webpack
進行模組化構建,前端編寫語言是 JavaScript ES6,利用 babel
進行轉換。
|--- project
|--- build // 專案打包編譯目錄
|--- src // 專案開發的原始碼
|--- actions // redux的動作
|--- components // redux的元件
|--- containers // redux的容器
|--- images // 靜態圖片
|--- mixins // 通用的函式庫
|--- reducers // redux的store操作
|--- configureStore.js // redux的store對映
|--- index.js // 頁面入口
| --- routes.js // 路由配置
|--- index.html // 入口檔案
|--- .babelrc // babel配置
|--- main.js // webkit打包的殼子
|--- package.json // 包資訊
|--- webpack.config.js // webpack配置檔案
|--- readme.md
"dependencies": {
"babel-polyfill": "^6.7.4",
"base-64": "^0.1.0",
"immutable": "^3.7.6",
"isomorphic-fetch": "^2.2.1",
"moment": "^2.13.0",
"normalizr": "^2.0.1",
"react": "^0.14.8",
"react-datetimepicker": "^2.0.0",
"react-dom": "^0.14.8",
"react-redux": "^4.4.1",
"react-redux-spinner": "^0.4.0",
"react-router": "^2.0.1",
"react-router-redux": "^4.0.1",
"redux": "^3.3.1",
"redux-immutablejs": "0.0.8",
"redux-logger": "^2.6.1",
"redux-thunk": "^2.0.1"
},
"devDependencies": {
"babel-core": "^6.7.5",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-1": "^6.5.0",
"css-loader": "^0.23.1",
"file-loader": "^0.8.5",
"img-loader": "^1.2.2",
"less": "^2.6.1",
"less-loader": "^2.2.3",
"mocha": "^2.4.5",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"webpack": "^1.12.14"
}
webpack配置
也算是實際體驗了一把webpack,不得不說,論React
最佳搭檔,非此貨莫屬!真的很強大,很好用。
var webpack = require('webpack'); // 引入webpack模組
var path = require('path'); // 引入node的path模組
var nodeModulesPath = path.join(__dirname, '/node_modules'); // 設定node_modules目錄
module.exports = {
// 配置入口(此處定義了雙入口)
entry: {
bundle: './src/index',
vendor: ['react', 'react-dom', 'redux']
},
// 配置輸出目錄
output: {
path: path.join(__dirname, '/build'),
publicPath: "/assets/",
filename: 'bundle.js'
},
module: {
noParse: [
path.join(nodeModulesPath, '/react/dist/react.min'),
path.join(nodeModulesPath, '/react-dom/dist/react-dom.min'),
path.join(nodeModulesPath, '/redux/dist/redux.min'),
],
// 載入器
loaders: [
// less載入器
{ test: /\.less$/, loader: 'style!css!less' },
// babel載入器
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
// 圖片載入器(圖片超過8k會自動轉base64格式)
{ test: /\.(gif|jpg|png)$/, loader: "url?limit=8192&name=images/[name].[hash].[ext]"},
// 載入icon字型檔案
{ test: /\.(woff|svg|eot|ttf)$/, loader: 'url?limit=50000&name=fonts/[name].[hash].[ext]'}
]
},
// 外部依賴(不會打包到bundle.js裡)
externals: {
'citys': 'Citys'
},
// 外掛
plugins: [
//new webpack.HotModuleReplacementPlugin(), // 版本上線時開啟
new webpack.DefinePlugin({
// 定義生產環境
"process.env": {
NODE_ENV: JSON.stringify("production")
}
}),
//new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), // 版本上線時開啟
// 公共部分會被抽離到vendor.js裡
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'),
// 比對id的使用頻率和分佈來得出最短的id分配給使用頻率高的模組
new webpack.optimize.OccurenceOrderPlugin(),
// 允許錯誤不打斷程式
new webpack.NoErrorsPlugin()
],
};
延伸-Webpack效能優化
最小化
為了瘦身你的js(還有你的css,如果你用到css-loader的話)webpack支援一個簡單的配置項:
new webpack.optimize.UglifyJsPlugin()
這是一種簡單而有效的方法來優化你的webapp。而webpack還提供了modules 和 chunks ids 來區分他們倆。利用下面的配置項,webpack就能夠比對id的使用頻率和分佈來得出最短的id分配給使用頻率高的模組。
new webpack.optimize.OccurenceOrderPlugin()
入口檔案對於檔案大小有較高的優先順序(入口檔案壓縮優化率儘量的好)
去重
如果你使用了一些有著很酷的依賴樹的庫,那麼它可能存在一些檔案是重複的。webpack可以找到這些檔案並去重。這保證了重複的程式碼不被大包到bundle檔案裡面去,取而代之的是執行時請求一個封裝的函式。不會影響語義
new webpack.optimize.DedupePlugin()
這個功能可能會增加入口模組的一些花銷
對於chunks的優化
當coding的時候,你可能已經添加了許多分割點來按需載入。但編譯完了之後你發現有太多細小的模組造成了很大的HTTP損耗。幸運的是Webpack可以處理這個問題,你可以做下面兩件事情來合併一些請求:
- Limit the maximum chunk count with
new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15})
- Limit the minimum chunk size with
new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000})
Webpack通過合併來管理這些非同步載入的模組(合併更多的時候發生在當前這個chunk有複用的地方)。檔案只要在入口頁面載入的時候沒有被引入,那麼就不會被合併到chunk裡面去。
單頁
Webpack 是為單頁應用量身定做的 你可以把app拆成很多chunk,這些chunk由路由來載入。入口模組僅僅包含路由和一些庫,沒有別的內容。這麼做在使用者通過導航瀏覽表現很好,但是初始化頁面載入的時候你需要2個網路請求:一個是請求路由,一個是載入當前內容。
如果你利用HTML5的HistoryAPI 來讓URL影響當前內容頁的話。你的伺服器可以知道那個內容頁面將被客戶端請求。為了節約請求數,服務端可以把要請求的內容模組放到響應頭裡面:以script標籤的形式來新增,瀏覽器將並行的載入這倆請求。
<script src="entry-chunk.js" type="text/javascript" charset="utf-8"></script>
<script src="3.chunk.js" type="text/javascript" charset="utf-8"></script>
你可以從build stas裡面提取出chunk的filename (stats-webpack-plugin )
多頁
當編譯一個多頁面的app時,你想要在頁面之間共享一些程式碼。這在webpack看來很簡單的:只需要和多個入口檔案一起編譯就好
webpack p1=./page1 p2=./page2 p3=./page3 [name].entry-chunk.js
module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
p3: "./page3"
},
output: {
filename: "[name].entry.chunk.js"
}
}
由上面可以產出多個入口檔案
p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js
但是可以增加一個chunk來共享她們中的一些程式碼。 如果你的chunks有一些公用的modules,那我推薦一個很酷的外掛CommonsChunkPlugin,它能辨別共用模組並把他們放倒一個檔案裡面去。你需要在你的頁面裡新增兩個script標籤來分別引入入口檔案和共用模組檔案。
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
p3: "./page3"
},
output: {
filename: "[name].entry.chunk.js"
},
plugins: [
new CommonsChunkPlugin("commons.chunk.js")
]
}
由上面可以產出入口檔案
p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js
和共用檔案
commons.chunk.js
在頁面中要首先載入 commons.chunk.js 在載入xx.entry.chunk.js 你可以出實話很多個commons chunks ,通過選擇不同的入口檔案。並且你可以堆疊使用這些commons chunks。
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
p3: "./page3",
ap1: "./admin/page1",
ap2: "./admin/page2"
},
output: {
filename: "[name].js"
},
plugins: [
new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]),
new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"])
]
};
輸出結果:
page1.html: commons.js, p1.js
page2.html: commons.js, p2.js
page3.html: p3.js
admin-page1.html: commons.js, admin-commons.js, ap1.js
admin-page2.html: commons.js, admin-commons.js, ap2.js
另外你可以將多個共用檔案打包到一個共用檔案中。
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
entry: {
p1: "./page1",
p2: "./page2",
commons: "./entry-for-the-commons-chunk"
},
plugins: [
new CommonsChunkPlugin("commons", "commons.js")
]
};