如何在項目中使用 webpack
項目中已經配置了很簡單的 babel 和 webpack,直接運行 npm
run start
即可
這時候你會發現這個 bundle.js 居然有這麼大,這肯定是不能接受的,所以接下來章節的主要目的就是將單個文件拆分
爲多個文件,優化項目。
分離代碼
先讓我們考慮下緩存機制。對於代碼中依賴的庫很少會去主動升級版本,但是我們自己的代碼卻每時每刻都在變更,所
以我們可以考慮將依賴的庫和自己的代碼分割開來,這樣用戶在下一次使用應用時就可以儘量避免重複下載沒有變更的
代碼,那麼既然要將依賴代碼提取出來,我們需要變更下入口和出口的部分代碼。
// 這是 packet.json 中 dependencies 下的
const VENOR = ["faker",
"lodash",
"react",
"react-dom",
"react-input-range",
"react-redux",
"redux",
"redux-form",
"redux-thunk"
]
module.exports = {
// 之前我們都是使用了單文件入口
// entry 同時也支持多文件入口,現在我們有兩個入口
// 一個是我們自己的代碼,一個是依賴庫的代碼
entry: {
// bundle 和 vendor 都是自己隨便取名的,會映射到 [name] 中
bundle: './src/index.js',
vendor: VENOR
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
// ...
}
現在我們 build 一下,看看是否有驚喜出現
真的有驚喜。。爲什麼 bundle 文件大小壓根沒變。這是因爲 bundle 中也引入了依賴庫的代碼,剛纔的步驟並沒有抽取
bundle 中引入的代碼,接下來讓我們學習如何將共同的代碼抽取出來。
抽取共同代碼
在這小節我們使用 webpack 自帶的插件 CommonsChunkPlugin
。
module.exports = {
//...
output: {
path: path.join(__dirname, 'dist'),
// 既然我們希望緩存生效,就應該每次在更改代碼以後修改文件名
// [chunkhash]會自動根據文件是否更改而更換哈希
filename: '[name].[chunkhash].js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
// vendor 的意義和之前相同
// manifest文件是將每次打包都會更改的東西單獨提取出來,保證沒有更改的代碼無需重新打包,
這樣可以加快打包速度
names: ['vendor', 'manifest'],
// 配合 manifest 文件使用
minChunks: Infinity
})
]
};
當我們重新 build 以後,會發現 bundle 文件很明顯的減小了體積
但是我們使用哈希來保證緩存的同時會發現每次 build 都會生成不一樣的文件,這時候我們引入另一個插件來幫助我們
刪除不需要的文件。
npm install --save-dev clean-webpack-plugin
然後修改配置文件
module.exports = {
//...
plugins: [
// 只刪除 dist 文件夾下的 bundle 和 manifest 文件
new CleanWebpackPlugin(['dist/bundle.*.js','dist/manifest.*.js'], {
// 打印 log
verbose: true,
// 刪除文件
dry: false
}),
]
};
然後 build 的時候會發現以上文件被刪除了。
因爲我們現在將文件已經打包成三個 JS 了,以後也許會更多,每次新增 JS 文件我們都需要手動在 HTML 中新增標籤,
現在我們可以通過一個插件來自動完成這個功能。
npm install html-webpack-plugin --save-dev
然後修改配置文件
module.exports = {
//...
plugins: [
// 我們這裏將之前的 HTML 文件當做模板
// 注意在之前 HTML 文件中請務必刪除之前引入的 JS 文件
new HtmlWebpackPlugin({
template: 'index.html'
})
]
};
執行 build 操作會發現同時生成了 HTML 文件,並且已經自動引入了 JS 文件
按需加載代碼
在這一小節我們將學習如何按需加載代碼,在這之前的 vendor 入口我發現忘記加入 router 這個庫了,大家可以加入這
個庫並且重新 build 下,會發現 bundle 只有不到 300KB 了。
現在我們的 bundle 文件包含了我們全部的自己代碼。但是當用戶訪問我們的首頁時,其實我們根本無需讓用戶加載除了
首頁以外的代碼,這個優化我們可以通過路由的異步加載來完成。
現在修改 src/router.js
// 注意在最新版的 V4路由版本中,更改了按需加載的方式,如果安裝了 V4版,可以自行前往官網學習
import React from 'react';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import Home from './components/Home';
import ArtistMain from './components/artists/ArtistMain';
const rootRoute = {
component: Home,
path: '/',
indexRoute: { component: ArtistMain },
childRoutes: [
{
path: 'artists/new',
getComponent(location, cb) {
System.import('./components/artists/ArtistCreate')
.then(module => cb(null, module.default))
}
},
{
path: 'artists/:id/edit',
getComponent(location, cb) {
System.import('./components/artists/ArtistEdit')
.then(module => cb(null, module.default))
}
},
{
path: 'artists/:id',
getComponent(location, cb) {
System.import('./components/artists/ArtistDetail')
.then(module => cb(null, module.default))
}
}
]
}
const Routes = () => {
return (
<Router history={hashHistory} routes={rootRoute} />
);
};
export default Routes;
然後執行 build 命令,可以發現我們的 bundle 文件又瘦身了,並且新增了幾個文件
將 HTML 文件在瀏覽器中打開,當點擊路由跳轉時,可以在開發者工具中的 Network 一欄中看到加載了一個 JS 文件。
首頁
點擊右上角 Random Artist 以後
自動刷新
每次更新代碼都需要執行依次 build,並且還要等上一會很麻煩,這一小節介紹如何使用自動刷新的功能。
首先安裝插件
npm i --save-dev webpack-dev-server
然後修改 packet.json 文件
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --open"
},
現在直接執行 npm
run dev
可以發現瀏覽器自動打開了一個空的頁面,並且在命令行中也多了新的輸出
等待編譯完成以後,修改 JS 或者 CSS 文件,可以發現 webpack 自動幫我們完成了編譯,並且只更新了需要更新的代碼
但是每次重新刷新頁面對於 debug 來說很不友好,這時候就需要用到模塊熱替換了。但是因爲項目中使用了 React,
並且 Vue 或者其他框架都有自己的一套 hot-loader,所以這裏就略過了,有興趣的可以自己學習下。
生成生產環境代碼
現在我們可以將之前所學和一些新加的插件整合在一起,build 生產環境代碼。
npm i --save-dev url-loader optimize-css-assets-webpack-plugin file-loader
extract-text-webpack-plugin
修改 webpack 配置
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CleanWebpackPlugin = require('clean-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const VENOR = ["faker",
"lodash",
"react",
"react-dom",
"react-input-range",
"react-redux",
"redux",
"redux-form",
"redux-thunk",
"react-router"
]
module.exports = {
entry: {
bundle: './src/index.js',
vendor: VENOR
},
// 如果想修改 webpack-dev-server 配置,在這個對象裏面修改
devServer: {
port: 8081
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
module: {
rules: [{
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000,
name: 'images/[name].[hash:7].[ext]'
}
}]
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [{
// 這邊其實還可以使用 postcss 先處理下 CSS 代碼
loader: 'css-loader'
}]
})
},
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'manifest'],
minChunks: Infinity
}),
new CleanWebpackPlugin(['dist/*.js'], {
verbose: true,
dry: false
}),
new HtmlWebpackPlugin({
template: 'index.html'
}),
// 生成全局變量
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("process.env.NODE_ENV")
}),
// 分離 CSS 代碼
new ExtractTextPlugin("css/[name].[contenthash].css"),
// 壓縮提取出的 CSS,並解決ExtractTextPlugin分離出的 JS 重複問題
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// 壓縮 JS 代碼
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
};
修改 packet.json 文件
"scripts": {
"build": "NODE_ENV=production webpack -p",
"dev": "webpack-dev-server --open"
}
執行 npm
run build
可以看到我們在經歷了這麼多步以後,將 bundle 縮小到了只有 27.1KB,像 vendor 這種常用的庫我們一般可以使用
CDN 的方式外鏈進來。
補充
webpack 配置上有些實用的小點在上文沒有提到,統一在這裏提一下。
module.exports = {
resolve: {
// 文件擴展名,寫明以後就不需要每個文件寫後綴
extensions: ['.js', '.css', '.json'],
// 路徑別名,比如這裏可以使用 css 指向 static/css 路徑
alias: {
'@': resolve('src'),
'css': resolve('static/css')
}
},
// 生成 source-map,用於打斷點,這裏有好幾個選項
devtool: '#cheap-module-eval-source-map',
}