webpack4 超詳細配置
核心概念
入口
它應該是一個.js 檔案,人們會在這個檔案中包含.scss 或.css 檔案,這樣做可能會導致很多意外的錯誤。有時候你可能會看到一個入口包含了幾個.js 檔案。雖然有時候你可以這麼做,但它通常會增加更多的複雜性。
輸出
這是 build/ 或 dist/ 或任意名稱的資料夾,用於存放最終的.js 檔案。
載入器
它們主要用來編譯或轉換你的程式碼,例如 postcss-loader。
外掛
外掛在將程式碼輸出到檔案方面起著至關重要的作用。
快速入門
建立一個新目錄,並進入這個目錄:
mkdir webpack-4-tutorial
cd webpack-4-tutorial
初始化 package.json:
npm init
或者:
yarn init
我們需要下載 Webpack 4 和 webpack-cli:
npm install webpack webpack-cli --save-dev
或者:
yarn add webpack webpack-cli --dev
現在,開啟 package.json 檔案,並新增一個構建指令碼:
"scripts": {
"dev": "webpack"
}
執行它,你可能會看到一個警告:
WARNING in configuration The ‘mode’ option has not been set, webpack will fallback to ‘production’ for this value. Set ‘mode’ option to ‘development’ or ‘production’ to enable defaults for each environment. You can also set it to ‘none’ to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Webpack 4 的模式
你需要編輯指令碼,在其中包含模式標誌:
"scripts": {
"dev": "webpack --mode development"
}
ERROR in Entry module not found: Error: Can’t resolve ‘./src’ in ‘~/webpack-4-quickstart’
Webpack 會查詢一個帶有 index.js 檔案的資料夾.src/。這是 Webpack 4 的預設行為,因為它是零配置的。
讓我們建立一個帶有 index.js 檔案的目錄./src,並這個檔案中加一些程式碼:
console.log("hello, world");
現在執行指令碼:
npm run dev
或者:
yarn dev
如果一切正常,你將得到一個./dist/main.js 檔案。好了,程式碼已經編譯好了。但剛剛都發生了什麼?
預設情況下,Webpack 是零配置的,所以你不需要配置 webpack.config.js。因此,它必須假設一些預設行為,它總是先查詢./src 資料夾,然後在其中查詢 index.js,並將輸出寫入./dist/main.js 檔案。
如果你遇到這個錯誤:
ERROR in ./node_modules/fsevents/node_modules/node-pre-gyp/lib/publish.js
Module not found: Error: Can't resolve 'aws-sdk' in '/Users/mobr/Documents/workshop/test-webpack-4-setup/node_modules/fsevents/node_modules/node-pre-gyp/lib'
這裡(https://github.com/webpack/webpack/issues/8400)描述了更多細節。要解決這個問題,你需要建立 webpack.config.js 檔案,然後按照本文後面的教程配置這個檔案。但是首先你需要下載 webpack-node-externals(https://github.com/liady/webpack-node-externals)。
npm install webpack-node-externals --save-dev
或者:
yarn add webpack-node-externals --dev
然後匯入這個模組:
const nodeExternals = require('webpack-node-externals');
...
module.exports = {
...
target: 'node',
externals: [nodeExternals()],
...
};
擁有 2 個配置檔案是 Webpack 的常見做法,尤其是在大型專案中,通常一個用於開發,一個用於生產環境。Webpack 4 提供了兩種模式:生產(production)和開發(development)。這樣可以避免使用兩個檔案(對於中型專案)。
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production"
}
如果你細心一點就會發現,main.js 檔案並沒有被最小化。
我將在示例中使用 build 指令碼,因為它提供了很多開箱即用的優化措施,但其實你可以隨意使用它們中的任何一個。build 和 dev 指令碼之間最主要的區別在於它們如何輸出檔案。build 用於建立用於生產環境的程式碼,而 dev 用於開發環境,所以它支援熱模組替換、開發伺服器以及其他很多有助於開發工作的東西。
你可以覆蓋 npm 指令碼中的預設值,只需使用標誌:
"scripts": {
"dev": "webpack --mode development ./src/index.js --output ./dist/main.js",
"build": "webpack --mode production ./src/index.js --output ./dist/main.js"
}
這將覆蓋預設選項,而無需配置任何東西。
作為練習,也可以試一試這些標誌:
--watch 標誌用於啟用監聽模式。它將監控你的檔案更改,並在每次檔案發生更新時重新編譯檔案。
"scripts": {
"dev": "webpack --mode development ./src/index.js --output ./dist/main.js --watch",
"build": "webpack --mode production ./src/index.js --output ./dist/main.js --watch"
}
--entry 標誌與 --output 標誌差不多,但會重寫入口路徑。
轉換.js 程式碼
現代 JS 程式碼主要是用 ES6 編寫的,但並非所有瀏覽器都支援 ES6。因此,你需要對程式碼進行轉換——將 ES6 程式碼轉換為 ES5。你可以使用 babel——現在最流行的工具。當然,我們不僅可以轉換 ES6 程式碼,也可以轉換其他 JS 實現,如 TypeScript、React,等等。
npm install babel-core babel-loader babel-preset-env --save-dev
或者:
yarn add babel-core babel-loader babel-preset-env --dev
為 babel 建立配置檔案.babelrc,並在檔案中貼上以下內容。
{
"presets": [
"env"
]
}
我們有兩個配置 babel-loader 的方式:
-
使用配置檔案 Webpack.config.js
-
在 npm 指令碼中使用 --module-bind
從技術上說,你可以通過 Webpack 引入的新標誌做很多事情,但為簡單起見,我選擇了 webpack.config.js。
配置檔案
雖然 Webpack 宣稱自己是零配置的,但這種零配置主要還是適用於預設設定,比如入口和輸出。
我們將建立包含以下內容的 webpack.config.js:
// webpack v4
const path = require('path');
// update from 23.12.2018
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
target: 'node', // update from 23.12.2018
externals: [nodeExternals()], // update from 23.12.2018
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
}
};
從 npm 指令碼中移除標誌。
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development"
},
現在,當我們執行 npm run build 或 yarn build 時,它應該會在./dist/main.js 中生成一個最小化的.js 檔案。如果沒有,請嘗試重新安裝 babel-loader。
如果遇到模組“@babel/core”衝突,說明某些預載入的 babel 依賴項不相容。我遇到的錯誤是:
Module build failed: Error: Cannot find module '@babel/core'
[email protected] requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install '[email protected]'.
我是這樣解決的:
yarn add @babel/core --dev
HTML 和 CSS 匯入
我們先在./dist 資料夾中建立 index.html 檔案。
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>Hello, world!</div>
<script src="main.js"></script>
</body>
</html>
我們在這裡匯入了 style.css,現在讓我們來配置它!正如我們所說,我們只有一個 Webpack 入口點。那麼應該將 css 檔案放在哪裡?在./src 資料夾中建立 style.css 檔案。
div {
color: red;
}
不要忘了將它包含在.js 檔案中:
import "./style.css";
console.log("hello, world");
在 Webpack 中為 css 檔案建立一個新規則:
// webpack v4
const path = require('path');
// update from 23.12.2018
const nodeExternals = require('webpack-node-externals');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
target: 'node', // update from 23.12.2018
externals: [nodeExternals()], // update from 23.12.2018
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract(
{
fallback: 'style-loader',
use: ['css-loader']
})
}
]
}
};
在終端中執行:
npm install extract-text-webpack-plugin --save-dev
npm install style-loader css-loader --save-dev
或者:
yarn add extract-text-webpack-plugin style-loader css-loader --dev
我們需要使用 extract-text-webpack-plugin 來編譯我們的.css。但是在 Webpack 4 中使用這個外掛有點問題,可能會遇到這個錯誤:
https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/701
要修復這個問題,需要執行:
npm install -D [email protected]
或者:
yarn add --dev [email protected]
然後你的 css 程式碼應該被編譯為./dist/style.css。
到了這個時候,我的 package.json 的 dev 依賴項看起來像這樣:
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.6.1",
"css-loader": "^0.28.11",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"style-loader": "^0.20.3",
"webpack": "^4.4.1",
"webpack-cli": "^2.0.12"
}
請注意,另一種組合可能無法正常執行,因為即使是將 webpack-cli 2.0.12 更新為 2.0.13 也可能會帶來問題。
現在它應該將 style.css 輸出到./dist 資料夾中了。
Mini-CSS 外掛
Mini-CSS 外掛旨在取代 extract-text 外掛,併為你提供更好的相容性。我重新修改了我的 Webpack 檔案,使用 mini-css-extract-plugin(https://github.com/webpack-contrib/mini-css-extract-plugin)來編譯 style.css。
npm install mini-css-extract-plugin --save-dev
或者:
yarn add mini-css-extract-plugin --dev
以及:
// webpack v4
const path = require('path');
// update from 23.12.2018
const nodeExternals = require('webpack-node-externals');
// const ExtractTextPlugin = require('extract-text-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
target: 'node', // update from 23.12.2018
externals: [nodeExternals()], // update from 23.12.2018
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
use: [ 'style-loader', MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.css',
})
]
};
我們需要使用 MiniCssExtractPlugin,因為預設情況下 Webpack 只能識別.js 格式。MiniCssExtractPlugin 將.css 內容提取到./dist 目錄下不同的.css 檔案中。
配置對 SCSS 的支援
使用 SASS 和 POSTCSS 開發網站已經非常普遍,因此,我們將首先包括對 SASS 的支援。讓我們重新命名./src/style.css 檔案,並建立另一個資料夾來存放.scss 檔案。現在我們需要新增對.scss 格式的支援。
npm install node-sass sass-loader --save-dev
或者:
yarn add node-sass sass-loader --dev
使用./scss/main.scss 替換 style.css,並修改 test 配置,以便支援.scss:
// webpack v4
const path = require('path');
// update 23.12.2018
const nodeExternals = require('webpack-node-externals');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
target: "node", // update 23.12.2018
externals: [nodeExternals()], // update 23.12.2018
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.scss$/,
use: [
"style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
} ...
HTML 模板
現在讓我們來建立.html 檔案模板。將以下內容新增到./src 目錄的 index.html 檔案中。
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>Hello, world!</div>
<script src="main.js"></script>
</body>
</html>
我們需要藉助 html 外掛才能將這個檔案作為模板使用。
npm install html-webpack-plugin --save-dev
或者:
yarn add html-webpack-plugin --dev
將其新增到你的 Webpack 檔案中:
// webpack v4
const path = require('path');
// update 23.12.2018
const nodeExternals = require('webpack-node-externals');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
target: "node", // update 23.12.2018
externals: [nodeExternals()], // update 23.12.2018
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.scss$/,
use: [
"style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "style.css"
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './src/index.html',
filename: 'index.html'
})
]
};
現在./src/index.html 檔案就變成了一個模板。現在刪除./dist 資料夾其其中的所有檔案,然後重新構建,看看是不是一切正常。
rm -rf ./dist
npm run dev
或者:
rm -rf ./dist
yarn dev
你會看到./dist 資料夾重新生成,其中包含了三個檔案:index.html、style.css 和 main.js.
快取和雜湊
開發中最常見的問題之一是如何實現快取。因為這篇文章主要是關於如何配置 Webpack,所以不會專注於介紹快取的實現細節。我只想說,解決快取問題最常用的方法之一是在檔案中(例如 style.css 和 script.js)新增一個雜湊值。你可以參考這篇文章:
https://developers.google.com/web/fundamentals/performance/webpack/use-long-term-caching#split-the-code-into-routes-and-pages
使用雜湊以後,瀏覽器只會請求發生變更的檔案。
Webpack 4 基於 chunkhash 實現了的內建的雜湊功能。修改 Webpack 檔案:
// webpack v4
const path = require('path');
// update 23.12.2018
const nodeExternals = require("webpack-node-externals");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
target: "node",
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.scss$/,
use: [
"style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "style.[contenthash].css"
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './src/index.html',
filename: 'index.html'
})
]
};
在./src/index.html 檔案中新增:
<html>
<head>
<link rel="stylesheet" href="<%=htmlWebpackPlugin.files.chunks.main.css %>">
</head>
<body>
<div>Hello, world!</div>
<script src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>
</body>
</html>
我們將使用 htmlWebpackPlugin.files.chunks.main 模式生成雜湊。看一下./dist 目錄下的 index.html 檔案:
如果我們在不修改.js 和.css 檔案的情況下執行 npm run dev,不管執行多少次,兩個檔案中的雜湊值都應該是一樣的。
CSS 的雜湊問題以及如何解決
如果你在 Webpack 4 中使用了 ExtractTextPlugin,可能會存在這個問題。如果你使用的是 MiniCssExtractPlugin,這個問題應該就不會發生。
我們的解決方案還不夠完美。如果我們修改了.scss 檔案中的某些程式碼會怎樣?你會發現,現在不生成新的雜湊了。如果我們在.js 檔案中增加一行 console.log,比如:
import "./style.css";
console.log("hello, world");
console.log("Hello, world 2");
再次執行 dev 指令碼,你將看到兩個檔案中的雜湊值已更新。
這個問題是已知的,Stack Overflow 上已經討論過這個問題:
https://stackoverflow.com/questions/44491064/updating-chunkhash-in-both-css-and-js-file-in-webpack
那麼應該怎麼解決這個問題?
在嘗試了很多聲稱可以解決這個問題的外掛後,我終於找到了兩個可行的解決方案。
解決方案 1
可能還存在一些衝突,可以試試 mini-css-extract 外掛。
解決方案 2
在 CSS Extract 外掛中使用 [hash] 替換 [chukhash],但這似乎會與 Webpack 4.3 提供的 [contenthash] 變數產生衝突,所以可以結合使用這個外掛:
webpack-md5-hash:
https://www.npmjs.com/package/webpack-md5-hash
現在再測試就會發現兩個檔案的雜湊都會更新。
JS 的雜湊問題以及如何解決?
如果你使用了 MiniCssExtractPlugin,可能會遇到另一個問題:每次修改 SCSS 中的某些內容時,.js 檔案和.css 輸出檔案的雜湊都會被更新。
解決方案:
使用 webpack-md5-hash 外掛。如果你修改了 main.scss 檔案並執行 dev 指令碼,應該只有新的 style.css 檔案裡會包含新的雜湊,而不是兩個檔案都會這樣。
// webpack v4
const path = require('path');
// update 23.12.2018
const nodeExternals = require("webpack-node-externals");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackMd5Hash = require("webpack-md5-hash");
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
target: "node", // update 23.12.2018
externals: [nodeExternals()], // update 23.12.2018
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract(
{
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
})
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "style.[contenthash].css"
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: "./src/index.html",
filename: "index.html"
}),
new WebpackMd5Hash()
]
};
整合 PostCSS
為了讓輸出的.css 更進一步,我們可以加入 PostCSS:
https://github.com/postcss/postcss
PostCSS 提供了 autoprefixer、cssnano 和其他好用的東西。我每天都在用它。我們需要安裝 postcss-loader,還有 autoprefixer,因為稍後會用到它。
npm install postcss-loader --save-dev
npm i -D autoprefixer
或者:
yarn add postcss-loader autoprefixer --dev
建立 postcss.config.js,並貼上以下內容:
module.exports = {
plugins: [
require('autoprefixer')
]
}
我們的 Webpack.config.js 現在應該是這樣的:
// webpack v4
const path = require('path');
// update 23.12.2018
const nodeExternals = require("webpack-node-externals");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackMd5Hash = require("webpack-md5-hash");
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
target: "node",
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.scss$/,
use: [ 'style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.[contenthash].css',
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './src/index.html',
filename: 'index.html'
}),
new WebpackMd5Hash()
]
};
請注意外掛的順序:
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']
載入器按照從尾到頭的順序使用外掛。
你可以向.scss 檔案中新增更多內容並檢查輸出,以此來測試 autoprefixer:
https://github.com/postcss/autoprefixer
你也可以在.browserslistrc 檔案中指定要支援的瀏覽器來調整輸出的內容。
你可以在 網站(https://www.postcss.parts/)上了解更多 PostCSS 可用的外掛,例如:
-
utilities:https://github.com/ismamz/postcss-utilities
-
cssnano:https://github.com/ben-eb/cssnano
-
style-lint:https://github.com/stylelint/stylelint
我使用 cssnano 來最小化輸出檔案,並用 css-mqpacker(https://github.com/hail2u/node-css-mqpacker)來處理媒體查詢。
也有人說可以試試 cleancss。
保持整潔
在重新生成檔案之前,我們可以嘗試使用 clean-Webpack-plugin 來清理./dist 資料夾。
// webpack v4
const path = require('path');
// update 23.12.2018
const nodeExternals = require("webpack-node-externals");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackMd5Hash = require("webpack-md5-hash");
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: { main: './src/index.js' },
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash].js'
},
target: "node",
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.scss$/,
use: [ 'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader']
}
]
},
plugins: [
new CleanWebpackPlugin('dist', {} ),
new MiniCssExtractPlugin({
filename: 'style.[contenthash].css',
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './src/index.html',
filename: 'index.html'
}),
new WebpackMd5Hash()
]
};
帶有最新版本外掛的 package.json 看起來是這樣的:
{
“name”: “webpack-test”,
“version”: “1.0.0”,
“description”: “”,
“main”: “index.js”,
“scripts”: {
“build”: “webpack --mode production”,
“dev”: “webpack --mode development”
},
“author”: “”,
“license”: “ISC”,
"devDependencies": {
"@babel/core": "^7.2.2",
"autoprefixer": "^9.4.3",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.4",
"babel-preset-env": "^1.7.0",
"css-loader": "^2.0.2",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"webpack": "4.28",
"webpack-cli": "^3.1.2"
},
"dependencies": {
"clean-webpack-plugin": "^1.0.0",
"webpack-md5-hash": "^0.0.6",
"webpack-node-externals": "^1.7.2"
}
}
英文原文:
https://hackernoon.com/a-tale-of-webpack-4-and-how-to-finally-configure-it-in-the-right-way-4e94c8e7e5c1