前言
webpack 作為前端最知名的打包工具,能夠把散落的模塊打包成一個完整的應用,大多數的知名框架 cli 都是基于 webpack 來撰寫,這些 cli 為使用者預設好各種處理配置,使用多了就會覺得理所當然,也就不在意是內部是如何配置,如果脫離 cli 開發,可能就無從下手了,
最近在開發一些單頁專案時,出于需求便開始從頭搭建專案配置,本文主要分享搭建時用到的配置,
準備作業
快速生成 package.json:
npm init -y
必不可少的 webpack 和 webpack-cli:
npm i webpack webpack-cli -D
入口、出口
webpack 的配置會統一放到組態檔中去管理,在根目錄下新建一個名為 webpack.config.js 的檔案:
const path = require('path')
module.exports = {
entry: {
main: './src/js/main.js'
},
output: {
// filename 定義打包的檔案名稱
// [name] 對應entry配置中的入口檔案名稱(如上面的main)
// [hash] 根據檔案內容生成的一段隨機字串
filename: '[name].[hash].js',
// path 定義整個打包檔案夾的路徑,檔案夾名為 dist
path: path.join(__dirname, 'dist')
}
}
entry 配置入口,可配置多個入口,webpack 會從入口檔案開始尋找相關依賴,進行決議和打包,
output 配置出口,多入口對應多出口,即入口配置多少個檔案,打包出來也是對應的檔案,
修改 package.json 的 script 配置:
"scripts": {
"build": "webpack --config webpack.config.js"
}
這樣一個最簡單的配置就完成了,通過命令列輸入 npm run build 就可以實作打包,
配置項智能提示
webpack 的配置項比較繁雜,對于不熟悉的同學來說,如果在輸入配置項能夠提供智能提示,那開發的效率和準確性會大大提高,
默認 VSCode 并不知道 webpack 配置物件的型別,通過 import 的方式匯入 webpack 模塊中的 Configuration 型別后,在書寫配置項就會有智能提示了,
/** @type {import('webpack').Configuration} */
module.exports = {
}

環境變數
一般開發中會分開發和生產兩種環境,而 webpack 的一些配置也會隨環境的不同而變化,因此環境變數是很重要的一項功能,使用 cross-env 模塊可以為組態檔注入環境變數,
安裝模塊 cross-env:
npm i cross-env -D
修改 package.json 的命令傳入變數:
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
在組態檔里,就可以這樣使用傳入的變數:
module.exports = {
devtool: process.env.NODE_ENV === 'production' ? 'none' : 'source-map'
}
同理,在專案的 js 內也可以使用該變數,
設定 source-map
該選項能設定不同型別的 source-map ,代碼經過壓縮后,一旦報錯不能準確定位到具體位置,而 source-map 就像一個地圖, 能夠對應源代碼的位置,這個選項能夠幫助開發者增強除錯程序,準確定位錯誤,
為了體驗它的作用,我在源代碼中故意輸出一個不存在的變數,模擬線上錯誤:

在預覽時,觸發錯誤:

很明顯錯誤的行數是不對應的,下面設定 devtool 讓 webpack 在打包后輸出 source-map 檔案,用于定位錯誤,
module.exports = {
devtool: 'source-map'
}
再次觸發錯誤,source-map 檔案起作用準確定位到代碼錯誤的行數,

