前言
在平時(shí)工作中,為減少開發(fā)成本,一般都會(huì)使用腳手架來進(jìn)行開發(fā),比如 create-react-app
。腳手架都會(huì)幫我們配置好了 webpack,但如果想自己搭建 webpack 項(xiàng)目要怎么做呢?這邊文章將介紹如何使用 webpack 5 來搭建 react 項(xiàng)目,項(xiàng)目地址在文末。
一、簡(jiǎn)單聊下 Webpack
1.1 Webpack 的好處
試想在不使用任何打包工具的情況下,我們很難在項(xiàng)目去使用 es6+
新語法,TypeScript
即使是新的瀏覽器,也不支持,更別說在項(xiàng)目中使用 React
、Vue
了。打包工具能幫我們解決這些問題,打包工具有很多,比如 Webpack、Vite、Snowpack、Rspack 等,這里介紹 Webapck,畢竟生態(tài)圈大。
Webpack 是一個(gè) JavaScript 應(yīng)用程序的靜態(tài)模塊打包工具。 它可以幫我們分析項(xiàng)目結(jié)構(gòu),將模塊打包,最終得以在瀏覽器中直接使用。 Webpack 有哪些好處呢?
開發(fā)環(huán)境:
-
新特性&新語法: 像
ESNext
新特性,.less、.ts、tsx/jsx、.vue 等瀏覽器無法識(shí)別的格式文件d都能在開發(fā)中使用。Webpack 的 Loader 機(jī)制能幫助進(jìn)行轉(zhuǎn)換。 - 模塊化: 在 Webpack 中,一切皆為模塊, 我們可以使用模塊化編程,把復(fù)雜的程序細(xì)化為小的模塊文件。
- 模塊熱替換(HMR): 提供模塊熱替換功能, 在修改代碼后,不需要重新加載整個(gè)頁面,只需要替換修改的模塊,從而提高開發(fā)效率。
- Source Map: 提供了 Source Map 功能,可以將編譯后的代碼映射回原始源代碼,從而方便我們進(jìn)行調(diào)試
生產(chǎn)環(huán)境:
- 性能優(yōu)化:可以壓縮代碼,合并文件,從而減少網(wǎng)絡(luò)請(qǐng)求。
- 代碼分割:可以進(jìn)行代碼分割,實(shí)現(xiàn)按需加載或者并行加載,從而減少頁面加載時(shí)間,提高用戶體驗(yàn)。
- 緩存優(yōu)化:可以根據(jù)文件內(nèi)容生成 hash 值,從而實(shí)現(xiàn)緩存優(yōu)化,減少網(wǎng)絡(luò)請(qǐng)求和服務(wù)器負(fù)載。
1.2 Webpack 的基本概念
這里我們先簡(jiǎn)單熟悉下 Webpack 基本概念,下面搭建項(xiàng)目時(shí)都要用到。
- entry: 使用哪個(gè)模塊來作為構(gòu)建的起始入口。
- output: 最終打包后的文件放在哪里,以及如何命名這些文件。
- loader: 是處理文件的轉(zhuǎn)換器,用于對(duì)模塊源碼進(jìn)行轉(zhuǎn)換,webpack 只能識(shí)別 js、json 文件,像 css 、ts 、jsx等文件都需要通過 loader 進(jìn)行轉(zhuǎn)換。
- plugin: 是一種可擴(kuò)展的機(jī)制,可以打包過程中添加額外的功能。比如打包優(yōu)化,資源管理,注入環(huán)境變量等。
-
mode: 對(duì)于不同的環(huán)境,我們往往需要不同的配置,通過設(shè)置
mode
參數(shù)來選擇環(huán)境。
二、搭建 React 項(xiàng)目
上面簡(jiǎn)單介紹了 webpack,接下來開始搭建我們的項(xiàng)目。
2.1 項(xiàng)目初始化
我們使用 pnpm 來初始化一個(gè)項(xiàng)目(8.x 版本需要 node 在 16 + ),為什么選用 pnpm ,可以看下包管理工具 —— 更推薦的 pnpm。
mkdir create-react
cd create-react
pnpm init --y
git init
2.2 安裝配置 react & TypeScript
引入 react
、react-dom
和對(duì)應(yīng)的類型包 @types/react 、@types/react-dom。這里使用的版本是18.2.0。
pnpm add react react-dom
pnpm add -D @types/react @types/react-dom
然后配置 TypeScript
pnpm add typescript -D
有了 TypeScript,就可以直接通過 tsc 命令生成一個(gè) tsconfig.json
的配置文件
tsc --init
可以按照所需手動(dòng)修改 ts 的配置文件。
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"module": "ESNext",
"moduleResolution": "node",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"common/*": [
"src/common/*"
],
"@/*": [
"src/*"
]
},
"strict": true,
"sourceMap": true,
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"importHelpers": true,
"noUnusedLocals": true,
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["src"]
}
接著我們創(chuàng)建 src 目錄,在根目錄創(chuàng)建 index.tsx
,在 src 下創(chuàng)建 App.tsx 。
// index.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom/client'
import App from './src/App'
const root = ReactDOM.createRoot(document.getElementById('app')!)
// v18 的新方法
root.render(<App />)
// App.tsx
import * as React from 'react'
const App: React.FC = () => {
return <div>hello 小柒</div>
}
export default App
前置準(zhǔn)備已經(jīng)做好, 接下來我們來一步一步的使用 webpack 打包 React 項(xiàng)目。
2.3 Webpack 相關(guān)
(1) 安裝 Webpack
- webpack 、webpack-cli :打包必備。
- webpack-dev-server: 一個(gè)提供熱更新的開發(fā)服務(wù)器,對(duì)開發(fā)階段友好。
- webpack-merge: 用來合并配置文件。
pnpm add webpack webpack-cli webpack-dev-server webpack-merge -D
(2) 配置 Webpack 文件
Webpack 默認(rèn)讀取的是 webpack.config.js
文件,但在實(shí)際開發(fā)中我們需要將生產(chǎn)環(huán)境和開發(fā)環(huán)境分開。我們先來整理下配置文件的目錄結(jié)構(gòu),在 scripts
目錄下創(chuàng)建三個(gè)配置文件。
修改下 package.json 中的 scripts 配置,用來簡(jiǎn)化命令。
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve -c scripts/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack -c scripts/webpack.prod.js"
},
我們使用 NODE_ENV = production
來設(shè)置環(huán)境變量,為了在不同的平臺(tái)上都能使用,我們使用 cross-env 來兼容,這樣在不同環(huán)境下也能正確獲取環(huán)境變量。
pnpm add cross-env -D
我們將一些通用的配置寫在 webpack.base.js
文件里。
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, '../src/index.tsx'),
output: {
path: path.resolve(__dirname, '../dist'), // 打包后的代碼放在dist目錄下
filename: '[name].[hash:8].js', // 打包的文件名
},
}
在 webpack.dev.js
文件和 webpack.prod.js
中引入通用配置。
// webpack.dev.js
const { merge } = require('webpack-merge')
const base = require('./webpack.base.js')
module.exports = merge(base, {
mode: 'development', // 開發(fā)模式
devServer: {
open: true, // 編譯完自動(dòng)打開瀏覽器
port: 8080,
},
})
// webpack.prod.js
const { merge } = require('webpack-merge')
const base = require('./webpack.base.js')
module.exports = merge(base, {
mode: 'producton', // 生產(chǎn)模式
})
到這里環(huán)境基本搭建好了,接下來我們就一步一步的來完善配置。
(3) 配置 babel
由于 webpack 只能識(shí)別js、json 文件, 無法識(shí)別 jsx/tsx 文件,此時(shí)如果我們嘗試啟動(dòng)項(xiàng)目肯定會(huì)報(bào)錯(cuò)。如何讓 webpack 能識(shí)別呢?此時(shí)我們就需要使用 babel-loader
來轉(zhuǎn)換代碼,babel-loader 可以讓 webpack 在構(gòu)建的時(shí)候借助 Babel 對(duì)JS代碼進(jìn)行轉(zhuǎn)譯。
注意??: Babel 是一個(gè) JavaScript 編譯器。主要用于將高版本的JavaScript代碼轉(zhuǎn)為向后兼容的JS代碼,從而能讓我們的代碼運(yùn)行在更低版本的瀏覽器或者其他的環(huán)境中。
babel-loader
的轉(zhuǎn)碼功能依賴 Babel 的核心轉(zhuǎn)碼包 @babel/core,如果要轉(zhuǎn)義 React 文件還需要引入 @babel/preset-react
這個(gè)預(yù)設(shè); 對(duì)于 ts 我們除了可以使用 ts-loader 外,也可以使用 @babel-preset-typescript
來編譯 ts 代碼;在實(shí)際項(xiàng)目中,考慮到瀏覽器的兼容性問題,我們都會(huì)設(shè)置目標(biāo)瀏覽器來轉(zhuǎn)換我們的代碼,這時(shí)候就需要使用到 @babel/preset-env
這個(gè)預(yù)設(shè)。這里就不過多的介紹 babel 配置 ,接下來我們來安裝上述提到的關(guān)于 babel 的依賴包并進(jìn)行 webpack 的配置。
pnpm add -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
我們?cè)?webpack.base.js
文件添加下面配置,我們將 js|ts、jsx|tsx 文件都交給 babel-loader 來處理,并配置對(duì)應(yīng)的 presets,這些 presets 會(huì)從右向左執(zhí)行。
{
...
resolve: {
// 配置 extensions 來告訴 webpack 在沒有書寫后綴時(shí),以什么樣的順序去尋找文件
extensions: ['.mjs','.js', '.json', '.jsx', '.ts', '.tsx'], // 如果項(xiàng)目中只有 tsx 或 ts 可以將其寫在最前面
},
module: {
rules: [
{
test: /.(jsx?)|(tsx?)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: 'iOS 9, Android 4.4, last 2 versions, > 0.2%, not dead', // 根據(jù)項(xiàng)目去配置
useBuiltIns: 'usage', // 會(huì)根據(jù)配置的目標(biāo)環(huán)境找出需要的polyfill進(jìn)行部分引入
corejs: 3, // 使用 core-js@3 版本
},
],
['@babel/preset-typescript'],
['@babel/preset-react'],
],
},
},
},
],
},
...
}
運(yùn)行 pnpm run build
,打包后會(huì)生成 dist 目錄,可以看到打包后的 js 文件。
此時(shí)如果想要在瀏覽器中訪問,我們需要手動(dòng)在 dist 目錄下添加 html 文件,并引入打包好的 js 文件:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer src="main.7a68ecf3.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
在瀏覽器中打開 html 文件,即可訪問。
如果想項(xiàng)目啟動(dòng)或打包時(shí)自動(dòng)生成 html 文件 ,要怎么做呢?我們可以借助 html-webpack-plugin 插件來幫忙自動(dòng)生成 html文件,先在根目錄創(chuàng)建一個(gè)模板 index.html
文件。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1, maximum-scale=1, minimal-ui, viewport-fit=cover"
/>
<title></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
安裝 html-webpack-plugin
:
pnpm add -D html-webpack-plugin
在 webpack.base.js
文件中添加以下配置:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../index.html'), // 使用自定義模板
}),
],
// ...
}
pnpm run build
一下,dist 目錄下已經(jīng)自動(dòng)生成了 index.html 文件,并引入了打包的 js 文件。
在 pnpm run dev
啟動(dòng)開發(fā)時(shí),我們也能在 localhost:8080 中看到頁面了。
注意??:在使用 dev-server 啟動(dòng)時(shí),它會(huì)讀取 Webpack 的配置文件(默認(rèn)是 webpack.config.js),然后將文件打包到內(nèi)存中(我們看不到 dist 文件夾的生成,Webpack 會(huì)打包到硬盤上),用默認(rèn)地址打開時(shí)默認(rèn)顯示index.html 的內(nèi)容,如果沒有index.html 文件,則顯示目錄。
(4) css 配置
項(xiàng)目中樣式的引用那是必不可少的,這里使用 less 來語法來舉例。在 src下創(chuàng)建 index.less 文件,在 App.tsx 中引入。此時(shí)啟動(dòng)項(xiàng)目,控制臺(tái)一定會(huì)報(bào)錯(cuò)。
// index.less
@color: red;
.wrapper {
display: flex;
color: @color;
}
// App.tsx
import * as React from 'react'
import './index.less'
const App: React.FC = () => {
return <div className="wrapper">hello 小柒</div>
}
export default App
less 文件可以使用 less-loader 將 less 編譯為 css,一般情況下我們還會(huì)使用 Postcss 來處理 CSS,在 Webpack 中我們可以使用 postcss-loader
來處理 css。
注意??: PostCSS
本身是一個(gè)工具,有了它我們可以使用 JavaScript 代碼來處理 CSS。它將 CSS 解析成抽象語法樹 AST, 將 AST 交給插件來處理并得到結(jié)果。PostCSS 的插件體系很強(qiáng)大,提供很多插件,比如:autoprefixer
用來添加瀏覽器前綴、 cssnano
用來壓縮 CSS、 postcss-preset-env
用來根據(jù)目標(biāo)瀏覽器生成 CSS的 polyfill等等。當(dāng)然我們也可以實(shí)現(xiàn)自己的 PostCSS 插件。
處理過的 css 可以使用 css-loader 來解析成 js ,我們來看看 css-loader 解析之后的內(nèi)容是什么。
打印出來是一個(gè)數(shù)組,第二個(gè)元素是我們想要的 css 樣式。
css-loader 只能幫我們將 css 解析成 js,但不能掛載到元素上。如果想讓 css 生效,我們要手動(dòng)掛載。
這樣就達(dá)到了我們想要的效果,不過這么寫未免有點(diǎn)憨憨。
想要自動(dòng)掛載樣式,style-loader 可以幫我們實(shí)現(xiàn),它負(fù)責(zé)將 css 樣式通過 style 標(biāo)簽插入到 DOM 中。下面是通過 style-loader 實(shí)現(xiàn)樣式掛載,自動(dòng)添加 style 標(biāo)簽到head 中。
通過上述,可以來配置我們的 webpack.dev.js 文件了。第一步還是先安裝所需要的依賴包。
// less-loader 默認(rèn)是11版本過高會(huì)報(bào)錯(cuò)。
// 兩種方法:1、要么指定低版本的 less-loader@^6.2.0 2、同時(shí)安裝 less 和 postcss
pnpm add -D style-loader css-loader postcss-loader less-loader@^6.2.0 postcss-preset-env
// 或者
pnpm add -D style-loader css-loader postcss postcss-loader less less-loader postcss-preset-env
第二步配置 webpack.dev.js
文件。這樣我們就可以在代碼中正常使用 less 啦~。
// webpack.dev.js
module: {
rules: [
// ...
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
[['postcss-preset-env', {}]]
},
},
},
'less-loader',
],
// 排除 node_modules 目錄
exclude: /node_modules/,
},
],
},
我們可以發(fā)現(xiàn),css 樣式都打包到最終中的 js 文件了,如果項(xiàng)目比較復(fù)雜,css 都打包在js文件,js的體積就會(huì)越來越大。在生產(chǎn)環(huán)境下,我們肯定希望打包出來的文件體積越小越好,在生產(chǎn)環(huán)境下,我們一般是用 MiniCssExtractPlugin 代替 style-loader
,來將打包后的 js 文件的css提取出來,單獨(dú)創(chuàng)建一個(gè) css 文件,使用 link 的方式引入。除了分離 css文件減小 js 體積, 還可以使用 CssMinimizerWebpackPlugin 優(yōu)化、壓縮來 CSS 體積。 看看 webpack.prod.js
文件中的配置:
// webpack.prod.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = merge(base, {
// ...
module: {
rules: [
{
test: /\.(css|less)$/,
use: [
MiniCssExtractPlugin.loader, // 使用 MiniCssExtractPlugin.loader 代替 style-loader
'css-loader',
{
loader: 'postcss-loader',
options: {
// 它可以幫助我們將一些現(xiàn)代的 CSS 特性,轉(zhuǎn)成大多數(shù)瀏覽器認(rèn)識(shí)的 CSS,并且會(huì)根據(jù)目標(biāo)瀏覽器或運(yùn)行時(shí)環(huán)境添加所需的 polyfill;
// 也包括會(huì)自動(dòng)幫助我們添加 autoprefixer
postcssOptions: {
plugins: [['postcss-preset-env', {}]],
},
},
},
'less-loader',
],
// 排除 node_modules 目錄
exclude: /node_modules/,
},
],
},
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 語法來擴(kuò)展現(xiàn)有的 minimizer(即 `terser-webpack-plugin`),將下一行取消注釋
// `...`,
new CssMinimizerPlugin({
// 默認(rèn)開啟
// parallel true: // 多進(jìn)程并發(fā)執(zhí)行,提升構(gòu)建速度 。 運(yùn)行時(shí)默認(rèn)的并發(fā)數(shù):os.cpus().length - 1
}),
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'assets/css/[hash:8].css', // 將css單獨(dú)提測(cè)出來放在assets/css 下
}),
],
})
打包后的效果如下:
(5) 圖片&字體
先來看看圖片的配置,創(chuàng)建文件夾 assets/images
,引入圖片 coffee.jpg。
我們?cè)?App.tsx
中引入張圖,這里使用 @ 符號(hào)來聲明路徑,需要在 base 文件中進(jìn)行alias 屬性的配置,同時(shí)還要對(duì) ts 類型聲明進(jìn)行聲明,否則 ts 類型會(huì)報(bào)錯(cuò)。
此時(shí)再編譯,會(huì)報(bào)錯(cuò),webpack 無法識(shí)別圖片,需要使用loader 去解析圖片資源。
對(duì)于圖片的處理,在 webpack 5 之前,我們可以使用 file-loader
和 url-loader
,這兩個(gè) loader 都可以幫我們解析圖片資源。
注意??
-
file-loader
:不僅僅可以處理圖片資源,本質(zhì)是處理文件導(dǎo)入地址并替換成其訪問地址,并把文件輸出到相應(yīng)位置,音視頻等資源也可以使用它。 -
url-loader
:file-loader 的升級(jí)版,包含 file-loader 的全部功能,并且能夠根據(jù)配置將符合配置的文件轉(zhuǎn)換成 Base64 方式引入,將小體積的圖片 Base64 引入項(xiàng)目可以減少 http 請(qǐng)求,也是一個(gè)前端常用的優(yōu)化方式。
我們先來看看 webpack 5 之前如何配置。先使用 file-loader
來處理圖片資源,可以看到圖片是一個(gè)可訪問的地址。
再使用url-loader
看看效果,在不設(shè)置 limit 限制時(shí),會(huì)轉(zhuǎn)換成 Base64 格式引入。
當(dāng)我們配置了 limit 時(shí)(這里設(shè)置比較小,項(xiàng)目中應(yīng)合理配置),圖片超過這個(gè)限制就會(huì)使用file-loader
去處理圖片,表現(xiàn)如上。一般情況下我們使用url-loader
來處理。在webpack.base.js
中配置:
// webpack.base.js
resolve: {
//...
alias: {
'@': path.resolve(__dirname, '../src'),
},
},
module: {
rules: [
//...
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
use: [
// {
// loader: 'file-loader',
// },
{
loader: 'url-loader',
options: {
limit: 2000,
// //限制打包圖片的大?。?/span>
// //如果大于或等于2000Byte,則按照相應(yīng)的文件名和路徑打包圖片;如果小于2000Byte,則將圖片轉(zhuǎn)成base64格式的字符串。
// name: 'img/[name].[hash:8].[ext]',
// //img:圖片打包的文件夾;
// //[name].[ext]:設(shè)定圖片按照本來的文件名和擴(kuò)展名打包,不用進(jìn)行額外編碼
// //[hash:8]:一個(gè)項(xiàng)目中如果兩個(gè)文件夾中的圖片重名,打包圖片就會(huì)被覆蓋,加上hash值的前八位作為圖片名,可以避免重名。
},
},
],
},
]
}
了解圖片的配置方式后,我們同樣可以使用 url-loader 去處理字體。一般數(shù)字類型都用din,這里我們引入一個(gè)ttf字體,使用@font-face 來定義字體名。
接著在 webpack.base.js 文件中配置下。
// webpack.base.js
module: {
rules: [
// ...
{
test: /\.(eot|ttf|woff|woff2)$/i,
use: [
{
loader: 'url-loader',
},
],
},
}
]
}
這樣我們就能使用 din 字體了,編譯運(yùn)行,字體生效~。
Webpack 5 中的 asset module 其實(shí)已經(jīng)幫我們處理了,可以直接使用。資源模塊(asset module)是一種模塊類型,它允許使用資源文件(字體,圖標(biāo)等)而無需配置額外 loader。
這里我們使用 asset 類型,并配置 parser.dataUrlCondition.maxSize
屬性,小于 maxSize 的會(huì)被打包成 base64,否則會(huì)被打包到目錄,以u(píng)rl的形式引入。話不多說直接上配置。
module: {
rules: [
// ...
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 25 * 1024, // 25kb
},
},
generator: {
filename: 'assets/imgs/[name].[hash:8][ext]',
},
},
{
test: /\.(eot|ttf|woff|woff2)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 25 * 1024, // 25kb
},
},
generator: {
filename: 'assets/fonts/[name].[hash:8][ext]',
},
},
]
}
build 一下,圖片和字體都被分別打包再 imgs 和 fonts下了。
三、小結(jié)
項(xiàng)目上需要用到的基本上都配置完了,當(dāng)然這只是一個(gè)簡(jiǎn)單的項(xiàng)目配置,隨著項(xiàng)目的復(fù)雜度變高,webpack 的配置也不同。webpack 5 其實(shí)已經(jīng)內(nèi)置了很多新的特性,比如內(nèi)置的 tree-shaking
、內(nèi)置靜態(tài)資源構(gòu)建能力、持久化緩存等,讓開發(fā)體驗(yàn)更友好了。感興趣的小伙伴也可以試著嘗試下哦~。文章來源:http://www.zghlxwxcb.cn/news/detail-714001.html
項(xiàng)目地址: 手把手教你搭建 Webpack 5 + React 項(xiàng)目文章來源地址http://www.zghlxwxcb.cn/news/detail-714001.html
到了這里,關(guān)于手把手教你搭建 Webpack 5 + React 項(xiàng)目的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!