webpack給前端開發帶來了毋庸置疑的改變,它把JS,圖片,css都作為模組處理,同時具有開發便捷,自動化,相容AMD寫法等等諸多無須贅述的優點,更令人稱道的是其外掛社群非常強大,對於不同的業務需求和技術需求社群都有大量外掛可供使用。
凡事都具有兩面性,許多人說:前端開發再也不能只需新建HTML檔案和JS檔案就可以開始寫程式碼了。webpack帶來了更高階更規範的前端開發模式,由於其本身也在不斷完善中,從1到2再到釋出不久的webpack3,頻繁的修改給新手帶來了許多困惑。而且網路上各種教程魚目混雜,經常出現別人的教程程式碼copy下來在自己的環境卻跑不通的蛋疼問題。就拿devtool
配置項來說,官方文件提供了多達7種的配置方法,連react核心團隊成員Pete Hunt都在twitter上調侃:我分不清webpack的許多配置之間的區別。所以今天我們拋開那些琳琅滿目的外掛和令人煩躁的配置項,筆者和大家一起5分鐘從零搭建一個簡易高效的webpack開發環境。
首先我們明確一下需求:
- 打包除錯
- 提取公共程式碼
- 壓縮
- 熱替換
1.打包除錯
第一步,我們在目標資料夾下安裝webpack(假設已有package.json
)npm i webpack@ -g
cnpm i webpack@ --save-dev
(這裡推薦大家安裝穩定的2.x版本)
專案結構如圖:
我們將編寫的js程式碼和樣式檔案放置在app
資料夾內(正常專案開發需要js
檔案和less
檔案更規範的組織檔案結構,此處僅為演示方便)。
第二步,我們在目標資料夾下新建webpack.config.js
module.exports = {
entry:{
main:__dirname + '/app/main.js',
},
output:{
path:__dirname + '/public',
filename:'[name].[id].js',//此格式寫法後續會提到為什麼
publicPath:'/public/'
}
}複製程式碼
我們已經完成了webpack最基礎的部分:新增了檔案的輸入和輸出。入口是app
資料夾內的main.js
檔案,出口為public
資料夾。接下來我們來處理各種檔案的解析,就是大名鼎鼎的loader
的舞臺了。假設我們使用es6
和less
開發,那麼我們需要:npm i babel-loader babel-core babel-preset-es2015 babel-preset-stage-0 --save-dev
npm i less less-loader css-loader style-loader --save-dev
接下來我們只需要在modules
欄位下把這些loader
加進去:
module.exports = {
devtool:'cheap-module-eval-source-map',//多種選擇,選擇最適合自己的
entry:{
main:__dirname + '/app/main.js',
},
output:{
path:__dirname + '/public',
filename:'[name].[id].js',
publicPath:'/public/'
},
module:{
loaders:[
{
test:/\.js$/, //解析檔案型別
exclude:/node_modules/, //排除node_modules檔案
loader:'babel-loader', //使用哪種loader解析
query:{
presets:['es2015','stage-0']//loader的配置項,解析es6
}
},
{
test:/\.less$/,
exclude:/node_modules/,
loader:'style-loader!css-loader!less-loader'//順序為從右向左
}
]
},
}複製程式碼
大功告成!
如果你在全域性安裝有webpack的話,可以在終端敲入webpack並回車,幾秒鐘後,main.js
檔案已經在public
打包出來了!
之後我們在index.html
中引入main.0.js
檔案,再開啟index.html
就可以看到效果了。
以上步驟,我們已經實現了檔案的打包除錯,但是現在有個問題擺在我們面前:第三方庫程式碼和業務程式碼打包到了同一個檔案main.0.js
內,每次更新程式碼都要更新整個檔案。那麼接下來我們對程式碼進行拆分。
2.提取公共程式碼
引入CommonsChunkPlugin
外掛,在webpack.config.js
新增如下內容:
module.exports = {
devtool:'cheap-module-eval-source-map',
entry:{
main:__dirname + '/app/main.js',
vendor:'moment'
},
output:{
path:__dirname + '/public',
filename:'[name].[id].js',
publicPath:'/public/'
},
module:{
loaders:[
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
query:{
presets:['es2015','stage-0']
}
},
{
test:/\.less$/,
exclude:/node_modules/,
loader:'style-loader!css-loader!less-loader'
}
]
},
plugins:[
new webpack.optimize.CommonsChunkPlugin({
names:['vendor','manifest']
})
]
}複製程式碼
我們看到向外掛的建構函式傳入了兩個引數vendor
和manifest
,以及我們在entry
也加入了新的入口moment
。moment
是常用的時間處理的第三方庫,我們可以通過npm i moment --save-dev
進行安裝。而entry
處的vendor
將成為output
欄位filename
中[name]
的值,也就是說將打包出main.x.js
和vendor.x.js
兩個檔案,main.x.js
檔案將儲存我們的業務程式碼,vendor.x.js
將儲存moment
的程式碼,這樣我們將公共程式碼和業務程式碼進行了初步分離。
在新新增的CommonmChunkPlugin
外掛中,我們新增了manifest
值,這是為什麼呢?如果你不新增這個值,你在打包時會發現,main.x.js
有更新,vendor.x.js
還是有更新,並未真正實現"分離"。官方文件對此的解釋是:
The issue here is that on every build, webpack generates some webpack runtime code, which helps webpack do it’s job. When there is a single bundle, the runtime code resides in it. But when multiple bundles are generated, the runtime code is extracted into the common module, here the vendor file.
大致的意思就是說,webpack每次編譯時執行的程式碼會影響到hash
值的變化,當只有一個打包檔案時這部分程式碼會塞進去,當有多個打包檔案時,這部分程式碼會進入公共的vendor
。所以解決辦法是使用manifest
欄位把這部分程式碼從vendor
中作為一個公共模組抽出來,從而不會影響vendor
。
將以上的配置寫入webpack.config.js
,執行webpack命令,我們發現業務程式碼和公共庫程式碼成功分離,改寫main.1.js
檔案的內容,再次打包,發現vendor
檔案並沒有變化,成功!
當我們再進行打包時,發現又會多出了新的main.x.js
等檔案,打包三次就會出現三個main.x.js
檔案,此時該怎麼辦呢?我們可以使用clean-webpack-plugin
外掛:
npm i clean-webpack-plugin --save-dev
然後在webpack.config.js
中引入:
var CleanWebpackPlugin = require('clean-webpack-plugin');
new CleanWebpackPlugin(
['public/main.*.js','public/manifest.*.js'],//要刪除的檔案目錄匹配
{
root:__dirname,
verbose:true,
dry:false
}
),複製程式碼
這樣我們每次在打包新的程式碼時,舊檔案就會刪除,不會再出現同一份檔案存在多份的情況。
3.壓縮
在webpack中,圖片,css,js等等其他資源皆可壓縮,本文僅以壓縮js為例。
安裝外掛:npm i uglifyjs-webpack-plugin --save-dev
在webpack.config.js
中引入:
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
new UglifyJsPlugin({
beautify:true,
exclude:['/node_modules/'],
compress:{
warnings:false
},
output:{
comments:false
}
})複製程式碼
我們指定了壓縮的方法,排除了不需要壓縮的node_modules
部分,同時我們去除了comments
部分(comments
為@license等註釋,是可觀的壓縮空間)。再次在終端輸入打包命令,可見js打包後的體積有令人滿意的減小。
4.熱替換
webpack總是繞不開熱替換的話題。熱替換的功能配置和原理是一大話題,三天三夜也說不完,也並非本文重點,本文只提供簡易高效的配置方法。
熱替換存在兩種使用方式,cli
和node
。cli
方式無需新增新的熱替換外掛,且無需在入口處新增webpack-dev-server
等入口,故本文采用cli
使用方式。
在webpack.config.js
中新增devServer
欄位,加入如下程式碼:
devServer:{
inline:true,
hot:true
},複製程式碼
儲存後執行webpack-dev-server --inline --hot --progress
,再修改下main.less
檔案的樣式,會發現瀏覽器並沒有重新整理,但頁面已經發生了變化,我們的熱替換功能也成功加入了!
tips:
在實際專案打包時,可以將filename
欄位的值換為[name].[chunkhash].js
,其中[chunkhash]
為webpack每次打包後給每個模組的標識值,這個值每次打包後都會更換。為什麼在此處我們使用[id]
呢,因為chunkhash
與熱替換存在衝突,終端會有報錯,那麼使用id
可以算作一個解決方案。這就引申出另一話題,我們可以使用兩套webpack配置分別用於生產環境和開發環境,通過webpack指定config來進行打包。例如我們在開發環境使用id
,在生產環境去掉熱替換並使用hash
的方式。而且,一些壓縮外掛也沒必要在開發環境過度使用,兩套配置能讓webpack發揮最大的威力。
另外,chunkhash
和hash
有區別,chunkhash
顧名思義是模組的標識,而hash
是webpack每次編譯的標識值,不同的資源如js和css存在chunkhash
解耦的問題,此處不進行過多討論。
關於熱替換的更多細節和原理,參考文章:www.cnblogs.com/wonyun/p/70…
5.執行
我們知道,每次打包後,都會有新的main.x.js
檔案生成,其hash值每次打包後都會發生變化,難道我們的index.html
檔案需要每次打包後都手動修改main.x.js
的路徑嗎?還好社群提供了html-webpack-plugin
外掛,可以在已有html模板的條件下自動為我們生成帶有最新程式碼的html檔案:
npm i html-webpack-plugin --save-dev
在webpack.config.js
中引入:
var HtmlWebpackPlugin = require('html-webpack-plugin');
new HtmlWebpackPlugin({
title:'demo',
template:'index.html'
}),複製程式碼
在終端執行打包命令,我們看到public
資料夾下生成了新的index.html
檔案:
以後我們再進行除錯時,以本文為例,則需要開啟localhost:8080/public/index.html
,因為每次webpack的HtmlWebpackPlugin
都會把新的js檔案加入到這個html檔案內。在開發全部完成後,我們可以將js路徑寫死,新增到原有的index.html
檔案中。
以下是我們webpack.config.js
全部的配置;
var webpack = require('webpack');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
devtool:'cheap-module-eval-source-map',
entry:{
main:__dirname + '/app/main.js',
vendor:'moment'
},
output:{
path:__dirname + '/public',
filename:'[name].[id].js',
publicPath:'/public/'
},
devServer:{
inline:true,
hot:true
},
module:{
loaders:[
{
test:/\.js$/,
exclude:/node_modules/,
loader:'babel-loader',
query:{
presets:['es2015','stage-0']
}
},
{
test:/\.less$/,
exclude:/node_modules/,
loader:'style-loader!css-loader!less-loader'
}
]
},
plugins:[
new CleanWebpackPlugin(
['public/main.*.js','public/manifest.*.js'],
{
root:__dirname,
verbose:true,
dry:false
}
),
new webpack.optimize.CommonsChunkPlugin({
names:['vendor','manifest']
}),
new HtmlWebpackPlugin({
title:'demo',
template:'index.html'
}),
new UglifyJsPlugin({
beautify:true,
exclude:['/node_modules/'],
compress:{
warnings:false
},
output:{
comments:false
}
})
]
}複製程式碼
整個專案,我們在app
檔案下的main.js
內寫業務程式碼,main.less
寫樣式,在public/index.html
下使用熱替換進行除錯,打包後的壓縮檔案在public
資料夾下,並且對業務程式碼,第三方程式碼進行了清晰地區分。
使用這份webpack配置,我們實現了:
- 工程的打包除錯
- 公共程式碼提取,提高開發效率
- 資源壓縮
- 熱替換
這份配置麻雀雖小,五臟俱全。本文還有許多不完善之處,比如一些外掛的使用方法,原理沒有與大家講清楚,但webpack實在太龐大了,一個外掛的使用方法和原理都可以寫上千字的文章了,學習不可淺嘗輒止,但也不能太鑽牛角尖,與大家共勉~