source-map 一般只在開發環境用于除錯,上線時絕對不能帶有 source-map 檔案,這樣會暴露源代碼,下面通過環境變數來正確設定 devtool 選項,
module.exports = {
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none'
}
devtool 的可選項很多,它的品質和生成速度有關聯,比如只定位到某個檔案,或者定位到某行某列,相應的生成速度會快或更慢,可以根據你的需求進行選擇,更多可選值請查看 webpack 檔案
loader 與 plugin
loader 與 plugin 是 webpack 的靈魂,如果把 webpack 比作成一個食品加工廠,那么 loader 就像很多條流水線,對食品原料進行加工處理,plugins 則是在原有的功能上,添加其他功能,
loader 基本用法
loader 配置在 module.rules 屬性上,
module.exports = {
module: {
rules: []
}
}
下面來簡單了解下 loader 的規則,一般常用的就是 test 和 use 兩個屬性:
test屬性,用于標識出應該被對應的 loader 進行轉換的某個或某些檔案,use屬性,表示進行轉換時,應該使用哪個 loader,
rules: [
{
test: /\.css$/,
use: ['css-loader']
}
]
// 也可以寫為
rules: [
{
test: /\.css$/,
loader: 'css-loader'
}
]
上面例子是匹配以 .css 結尾的檔案,使用 css-loader 決議,
當有些 loader 可傳入配置時,可以改為物件的形式:
rules: [
{
test: /\.css$/,
user: [
{
loader: 'css-loader',
options: {}
}
]
}
]
最后需要記住多個 loader 的執行是嚴格區分先后順序的,從右到左,從下到上,
plugin 基本用法
plugin 配置在 plugins 屬性上,
module.exports = {
plugins: []
}
一般 plugin 會暴露一個建構式,通過 new 的方式使用,plugin 的引數則在呼叫函式時傳入,plugin 命名一般是將按照原名轉為大駝峰,
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugin: [
new CleanWebpackPlugin()
]
}
生成 html 檔案
沒有經過任何配置的 webpack 打包出來只有 js 檔案,使用插件 html-webpack-plugin 可以自定義一個 html 檔案作為模版,最終 html 會被打包到 dist 中,而 js 也會被引入其中,
安裝 html-webpack-plugin:
npm i html-webpack-plugin -D
配置 plugins:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: {
// 使用html模版
new HtmlWebpackPlugin({
// 配置html標題
title: 'home',
// 模版路徑
template: './src/index.html',
// 壓縮
minify: true
})
}
}
此時想要配置的標題生效還需要在 html 中為 title 標簽插值:
<title><%= htmlWebpackPlugin.options.title %></title>
除了 title 外,還可以配置諸如 meta 標簽等,
多頁面
上面的使用方法,在打包后只會有一個 html,對于多頁面的需求其實也很簡單,有多少個頁面就 new 幾次 htmlWebpackPlugin,
但是需要注意一點,入口配置的 js 是會被全部引入到 html 的,如果想某個 js 對應某個 html,可以配置插件的 chunks 選項,而且需要配置 filename 屬性,因為 filename 屬性默認是 index.html,同名會被覆寫,
module.exports = {
entry: {
main: './src/js/main.js',
news: './src/js/news.js'
},
output: {
filename: '[name].[hash].js',
path: path.join(__dirname, 'dist')
},
plugins: {
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
minify: true,
chunks: ['main']
}),
new HtmlWebpackPlugin({
template: './src/news.html',
filename: 'news.html',
minify: true,
chunks: ['news']
}),
}
}
es6轉es5
安裝 babel 的相關模塊:
npm i babel-loader @babel/core @babel/preset-env -D
@babel/core 是 babel 的核心模塊,@babel/preset-env 內預設不同環境的語法轉換插件,默認將 es6 轉 es5,
根目錄下創建 .babelrc 檔案:
{
"presets": ["@babel/preset-env"]
}
配置 loader:
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
決議 css
安裝 loader:
npm i css-loader style-loader -D
css-loader 只負責決議 css 檔案,通常需要配合 style-loader 將決議的內容插入到頁面,讓樣式生效,順序是先決議后插入,所以 css-loader 放在最右邊,第一個先執行,
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
但要注意最終打包出來的并不是css檔案,而是js,它是通過創建 style 標簽去插入樣式,

分離css
經過上面的 css 決議,打包出來的樣式會混在 js 中,某些場景下,我們希望把 css 單獨打包出來,這時可以使用 mini-css-extract-plugin 分離 css,
安裝 mini-css-extract-plugin:
npm i mini-css-extract-plugin -D
配置 plugins:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css'
})
]
}
配置 loader:
rules: [
{
test: /\.css$/,
use: [
// 插入到頁面中
'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: {
// 為外部資源(如影像,檔案等)指定自定義公共路徑
publicPath: '../',
}
},
'css-loader',
]
},
]
經過上面配置后,打包后 css 就會被分離出來,

但要注意如果 css 檔案不是很大的話,分離出來效果可能會適得其反,因為這樣會多一次檔案請求,一般來說單個 css 檔案超過 200kb 再考慮分離,
css瀏覽器兼容前綴
安裝相關依賴:
npm i postcss postcss-loader autoprefixer -D
專案根目錄下新建 postcss.config.js:
module.exports = {
plugins: [
require('autoprefixer')()
]
}
package.json 新增 browserslist 配置:
{
"browserslist": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
}
最后配置 postcss-loader:
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
處理前后對比:

壓縮 css
webpack 內部默認只對 js 檔案進行壓縮,css 的壓縮可以使用 optimize-css-assets-webpack-plugin 來完成,
安裝 optimize-css-assets-webpack-plugin:
npm i optimize-css-assets-webpack-plugin -D
配置 plugins:
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
plugins: [
new OptimizeCssAssetsWebpackPlugin()
]
}
壓縮結果:

