webpack 中的 plugin 大多都提供額外的能力,它們在 webpack 中的配置都只是把插件實例添加到 `plugins` 字段的數組中。不過由于需要提供不同的功能,不同的插件本身的配置比較多樣化。
社區中有很多 webpack 插件可供使用,而優秀的插件基本上都提供了詳細的使用說明文檔。更多的插件可以在這里查找:[plugins in awesome-webpack](https://github.com/webpack-contrib/awesome-webpack#webpack-plugins)。
下面通過介紹幾個常用的插件來了解插件的使用方法。
## DefinePlugin
DefinePlugin 是 webpack 內置的插件,可以使用 `webpack.DefinePlugin` 直接獲取。
這個插件用于創建一些在編譯時可以配置的全局常量,這些常量的值我們可以在 webpack 的配置中去指定,例如:
```
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true), // const PRODUCTION = true
VERSION: JSON.stringify('5fa3b9'), // const VERSION = '5fa3b9'
BROWSER_SUPPORTS_HTML5: true, // const BROWSER_SUPPORTS_HTML5 = 'true'
TWO: '1+1', // const TWO = 1 + 1,
CONSTANTS: {
APP_VERSION: JSON.stringify('1.1.2') // const CONSTANTS = { APP_VERSION: '1.1.2' }
}
}),
],
}
```
有了上面的配置,就可以在應用代碼文件中,訪問配置好的變量了,如:
```
console.log("Running App version " + VERSION);
if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
```
上面配置的注釋已經簡單說明了這些配置的效果,這里再簡述一下整個配置規則。
* 如果配置的值是字符串,那么整個字符串會被當成代碼片段來執行,其結果作為最終變量的值,如上面的 `"1+1"`,最后的結果是 `2`
* 如果配置的值不是字符串,也不是一個對象字面量,那么該值會被轉為一個字符串,如 `true`,最后的結果是 `'true'`
* 如果配置的是一個對象字面量,那么該對象的所有 key 會以同樣的方式去定義
這樣我們就可以理解為什么要使用 `JSON.stringify()` 了,因為 `JSON.stringify(true)` 的結果是 `'true'`,`JSON.stringify("5fa3b9")` 的結果是 `"5fa3b9"`。
社區中關于 DefinePlugin 使用得最多的方式是定義環境變量,例如 `PRODUCTION = true` 或者 `__DEV__ = true` 等。部分類庫在開發環境時依賴這樣的環境變量來給予開發者更多的開發調試反饋,例如 `react` 等。
> 建議使用 process.env.NODE\_ENV: ... 的方式來定義 process.env.NODE\_ENV,而不是使用 process: { env: { NODE\_ENV: ... } } 的方式,因為這樣會覆蓋掉 process 這個對象,可能會對其他代碼造成影響。
## copy-webpack-plugin
這個插件看名字就知道它有什么作用,沒錯,就是用來復制文件的。
我們一般會把開發的所有源碼和資源文件放在 src/ 目錄下,構建的時候產出一個 build/ 目錄,通常會直接拿 build 中的所有文件來發布。有些文件沒經過 webpack 處理,但是我們希望它們也能出現在 build 目錄下,這時就可以使用 CopyWebpackPlugin 來處理了。
我們來看下如何配置這個插件:
```
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
// ...
plugins: [
new CopyWebpackPlugin([
{ from: 'src/file.txt', to: 'build/file.txt', }, // 顧名思義,from 配置來源,to 配置目標路徑
{ from: 'src/*.ico', to: 'build/*.ico' }, // 配置項可以使用 glob
// 可以配置很多項復制規則
]),
],
}
```
> glob 用法可以參考 [glob-primer](https://github.com/isaacs/node-glob#glob-primer)。
上述的配置日常應用已經足夠,更多的配置內容可以參考 [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin)。
## extract-text-webpack-plugin
extract-text-webpack-plugin 之前的章節有簡單介紹過,我們用它來把依賴的 CSS 分離出來成為單獨的文件。這里再看一下使用 extract-text-webpack-plugin 的配置:
```
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
// 因為這個插件需要干涉模塊轉換的內容,所以需要使用它對應的 loader
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader',
}),
},
],
},
plugins: [
// 引入插件,配置文件名,這里同樣可以使用 [hash]
new ExtractTextPlugin('index.css'),
],
}
```
在上述的配置中,我們使用了 index.css 作為單獨分離出來的文件名,但有的時候構建入口不止一個,extract-text-webpack-plugin 會為每一個入口創建單獨分離的文件,因此最好這樣配置:
```
plugins: [
new ExtractTextPlugin('[name].css'),
],
```
這樣確保在使用多個構建入口時,生成不同名稱的文件。
這里再次提及 extract-text-webpack-plugin,一個原因是它是一個蠻常用的插件,另一個原因是它的使用方式比較特別,除了在 `plugins` 字段添加插件實例之外,還需要調整 loader 對應的配置。
在這里要強調的是,在 webpack 中,loader 和 plugin 的區分是很清楚的,針對文件模塊轉換要做的使用 loader,而其他干涉構建內容的可以使用 plugin。 ExtractTextWebpackPlugin 既提供了 plugin,也提供了 extract 方法來獲取對應需要的 loader。
## ProvidePlugin
ProvidePlugin 也是一個 webpack 內置的插件,我們可以直接使用 `webpack.ProvidePlugin` 來獲取。
該組件用于引用某些模塊作為應用運行時的變量,從而不必每次都用 `require` 或者 `import`,其用法相對簡單:
```
new webpack.ProvidePlugin({
identifier: 'module',
// ...
})
// 或者
new webpack.ProvidePlugin({
identifier: ['module', 'property'], // 即引用 module 下的 property,類似 import { property } from 'module'
// ...
})
```
在你的代碼中,當 `identifier` 被當作未賦值的變量時,module 就會被自動加載了,而 `identifier` 這個變量即 module 對外暴露的內容。
注意,如果是 ES 的 `default export`,那么你需要指定模塊的 `default` 屬性:`identifier: ['module', 'default'],`。
更多使用例子可以查看官方文檔 [ProvidePlugin](https://doc.webpack-china.org/plugins/provide-plugin/)。
## IgnorePlugin
IgnorePlugin 和 ProvidePlugin 一樣,也是一個 webpack 內置的插件,可以直接使用 `webpack.IgnorePlugin` 來獲取。
這個插件用于忽略某些特定的模塊,讓 webpack 不把這些指定的模塊打包進去。例如我們使用 [moment.js](http://momentjs.com/),直接引用后,里邊有大量的 i18n 的代碼,導致最后打包出來的文件比較大,而實際場景并不需要這些 i18n 的代碼,這時我們可以使用 IgnorePlugin 來忽略掉這些代碼文件,配置如下:
```
module.exports = {
// ...
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
}
```
IgnorePlugin 配置的參數有兩個,第一個是匹配引入模塊路徑的正則表達式,第二個是匹配模塊的對應上下文,即所在目錄名。
## 小結
本小節介紹了幾個相對常見的 webpack plugin 的使用:
* DefinePlugin
* copy-webpack-plugin
* extract-text-webpack-plugin
* ProvidePlugin
* IgnorePlugin
更多其他組件的使用就請有興趣的同學自行摸索了:[plugins in awesome-webpack](https://github.com/webpack-contrib/awesome-webpack#webpack-plugins)。
## 例子
本小節提及的一些簡單的 Demo 可以在 [webpack-examples](https://github.com/teabyii/webpack-examples) 找到。