配置 Webpack 的方式有兩種:
- 通過一個 JavaScript 文件描述配置,例如使用
webpack.config.js文件里的配置; - 執(zhí)行 Webpack 可執(zhí)行文件時通過命令行參數(shù)傳入,例如
webpack --devtool source-map。
這兩種方式可以相互搭配,例如執(zhí)行 Webpack 時通過命令 webpack --config webpack-dev.config.js 指定配置文件,再去 webpack-dev.config.js 文件里描述部分配置。
按照配置方式來劃分,可分為:
- 只能通過命令行參數(shù)傳入的選項,這種最為少見;
- 只能通過配置文件配置的選項;
- 通過兩種方式都可以配置的選項。
按照配置所影響的功能來劃分,可分為:
- 配置模塊的入口(
entry); - 配置如何輸出最終想要的代碼(
output); - 配置處理模塊的規(guī)則(
module); - 配置尋找模塊的規(guī)則(
resolve); - 配置擴(kuò)展插件(
plugin); - 配置 DevServer;
- 其它零散的配置項;
- 整體配置結(jié)構(gòu)整體地描述各配置項的結(jié)構(gòu);
- 多種配置類型:配置文件不止可以返回一個
Object,還有其他返回形式。
Entry
entry是配置模塊的入口,可抽象成輸入,Webpack執(zhí)行構(gòu)建的第一步將從入口開始搜尋及遞歸解析出所有入口依賴的模塊。
entry配置是必填的,若不填則將導(dǎo)致Webpack報錯退出。
context
Webpack在尋找相對路徑的文件時會以context為根目錄,context默認(rèn)為執(zhí)行啟動Webpack時所在的當(dāng)前工作目錄。 如果想改變context的默認(rèn)配置,可以在配置文件里設(shè)置。
module.exports = {
context: path.resolve(__dirname, 'app')
}
注意,context必須是一個絕對路徑的字符串。除此之外,還可以通過在啟動Webpack時帶上參數(shù)webpack --context來設(shè)置context。
entry的路徑和其依賴的模塊的路徑可以采用相對于context的路徑來描述,context會影響到這些相對路徑所指向的真實文件。
Entry類型
Entry類型可以是以下三種中的一種或者相互組合:
| 類型 | 例子 | 含義 |
|---|---|---|
string |
'./app/entry' |
入口模塊的文件路徑,可以是相對路徑。 |
array |
['./app/entry1', './app/entry2'] |
入口模塊的文件路徑,可以是相對路徑。 |
object |
{ a: './app/entry-a', b: ['./app/entry-b1', './app/entry-b2']} |
配置多個入口,每個入口生成一個Chunk
|
如果是array類型,則搭配output.library配置項使用時,只有數(shù)組里的最后一個入口文件的模塊會被導(dǎo)出。
Chunk名稱
Webpack會為每個生成的Chunk取一個名稱,Chunk的名稱和Entry的配置有關(guān):
- 如果
entry是一個string或array,就只會生成一個Chunk,這時Chunk的名稱是main; - 如果
entry是一個object,就可能會出現(xiàn)多個Chunk,這時Chunk的名稱是object鍵值對里鍵的名稱。
配置動態(tài)Entry
假如項目里有多個頁面需要為每個頁面的入口配置一個Entry,但這些頁面的數(shù)量可能會不斷增長,則這時Entry的配置會受到到其他因素的影響導(dǎo)致不能寫成靜態(tài)的值。其解決方法是把Entry設(shè)置成一個函數(shù)去動態(tài)返回上面所說的配置。
// 同步函數(shù)
entry: () => {
return {
a:'./pages/a',
b:'./pages/b',
}
};
// 異步函數(shù)
entry: () => {
return new Promise((resolve)=>{
resolve({
a:'./pages/a',
b:'./pages/b',
});
});
};
Output
output配置如何輸出最終想要的代碼。output是一個object,里面包含一系列配置項。
filename
output.filename 配置輸出文件的名稱,為string類型。 如果只有一個輸出文件,則可以把它寫成靜態(tài)不變的:
filename: 'bundle.js'
但是在有多個Chunk要輸出時,就需要借助模板和變量了。Webpack會為每個Chunk取一個名稱,可以根據(jù)Chunk的名稱來區(qū)分輸出的文件名:
filename: '[name].js'
代碼里的[name]代表用內(nèi)置的name變量去替換[name],這時你可以把它看作一個字符串模塊函數(shù), 每個要輸出的Chunk都會通過這個函數(shù)去拼接出輸出的文件名稱。
內(nèi)置變量除了name還包括:
| 變量名 | 含義 |
|---|---|
id |
Chunk的唯一標(biāo)識,從0開始 |
name |
Chunk的名稱 |
hash |
Chunk的唯一標(biāo)識的Hash值 |
chunkhash |
Chunk內(nèi)容的Hash值 |
其中hash和chunkhash的長度是可指定的,[hash:8]代表取8位Hash值,默認(rèn)是20位。
chunkFilename
output.chunkFilename配置無入口的Chunk在輸出時的文件名稱。chunkFilename和上面的filename非常類似,但chunkFilename只用于指定在運行過程中生成的Chunk在輸出時的文件名稱。 常見的會在運行時生成Chunk場景有在使用CommonChunkPlugin、使用import('path/to/module')動態(tài)加載等時。 chunkFilename支持和filename一致的內(nèi)置變量。
path
output.path配置輸出文件存放在本地的目錄,必須是string類型的絕對路徑。通常通過Node.js的path模塊去獲取絕對路徑:
path: path.resolve(__dirname, 'dist_[hash]')
publicPath
在復(fù)雜的項目里可能會有一些構(gòu)建出的資源需要異步加載,加載這些異步資源需要對應(yīng)的URL地址。
output.publicPath配置發(fā)布到線上資源的URL前綴,為string類型。 默認(rèn)值是空字符串'',即使用相對路徑。
舉個例子,需要把構(gòu)建出的資源文件上傳到CDN服務(wù)上,以利于加快頁面的打開速度。配置代碼如下:
filename:'[name]_[chunkhash:8].js'
publicPath: 'https://cdn.example.com/assets/'
這時發(fā)布到線上的HTML在引入JS文件時就需要:
<script src='https://cdn.example.com/assets/a_12345678.js'></script>
使用該配置項時要小心,稍有不慎將導(dǎo)致資源加載404錯誤。
output.path和output.publicPath都支持字符串模板,內(nèi)置變量只有一個:hash代表一次編譯操作的Hash值。
crossOriginLoading
Webpack輸出的部分代碼塊可能需要異步加載,而異步加載是通過JSONP方式實現(xiàn)的。 JSONP的原理是動態(tài)地向HTML中插入一個<script src="url"></script>標(biāo)簽去加載異步資源。output.crossOriginLoading則是用于配置這個異步插入的標(biāo)簽的crossorigin值。
script標(biāo)簽的crossorigin屬性可以取以下值:
-
anonymous(默認(rèn)) 在加載此腳本資源時不會帶上用戶的Cookies; -
use-credentials在加載此腳本資源時會帶上用戶的Cookies。
通常用設(shè)置crossorigin來獲取異步加載的腳本執(zhí)行時的詳細(xì)錯誤信息。
libraryTarget和library
當(dāng)用Webpack去構(gòu)建一個可以被其他模塊導(dǎo)入使用的庫時需要用到它們。
-
output.libraryTarget配置以何種方式導(dǎo)出庫。 -
output.library配置導(dǎo)出庫的名稱。
它們通常搭配在一起使用。
output.libraryTarget是字符串的枚舉類型,支持以下配置。
var (默認(rèn))
編寫的庫將通過var被賦值給通過library指定名稱的變量。
假如配置了output.library='LibraryName',則輸出和使用的代碼如下:
// Webpack 輸出的代碼
var LibraryName = lib_code;
// 使用庫的方法
LibraryName.doSomething();
假如output.library為空,則將直接輸出:
lib_code
其中lib_code代指導(dǎo)出庫的代碼內(nèi)容,是有返回值的一個自執(zhí)行函數(shù)。
commonjs
編寫的庫將通過CommonJS規(guī)范導(dǎo)出。
假如配置了output.library='LibraryName',則輸出和使用的代碼如下:
// Webpack 輸出的代碼
exports['LibraryName'] = lib_code;
// 使用庫的方法
require('library-name-in-npm')['LibraryName'].doSomething();
其中library-name-in-npm是指模塊發(fā)布到Npm代碼倉庫時的名稱。
commonjs2
編寫的庫將通過CommonJS2規(guī)范導(dǎo)出,輸出和使用的代碼如下:
// Webpack 輸出的代碼
module.exports = lib_code;
// 使用庫的方法
require('library-name-in-npm').doSomething();
CommonJS2和CommonJS規(guī)范很相似,差別在于CommonJS只能用
exports導(dǎo)出,而CommonJS2在CommonJS的基礎(chǔ)上增加了module.exports的導(dǎo)出方式。
在output.libraryTarget為commonjs2時,配置output.library將沒有意義。
this
編寫的庫將通過this被賦值給通過library指定的名稱,輸出和使用的代碼如下:
// Webpack 輸出的代碼
this['LibraryName'] = lib_code;
// 使用庫的方法
this.LibraryName.doSomething();
window
編寫的庫將通過window被賦值給通過library指定的名稱,即把庫掛載到window上,輸出和使用的代碼如下:
// Webpack 輸出的代碼
window['LibraryName'] = lib_code;
// 使用庫的方法
window.LibraryName.doSomething();
global
編寫的庫將通過global被賦值給通過library指定的名稱,即把庫掛載到global上,輸出和使用的代碼如下:
// Webpack 輸出的代碼
global['LibraryName'] = lib_code;
// 使用庫的方法
global.LibraryName.doSomething();
libraryExport
output.libraryExport配置要導(dǎo)出的模塊中哪些子模塊需要被導(dǎo)出。 它只有在output.libraryTarget被設(shè)置成commonjs或者commonjs2時使用才有意義。
假如要導(dǎo)出的模塊源代碼是:
export const a=1;
export default b=2;
現(xiàn)在你想讓構(gòu)建輸出的代碼只導(dǎo)出其中的a,可以把output.libraryExport設(shè)置成a,那么構(gòu)建輸出的代碼和使用方法將變成如下:
// Webpack 輸出的代碼
module.exports = lib_code['a'];
// 使用庫的方法
require('library-name-in-npm')===1;
以上是output里常用的配置項。
Module
module配置如何處理模塊。
配置Loader
rules配置模塊的讀取和解析規(guī)則,通常用來配置Loader。其類型是一個數(shù)組,數(shù)組里每一項都描述了如何去處理部分文件。 配置一項rules時大致通過以下方式:
- 條件匹配:通過
test、include、exclude三個配置項來命中Loader要應(yīng)用規(guī)則的文件。 - 應(yīng)用規(guī)則:對選中后的文件通過
use配置項來應(yīng)用Loader,可以只應(yīng)用一個Loader或者按照從后往前的順序應(yīng)用一組Loader,同時還可以分別給Loader傳入?yún)?shù)。 - 重置順序:一組
Loader的執(zhí)行順序默認(rèn)是從右到左執(zhí)行,通過enforce選項可以讓其中一個Loader的執(zhí)行順序放到最前或者最后。
module: {
rules: [
{
test: /\.js$/, // 命中 JavaScript 文件
// 用 babel-loader 轉(zhuǎn)換 JavaScript 文件
// ?cacheDirectory 表示傳給 babel-loader 的參數(shù),用于緩存 babel 編譯結(jié)果加快重新編譯速度
use: ['babel-loader?cacheDirectory'],
// 只命中src目錄里的js文件,加快 Webpack 搜索速度
include: path.resolve(__dirname, 'src')
},
{
test: /\.scss$/, // 命中 SCSS 文件
// 使用一組 Loader 去處理 SCSS 文件。
// 處理順序為從后到前,即先交給 sass-loader 處理,再把結(jié)果交給 css-loader 最后再給 style-loader。
use: ['style-loader', 'css-loader', 'sass-loader'],
// 排除 node_modules 目錄下的文件
exclude: path.resolve(__dirname, 'node_modules'),
},
{
// 對非文本文件采用 file-loader 加載
test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,
use: ['file-loader'],
},
]
}
在Loader需要傳入很多參數(shù)時,你還可以通過一個Object來描述,例如在上面的babel-loader配置中有如下代碼:
use: [
{
loader:'babel-loader',
options:{
cacheDirectory:true,
},
// enforce:'post' 的含義是把該 Loader 的執(zhí)行順序放到最后
// enforce 的值還可以是 pre,代表把 Loader 的執(zhí)行順序放到最前面
enforce:'post'
},
// 省略其它 Loader
]
上面的例子中test include exclude這三個命中文件的配置項只傳入了一個字符串或正則,其實它們還都支持?jǐn)?shù)組類型,使用如下:
{
test:[
/\.jsx?$/,
/\.tsx?$/
],
include:[
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'tests'),
],
exclude:[
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, 'bower_modules'),
]
}
數(shù)組里的每項之間是或的關(guān)系,即文件路徑符合數(shù)組中的任何一個條件就會被命中。
noParse
noParse配置項可以讓W(xué)ebpack忽略對部分沒采用模塊化的文件的遞歸解析和處理,這樣做的好處是能提高構(gòu)建性能。 原因是一些庫例如jQuery、ChartJS它們龐大又沒有采用模塊化標(biāo)準(zhǔn),讓W(xué)ebpack去解析這些文件耗時又沒有意義。
noParse是可選配置項,類型需要是RegExp、[RegExp]、function其中一個。
例如想要忽略掉jQuery、ChartJS,可以使用如下代碼:
// 使用正則表達(dá)式
noParse: /jquery|chartjs/
// 使用函數(shù),從 Webpack 3.0.0 開始支持
noParse: (content)=> {
// content 代表一個模塊的文件路徑
// 返回 true or false
return /jquery|chartjs/.test(content);
}
注意被忽略掉的文件里不應(yīng)該包含import、require、define等模塊化語句,不然會導(dǎo)致構(gòu)建出的代碼中包含無法在瀏覽器環(huán)境下執(zhí)行的模塊化語句。
parser
因為Webpack是以模塊化的JavaScript文件為入口,所以內(nèi)置了對模塊化JavaScript的解析功能,支持AMD、CommonJS、SystemJS、ES6。parser屬性可以更細(xì)粒度的配置哪些模塊語法要解析哪些不解析,和noParse配置項的區(qū)別在于parser可以精確到語法層面, 而noParse只能控制哪些文件不被解析。 parser使用如下:
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
parser: {
amd: false, // 禁用 AMD
commonjs: false, // 禁用 CommonJS
system: false, // 禁用 SystemJS
harmony: false, // 禁用 ES6 import/export
requireInclude: false, // 禁用 require.include
requireEnsure: false, // 禁用 require.ensure
requireContext: false, // 禁用 require.context
browserify: false, // 禁用 browserify
requireJs: false, // 禁用 requirejs
}
},
]
}
Resolve
Webpack在啟動后會從配置的入口模塊出發(fā)找出所有依賴的模塊,Resolve配置Webpack如何尋找模塊所對應(yīng)的文件。Webpack內(nèi)置JavaScript模塊化語法解析功能,默認(rèn)會采用模塊化標(biāo)準(zhǔn)里約定好的規(guī)則去尋找,但也可以根據(jù)自己的需要修改默認(rèn)的規(guī)則。
alias
resolve.alias配置項通過別名來把原導(dǎo)入路徑映射成一個新的導(dǎo)入路徑。例如使用以下配置:
// Webpack alias 配置
resolve:{
alias:{
components: './src/components/'
}
}
當(dāng)通過import Button from 'components/button'導(dǎo)入時,實際上被alias等價替換成了import Button from './src/components/button'。
以上alias配置的含義是把導(dǎo)入語句里的components關(guān)鍵字替換成./src/components/。
這樣做可能會命中太多的導(dǎo)入語句,alias還支持$符號來縮小范圍到只命中以關(guān)鍵字結(jié)尾的導(dǎo)入語句:
resolve:{
alias:{
'react$': '/path/to/react.min.js'
}
}
react$只會命中以react結(jié)尾的導(dǎo)入語句,即只會把import 'react'關(guān)鍵字替換成import '/path/to/react.min.js'。
mainFields
有一些第三方模塊會針對不同環(huán)境提供幾分代碼。 例如分別提供采用ES5和ES6的2份代碼,這2份代碼的位置寫在package.json文件里,如下:
{
"jsnext:main": "es/index.js",// 采用 ES6 語法的代碼入口文件
"main": "lib/index.js" // 采用 ES5 語法的代碼入口文件
}
Webpack會根據(jù)mainFields的配置去決定優(yōu)先采用那份代碼,mainFields默認(rèn)如下:
mainFields: ['browser', 'main']
Webpack會按照數(shù)組里的順序去package.json文件里尋找,只會使用找到的第一個。
假如你想優(yōu)先采用ES6的那份代碼,可以這樣配置:
mainFields: ['jsnext:main', 'browser', 'main']
extensions
在導(dǎo)入語句沒帶文件后綴時,Webpack會自動帶上后綴后去嘗試訪問文件是否存在。resolve.extensions用于配置在嘗試過程中用到的后綴列表,默認(rèn)是:
extensions: ['.js', '.json']
也就是說當(dāng)遇到require('./data')這樣的導(dǎo)入語句時,Webpack會先去尋找./data.js文件,如果該文件不存在就去尋找./data.json文件, 如果還是找不到就報錯。
假如你想讓W(xué)ebpack優(yōu)先使用目錄下的TypeScript文件,可以這樣配置:
extensions: ['.ts', '.js', '.json']
modules
resolve.modules配置Webpack去哪些目錄下尋找第三方模塊,默認(rèn)是只會去node_modules目錄下尋找。 有時你的項目里會有一些模塊會大量被其它模塊依賴和導(dǎo)入,由于其它模塊的位置分布不定,針對不同的文件都要去計算被導(dǎo)入模塊文件的相對路徑, 這個路徑有時候會很長,就像這樣import '../../../components/button'這時你可以利用modules配置項優(yōu)化,假如那些被大量導(dǎo)入的模塊都在./src/components目錄下,把modules配置成modules:['./src/components','node_modules']
后,你可以簡單通過import 'button'導(dǎo)入。
descriptionFiles
resolve.descriptionFiles配置描述第三方模塊的文件名稱,也就是package.json文件。默認(rèn)如下:
descriptionFiles: ['package.json']
enforceExtension
resolve.enforceExtension如果配置為true所有導(dǎo)入語句都必須要帶文件后綴, 例如開啟前import './foo'能正常工作,開啟后就必須寫成import './foo.js'。
enforceModuleExtension
enforceModuleExtension和enforceExtension作用類似,但enforceModuleExtension只對node_modules下的模塊生效。enforceModuleExtension通常搭配enforceExtension使用,在enforceExtension:true時,因為安裝的第三方模塊中大多數(shù)導(dǎo)入語句沒帶文件后綴,所以這時通過配置enforceModuleExtension:false來兼容第三方模塊。
Plugin
Plugin用于擴(kuò)展Webpack功能,各種各樣的Plugin幾乎讓W(xué)ebpack可以做任何構(gòu)建相關(guān)的事情。
配置Plugin
Plugin的配置很簡單,plugins配置項接受一個數(shù)組,數(shù)組里每一項都是一個要使用的Plugin的實例,Plugin需要的參數(shù)通過構(gòu)造函數(shù)傳入。
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
module.exports = {
plugins: [
// 所有頁面都會用到的公共代碼提取到 common 代碼塊中
new CommonsChunkPlugin({
name: 'common',
chunks: ['a', 'b']
}),
]
};
使用Plugin的難點在于掌握Plugin本身提供的配置項,而不是如何在Webpack中接入Plugin。
devServer
使用DevServer
DevServer會啟動一個HTTP服務(wù)器用于服務(wù)網(wǎng)頁請求,同時會幫助啟動Webpack ,并接收Webpack發(fā)出的文件更變信號,通過WebSocket協(xié)議自動刷新網(wǎng)頁做到實時預(yù)覽。
集成DevServer首先需要安裝DevServer:
npm i -D webpack-dev-server
安裝成功后執(zhí)行webpack-dev-server命令,DevServer就啟動了,這時你會看到控制臺有一串日志輸出:
Project is running at http://localhost:8080/
webpack output is served from /
這意味著DevServer啟動的HTTP服務(wù)器監(jiān)聽在http://localhost:8080/,DevServer啟動后會一直駐留在后臺保持運行,訪問這個網(wǎng)址你就能獲取項目根目錄下的index.html。用瀏覽器打開這個地址你會發(fā)現(xiàn)頁面空白錯誤原因是./dist/bundle.js加載404了。 同時你會發(fā)現(xiàn)并沒有文件輸出到dist目錄,原因是DevServer會把Webpack構(gòu)建出的文件保存在內(nèi)存中,在要訪問輸出的文件時,必須通過HTTP服務(wù)訪問。由于DevServer不會理會webpack.config.js里配置的output.path屬性,所以要獲取bundle.js的正確URL是http://localhost:8080/bundle.js,對應(yīng)的index.html應(yīng)該修改為:
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<!--導(dǎo)入 DevServer 輸出的 JavaScript 文件-->
<script src="bundle.js"></script>
</body>
</html>
實時預(yù)覽
你可以試試修改main.js,保存后你會發(fā)現(xiàn)瀏覽器會被自動刷新,運行出修改后的效果。
Webpack在啟動時可以開啟監(jiān)聽模式,開啟監(jiān)聽模式后Webpack會監(jiān)聽本地文件系統(tǒng)的變化,發(fā)生變化時重新構(gòu)建出新的結(jié)果。Webpack默認(rèn)是關(guān)閉監(jiān)聽模式的,你可以在啟動Webpack時通過webpack --watch來開啟監(jiān)聽模式。
通過DevServer啟動的Webpack會開啟監(jiān)聽模式,當(dāng)發(fā)生變化時重新執(zhí)行完構(gòu)建后通知DevServer。 DevServer會讓W(xué)ebpack在構(gòu)建出的JavaScript代碼里注入一個代理客戶端用于控制網(wǎng)頁,網(wǎng)頁和DevServer之間通過WebSocket協(xié)議通信, 以方便DevServer主動向客戶端發(fā)送命令。DevServer在收到來自Webpack的文件變化通知時通過注入的客戶端控制網(wǎng)頁刷新。
如果嘗試修改index.html文件并保存,你會發(fā)現(xiàn)這并不會觸發(fā)以上機(jī)制,導(dǎo)致這個問題的原因是Webpack在啟動時會以配置里的entry為入口去遞歸解析出entry所依賴的文件,只有entry本身和依賴的文件才會被Webpack添加到監(jiān)聽列表里。 而index.html文件是脫離了JavaScript模塊化系統(tǒng)的,所以Webpack不知道它的存在。
模塊熱替換
除了通過重新刷新整個網(wǎng)頁來實現(xiàn)實時預(yù)覽,DevServer還有一種被稱作模塊熱替換的刷新技術(shù)。 模塊熱替換能做到在不重新加載整個網(wǎng)頁的情況下,通過將被更新過的模塊替換老的模塊,再重新執(zhí)行一次來實現(xiàn)實時預(yù)覽。 模塊熱替換相對于默認(rèn)的刷新機(jī)制能提供更快的響應(yīng)和更好的開發(fā)體驗。模塊熱替換默認(rèn)是關(guān)閉的,要開啟模塊熱替換,需在啟動DevServer時帶上--hot參數(shù),重啟DevServer后再去更新文件就能體驗到模塊熱替換的神奇了。
支持 Source Map
在瀏覽器中運行的JavaScript代碼都是編譯器輸出的代碼,這些代碼的可讀性很差。如果在開發(fā)過程中遇到一個不知道原因的Bug,則你可能需要通過斷點調(diào)試去找出問題。 在編譯器輸出的代碼上進(jìn)行斷點調(diào)試是一件辛苦和不優(yōu)雅的事情, 調(diào)試工具可以通過Source Map映射代碼,讓你在源代碼上斷點調(diào)試。 Webpack支持生成Source Map,只需在啟動時帶上--devtool source-map參數(shù)。 加上參數(shù)重啟DevServer后刷新頁面,再打開開發(fā)者工具,就可在Sources欄中看到可調(diào)試的源代碼了。
DevServer配置
DevServer提供了一些配置項可以改變DevServer的默認(rèn)行為。 要配置DevServer,除了在配置文件里通過devServer傳入?yún)?shù)外,還可以通過命令行參數(shù)傳入。 注意只有在通過DevServer去啟動Webpack時配置文件里devServer才會生效,因為這些參數(shù)所對應(yīng)的功能都是DevServer提供的,Webpack本身并不認(rèn)識devServer配置項。
hot
devServer.hot 配置是否啟用模塊熱替換功能。DevServer默認(rèn)的行為是在發(fā)現(xiàn)源代碼被更新后會通過自動刷新整個頁面來做到實時預(yù)覽,開啟模塊熱替換功能后將在不刷新整個頁面的情況下通過用新模塊替換老模塊來做到實時預(yù)覽。
inline
DevServer的實時預(yù)覽功能依賴一個注入到頁面里的代理客戶端去接受來自DevServer的命令和負(fù)責(zé)刷新網(wǎng)頁的工作。devServer.inline用于配置是否自動注入這個代理客戶端到將運行在頁面里的Chunk里去,默認(rèn)是會自動注入。 DevServer會根據(jù)你是否開啟inline來調(diào)整它的自動刷新策略:
- 如果開啟
inline,DevServer會在構(gòu)建完變化后的代碼時通過代理客戶端控制網(wǎng)頁刷新。 - 如果關(guān)閉
inline,DevServer將無法直接控制要開發(fā)的網(wǎng)頁。這時它會通過iframe的方式去運行要開發(fā)的網(wǎng)頁,當(dāng)構(gòu)建完變化后的代碼時通過刷新iframe來實現(xiàn)實時預(yù)覽。 但這時你需要去http://localhost:8080/webpack-dev-server/實時預(yù)覽你的網(wǎng)頁了。
如果你想使用DevServer去自動刷新網(wǎng)頁實現(xiàn)實時預(yù)覽,最方便的方法是直接開啟inline。
historyApiFallback
devServer.historyApiFallback用于方便的開發(fā)使用了HTML5 History API的單頁應(yīng)用。 這類單頁應(yīng)用要求服務(wù)器在針對任何命中的路由時都返回一個對應(yīng)的HTML文件,例如在訪問http://localhost/user和http://localhost/home時都返回index.html文件, 瀏覽器端的JavaScript代碼會從URL里解析出當(dāng)前頁面的狀態(tài),顯示出對應(yīng)的界面。
配置historyApiFallback最簡單的做法是:
historyApiFallback: true
這會導(dǎo)致任何請求都會返回index.html文件,這只能用于只有一個HTML文件的應(yīng)用。
如果你的應(yīng)用由多個單頁應(yīng)用組成,這就需要DevServer根據(jù)不同的請求來返回不同的HTML文件,配置如下:
historyApiFallback: {
// 使用正則匹配命中路由
rewrites: [
// /user 開頭的都返回 user.html
{ from: /^\/user/, to: '/user.html' },
{ from: /^\/game/, to: '/game.html' },
// 其它的都返回 index.html
{ from: /./, to: '/index.html' },
]
}
contentBase
devServer.contentBase配置DevServer HTTP服務(wù)器的文件根目錄。 默認(rèn)情況下為當(dāng)前執(zhí)行目錄,通常是項目根目錄,一般情況下你不必設(shè)置它,除非你有額外的文件需要被DevServer服務(wù)。 例如你想把項目根目錄下的public目錄設(shè)置成DevServer服務(wù)器的文件根目錄,你可以這樣配置:
devServer:{
contentBase: path.join(__dirname, 'public')
}
這里需要指出可能會讓你疑惑的地方,DevServer服務(wù)器通過HTTP服務(wù)暴露出的文件分為兩類:
- 暴露本地文件。
- 暴露Webpack構(gòu)建出的結(jié)果,由于構(gòu)建出的結(jié)果交給了
DevServer,所以你在使用了DevServer時在本地找不到構(gòu)建出的文件。
contentBase只能用來配置暴露本地文件的規(guī)則,可以通過contentBase:false來關(guān)閉暴露本地文件。
headers
devServer.headers配置項可以在HTTP響應(yīng)中注入一些 HTTP響應(yīng)頭,使用如下:
devServer:{
headers: {
'X-foo':'bar'
}
}
host
devServer.host配置項用于配置DevServer服務(wù)監(jiān)聽的地址。 例如你想要局域網(wǎng)中的其它設(shè)備訪問你本地的服務(wù),可以在啟動DevServer時帶上--host 0.0.0.0。 host的默認(rèn)值是127.0.0.1即只有本地可以訪問DevServer的HTTP服務(wù)。
port
devServer.port配置項用于配置DevServer服務(wù)監(jiān)聽的端口,默認(rèn)使用8080端口。 如果8080端口已經(jīng)被其它程序占有就使用8081,如果8081還是被占用就使用8082,以此類推。
allowedHosts
devServer.allowedHosts配置一個白名單列表,只有HTTP請求的HOST在列表里才正常返回,使用如下:
allowedHosts: [
// 匹配單個域名
'host.com',
'sub.host.com',
// host2.com 和所有的子域名 *.host2.com 都將匹配
'.host2.com'
]
disableHostCheck
devServer.disableHostCheck配置項用于配置是否關(guān)閉用于DNS重綁定的HTTP請求的HOST檢查。 DevServer默認(rèn)只接受來自本地的請求,關(guān)閉后可以接受來自任何HOST的請求。 它通常用于搭配--host 0.0.0.0使用,因為你想要其它設(shè)備訪問你本地的服務(wù),但訪問時是直接通過IP地址訪問而不是HOST訪問,所以需要關(guān)閉HOST檢查。
https
DevServer默認(rèn)使用HTTP協(xié)議服務(wù),它也能通過HTTPS協(xié)議服務(wù)。 有些情況下你必須使用HTTPS,例如HTTP2和Service Worker就必須運行在HTTPS之上。 要切換成HTTPS服務(wù),最簡單的方式是:
devServer:{
https: true
}
DevServer會自動的為你生成一份HTTPS證書。
如果你想用自己的證書可以這樣配置:
devServer:{
https: {
key: fs.readFileSync('path/to/server.key'),
cert: fs.readFileSync('path/to/server.crt'),
ca: fs.readFileSync('path/to/ca.pem')
}
}
clientLogLevel
devServer.clientLogLevel配置在客戶端的日志等級,這會影響到你在瀏覽器開發(fā)者工具控制臺里看到的日志內(nèi)容。clientLogLevel是枚舉類型,可取如下之一的值none | error | warning | info。 默認(rèn)為info級別,即輸出所有類型的日志,設(shè)置成none可以不輸出任何日志。
compress
devServer.compress配置是否啟用gzip壓縮。boolean為類型,默認(rèn)為false。
open
devServer.open用于在DevServer啟動且第一次構(gòu)建完時自動用你系統(tǒng)上默認(rèn)的瀏覽器去打開要開發(fā)的網(wǎng)頁。同時還提供devServer.openPage配置項用于打開指定URL的網(wǎng)頁。
其它配置項
除了前面介紹到的配置項外,Webpack還提供了一些零散的配置項。
Target
JavaScript的應(yīng)用場景越來越多,從瀏覽器到Node.js,這些運行在不同環(huán)境的JavaScript代碼存在一些差異。target配置項可以讓W(xué)ebpack構(gòu)建出針對不同運行環(huán)境的代碼。target可以是以下之一:
| target值 | 描述 |
|---|---|
web |
針對瀏覽器(默認(rèn)),所有代碼都集中在一個文件里 |
node |
針對 Node.js,使用 require 語句加載Chunk代碼 |
async-node |
針對Node.js,異步加載Chunk代碼 |
webworker |
針對WebWorker |
electron-main |
針對Electron主線程 |
electron-renderer |
針對Electron渲染線程 |
例如當(dāng)你設(shè)置target:'node'時,源代碼中導(dǎo)入Node.js原生模塊的語句require('fs')將會被保留,fs模塊的內(nèi)容不會打包進(jìn)Chunk里。
Devtool
devtool配置Webpack如何生成Source Map,默認(rèn)值是false即不生成Source Map,想為構(gòu)建出的代碼生成Source Map以方便調(diào)試,可以這樣配置:
module.export = {
devtool: 'source-map'
}
Watch和WatchOptions
前面介紹過Webpack的監(jiān)聽模式,它支持監(jiān)聽文件更新,在文件發(fā)生變化時重新編譯。在使用Webpack時監(jiān)聽模式默認(rèn)是關(guān)閉的,想打開需要如下配置:
module.export = {
watch: true
}
在使用DevServer時,監(jiān)聽模式默認(rèn)是開啟的。
除此之外,Webpack 還提供了watchOptions配置項去更靈活的控制監(jiān)聽模式,使用如下:
module.export = {
// 只有在開啟監(jiān)聽模式時,watchOptions 才有意義
// 默認(rèn)為 false,也就是不開啟
watch: true,
// 監(jiān)聽模式運行時的參數(shù)
// 在開啟監(jiān)聽模式時,才有意義
watchOptions: {
// 不監(jiān)聽的文件或文件夾,支持正則匹配
// 默認(rèn)為空
ignored: /node_modules/,
// 監(jiān)聽到變化發(fā)生后會等300ms再去執(zhí)行動作,防止文件更新太快導(dǎo)致重新編譯頻率太高
// 默認(rèn)為 300ms
aggregateTimeout: 300,
// 判斷文件是否發(fā)生變化是通過不停的去詢問系統(tǒng)指定文件有沒有變化實現(xiàn)的
// 默認(rèn)每秒輪詢1000次
poll: 1000
}
}
Externals
Externals用來告訴Webpack要構(gòu)建的代碼中使用了哪些不用被打包的模塊,也就是說這些模版是外部環(huán)境提供的,Webpack在打包時可以忽略它們。
有些JavaScript運行環(huán)境可能內(nèi)置了一些全局變量或者模塊,例如在你的HTML HEAD標(biāo)簽里通過以下代碼:
<script src="path/to/jquery.js"></script>
引入jQuery后,全局變量jQuery就會被注入到網(wǎng)頁的JavaScript運行環(huán)境里。
如果想在使用模塊化的源代碼里導(dǎo)入和使用jQuery,可能需要這樣:
import $ from 'jquery';
$('.my-element');
構(gòu)建后你會發(fā)現(xiàn)輸出的Chunk里包含的jQuery庫的內(nèi)容,這導(dǎo)致jQuery庫出現(xiàn)了2次,浪費加載流量,最好是Chunk里不會包含jQuery庫的內(nèi)容。
``Externals配置項就是為了解決這個問題。 通過externals可以告訴Webpack JavaScript運行環(huán)境已經(jīng)內(nèi)置了那些全局變量,針對這些全局變量不用打包進(jìn)代碼中而是直接使用全局變量。 要解決以上問題,可以這樣配置externals`:
module.export = {
externals: {
// 把導(dǎo)入語句里的 jquery 替換成運行環(huán)境里的全局變量 jQuery
jquery: 'jQuery'
}
}
ResolveLoader
ResolveLoader用來告訴Webpack如何去尋找Loader,因為在使用Loader時是通過其包名稱去引用的, Webpack需要根據(jù)配置的Loader包名去找到Loader的實際代碼,以調(diào)用Loader去處理源文件。
ResolveLoader的默認(rèn)配置如下:
module.exports = {
resolveLoader:{
// 去哪個目錄下尋找 Loader
modules: ['node_modules'],
// 入口文件的后綴
extensions: ['.js', '.json'],
// 指明入口文件位置的字段
mainFields: ['loader', 'main']
}
}
該配置項常用于加載本地的Loader。
整體配置結(jié)構(gòu)
const path = require('path');
module.exports = {
// entry 表示 入口,Webpack 執(zhí)行構(gòu)建的第一步將從 Entry 開始,可抽象成輸入。
// 類型可以是 string | object | array
entry: './app/entry', // 只有1個入口,入口只有1個文件
entry: ['./app/entry1', './app/entry2'], // 只有1個入口,入口有2個文件
entry: { // 有2個入口
a: './app/entry-a',
b: ['./app/entry-b1', './app/entry-b2']
},
// 如何輸出結(jié)果:在 Webpack 經(jīng)過一系列處理后,如何輸出最終想要的代碼。
output: {
// 輸出文件存放的目錄,必須是 string 類型的絕對路徑。
path: path.resolve(__dirname, 'dist'),
// 輸出文件的名稱
filename: 'bundle.js', // 完整的名稱
filename: '[name].js', // 當(dāng)配置了多個 entry 時,通過名稱模版為不同的 entry 生成不同的文件名稱
filename: '[chunkhash].js', // 根據(jù)文件內(nèi)容 hash 值生成文件名稱,用于瀏覽器長時間緩存文件
// 發(fā)布到線上的所有資源的 URL 前綴,string 類型
publicPath: '/assets/', // 放到指定目錄下
publicPath: '', // 放到根目錄下
publicPath: 'https://cdn.example.com/', // 放到 CDN 上去
// 導(dǎo)出庫的名稱,string 類型
// 不填它時,默認(rèn)輸出格式是匿名的立即執(zhí)行函數(shù)
library: 'MyLibrary',
// 導(dǎo)出庫的類型,枚舉類型,默認(rèn)是 var
// 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
libraryTarget: 'umd',
// 是否包含有用的文件路徑信息到生成的代碼里去,boolean 類型
pathinfo: true,
// 附加 Chunk 的文件名稱
chunkFilename: '[id].js',
chunkFilename: '[chunkhash].js',
// JSONP 異步加載資源時的回調(diào)函數(shù)名稱,需要和服務(wù)端搭配使用
jsonpFunction: 'myWebpackJsonp',
// 生成的 Source Map 文件名稱
sourceMapFilename: '[file].map',
// 瀏覽器開發(fā)者工具里顯示的源碼模塊名稱
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]',
// 異步加載跨域的資源時使用的方式
crossOriginLoading: 'use-credentials',
crossOriginLoading: 'anonymous',
crossOriginLoading: false,
},
// 配置模塊相關(guān)
module: {
rules: [ // 配置 Loader
{
test: /\.jsx?$/, // 正則匹配命中要使用 Loader 的文件
include: [ // 只會命中這里面的文件
path.resolve(__dirname, 'app')
],
exclude: [ // 忽略這里面的文件
path.resolve(__dirname, 'app/demo-files')
],
use: [ // 使用那些 Loader,有先后次序,從后往前執(zhí)行
'style-loader', // 直接使用 Loader 的名稱
{
loader: 'css-loader',
options: { // 給 html-loader 傳一些參數(shù)
}
}
]
},
],
noParse: [ // 不用解析和處理的模塊
/special-library\.js$/ // 用正則匹配
],
},
// 配置插件
plugins: [
],
// 配置尋找模塊的規(guī)則
resolve: {
modules: [ // 尋找模塊的根目錄,array 類型,默認(rèn)以 node_modules 為根目錄
'node_modules',
path.resolve(__dirname, 'app')
],
extensions: ['.js', '.json', '.jsx', '.css'], // 模塊的后綴名
alias: { // 模塊別名配置,用于映射模塊
// 把 'module' 映射 'new-module',同樣的 'module/path/file' 也會被映射成 'new-module/path/file'
'module': 'new-module',
// 使用結(jié)尾符號 $ 后,把 'only-module' 映射成 'new-module',
// 但是不像上面的,'module/path/file' 不會被映射成 'new-module/path/file'
'only-module$': 'new-module',
},
alias: [ // alias 還支持使用數(shù)組來更詳細(xì)的配置
{
name: 'module', // 老的模塊
alias: 'new-module', // 新的模塊
// 是否是只映射模塊,如果是 true 只有 'module' 會被映射,如果是 false 'module/inner/path' 也會被映射
onlyModule: true,
}
],
symlinks: true, // 是否跟隨文件軟鏈接去搜尋模塊的路徑
descriptionFiles: ['package.json'], // 模塊的描述文件
mainFields: ['main'], // 模塊的描述文件里的描述入口的文件的字段名稱
enforceExtension: false, // 是否強(qiáng)制導(dǎo)入語句必須要寫明文件后綴
},
// 輸出文件性能檢查配置
performance: {
hints: 'warning', // 有性能問題時輸出警告
hints: 'error', // 有性能問題時輸出錯誤
hints: false, // 關(guān)閉性能檢查
maxAssetSize: 200000, // 最大文件大小 (單位 bytes)
maxEntrypointSize: 400000, // 最大入口文件大小 (單位 bytes)
assetFilter: function(assetFilename) { // 過濾要檢查的文件
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
}
},
devtool: 'source-map', // 配置 source-map 類型
context: __dirname, // Webpack 使用的根目錄,string 類型必須是絕對路徑
// 配置輸出代碼的運行環(huán)境
target: 'web', // 瀏覽器,默認(rèn)
target: 'webworker', // WebWorker
target: 'node', // Node.js,使用 `require` 語句加載 Chunk 代碼
target: 'async-node', // Node.js,異步加載 Chunk 代碼
target: 'node-webkit', // nw.js
target: 'electron-main', // electron, 主線程
target: 'electron-renderer', // electron, 渲染線程
externals: { // 使用來自 JavaScript 運行環(huán)境提供的全局變量
jquery: 'jQuery'
},
stats: { // 控制臺輸出日志控制
assets: true,
colors: true,
errors: true,
errorDetails: true,
hash: true,
},
devServer: { // DevServer 相關(guān)的配置
proxy: { // 代理到后端服務(wù)接口
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'public'), // 配置 DevServer HTTP 服務(wù)器的文件根目錄
compress: true, // 是否開啟 gzip 壓縮
historyApiFallback: true, // 是否開發(fā) HTML5 History API 網(wǎng)頁
hot: true, // 是否開啟模塊熱替換功能
https: false, // 是否開啟 HTTPS 模式
},
profile: true, // 是否捕捉 Webpack 構(gòu)建的性能信息,用于分析什么原因?qū)е聵?gòu)建性能不佳
cache: false, // 是否啟用緩存提升構(gòu)建速度
watch: true, // 是否開始
watchOptions: { // 監(jiān)聽模式選項
// 不監(jiān)聽的文件或文件夾,支持正則匹配。默認(rèn)為空
ignored: /node_modules/,
// 監(jiān)聽到變化發(fā)生后會等300ms再去執(zhí)行動作,防止文件更新太快導(dǎo)致重新編譯頻率太高
// 默認(rèn)為300ms
aggregateTimeout: 300,
// 判斷文件是否發(fā)生變化是不停的去詢問系統(tǒng)指定文件有沒有變化,默認(rèn)每秒問 1000 次
poll: 1000
},
}
多種配置類型
除了通過導(dǎo)出一個Object來描述Webpack所需的配置外,還有其它更靈活的方式,以簡化不同場景的配置。
導(dǎo)出一個Function
在大多數(shù)時候你需要從同一份源代碼中構(gòu)建出多份代碼,例如一份用于開發(fā)時,一份用于發(fā)布到線上。
如果采用導(dǎo)出一個Object來描述Webpack所需的配置的方法,需要寫兩個文件。 一個用于開發(fā)環(huán)境,一個用于線上環(huán)境。再在啟動時通過webpack --config webpack.config.js指定使用哪個配置文件。
采用導(dǎo)出一個Function的方式,能通過JavaScript靈活的控制配置,做到只用寫一個配置文件就能完成以上要求。
導(dǎo)出一個Function的使用方式如下:
const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
module.exports = function (env = {}, argv) {
const plugins = [];
const isProduction = env['production'];
// 在生成環(huán)境才壓縮
if (isProduction) {
plugins.push(
// 壓縮輸出的 JS 代碼
new UglifyJsPlugin()
)
}
return {
plugins: plugins,
// 在生成環(huán)境不輸出 Source Map
devtool: isProduction ? undefined : 'source-map',
};
}
在運行Webpack時,會給這個函數(shù)傳入2個參數(shù),分別是:
-
env:當(dāng)前運行時的Webpack專屬環(huán)境變量,env是一個Object。讀取時直接訪問Object的屬性,設(shè)置它需要在啟動Webpack時帶上參數(shù)。例如啟動命令是webpack --env.production --env.bao=foo時,則env的值是{"production":"true","bao":"foo"}。 -
argv:代表在啟動Webpack時所有通過命令行傳入的參數(shù),例如--config、--env、--devtool,可以通過webpack -h列出所有Webpack支持的命令行參數(shù)。
就以上配置文件而言,在開發(fā)時執(zhí)行命令webpack構(gòu)建出方便調(diào)試的代碼,在需要構(gòu)建出發(fā)布到線上的代碼時執(zhí)行webpack --env.production構(gòu)建出壓縮的代碼。
導(dǎo)出一個返回Promise的函數(shù)
在有些情況下你不能以同步的方式返回一個描述配置的Object,Webpack還支持導(dǎo)出一個返回Promise的函數(shù),使用如下:
module.exports = function(env = {}, argv) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
// ...
})
}, 5000)
})
}
導(dǎo)出多份配置
除了只導(dǎo)出一份配置外,Webpack還支持導(dǎo)出一個數(shù)組,數(shù)組中可以包含每份配置,并且每份配置都會執(zhí)行一遍構(gòu)建。
使用如下:
module.exports = [
// 采用 Object 描述的一份配置
{
// ...
},
// 采用函數(shù)描述的一份配置
function() {
return {
// ...
}
},
// 采用異步函數(shù)描述的一份配置
function() {
return Promise();
}
]
以上配置會導(dǎo)致Webpack針對這三份配置執(zhí)行三次不同的構(gòu)建。
這特別適合于用Webpack構(gòu)建一個要上傳到Npm倉庫的庫,因為庫中可能需要包含多種模塊化格式的代碼,例如CommonJS、UMD。
總結(jié)
通常你可用如下經(jīng)驗去判斷如何配置 Webpack:
- 想讓源文件加入到構(gòu)建流程中去被Webpack控制,配置
entry。 - 想自定義輸出文件的位置和名稱,配置
output。 - 想自定義尋找依賴模塊時的策略,配置
resolve。 - 想自定義解析和轉(zhuǎn)換文件的策略,配置
module,通常是配置module.rules里的Loader。 - 其它的大部分需求可能要通過Plugin去實現(xiàn),配置
plugin。