前言: 作為一個現代javascript 應用程式的靜態模組打包器,webpack能將各種資源,如js,css, 圖片等作為模組來處理,是當下前端工程化的一個很受歡迎的工具,webpack目前最新的版本是4.0,文章將在4.0 的基礎上,從使用者的角度,一步步教你認識並搭建一個簡單的webpack配置專案,當然webpack的配置和使用較為豐富且複雜。
1. 兩個基本的依賴:
首先webpack 專案的兩個核心基礎模組是webpack 和webpack-cli,這是webpack專案構建的前提
npm install --save-dev webpack webpack-cli
複製程式碼
2. 執行webpack
預設情況下,webpack 執行構建指令預設 以專案資料夾下的 src/index.js 作為入口檔案, 執行 webpack指令會執行預設的webpack 配置檔案。 而在一般情況下,需要構建符合專案要求的配置檔案,可在package.json 中同過--config配置webpack的執行檔案(如下)
"script": {
"build": "webpack --config ./config/webpack.base.js"
}
複製程式碼
- webpack 配置檔案的設定 通過指定配置檔案後,接下來的工作是根據需要配置執行的配置檔案
module.exports = {
}
複製程式碼
3.1 入口檔案
指定專案的入口檔案
module.exports = {
entry: "./****", // 指定入口檔案
}
複製程式碼
3.2 出口檔案
module.exports = {
entry: "./****", // 指定入口檔案
output: {
path: path.resolve(__dirname, ....),// 輸出路徑,一般為絕對路徑
filename: '****', // 輸出檔名 [hash]可以用來每次以hash值的區別生成檔案
publicPath: 'static', //輸出解析檔案的目錄,url 相對於 HTML 頁面
},
chunkFilename: '***'
}
複製程式碼
注意: // publicPath 的解釋 比如 publicPath 設定為static 之後,html 頁面中引用的url 會自動加上static
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>test</title>
</head>
<body>
<script type="text/javascript" src="static/bundle.67c249f5bcf3681ab97e.js"></script></body>
</html>
複製程式碼
tips: 如何理解chunkFileName: chunkname 是未被列入entry 中, 卻有需要被打包出來的檔案命名配置, 例如,某些公共模組需要單獨的抽離出來,這些公共模組就可以用chunkname 來命名 可以見下面的程式碼分離部分
3.3 配置多個入口檔案
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
複製程式碼
// 寫入到硬碟:./dist/app.js, ./dist/search.js
3.4 clean-webpack-plugin
不斷執行 webpack 的指令,每次都會生成不同的不同hash 值的js 指令碼,因此,我們需要一個外掛,每次構建專案之前,將原先的構建完成的資料夾刪除,首選 clean-webpack-plugin 的外掛 配置相關如下
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(['dist'], {
{
root: '', // 刪除資料夾的根路徑
verbose: true, // 是否開啟日誌
}
}) // 第一個引數為刪除的資料夾陣列
]
}
複製程式碼
相關引數配置clean-webpack-plugin
3.5 html-webpack-plugin
webpack 構建專案時, 通過指定的入口檔案,會將所有的js css 等以依賴模組的形式打包成一個或多個的指令碼檔案,通常情況下,指令碼檔案會附屬於html 檔案執行,這時候需要將 打包好的指令碼檔案,注入到html 中, html-webpack-plugin 外掛的目的是, 以一個html 為模板, 將打包好的指令碼注入到模板中, 相關的配置如下
const HtmlWebpackPlugin = require('html-webpack-plugin');
new HtmlWebpackPlugin() // 不帶任何配置時, 預設會以一個內建普通的html 作為模板html
new HtmlWebpackPlugin({
title: 'title', // 給模板中的html 注入標題, 需要在模板的html 中指明配置, <%= htmlWebpackPlugin.options.title %>
filename: '', // 指定轉換後的html 檔名
template: './',// 模板檔案的路徑
chunk: ['main']// chunk 指定了該模板匯入的模組,在多頁面的配置中,可以在該屬性中配置多個入口中的一個或者多個指令碼檔案
})
複製程式碼
4. mode 模式
所謂模式,webpack4.0預設的模式是 'production',可以通過 mode 來更改模式為'development' module.exports = { mode: 'development' // 會將 process.env.NODE_ENV === 'development' mode: 'production' // 會將 process.env.NODE_ENV === 'production' }
5. webpack-dev-server
5.1 生產配置/ 開發配置
生產模式下的要求: 注重模組的大小 開發模式下的要求: 除錯, 熱更新
在生產環境中,預設會進行指令碼的壓縮。 在開發環境中,我們需要快速的除錯程式碼,因此需要有一個本地的伺服器環境,用於訪問 webpack 構建好的靜態檔案,webpack-dev-server 是 webpack 官方提供的一個工具,可以基於當前的 webpack 構建配置快速啟動一個靜態服務。當 mode 為 development 時,會具備 hot reload 的功能,即當原始碼檔案變化時,會即時更新當前頁面,以便你看到最新的效果。 根據需要,需要將配置檔案抽離成生產配置和開發配置,並留一個共同的配置檔案 使用 webpack-merge 來合併物件
npm i --save-dev webpack-dev-server
// package.json
{
"name": "development",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"dev": "webpack-dev-server --open", // webpack-dev-server 啟動
},
"keywords": [],
"author": "",
"license": "ISC",
}
webpack.dev.js
const merge = require('webpack-merge')
module.exports = merge(base, {
devtool: "cheap-module-eval-source-map"
devServer: {
port: 8088,
compress: true,
...
}
})
複製程式碼
講講webpack-dev-server 的配置,webpack-dev-server 的配置比較多,具體可以參考webpack-dev-server官方文件 常見的配置:
public: 指定靜態服務的域名,當你使用 Nginx 來做反向代理時,應該就需要使用該配置來指定 Nginx 配置使用的服務域名 port : 指定埠號 openPage: 指定初次訪問的頁面 publicPath:指定構建好的靜態檔案在瀏覽器中用什麼路徑去訪問,預設是 /,比如設為 static 時, 預設訪問靜態的路徑變成 http://localhost:8080/static/bundle.js
proxy 用於配置 webpack-dev-server 將特定 URL 的請求代理到另外一臺伺服器上。當你有單獨的後端開發伺服器用於請求 API 時,這個配置相當有用
proxy: {
'/api': {
target: "http://localhost:3000", // 將 URL 中帶有 /api 的請求代理到本地的 3000 埠的服務上
pathRewrite: { '^/api': '' }, // 把 URL 中 path 部分的 `api` 移除掉
},
}...
複製程式碼
6. loader
webpack 中提供一種處理多種檔案格式的機制,便是使用 loader。我們可以把 loader 理解為是一個轉換器,負責把某種檔案格式的內容轉換成 webpack 可以支援打包的模組。 舉個例子,在沒有新增額外外掛的情況下,webpack 會預設把所有依賴打包成 js 檔案,如果入口檔案依賴一個 .hbs 的模板檔案以及一個 .css 的樣式檔案,那麼我們需要 handlebars-loader 來處理 .hbs 檔案,需要 css-loader 來處理 .css 檔案(這裡其實還需要 style-loader,後續詳解),最終把不同格式的檔案都解析成 js 程式碼,以便打包後在瀏覽器中執行。...
6.1 css-loader
css-loader 負責解析 CSS 程式碼,主要是為了處理 CSS 中的依賴,例如 @import 和 url() 等引用外部檔案的宣告
6.2 style-loader
style-loader 會將 css-loader 解析的結果轉變成 JS 程式碼,執行時動態插入 style 標籤來讓 CSS 程式碼生效。
6.3 file-loader
file-loader 用來處理jpg/png/gif 等檔案格式
6.4 sass-loader node-sass
這兩個loader 用於解析scss 檔案
module.exports = {
module: {
rules: [
{
test: /\.(css|scss)$/,
use: [
process.env.NODE ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
}
}
複製程式碼
6.5 babel 相關
為什麼要使用babel, 目前低版本的部分瀏覽器,並不支援es6的相關語法,babel 的目的就是為了講es6 的相關語法,轉換成es5 的語法 npm install -D babel-loader @babel/core @babel/preset-env
webpack 4中需要安裝這三個loader 配置如下:
module.exports = {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
}
}
}
]
}
複製程式碼
babel的三個loader 可以解決 箭頭函式這類es6 語法, 但是無法解決promise, symbol 這類新增的內建物件, 因此需要引入 babel-polyfill來進行全域性的轉換 ps: babel-polyfill 需要放在生產依賴中, 即npm i --save babel-polyfill 並且需要配置 entry
module.exports = {
entry: ['babel-polyfill', './src/main.js'],
···
}
使用前
main.f66bf9d8d1fd5b0f62ae.css 88 bytes 0 [emitted] main
bundle.f66bf9d8d1fd5b0f62ae.js 66.6 KiB 0 [emitted] main
main.f66bf9d8d1fd5b0f62ae.css.map 194 bytes 0 [emitted] main
bundle.f66bf9d8d1fd5b0f62ae.js.map 219 KiB 0 [emitted] main
使用後
main.0c090c129f1d9d4804b0.css 88 bytes 0 [emitted] main
bundle.0c090c129f1d9d4804b0.js 154 KiB 0 [emitted] main
main.0c090c129f1d9d4804b0.css.map 194 bytes 0 [emitted] main
bundle.0c090c129f1d9d4804b0.js.map 219 KiB 0 [emitted] main
複製程式碼
tips: 可以很明顯的發現,打包後的bundle會比之前要大大概100k 左右,原因是, babel-polyfill 會對全域性進行改寫,這樣其實壞處是汙染了全域性的環境,並且增加了打包後的檔案大小,這也是要進行安裝在dependency 而不是 devDependency的原因 因此, webpack4.0 提供了另外的 plugin-transform-runtime npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
複製程式碼
7.其他plugin
在 webpack 的構建流程中,plugin 用於處理更多其他的一些構建任務。可以這麼理解,模組程式碼轉換的工作由 loader 來處理,除此之外的其他任何工作都可以交由 plugin 來完成
7.1 uglifyjs-webpack-plugin
uglifyjs-webpack-plugin 是用來對js 程式碼進行壓縮體積用的,在webpack4.0中, 預設的配置是進行壓縮,可以通過 mode 模式的 development 來設定成不進行壓縮,預設模式是production 其他的預設配置可以參考:
uglifyjs-webpack-plugin
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
//...
optimization: {
minimizer: [
new UglifyJsPlugin()
]
}
}
複製程式碼
7.2 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");
7.3 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
// 可以配置很多項複製規則
]),
],
}...
複製程式碼
7.4 extract-text-webpack-plugin
extract-text-webpack-plugin 是在webpack4.0 之前用來把 依賴的css 分離出來成為單獨的檔案,可以讓指令碼檔案變得更小, webpack 4.0 不再使用extra-text-webpack-plugin來分離css 轉而使用mini-css-extract-plugin
7.5 mini-css-extract-plugin
mini-css-extract-plugin 既需要配置plugin 也需要配置loader
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].[contenthash].css",
chunkFilename: "[id].css"
})
],
module: {
rules: [{
test: /\.(css|scss)$/,
include: [path.resolve(__dirname, '../src')],
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}, ]
}
}
複製程式碼
tips: 這個外掛一般在生產環境中使用,並且使用時不能使用style-loader== ps: contenthash 和 hash 的區別 見第八部分
7.5 IgnorePlugin
IgnorePlugin 和 ProvidePlugin 一樣,也是一個 webpack 內建的外掛,可以直接使用 webpack.IgnorePlugin 來獲取。 這個外掛用於忽略某些特定的模組,讓 webpack 不把這些指定的模組打包進去。例如我們使用 moment.js,直接引用後,裡邊有大量的 i18n 的程式碼,導致最後打包出來的檔案比較大,而實際場景並不需要這些 i18n 的程式碼,這時我們可以使用 IgnorePlugin 來忽略掉這些程式碼檔案,配置如下:...
module.exports = {
// ...
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
}
複製程式碼
IgnorePlugin 配置的引數有兩個,第一個是匹配引入模組路徑的正規表示式,第二個是匹配模組的對應上下文,即所在目錄名。
8 分離程式碼檔案
為了實現減小打包後程式碼的體積,利用快取來加速靜態資源訪問,需要將不同,且相互不影響的程式碼塊分離出來, 在plugin 中介紹過mini-css-extract-plugin 來對css 檔案進行分離, 除此之外, 還建議 公共使用的第三方類庫顯式地配置為公共的部分,因為第三方庫在實際開發中,改變的頻率比較小,可以避免因公共 chunk 的頻繁變更而導致快取失效。
module.exports = {
entry: {
vendor: ["react", "lodash", "angular", ...], // 指定公共使用的第三方類庫
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "initial",
test: "vendor",
name: "vendor", // 使用 vendor 入口作為公共部分
enforce: true,
},
},
},
},
// ... 其他配置
}
// 或者
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /react|angluar|lodash/, // 直接使用 test 來做路徑匹配
chunks: "initial",
name: "vendor",
enforce: true,
},
},
},
},
}
// 或者
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "initial",
test: path.resolve(__dirname, "node_modules") // 路徑在 node_modules 目錄下的都作為公共部分
name: "vendor", // 使用 vendor 入口作為公共部分
enforce: true,
},
},
},
},
}...
複製程式碼
ps: 在次基礎上, 需要在配置的output 中設定 chunkFilename 來配置打包後這些第三方庫的名字
module.exports = {
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'bundle.[hash].js',
chunkFilename: 'vendor.[chunkhash].js'
},
}
複製程式碼
ps: hash 和 chunkhash, contenthash 的區別==
hash 在每次構建的時候都會重新全部生成 ,所有的檔案的hash 都是同一個值, 無論是否修改了檔案,所有的檔案都將重新生成, 起不到快取的效果; chunkhash根據不同的入口檔案(Entry)進行依賴檔案解析、構建對應的chunk,生成對應的雜湊值,比如我們將一些公共模組,或者第三方依賴包獨立開來,接著用chunkhash 生成雜湊值,只要不改變公共程式碼,就不需要重新構建; 然而當chunkhash 用在css 中時, 由於css 和js 用了同一個chunkhash,所以當只改變js 時,css 檔案也會重新生成, 所以css 中我們使用contenthash==
9 其他一些常用配置
resolve resolve.alias 配置常用模組的相對路徑
module.exports = {
resolve: {
alias: {
Util: path.resolve(__dirname, 'src/util/'),
}
}
};
複製程式碼
實際引用中 imoprt uitl from 'Util/sss' 原始: import util from './src/util/sss'
resolve.extensions 這個配置可以定義在進行模組路徑解析時,webpack 會嘗試幫你補全那些字尾名來進行查詢
resolve: {
extensions: [".js", ".vue"],
},
複製程式碼