前言
webpack
是一個當下最流行的前端資源的模塊打包器。當 webpack
處理應用程序時,它會遞歸地構建一個依賴關系圖(dependency graph
),其中包含應用程序需要的每個模塊,然后將所有這些模塊打包成少量的bundle
- 通常只有一個,由瀏覽器加載。
它是高度可配置的,我們先理解四個核心概念:入口(entry
)、輸出(output
)、loader、插件(plugins
)
入口(entry)
webpack 創建應用程序所有依賴的關系圖。圖的起點被稱之為入口起點(entry point
)。入口起點告訴 webpack 從哪里開始,并根據依賴關系圖確定需要打包的內容。可以將應用程序的入口起點認為是根上下文或 APP第一個啟動文件。
簡單規則:每個 HTML 頁面都有一個入口起點。單頁應用(SPA):一個入口起點,多頁應用(MPA):多個入口起點。
簡單語法
用法:entry: string | Arrary<string>
//webpack.config.js
module.exports = {
entry: './src/index.js'
};
entry
屬性的單個入口語法,在擴展配置的時候有失靈活性。它是下面的簡寫:
//webpack.config.js
module.exports = {
entry: {
main: './src/index.js'
}
};
向entry
傳入一個數組時候,將創建"多個主入口(multi-main entry)"。在你想要多個依賴文件一起注入,并且將他們的依賴導向(graph)到一個"chunk"時候,傳入數組的方式就很有用。
//webpack.config.js
module.exports = {
entry: [
'./src/index.js',
'babel-polyfill',
]
};
對象語法
用法: entry: {[entryChunkName: string]: string|Array<string>}
//webpack.config.js
module.export = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};
對象語法會比較繁瑣。然而,這是應用程序中定義入口的最可擴展的方式。
這個配置告訴我們 webpack 從 app.js
和 vendors.js
開始創建依賴圖。這些依賴圖是彼此完全分離、互相獨立的。這種方式比較常見于,只有一個入口起點(不包括 vendor
,vendor
一般都是動態加載的第三方模塊。動態加載的模塊不是入口起點。)的單頁應用程序(single page application)中。不過,為了支持提供更佳 vendor
分離能力的 DllPlugin
。 官方現在不太建議將第三方模塊放到entry.vendors
中。
對象語法,更常見的應該是多入口應用程序-多頁應用(MPA)。
//webpack.config.js
module.export = {
entry: {
home: "./home.js",
about: "./about.js",
contact: "./contact.js"
}
};
此配置,告訴 webpack,我們 需要 3 個獨立分離的依賴關系圖.
在多頁應用中,每當頁面跳轉時,服務器將為你獲取一個新的 HTML 文檔。頁面重新加載新文檔,并且資源被重新下載。
根據經驗:每個 HTML 文檔只使用一個入口起點。
輸出(output)
將所有的資源(assets)歸攏在一起后,還需要告訴 webpack 在哪里打包應用程序。webpack 的 output
屬性描述了如何處理歸攏在一起的代碼(bundled code)。output
選項可以控制webpack如何向硬盤寫入編譯文件。注意,即使可以存在多個entry
起點,但只指定一個output
配置。
簡單用法
在 webpack 中配置output
屬性的最低要求是,將它的值設置為一個對象,包括以下兩點:
-
filename
用于輸出文件的文件名。 - 目標輸出目錄
path
的絕對路徑。
//webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
path: './home/proj/public/assets',
filename: 'bundle.js'
}
};
此配置將一個單獨的 bundle.js
文件輸出到 ./home/proj/public/assets
目錄中。
多個入口起點
如果配置創建了多個單獨的 "chunk"
(例如,使用多個入口起點或使用像 CommonsChunkPlugin
這樣的插件),則應該使用占位符來確保每個文件具有唯一的名稱。
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js', //使用占位符
path: __dirname + '/dist'
}
}
// 寫入到硬盤:./dist/app.js, ./dist/search.js
loader
webpack的目標是,讓webpack聚焦于項目中的所有資源(asset), 而瀏覽器不需要關注考慮這些。 webpack把每個文件(.css
, .html
, .scss
, .jpg
, etc.
)都作為模塊處理。然而webpack自身只理解JavaScript。
webpack loader 在文件被添加到依賴圖中時,將文件源代碼轉換為模塊。
loader
可以使你在import
或"加載"模塊時預處理文件。因此,loader
類似于其他構建工具中“任務(task)”,并提供了處理前端構建步驟的強大方法。
在更高層面,在webpack的配置中loader
有兩個目標:
- 識別出應該被對應的
loader
進行轉換的那些文件。(test
屬性) - 轉換這些文件,從而使其能夠被添加到依賴圖中(并且最終添加到bundle中)。(
use
屬性)
//webpack.config.js
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{
test: /\.txt$/,
use: 'raw-loader'
}
]
}
};
以上配置中,對一個單獨的 module
對象定義了 rules
屬性,里面包含兩個必須屬性:test
和use
。這相當于告訴 webpack 編譯器,當碰到「在 require()/import
語句中被解析為 .txt
的路徑」時,在對它打包之前,先使用 raw-loader
轉換一下。
在webpack配置中定義
loader
時,要定義在module.rules
中,而不是rules
。如果不這么做,webpack會給出嚴重警告
示例
例如,你可以使用loader
告訴webpack加載css
文件,或者將TypeScript轉為JavaScript。為此,首先安裝相對應的loader
:
npm install -D css-loader
npm install -D ts-loader
然后指示webpack對每個.css
使用css-loader
, 以及對所有.ts
文件使用ts-loader
:
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' },
{ test: /\.ts$/, use: 'ts-loader' }
]
}
};
根據配置選項,下面的規范定義了同等的loader
用法:
{test: /\.css$/, use: 'css-loader'}
// 等同于
{test: /\.css$/, use: [{
loader: 'css-loader'
}]}
// 等同于
{test: /\.css$/, loader: 'css-loader'}
module.rules.loader
是 module.rules.use: [ { loader } ]
的簡寫。
在應用程序中,有三種使用loader的方式:
- 配置(推薦):在webpack.config.js文件中指定loader。
- 內聯: 在每個
imort
語句中顯示指定loader。 - CLI: 在
shell
命令中指定它們。
配置
module.rules
允許你在 webpack 配置中指定多個 loader
。 這是展示loader
的一種簡明方式,并且有助于使代碼變得簡潔。同時讓你對各個loader
有個全局概覽:
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'scss-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}]
}
內聯
可以在import
語句或任何等效于 "import" 的方式中指定 loader。使用 !
將資源中的 loader 分開。分開的每個部分都相對于當前目錄解析。
import Styles from 'style-loader!css-loader?modules!./styles.css'
通過前置所有規則及使用!
, 可以對應覆蓋到配置中的任意loader
。
選項可以傳遞查詢參數,例如 ?key=value&foo=bar
,或者一個 JSON 對象,例如 ?{"key":"value","foo":"bar"}
。
盡可能使用
module.rules
,因為這樣可以減少源碼中的代碼量,并且可以在出錯時,更快地調試和定位 loader 中的問題。
CLI
你也可以通過CLI
使用loader:
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
這會對.jade
文件使用 jade-loader
,對 .css
文件使用 style-loader
和 css-loader
。
Loader特性
- loader 支持鏈式傳遞。能夠對資源使用流水線(pipeline)。一組鏈式的 loader 將按照先后順序進行編譯。loader 鏈中的第一個 loader 返回值給下一個 loader。在最后一個 loader,返回 webpack 所預期的 JavaScript。
- loader 可以是同步的,也可以是異步的。
- loader 運行在 Node.js 中,并且能夠執行任何可能的操作。
- loader 接收查詢參數。用于對 loader 傳遞配置。
- loader 也能夠使用
options
對象進行配置。 - 除了使用
package.json
常見的main
屬性,還可以將普通的 npm 模塊導出為loader,做法是在package.json
里定義一個loader
字段。 - 插件(plugin)可以為 loader 帶來更多特性。
- loader 能夠產生額外的任意文件。
loader 通過(loader)預處理函數,為 JavaScript 生態系統提供了更多能力。
解析Loader
loader 遵循標準的模塊解析。多數情況下,loader 將從模塊路徑(通常將模塊路徑認為是npm install
, node_modules
)解析。
loader 模塊需要導出為一個函數,并且使用 Node.js 兼容的 JavaScript 編寫。通常使用 npm 來管理,也可以將自定義 loader 作為應用程序中的文件。按照約定,loader 通常被命名為 xxx-loader(例如 json-loader
)。
插件-plugins
插件是 wepback 的支柱功能。webpack 自身也是構建于-你在 webpack 配置中用到的相同的插件系統之上!
插件目的在于解決 loader無法實現的其他事。
由于loader僅在每個文件的基礎上執行轉換,而插件(plugins) 常用于(但不限于)在打包模塊的 “compilation” 和 “chunk” 生命周期執行操作和自定義功能。想要使用一個插件,你只需要 require()
它,然后把它添加到 plugins
數組中。
多數插件可以通過選項(option)自定義。你也可以在一個配置文件中因為不同目的而多次使用同一個插件,這時需要通過使用 new 來創建它的一個實例。
剖析
webpack 插件是一個具有 apply
屬性的 JavaScript 對象。apply
屬性會被 webpack compiler 調用,并且 compiler
對象可在整個編譯生命周期訪問。通過Function.prototype.apply
方法,你可以把任意函數作為插件傳遞(this
將指向compiler
)。我們可以在配置中使用這樣的方式來內聯自定義插件。
//ConsoleLogOnBuildWebpackPlugin.js
function ConsoleLogOnBuildWebpackPlugin() {
};
ConsoleLogOnBuildWebpackPlugin.prototype.apply = function(compiler) {
compiler.plugin('run', function(compiler, callback) {
console.log("webpack 構建過程開始!!!");
callback();
});
};
用法
由于插件可以攜帶參數/選項,你必須在webpack配置中,向plugins
屬性中傳入new
實例。
根據webpack的用法,可以有多種方式使用插件。但是,通過配置的方式使用是比較推薦的做法。
配置 (推薦)
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
const path = require('path');
const config = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]0
};
module.exports = config;
Node API(不推薦)
即便使用 Node API,用戶也應該在配置中傳入 plugins 屬性。compiler.apply
并不是推薦的使用方式。
// some-node-script.js
const webpack = require('webpack'); //訪問 webpack 運行時(runtime)
const configuration = require('./webpack.config.js');
let compiler = webpack(configuration);
compiler.apply(new webpack.ProgressPlugin());
compiler.run(function(err, stats) {
// ...
});
總結
以上就是webpack中比較重要的四個概念,在平時開發過程中會經常遇到。里面還可以有很多的詳細配置,需要我們在項目開發的過程中慢慢了解。