PJCHENder 未整理筆記

[Unit3] Webpack: 拆檔和 cache 處理

2017-09-26

[Unit3] Webpack: 拆檔和 cache 處理

@([Udemy] Webpack 2: The Complete Developer’s Guide)

先把專案 clone 下來,專案連結。接著執行 npm install

初始化設定 webpack.config

我們在 webpack.config.js 中定義我們要使用的 rules,在 babel-loader 中我們還使用到 exclude ,如果我們知道在 /node_module/ 的檔案裡面都是 ES5 以前的話,我們就可以用 exlcude 把這個資料夾裡面的檔案去除,不去做從 ES6 轉成 ES5 編譯的動作。

1
2
3
4
5
6
7
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_module/
}
]

另外要記得加入 .babelrc 的檔案。

接著執行 npm run build ,會發現檔案達 3 MB 以上,非常大,因此接下來我們要想辦法讓它最佳化。

img

將檔案拆分

STEP 1: 拆檔概念

因為我們自己的程式碼在開發的過程中常常會更新,相反地使用到的第三方套件則比較少會更新。因此我們可以把所有的 js 檔案拆成的第三方套件的 vendor.js 和我們核心的程式碼 bundle.js,如此使用者在下載的時候,大部分都只需要重新載 bundle.js 而不需要重新載 vendor.js

img

除了使用 System.import('module') 的方法來做 codesplitting 外,我們也可以在 webpack.config.js 中去做 codesplitting 的動作。通常如果是要在自己的 app 中載入自己的其他 JS 時,會使用 System.import

在 webpack.config.js 中,我們把 entry 拆成兩個不同的來源,第一個是我們核心程式(index.js),另一個則是把所有 package.json 中所使用到的相依套件放到陣列中。在 output 中,我們把原本 filename 的值改成 [name].js,這樣它就會動態命名,它會以 entry 中的 key 來替換調 [name]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//  webpack.config.js
var webpack = require('webpack')
var path = require('path')

// 放入 package.json 中所有 dependency 的 module
const VENDOR_LIBS = ['faker', 'lodash', 'react', 'react-dom', 'react-input-range',
'react-redux', 'react-router', 'redux', 'redux-form', 'redux-thunk']

module.exports = {
// entry: './src/index.js',
entry: {
bundle: './src/index.js', // 我們要根據 index.js 產一支 bundle.js
vendor: VENDOR_LIBS // 產生 vendor.js
},
output: {
path: path.join(__dirname, 'dist'),
// filename: 'bundle.js'
filename: '[name].js' // [name] 會被 entry 中的 key 換調
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_module/
},
{
use: ['style-loader', 'css-loader'],
test: /\.css$/
}
]
}
}

接著我們可以在執行 npm run build ,結果會發現多了一支 vendor.js 檔沒錯,但是原本的 bundle.js 檔案並沒有變小:

img

這是因為我們在 index.js 中用仍然有用 import 去匯入這些 module,因此我們等於是在 bundle.js 中有載入一次這些 module,而在 vendor.js 中又載入一次這些 module

img

STEP 2: 透過 CommonsChunkPlugin 避免載入重複的檔案

為了解決這樣的問題,我們要使用另一個 plugin,webpack.optimize.CommonsChunkPlugin

1
2
3
4
5
6
plugins: [
// 避免 vendor 內的程式碼同時出現在 vendor.js 和 bundle.js 中
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
})
]

重新執行 npm run install 之後,可以看到我們的 bundle.js 只剩下幾百 kb

img

STEP 3: 透過 HtmlWebpackPlugin 將 JS 檔注入 html 中

這時候因為我們沒有把 vendor.js 載入網頁中,因此這時候開啟網頁時會出現錯誤。

一種解決方式是把 vendor.js 這支檔案用 <script></script> 的方式載入。

另一種方式是使用另一個 plugin 來自動幫我們把 dist 中的 js 檔注入 html 當中:

1
npm install --save-dev html-webpack-plugin

設定 webpack.config.js 檔

1
2
3
4
5
6
7
8
9
10
11
plugins: [
// 避免 vendor 內的程式碼同時出現在 vendor.js 和 bundle.js 中
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),

// 幫我們把 dist 中的 js 檔注入 html 當中
new HtmlWebpackPlugin({
template: './src/index.html' // 以 index.html 這支檔案當作模版注入 html
})
]

接著一樣執行 webpack 後,會發現在我們的 dist 資料夾中多了一支 index.html,而這支 html 檔自動幫我們代入了 dist 中的其他 js 檔。

