webpack給前端開發帶來了毋庸置疑的改變,它把JS,圖片,css都做爲模塊處理,同時具備開發便捷,自動化,兼容AMD寫法等等諸多無須贅述的優勢,更使人稱道的是其插件社區很是強大,對於不一樣的業務需求和技術需求社區都有大量插件可供使用。css
凡事都具備兩面性,許多人說:前端開發不再能只需新建HTML文件和JS文件就能夠開始寫代碼了。webpack帶來了更高級更規範的前端開發模式,因爲其自己也在不斷完善中,從1到2再到發佈不久的webpack3,頻繁的修改給新手帶來了許多困惑。並且網絡上各類教程魚目混雜,常常出現別人的教程代碼copy下來在本身的環境卻跑不通的蛋疼問題。就拿devtool
配置項來講,官方文檔提供了多達7種的配置方法,連react核心團隊成員Pete Hunt都在twitter上調侃:我分不清webpack的許多配置之間的區別。因此今天咱們拋開那些琳琅滿目的插件和使人煩躁的配置項,筆者和你們一塊兒5分鐘從零搭建一個簡易高效的webpack開發環境。html
首先咱們明確一下需求:前端
第一步,咱們在目標文件夾下安裝webpack(假設已有package.json
)npm i webpack@ -g
cnpm i webpack@ --save-dev
(這裏推薦你們安裝穩定的2.x版本)node
項目結構如圖:react
app
文件夾內(正常項目開發須要
js
文件和
less
文件更規範的組織文件結構,此處僅爲演示方便)。
第二步,咱們在目標文件夾下新建webpack.config.js
webpack
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
es6
npm i less less-loader css-loader style-loader --save-dev
web
接下來咱們只須要在modules
字段下把這些loader
加進去:npm
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'//順序爲從右向左
}
]
},
}複製代碼
大功告成!json
若是你在全局安裝有webpack的話,能夠在終端敲入webpack並回車,幾秒鐘後,main.js
文件已經在public
打包出來了!
以後咱們在index.html
中引入main.0.js
文件,再打開index.html
就能夠看到效果了。
以上步驟,咱們已經實現了文件的打包調試,可是如今有個問題擺在咱們面前:第三方庫代碼和業務代碼打包到了同一個文件main.0.js
內,每次更新代碼都要更新整個文件。那麼接下來咱們對代碼進行拆分。
引入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
}
),複製代碼
這樣咱們每次在打包新的代碼時,舊文件就會刪除,不會再出現同一份文件存在多份的狀況。
在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打包後的體積有使人滿意的減少。
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
文件的樣式,會發現瀏覽器並無刷新,但頁面已經發生了變化,咱們的熱替換功能也成功加入了!
在實際項目打包時,能夠將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…
咱們知道,每次打包後,都會有新的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實在太龐大了,一個插件的使用方法和原理均可以寫上千字的文章了,學習不可淺嘗輒止,但也不能太鑽牛角尖,與你們共勉~