決議圖片
專案中肯定少不了圖片,對于圖片資源,webpack 也有相應的 url-loader 來決議,url-loader 除了決議圖片外,還可以將比較小的圖片可以轉為base64,減少線上對圖片的請求,
安裝 url-loader:
npm i url-loader -D
配置 loader:
rules: [
{
test: /\.(jpg|png|jpeg|gif)$/,
use: [{
loader: 'url-loader',
// 小于50k的圖片轉為base64,超出的打包到images檔案夾
options: {
limit: 1024 * 50,
outputPath: './images/',
pulbicPath: './images/'
}
}]
}
]
配置完成后,只需要在入口檔案內引入圖片使用,webpack 就可以幫助我們把圖片打包出來了,
但有時候,圖片鏈接是直接寫到 html 中,這種情況 url-loader 無法決議,不慌,使用 html-loader 能完成這項需求,
rules: [
{
test: /\.(html)$/,
use: [{
loader: 'html-loader',
options: {
// 壓縮html模板空格
minimize: true,
attributes: {
// 配置需要決議的屬性和標簽
list: [{
tag: 'img',
attribute: 'src',
type: 'src',
}]
},
}
}]
},
]
注意這里 html-loader 只是起到決議的作用,需要配合 url-loader 或者 file-loader 去使用,也就是說決議模板的圖片鏈接后,還是會走上面所配置的 url-loader 的流程,
還有一點,使用 html-loader 后, html-webpack-plugin 在 html 中的插值會失效,
其他型別資源決議
決議其他資源和上面差不多,不過這里用到的是 file-loader,file-loader 和 url-loader 主要是將檔案上的 import / require() 引入的資源決議為url,并將該資源發送到輸出目錄,區別在于 url-loader 能將資源轉為 base64,
安裝 file-loader:
npm i file-loader -D
配置 loader:
rules: [
{
test:/\.(mp3)$/,
use: [{
loader: 'file-loader',
options: {
outputPath: './music/',
pulbicPath: './music/'
}
}]
},
{
test:/\.(mp4)$/,
use: [{
loader: 'file-loader',
options: {
outputPath: './video/',
pulbicPath: './video/'
}
}]
}
]
上面只是列舉了部分,需要決議其他型別資源,參照上面的格式添加配置,
決議 html 中的其他型別資源也和上面同理,使用 html-loader 配置物件的標簽和屬性即可,
devServer 提高開發效率
每次想運行專案時,都需要 build 完再去預覽,這樣的開發效率很低,
官方為此提供了插件 webpack-dev-server,它可以本地開啟一個服務器,通過訪問本地服務器來預覽專案,當專案檔案發生變化時會熱更新,無需再去手動重繪,以此提高開發效率,
安裝 webpack-dev-server:
npm i webpack-dev-server -D
配置 webpack.config.js:
const path = require('path')
module.exports = {
devServer: {
contentBase: path.join(__dirname, 'dist'),
// 默認 8080
port: 8000,
compress: true,
open: true,
}
}
webpack-dev-server 用法和其他插件不同,它可以不添加到 plugins,只需將配置添加到 devServer 屬性下即可,
添加啟動命令 package.json:
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve"
},
}
命令列運行 npm run dev,就可以感受到飛一般的體驗,
復制檔案到 dist
對于一些不需要經過決議的檔案,在打包后也想將它放到 dist 中,可以使用 copy-webpack-plugin,
安裝 copy-webpack-plugin:
npm i copy-webpack-plugin -D
配置 plugins:
const CopyPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
// 資源路徑
from: 'src/json',
// 目標檔案夾
to: 'json',
}
]
}),
]
}
打包前清除舊 dist
打包后檔案一般都會帶有哈希值,它是根據檔案的內容來生成的,由于名稱不同,可能會導致 dist 殘留有上一次打包的檔案,如果每次都手動去清除顯得不那么智能,利用 clean-webpack-plugin 可以幫助我們將上一次的 dist 清除,保證無冗余檔案,
安裝 clean-webpack-plugin:
npm i clean-webpack-plugin -D
配置 plugins:
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
插件會默認清除 output.path 的檔案夾,
自定義壓縮選項
webpack 從 v4.26.0 開始內置的壓縮插件變為 terser-webpack-plugin,如果沒有其他需求,自定義壓縮插件也盡量保持與官方的一致,
安裝 terser-webpack-plugin:
npm i terser-webpack-plugin -D
配置 plugins:
const TerserPlugin = reuqire('terser-webpack-plugin')
module.exports = {
optimization: {
// 默認為 true
minimize: true,
minimizer: [
new TerserPlugin()
]
},
}
插件壓縮配置可以查閱 terser-webpack-plugin 檔案,在呼叫時自定義選項,
像上面的 css 壓縮插件也可以添加到 optimization.minimizer,與配置到 plugins 的區別是,配置到 plugins 的插件在任何情況都會去執行,而配置到 minimizer 中,只有在 minimize 屬性開啟時才會作業,
這樣做的目的便于通過 minimize 屬性來統一控制壓縮,
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = reuqire('terser-webpack-plugin')
module.exports = {
optimization: {
// 默認為 true
minimize: true,
minimizer: [
new TerserPlugin(),
new OptimizeCssAssetsWebpackPlugin()
]
},
}
注意如果你提供 minimizer 選項而沒有使用 js 壓縮插件,即使 webpack 內置 js 壓縮,打包出來的 js 也不會被壓縮,因為 webpack 壓縮配置會被 minimizer 覆寫,
排查錯誤的建議
在使用 webpack 的程序中,這玩意偶爾會有些奇奇怪怪的報錯,
下面是我遇到的一些錯誤以及解決方法(僅供參考并不是萬能法則):
- 一些 loader 和 plugin 在使用時,會依賴 webpack 的版本,如果使用程序發生錯誤,檢查是否有版本不兼容的問題,可以嘗試降一個版本,
- 重新安裝依賴,有可能下載程序中,一些依賴會沒裝上,
- 查看使用檔案,不同版本所傳入的選項屬性可能會不一樣(被坑過) ,
還有注意控制臺的提示,一般根據錯誤提示都能猜出大概是什么問題,
依賴版本和完整配置
專案結構:

