學(xué)習(xí) Webpack5 之路(實(shí)踐篇)
點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號
回復(fù)算法,加入前端編程面試算法每日一題群
前言
在上篇 學(xué)習(xí) Webpack5 之路(基礎(chǔ)篇) 中介紹了 Webpack 是什么,為什么選擇 Webpack,Webpack 的基本概念介紹 3 個(gè)問題。
本篇將從實(shí)踐出發(fā),在第一章節(jié)《基礎(chǔ)配置》中使用 webpack 搭建一個(gè)基礎(chǔ)的支持模塊化開發(fā)的項(xiàng)目,在第二章節(jié)《進(jìn)階配置》中使用 webpack 搭建一個(gè) SASS + TS + React 的項(xiàng)目。
本文依賴的 webpack 版本信息如下:
-
webpack-cli\@4.7.2[2] -
webpack\@5.46.0[3]
一、基礎(chǔ)配置
接下來一起配置一個(gè)基礎(chǔ)的 Webpack。
將支持以下功能:
-
分離開發(fā)環(huán)境、生產(chǎn)環(huán)境配置; -
模塊化開發(fā); -
sourceMap 定位警告和錯(cuò)誤; -
動態(tài)生成引入 bundle.js 的 HTML5 文件; -
實(shí)時(shí)編譯; -
封裝編譯、打包命令。
想直接看配置的同學(xué) -> 本文源碼地址:webpack Demo0[4]
1. 新建項(xiàng)目
新建一個(gè)空項(xiàng)目:
// 新建 webpack-demo 文件夾
mkdir webpack-demo
// 進(jìn)入 webpack-demo 目錄
cd ./webpack-demo
// 初始化項(xiàng)目
npm init -y
復(fù)制代碼
新建 2 個(gè) js 文件,并進(jìn)行模塊化開發(fā):
// 進(jìn)入項(xiàng)目目錄
cd ./webpack-demo
// 創(chuàng)建 src 文件夾
mkdir src
// 創(chuàng)建 js文件
touch index.js
touch hello.js
復(fù)制代碼
index.js:
// index.js
import './hello.js'
console.log('index')
復(fù)制代碼
hello.js:
// hello.js
console.log('hello webpack')
復(fù)制代碼
項(xiàng)目結(jié)構(gòu)如下:
- src
- index.js
- hello.js
- package.json
- node_modules
復(fù)制代碼
2. 安裝
-
安裝 Node
Node 需要是最新版本,推薦使用 nvm 來管理 Node 版本。
將 Node.js 更新到最新版本,也有助于提高性能。除此之外,將你的 package 管理工具(例如
npm或者yarn)更新到最新版本,也有助于提高性能。較新的版本能夠建立更高效的模塊樹以及提高解析速度。
-
Node:安裝地址[5]
-
nvm:安裝地址[6]
我安裝的版本信息如下:
-
node v14.17.3 -
npm v6.14.13)
-
安裝 webpack
npm install webpack webpack-cli --save-dev
復(fù)制代碼
3. 新建配置文件
development(開發(fā)環(huán)境) 和 production(生產(chǎn)環(huán)境) 這兩個(gè)環(huán)境下的構(gòu)建目標(biāo)存在著巨大差異。為代碼清晰簡明,為每個(gè)環(huán)境編寫彼此獨(dú)立的 webpack 配置。
// 進(jìn)入項(xiàng)目目錄
cd ./webpack-demo
// 創(chuàng)建 config 目錄
mkdir config
// 進(jìn)入 config 目錄
cd ./config
// 創(chuàng)建通用環(huán)境配置文件
touch webpack.common.js
// 創(chuàng)建開發(fā)環(huán)境配置文件
touch webpack.dev.js
// 創(chuàng)建生產(chǎn)環(huán)境配置文件
touch webpack.prod.js
復(fù)制代碼
webpack-merge
使用 webpack-marge 合并通用配置和特定環(huán)境配置。
安裝 webpack-merge:
npm i webpack-merge -D
復(fù)制代碼
通用環(huán)境配置:
// webpack.common.js
module.exports = {} // 暫不添加配置
復(fù)制代碼
開發(fā)環(huán)境配置:
// webpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {}) // 暫不添加配置
復(fù)制代碼
生產(chǎn)環(huán)境配置:
// webpack.prod.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {}) // 暫不添加配置
復(fù)制代碼
項(xiàng)目結(jié)構(gòu)如下:
- config
- webpack.common.js
- webpack.dev.js
- webpack.prod.js
- src
- index.js
- hello.js
- package.json
- node_modules
復(fù)制代碼
4. 入口(entry)
入口起點(diǎn)(entry point) 指示 webpack 應(yīng)該使用哪個(gè)模塊,來作為構(gòu)建其內(nèi)部 依賴圖\(dependency graph\)[7] 的開始。進(jìn)入入口起點(diǎn)后,webpack 會找出有哪些模塊和庫是入口起點(diǎn)(直接和間接)依賴的。
在本例中,使用 src/index.js 作為項(xiàng)目入口,webpack 以 src/index.js 為起點(diǎn),查找所有依賴的模塊。
修改 webpack.commom.js:
module.exports = merge(common, {
// 入口
entry: {
index: './src/index.js',
},
})
復(fù)制代碼
5. 輸出(output)
output 屬性告訴 webpack 在哪里輸出它所創(chuàng)建的 bundle,以及如何命名這些文件。
生產(chǎn)環(huán)境的 output 需要通過 contenthash 值來區(qū)分版本和變動,可達(dá)到清緩存的效果,而本地環(huán)境為了構(gòu)建效率,則不引人 contenthash。
新增 paths.js,封裝路徑方法:
const fs = require('fs')
const path = require('path')
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
resolveApp,
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appSrc: resolveApp('src'),
appDist: resolveApp('dist'),
appTsConfig: resolveApp('tsconfig.json'),
}
復(fù)制代碼
修改開發(fā)環(huán)境配置文件 webpack.dev.js:
module.exports = merge(common, {
// 輸出
output: {
// bundle 文件名稱
filename: '[name].bundle.js',
// bundle 文件路徑
path: resolveApp('dist'),
// 編譯前清除目錄
clean: true
},
})
復(fù)制代碼
修改生產(chǎn)環(huán)境配置文件 webpack.prod.js:
module.exports = merge(common, {
// 輸出
output: {
// bundle 文件名稱 【只有這里和開發(fā)環(huán)境不一樣】
filename: '[name].[contenthash].bundle.js',
// bundle 文件路徑
path: resolveApp('dist'),
// 編譯前清除目錄
clean: true
},
})
復(fù)制代碼
上述 filename 的占位符解釋如下:
-
[name]- chunk name(例如[name].js->app.js)。如果 chunk 沒有名稱,則會使用其 id 作為名稱 -
[contenthash]- 輸出文件內(nèi)容的 md4-hash(例如[contenthash].js->4ea6ff1de66c537eb9b2.js)
6. 模式(mode)
通過 mode 配置選項(xiàng),告知 webpack 使用相應(yīng)模式的內(nèi)置優(yōu)化。
| 選項(xiàng) | 描述 |
|---|---|
development |
會將 DefinePlugin 中 process.env.NODE_ENV 的值設(shè)置為 development. 為模塊和 chunk 啟用有效的名。 |
production |
會將 DefinePlugin 中 process.env.NODE_ENV 的值設(shè)置為 production。為模塊和 chunk 啟用確定性的混淆名稱,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。 |
修改開發(fā)環(huán)境配置文件 webpack.dev.js:
module.exports = merge(common, {
// 開發(fā)模式
mode: 'development',
})
復(fù)制代碼
修改生產(chǎn)環(huán)境配置文件 webpack.prod.js:
module.exports = merge(common, {
// 生產(chǎn)模式
mode: 'production',
})
復(fù)制代碼
7. Source Map
當(dāng) webpack 打包源代碼時(shí),可能會很難追蹤到 error 和 warning 在源代碼中的原始位置。
為了更容易地追蹤 error 和 warning,JavaScript 提供了 source maps[8] 功能,可以將編譯后的代碼映射回原始源代碼。
修改開發(fā)環(huán)境配置文件 webpack.dev.js:
module.exports = merge(common, {
// 開發(fā)工具,開啟 source map,編譯調(diào)試
devtool: 'eval-cheap-module-source-map',
})
復(fù)制代碼
source map 有許多 可用選項(xiàng)[9]。本例選擇的是 eval-cheap-module-source-map
注:為加快生產(chǎn)環(huán)境打包速度,不為生產(chǎn)環(huán)境配置 devtool。
完成上述配置后,可以通過 npx webpack \--config config/webpack.prod.js 打包編譯。
編譯后,會生成這樣的目錄結(jié)構(gòu):
8. HtmlWebpackPlugin
npx webpack \--config config/webpack.prod.js 后僅生成了 bundle.js,我們還需要一個(gè) HTML5 文件,用來動態(tài)引入打包生成的 bundle 文件。
引入 HtmlWebpackPlugin 插件,生成一個(gè) HTML5 文件, 其中包括使用 script 標(biāo)簽的 body 中的所有 webpack 包。
安裝:
npm install --save-dev html-webpack-plugin
復(fù)制代碼
修改通用環(huán)境配置文件 webpack.commom.js:
module.exports = {
plugins: [
// 生成html,自動引入所有bundle
new HtmlWebpackPlugin({
title: 'release_v0',
}),
],
}
復(fù)制代碼
重新 webpack 編譯 npx webpack \--config config/webpack.prod.js,生成的目錄結(jié)構(gòu)如下:
新生成了 index.html,動態(tài)引入了 bundle.js 文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>release_v0</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script defer="defer" src="index.468d741515bfc390d1ac.bundle.js"></script>
</head>
<body></body>
</html>
復(fù)制代碼
9. DevServer
在每次編譯代碼時(shí),手動運(yùn)行 npx webpack \--config config/webpack.prod.js 會顯得很麻煩,webpack-dev-server[10] 幫助我們在代碼發(fā)生變化后自動編譯代碼。
webpack-dev-server 提供了一個(gè)基本的 web server,并且具有實(shí)時(shí)重新加載功能。
webpack-dev-server 默認(rèn)配置 conpress: true,為每個(gè)靜態(tài)文件開啟 gzip compression[11]。
安裝:
npm install --save-dev webpack-dev-server
復(fù)制代碼
修改開發(fā)環(huán)境配置文件 webpack.dev.js:
module.exports = merge(common, {
devServer: {
// 告訴服務(wù)器從哪里提供內(nèi)容,只有在你想要提供靜態(tài)文件時(shí)才需要。
contentBase: './dist',
},
})
復(fù)制代碼
完成上述配置后,可以通過 npx webpack serve \--open \--config config/webpack.dev.js 實(shí)時(shí)編譯。
效果如圖:
10.執(zhí)行命令
上述配置文件完成后,優(yōu)化 webpack 的實(shí)時(shí)編譯、打包編譯指令。
通過 cross-env 配置環(huán)境變量,區(qū)分開發(fā)環(huán)境和生產(chǎn)環(huán)境。
安裝:
npm install --save-dev cross-env
復(fù)制代碼
修改 package.json:
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve --open --config config/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"
},
}
復(fù)制代碼
現(xiàn)在可以運(yùn)行 webpack 指令:
-
npm run dev:本地構(gòu)建; -
npm run build:生產(chǎn)打包。
以上我們完成了一個(gè)基于 webpack 編譯的支持模塊化開發(fā)的簡單項(xiàng)目。
源碼地址:webpack Demo0[12]
二、進(jìn)階配置
本章節(jié)將繼續(xù)完善配置,在上述配置基礎(chǔ)上,用 Webpack 搭建一個(gè) SASS + TS + React 的項(xiàng)目。
將支持以下功能:
-
加載圖片; -
加載字體; -
加載 CSS; -
使用 SASS; -
使用 PostCSS,并自動為 CSS 規(guī)則添加前綴,解析最新的 CSS 語法,引入 css-modules 解決全局命名沖突問題; -
使用 React; -
使用 TypeScript。
想直接看配置的同學(xué) -> 本文源碼地址:webpack Demo1[13]
1. 加載圖片(Image)
在 webpack 5 中,可以使用內(nèi)置的 Asset Modules[14],將 images 圖像混入我們的系統(tǒng)中。
修改通用環(huán)境配置文件 webpack.commom.js:
const paths = require('./paths');
module.exports = {
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
include: paths.appSrc,
type: 'asset/resource',
},
],
},
}
復(fù)制代碼
在實(shí)際開發(fā)過程中,推薦將大圖片上傳至 CDN,提高加載速度。
2. 加載字體(Font)
使用 Asset Modules[15] 接收字體文件。
修改通用環(huán)境配置文件 webpack.commom.js:
module.exports = {
module: {
rules: [
{
test: /.(woff|woff2|eot|ttf|otf)$/i,
include: [
resolveApp('src'),
],
type: 'asset/resource',
},
]
}
}
復(fù)制代碼
在這里我發(fā)現(xiàn),我不新增對 ttf 文件的配置,也能夠引入字體不報(bào)錯(cuò),不知道是什么問題,先記錄一下,有知道原因的大佬移步評論區(qū)。
在實(shí)際開發(fā)過程中,推薦將字體文件壓縮上傳至 CDN,提高加載速度。如配置字體的文字是固定的,還可以針對固定的文字生成字體文件,可以大幅縮小字體文件體積。
3. 加載 CSS
為了在 JavaScript 模塊中 import 一個(gè) CSS 文件,需要安裝并配置 style-loader[16] 和 css-loader[17]。
3.1 style-loader[18]
style-loader 用于將 CSS 插入到 DOM 中,通過使用多個(gè) <style></style> 自動把 styles 插入到 DOM 中.
3.2 css-loader[19]
css-loader 對 @import 和 url() 進(jìn)行處理,就像 js 解析 import/require() 一樣,讓 CSS 也能模塊化開發(fā)。
3.3 安裝配置
安裝 CSS 相關(guān)依賴:
npm install --save-dev style-loader css-loader
復(fù)制代碼
修改通用環(huán)境配置文件 webpack.commom.js:
const paths = require('./paths');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
include: paths.appSrc,
use: [
// 將 JS 字符串生成為 style 節(jié)點(diǎn)
'style-loader',
// 將 CSS 轉(zhuǎn)化成 CommonJS 模塊
'css-loader',
],
},
]
}
}
復(fù)制代碼
4. 使用 SASS
4.1 Sass[20]
Sass[21] 是一款強(qiáng)化 CSS 的輔助工具,它在 CSS 語法的基礎(chǔ)上增加了變量、嵌套、混合、導(dǎo)入等高級功能。
4.2 sass-loader[22]
sass-loader[23] 加載 Sass/SCSS 文件并將他們編譯為 CSS。
4.3 安裝配置
安裝 SASS 相關(guān)依賴:
npm install --save-dev sass-loader sass
復(fù)制代碼
修改通用環(huán)境配置文件 webpack.commom.js:
const paths = require('./paths');
module.exports = {
module: {
rules: [
{
test: /.(scss|sass)$/,
include: paths.appSrc,
use: [
// 將 JS 字符串生成為 style 節(jié)點(diǎn)
'style-loader',
// 將 CSS 轉(zhuǎn)化成 CommonJS 模塊
'css-loader',
// 將 Sass 編譯成 CSS
'sass-loader',
],
},
復(fù)制代碼
5. 使用 PostCSS
5.1 PostCSS[24]
PostCSS[25] 是一個(gè)用 JavaScript 工具和插件轉(zhuǎn)換 CSS 代碼的工具。
-
可以自動為 CSS 規(guī)則添加前綴; -
將最新的 CSS 語法轉(zhuǎn)換成大多數(shù)瀏覽器都能理解的語法; -
css-modules 解決全局命名沖突問題。
5.2 postcss-loader[26]
postcss-loader[27] 使用 PostCSS[28] 處理 CSS 的 loader。
5.3 安裝配置
安裝 PostCSS 相關(guān)依賴:
npm install --save-dev postcss-loader postcss postcss-preset-env
復(fù)制代碼
修改通用環(huán)境配置文件 webpack.commom.js:
const paths = require('./paths');
module.exports = {
module: {
rules: [
{
test: /\.module\.(scss|sass)$/,
include: paths.appSrc,
use: [
// 將 JS 字符串生成為 style 節(jié)點(diǎn)
'style-loader',
// 將 CSS 轉(zhuǎn)化成 CommonJS 模塊
{
loader: 'css-loader',
options: {
// Enable CSS Modules features
modules: true,
importLoaders: 2,
// 0 => no loaders (default);
// 1 => postcss-loader;
// 2 => postcss-loader, sass-loader
},
},
// 將 PostCSS 編譯成 CSS
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
// postcss-preset-env 包含 autoprefixer
'postcss-preset-env',
],
],
},
},
},
// 將 Sass 編譯成 CSS
'sass-loader',
],
},
],
},
}
復(fù)制代碼
為提升構(gòu)建效率,為 loader 指定 include,通過使用
include字段,僅將 loader 應(yīng)用在實(shí)際需要將其轉(zhuǎn)換的模塊。
5. 使用 React + TypeScript
為了讓項(xiàng)目的配置靈活性更高,不使用 create-reate-app 一鍵搭建項(xiàng)目,而是手動搭建 React 對應(yīng)的配置項(xiàng)。
安裝 React 相關(guān):
npm i react react-dom @types/react @types/react-dom -D
復(fù)制代碼
安裝 TypeScript 相關(guān):
npm i -D typescript esbuild-loader
復(fù)制代碼
為提高性能,摒棄了傳統(tǒng)的 ts-loader,選擇最新的 esbuild-loader。
修改通用環(huán)境配置文件 webpack.commom.js:
const paths = require('./paths');
module.exports = {
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
module: {
rules: [
{
test: /\.(js|ts|jsx|tsx)$/,
include: paths.appSrc,
use: [
{
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015',
},
}
]
},
]
}
}
復(fù)制代碼
TypeScript[29] 是 JavaScript 的超集,為其增加了類型系統(tǒng),可以編譯為普通 JavaScript 代碼。
為兼容 TypeScript 文件,新增 typescript 配置文件 tsconfig.json:
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
}
}
復(fù)制代碼
如果想在 TypeScript 中保留如
import _ from 'lodash';的語法被讓它作為一種默認(rèn)的導(dǎo)入方式,需要在文件 tsconfig.json 中設(shè)置"allowSyntheticDefaultImports" : true和"esModuleInterop" : true。
注意:這兒有坑
1. "allowSyntheticDefaultImports": true 配置
TypeScript 配置文件 tsconfig.json 需要加 "allowSyntheticDefaultImports": true 配置,否則會提示 can only be default-imported using the 'allowSyntheticDefaultImports' flag。
{
"compilerOptions": {
"allowSyntheticDefaultImports": true
},
}
復(fù)制代碼
不加 "allowSyntheticDefaultImports": true 的加報(bào)錯(cuò)信息如下:
2. tsx 和 jsx 不能混合使用
在 tsx 中引入 jsx 文件報(bào)錯(cuò)如下:
以上我們完成了一個(gè)基于 webpack 編譯的 SASS + TS + React 項(xiàng)目。
源碼地址:webpack Demo1[30]:https://github.com/jiaozitang/webpack-demo/tree/release_v1
三、總結(jié)
本文從 Webpack 基礎(chǔ)配置、Webpack 進(jìn)階配置 2 個(gè)角度進(jìn)行講述,從 Webpack 實(shí)踐著手,和你一起了解 Webpack。
下一篇《學(xué)習(xí) Webpack5 之路(優(yōu)化篇)》將從繼續(xù)優(yōu)化項(xiàng)目配置,嘗試搭建一個(gè)最優(yōu)的 Webpack 配置,敬請期待。
本文源碼:
-
webpack Demo0[31]:https://github.com/jiaozitang/webpack-demo/tree/release_v0 -
webpack Demo1[32]:https://github.com/jiaozitang/webpack-demo/tree/release_v1
希望能對你有所幫助,感謝閱讀~
別忘了點(diǎn)個(gè)贊鼓勵(lì)一下我哦,筆芯??
關(guān)于本文
來源:清湯餃子
https://juejin.cn/post/6991774994552324133