處理 cache

一般來說我們的瀏覽器會用檔名來判斷是否需要更新檔案,如果檔案名稱一樣,它就不會重新下載。

設定 webpack.config.js 檔

在 webpack 中,我們使用 [chunkhash] 這個變數,一旦檔案有變更時,這個 hash 就會改變。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
output: {
path: path.join(__dirname, 'dist'),
// 使用 [chunkhash] 的方式,來讓瀏覽器知道是否需要重新載入檔案
filename: '[name].[chunkhash].js' // [name] 會被 entry 中的 key 替換調
},

plugins: [
// 避免 vendor 內的程式碼同時出現在 vendor.js 和 bundle.js 中
new webpack.optimize.CommonsChunkPlugin({
// webpack 不知道 vendor 是否有更新,所以我們多加一個 'manifest'
name: ['vendor', 'manifest']
}),

// 幫我們把 dist 中的 js 檔注入 html 當中
new HtmlWebpackPlugin({
template: './src/index.html' // 以 index.html 這支檔案當作模版注入 html
})
]

這時候當我們檔案有更新的時候,這個 hash 就會改變,同時這個新的檔名會透過上面的 HtmlWebpackPlugin 直接注入 html 當中。

刪除 dist 中重複的檔案

最後每當我們更新檔案的時候,dist 中就會多一支檔案,這其實讓人覺得有點煩。因此,我們可已安裝 rimraf 這個 module,幫助我們在每次執行 build 前,先把 dist 裡面的內容清光。

1
npm install --save-dev rimraf

安裝完後我們可以修改 package.json 這個檔案,建立一個 clean 指令,讓它可以刪除 dist 資料夾;接著當我們執行 build 的時候,先以刪除 dist 資料夾,再執行 webpack。

1
2
3
4
"scripts": {
"clean": "rimraf dist", // 透過套件 rimraf 清除 dist 中的檔案
"build": "npm run clean && webpack" // 先執行 clean 再執行 webpack
},

檔案內容

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"name": "upstar_music",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && webpack"
},
"repository": "https://github.com/StephenGrider/WebpackProject",
"author": "",
"license": "ISC",
"dependencies": {
"faker": "^3.1.0",
"lodash": "^4.17.2",
"react": "15.4.1",
"react-dom": "15.4.1",
"react-input-range": "^0.9.2",
"react-redux": "^4.4.6",
"react-router": "^3.0.0",
"redux": "^3.6.0",
"redux-form": "^6.3.2",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-core": "^6.17.0",
"babel-loader": "^6.2.0",
"babel-preset-env": "^1.1.4",
"babel-preset-react": "^6.16.0",
"css-loader": "^0.26.1",
"html-webpack-plugin": "^2.28.0",
"rimraf": "^2.6.1",
"style-loader": "^0.13.1",
"webpack": "2.2.0-rc.0"
}
}

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// webpack.config.js

var webpack = require('webpack')
var path = require('path')
var HtmlWebpackPlugin = require('html-webpack-plugin')

// 放入 package.json 中所有 dependency 的 module
const VENDOR_LIBS = ['faker', 'lodash', 'react', 'react-dom', 'react-input-range',
'react-redux', 'react-router', 'redux', 'redux-form', 'redux-thunk']

module.exports = {
// entry: './src/index.js',
entry: {
bundle: './src/index.js', // 我們要根據 index.js 產一支 bundle.js
vendor: VENDOR_LIBS // 產生 vendor.js
},
output: {
path: path.join(__dirname, 'dist'),
// 使用 [chunkhash] 的方式,來讓瀏覽器知道是否需要重新載入檔案
filename: '[name].[chunkhash].js' // [name] 會被 entry 中的 key 替換調
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_module/
},
{
use: ['style-loader', 'css-loader'],
test: /\.css$/
}
]
},
plugins: [
// 避免 vendor 內的程式碼同時出現在 vendor.js 和 bundle.js 中
new webpack.optimize.CommonsChunkPlugin({
// webpack 不知道 vendor 是否有更新,所以我們多加一個 'manifest'
name: ['vendor', 'manifest']
}),

// 幫我們把 dist 中的 js 檔注入 html 當中
new HtmlWebpackPlugin({
template: './src/index.html' // 以 index.html 這支檔案當作模版注入 html
})
]
}

.babelrc

1
2
3
{
"presets": ["babel-preset-env", "react"]
}

掃描二維條碼,分享此文章