依賴版本:
{
"@babel/core": "^7.12.3",
"@babel/preset-env": "^7.12.1",
"autoprefixer": "^10.0.1",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.3.0",
"cross-env": "^7.0.2",
"css-loader": "^5.0.0",
"file-loader": "^6.2.0",
"html-loader": "^1.3.2",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss": "^8.1.6",
"postcss-loader": "^4.0.4",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^4.44.2",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
}
webpack.config.js:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
/** @type {import('webpack').Configuration} */
module.exports = {
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none',
entry: {
main: './src/js/main.js'
},
output: {
filename: '[name].[hash].js',
path: path.join(__dirname, 'dist')
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 8000,
compress: true,
open: true,
},
plugins: [
// 清除上一次的打包內容
new CleanWebpackPlugin(),
// 復制檔案到dist
new CopyPlugin({
patterns: [
{
from: 'src/music',
to: 'music',
}
]
}),
// 使用html模版
new HtmlWebpackPlugin({
template: './src/index.html',
minify: true
}),
// 分離css
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css'
}),
// 壓縮css
new OptimizeCssAssetsWebpackPlugin()
],
module: {
rules: [
// 決議js(es6轉es5)
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
}
},
'css-loader',
'postcss-loader'
]
},
{
test: /\.(html)$/,
use: [{
// 主要為了決議html中的img圖片路徑 需要配合url-loader或file-loader使用
loader: 'html-loader',
options: {
attributes: {
list: [{
tag: 'img',
attribute: 'src',
type: 'src',
},{
tag: 'source',
attribute: 'src',
type: 'src',
}]
},
minimize: true
}
}]
},
// 決議圖片
{
test: /\.(jpg|png|gif|svg)$/,
use: [{
loader: 'url-loader',
// 小于50k的圖片轉為base64,超出的打包到images檔案夾
options: {
limit: 1024 * 50,
outputPath: './images/',
pulbicPath: './images/'
}
}]
},
// 決議其他型別檔案(如字體)
{
test: /\.(eot|ttf)$/,
use: ['file-loader']
},
{
test: /\.(mp3)$/,
use: [{
loader: 'file-loader',
options: {
outputPath: './music/',
pulbicPath: './music/'
}
}]
}
]
},
}
最后
由于單頁專案簡單,配置項比較樸實無華,本文主要是些基礎配置,不過套路都差不多,根據專案的需求去選擇 loader 和 plugin,更多的還是要了解這些插件的作用和使用方法,以及其他常用的插件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/237057.html
標籤:架構設計
上一篇:springcloud 專案原始碼 微服務 分布式 flowable作業流 vue.js html 跨域 前后分離
