实践!使用Webpack搭建【React + TS】开发环境
大家好我是来蹭饭,一个会点儿吉他和编曲,绞尽脑汁想傍个富婆的摸鱼大师。
回顾咱们webpack系列的历程,行程已经过半,前两篇文章是备受好评,今天总算来到了我们的实战篇——使用webpack搭建React的开发环境:
-
实战篇——使用webpack从0到1搭建React/Vue的开发环境
-
原理篇——剖析webpack打包原理,loader与plugin的原理。手写一个小型的webpack和各种loader与plugin
本篇将结合前两篇章的知识点进行实践,如果对webpack的 基础 和 进阶 相关知识了解不多的话,建议先去阅读这两篇文章,再来进行本次实战。
一. 前言
本次实战将以 webpack 5.74.0,react 18.2.0 为地基,从 搭建基础开发环境 和 优化环境配置 两个维度跟大家一起从0到1完成脚手架的搭建。还是老规矩,相关的案例代码已全部上传至 Git ,欢迎自取,不嫌麻烦的话欢迎点个star。接下来闲言少叙,大家坐稳扶好,我们发车!
二. 搭建基础开发环境
2.1 前置工作
本章节将按照 entry,output,loader,plugin,其他配置 的顺序带大家搭建一个基础的React开发环境。在正式开始之前我们先做好如下前置工作:
- 步骤1: 安装 webpack 与 webpack-cli
yarn add webpack webpack-cli -D
- 步骤2: 调整项目结构
├─ public (存放静态资源)
│ ├─ favicon.ico
│ └─ index.html
├─ src(存放项目资源)
│ ├─ App.tsx
│ └─ index.tsx
├─ webpack(存放webpack的配置)
│ ├─ webpack.config.js
├─ .gitignore
├─ package.json
└─ README.md
- 步骤3: 配置 package.json 启动服务的脚本命令
"scripts": {
"start": "webpack-dev-server --config ./webpack/webpack.config.js"
},
- 步骤4: 配置 public/index.html 的内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>webpack-react</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
到这里前置工作就准备完毕了,其他需要安装的依赖等具体场景分析之后进行相应的解决。待后续所有配置工作完成后,再启动服务查看页面是否渲染成功。
下面我们开始配置 webpack.config.js ,本篇默认大家对webpack有一定的了解,配置项不做过多赘述如有不了解处可查看 基础篇 和 进阶篇 进行查漏补缺。
2.2 配置entry
由于后面我们会有大量路径的获取操作,这里我们引入node的path模块,封装一个转换路径的 resolvePath 函数后进行entry的配置。
const path = require('path')
// 相对路径转绝对路径
const resolvePath = _path => path.resolve(__dirname, _path)
module.exports = {
entry: resolvePath('../src/index.tsx')
}
2.3 配置output
指定 dist 为bundle输出的路径。
module.exports = {
// ...
output: {
path: resolvePath('../dist'),
clean: true,
filename: 'scripts/[name].js'
}
}
2.4 配置loader
本小节我们通过安装和配置loader来解析 样式资源,图片/字体资源,js/ts,jsx/tsx
2.4.1 解析样式
本案例中我们需要webpack能识别 css,less,sass 等样式文件,因此需要安装如下loader:
yarn add less less-loader sass sass-loader css-loader style-loader -D
安装完成后对 webpack.config.js 进行配置:
module.exports = {
// ...
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}, {
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},{
test: /\.s[ac]ss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}]
}
}
2.4.2 解析图片/字体
在webpack5之前,处理图片,字体类资源时通常需要使用如下3个loader:
名称 | 作用 |
---|---|
raw-loader | 将文件导入为字符串 |
url-loader | 将文件作为 data URI 内联到 bundle 中 |
file-loader | 将文件发送到输出目录 |
webpack5之后可以使用资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换这些 loader:
名称 | 作用 |
---|---|
asset/resource | 导出一个单独的文件到bundle中,并导出 URL。之前通过使用 file-loader 实现 |
asset/inline | 不导出单独的资源到bundle中。导出一个资源的 data URI。之前通过使用 url-loader 实现 |
asset/source | 导出资源的源代码到bundle中。之前通过使用 raw-loader 实现 |
asset | 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader ,并且配置资源体积限制实现 |
这些配置已被内置在webpack5中,我们仅需激活配置即可完成对应的功能,下面我们对 webpack.config.js 进行配置:
module.exports = {
// ...
module: {
rules: [
// ...
{
// 处理图片
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: 'asset',
generator: {
filename: 'assets/img/[hash:10][ext]'
},
parser: {
dataUrlCondition: {
// 小于60kb的图片会被base64处理
maxSize: 60 * 1024
}
}
}, {
// 处理字体资源
test: /\.(woff2?|ttf)$/,
type: 'asset/resource'
}]
}
}
2.4.3 解析jsx/tsx
这部分的内容有点多,我们理一下整个过程再分步操作。首先我们搭建的是react开发环境,这就少不了react相关的依赖 react,react-dom 。
其次我们要解析 ts,tsx 这就需要使用 babel-loader,因此配合babel-loader的配置文件 .babelrc 和配合 ts 的 tsconfig.json 文件也要被创建。
另外需要特别注意在写tsx函数组件时,我们会指定组件的类型React.FC,这个类型由 @types/react,@types/react-dom 提供,因此这些依赖也要被安装,否则后续编译会报错。
过程理顺后我们分步骤来操作。
- 步骤1: 安装 react,react-dom
yarn add react react-dom
react | react-dom |
---|---|
react 核心库 | 用于支持 react 操作 DOM |
- 步骤2: 安装 @types/react,@types/react-dom
yarn add @types/react @types/react-dom -D
- 步骤3: 安装 babel-loader 以及react的babel官方预设 @babel/preset-react 相关的依赖
yarn add @babel/core babel-loader babel-preset-react-app @babel/preset-react -D
- 步骤4: 配置webpack.config.js
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.(js|jsx|ts|tsx)$/,
// 只处理 src 下的文件,排除其他如 node_modules 的处理
include: resolvePath('../src'),
loader:'babel-loader',
options: {
// 开启 babel 缓存
cacheDirectory: true,
// 关闭缓存压缩
cacheCompression: false
}
}]
}
}
- 步骤5: 调整目录结构新增babel和ts的配置文件 .babelrc,tsconfig.json
├─ public
│ ├─ favicon.ico
│ └─ index.html
├─ src
│ ├─ App.tsx
│ └─ index.tsx
├─ webpack
│ ├─ webpack.config.js
├─ .babelrc
├─ tsconfig.json
├─ .gitignore
├─ package.json
└─ README.md
- 步骤6: 配置 .babelrc
{
"presets": ["react-app"]
}
- 步骤7: 配置 tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
到这里一个基本的开发环境所需的loader就安装并配置完毕了,接下来我们进入plugin的安装和配置。
2.5 配置plugin
配置一个基础的react开发环境,plugin我们只需要用到 html-webpack-plugin,eslint 即可。
2.5.1 配置HtmlWebpackPlugin
- 步骤1: 安装 html-webpack-plugin
yarn add html-webpack-plugin -D
- 步骤2: 配置 webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
template: resolvePath('../public/index.html'),
}),
]
}
2.5.2 配置EsLint
使用eslint除了安装依赖, 配置 webpack.config.js 以外,还需要创建eslint校验的配置文件下面我们开始逐步操作。
- 步骤1: 安装 eslint,eslint-webpack-plugin
yarn add eslint eslint-webpack-plugin -D
- 步骤2: 配置 webpack.config.js
const EsLintWebpackPlugin = require('eslint-webpack-plugin')
module.exports = {
//...
plugins: [
//...
new EsLintWebpackPlugin({
context: resolvePath('../src'),
exclude:'node_modules',
cache: true,
cacheLocation: resolvePath('../node_modules/.cache/.eslintCache')
})
]
}
- 步骤3: 调整目录结构,新增 .eslintrc.js 文件
├─ public
│ ├─ favicon.ico
│ └─ index.html
├─ src
│ ├─ App.tsx
│ └─ index.tsx
├─ webpack
│ ├─ webpack.config.js
├─ .babelrc
├─ .eslintrc.js
├─ tsconfig.json
├─ .gitignore
├─ package.json
└─ README.md
- 步骤4: 配置 .eslintrc.js 文件
module.exports = {
// 继承官方规则
extends: ['react-app'],
parserOptions:{
babelOptions: {
presets: [
['babel-preset-react-app', false],
'babel-preset-react-app/prod'
]
}
}
}
2.6 配置其他选项
接下来我们配置下 resolve,mode,devServer 选项。
- 步骤1: 安装 webpack-dev-server
yarn add webpack-dev-server -D
- 步骤2: 配置 webpack.config.js
module.exports = {
//...
resolve: {
alias: {
'@': resolvePath('../src')
},
extensions: [".js", ".ts", ".jsx", ".tsx"]
},
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
host: 'localhost',
port: 8080,
open: true,
hot: true,
},
}
到这里一个基本的react开发环境已经搭建完毕,接下来我们把这个环境启动起来
2.7 启动项目
2.7.1 启动基础环境
启动环境前我们先创建一些tsx组件,并把react的入口文件写好。
- 步骤1: 设置 react 入口文件 src/index.tsx,react 18开始入口文件内容有变化如下所示
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
- 步骤2: 设置 src/App.tsx 的内容
import React from 'react'
const App: React.FC<any> = () => {
return(
<div className='App'>
<h2>hello react</h2>
</div>
)
}
export default App
好了前置工作准备完毕,接下来我们使用 yarn start 启动服务查看结果:
真是炸了个茄子了,这是什么报错?搞这么半天都白忙活了?
不着急我们看下报错信息: Using babel-preset-react-app
requires that you specify NODE_ENV
or BABEL_ENV
environment variables
哦!原来是运行react官方预设时,预设的运行环境需要获取全局 NODE_ENV 或 BABEL_ENV 的配置。这里的环境配置跟webpack中 mode 的配置不一样,需要我们单独设置后,官方预设才能获取到。我们更改下 package.json 中的 script 脚本,增加环境相关的配置。
"scripts": {
"start": "NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.js"
},
再次运行 yarn start 查看结果。
坏菜了!直接报错,windows不支持这样的写法。
在设置整体环境变量时,windows,Linux,MAC 的设置方法都不相同。这里推荐安装并使用 cross-env,它支持跨平台设置和使用环境变量,安装和使用步骤如下:
- 步骤1: 安装 cross-env
yarn add cross-env -D
- 步骤2: 配置 package.json 中的 script 脚本
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.js"
},
配置完成后启动服务查看结果,一个基本的react开发环境就启动起来了:
2.7.2 增加项目内容
接下来按步骤我们稍微丰富一下业务逻辑,引入路由并渲染一些数据,实现一个单的路由切换效果:
- 步骤1: 安装 react-router-dom
yarn add react-router-dom
yarn add @types/react-router-dom -D
- 步骤2: 修改 src 目录结构,新增 components 文件夹,新增几个组件;新增 assets 文件夹,往里面存入一张图片
├─ src
│ ├─ assets
│ │ ├─ img
│ │ │ └─ img.png
│ ├─ components
│ │ ├─ Computer
│ │ │ └─ index.tsx
│ │ ├─ Phone
│ │ │ └─ index.tsx
│ ├─ App.tsx
│ ├─ index.scss
│ ├─ App.tsx
│ └─ index.tsx
- 步骤3: 往各个tsx文件里填充内容
src/index.tsx:
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
)
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
src/App.tsx:
import React, { Suspense, lazy } from 'react'
import { Link, Routes, Route } from 'react-router-dom'
import './index.scss'
const Computer = lazy(() => import(/* webpackChunkName:"Computer", webpackPrefetch: true*/ './components/Computer'))
const Phone = lazy(() => import(/* webpackChunkName:"Phone", webpackPrefetch: true */ './components/Phone'))
const App: React.FC = () => {
return (
<div className='App'>
<h2>hello react</h2>
<div className='App-img'></div>
<div className='App-content'>
<ul>
<li>
<Link to="/Computer">Computer</Link>
</li>
<li>
<Link to="/Phone">Phone</Link>
</li>
</ul>
</div>
<hr/>
<Suspense fallback={<div>loading...</div>}>
<Routes>
<Route path="/Computer" element={<Computer />}></Route>
<Route path="/Phone" element={<Phone />}></Route>
</Routes>
</Suspense>
</div>
)
}
export default App
src/components/Computer/index.tsx:
import React from "react";
const Computer: React.FC = () => {
return (
<div className="App-route-component">
I am Computer
</div>
)
}
export default Computer
src/components/Phone/index.tsx:
import React from "react";
const Phone: React.FC = () => {
const hello = (str: String) => {
return str
}
return (
<div className="App-route-component">
I am {hello('Phone')}
</div>
)
}
export default Phone
- 步骤4: 给 index.scss 写一个简单的样式
$baseCls: 'App';
.#{$baseCls} {
&-img{
width: 200px;
height: 200px;
background: url(./assets/img/img.png) no-repeat center /contain;
transition: all .3s;
&:hover{
transform: scale(1.5);
}
}
&-route-component{
color: #4285f4;
font-weight: bold;
}
}
再次启动服务查看效果,当点击 Computer 组件时,由于路由组件的懒加载,文件还未加载完成时,屏幕显示 loading。
加载完成后,显示 Computer 组件的内容。
到这里,我们的演示效果就完成了,接下来我们看看当前还存在哪些待解决的问题。
2.7.3 待解决的问题
tsx文件没有热更新功能。
如果我们在样式中,将图片的大小更改,页面会被无感刷新(HMR),如下所示。
但是如果我们尝试更改 src/App.tsx 文件,给 hello react 加几个标点符号,看看会发生什么。
注意图片左上角,浏览器的角标正在转圈,页面会被刷新而不是直接进行热更新,这正是问题所在。
我们的 HMR 功能支持样式文件等。但像 tsx 这样的文件,如何支持热更新呢?这里需要安装 tsx 热更新相关的依赖并做好配置,具体步骤如下:
- 步骤1: 安装依赖
yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh -D
- 步骤2: 配置 webpack.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
// ...
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.(js|jsx|ts|tsx)$/,
// 只处理 src 下的文件,排除其他如 node_modules 的处理
include: resolvePath('../src'),
loader:'babel-loader',
options: {
// 开启babel缓存
cacheDirectory: true,
// 关闭缓存压缩
cacheCompression: false,
plugins: [
'react-refresh/babel'
]
}
}]
},
plugins: [
// ...
new ReactRefreshWebpackPlugin()
],
// ...
}
当完成配置后我们修改tsx文件,查看打印的日志,tsx也支持热更新了。
接下来我们看下一个待解决的问题 前端路由刷新404
前端路由刷新404
这是什么问题呢,我们直接演示一下,这里将路由切换成 Computer 组件。
之后我们在当前路由下进行页面的刷新,页面 404 了。
原因是这样的,当访问 localhost:8080/Computer 这个地址时,devServer 会去对应的 Sources 寻找资源。可是依据我们在 webpack.config.js 中配置的 output 属性可知,在 devServer启动的开发服务器中 并不直接存在 Computer 这样的资源。它正确的匹配路径应该是 localhost:8080/script/Computer 。因此,当出现错误匹配时需要返回 index.html,它会自动匹配路径,从而渲染路由。
webpack的 devServer 中有对应的配置 historyApiFallback 它能够用 index.html 替代所有 404 响应的资源,以此解决问题。配置方式如下:
module.exports = {
//...
devServer: {
host: 'localhost',
port: 8080,
open: true,
hot: true,
// 使用 index.html 代替所有404页面,解决使用H5的history API刷新页面导致404的问题
historyApiFallback: true,
},
}
配置完成后,我们在 Computer 路由下重新刷新页面,直接访问 Computer 时,返回了 index.html 的内容,之后它帮我们做好了路由匹配,完成了这次渲染。因此也不会发生404的现象了。
到这里开发环境的内容搭建就彻底完毕了,接下来我们针对不同的环境,对webpack的配置进行优化。
三. 优化环境配置
本章节我们通过调整 webpack.config.js 针对开发/生产环境进行不同的优化。在此之前,我们可以看看react官方是如何写 webpack.config.js 的。本地使用react安装命令,安装一个项目。之后使用 eject 命令弹出 webpack 的配置然后进行查看。
如果你恰巧比较懒可以直接移步react官方团队的 Git仓库 ,查看官方 webpack 的配置。
由于代码较多这里我大概说下思路,官方用 isEnvDevelopment,isEnvProduction 标识开发和生产环境,依据环境的不同通过 .filter() 方法,过滤生产环境使用不到的 webpack 配置参数。
顺着官方的思路,我们对自己的 webpack.config.js 分以下三大步进行优化。优化完毕后,打包生产环境代码再运行查看结果。
3.1 获取环境标识
由于配置了 cross-env 的缘故。我们可以在 webpack.config.js 中直接获取当前所处环境,再依据不同的环境去做优化。
这里我们只取生产环境的标识 isEnvProduction
// 获取 cross-env 环境变量
const isEnvProduction = process.env.NODE_ENV === 'production'
3.2 抽取公共配置
之前对样式相关的loader处理中有大量重复的地方,另外针对不同的环境我们希望启用不同的样式loader,因此我们将样式相关的loader进行封装并安装一些依赖,接下来我们开始分步操作。
- 步骤1: 安装依赖
yarn add postcss-loader postcss-preset-env cache-loader mini-css-extract-plugin -D
依赖名称 | 作用 |
---|---|
postcss-loader | 处理css的兼容问题 |
cache-loader | 缓存解析的css文件 |
mini-css-extract-plugin | 将css抽取成单独文件 |
- 步骤2: 配置 webpack.config.js,使用刚刚安装的loader和plugin,并封装 getStyleLoaders 函数,用以返回解析对应样式文件时,使用的loader数组。之后只用 oneOf 增加唯一匹配loader的功能。
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 获取cross-env环境变量
const isEnvProduction = process.env.NODE_ENV === 'production'
const getStyleLoaders = (prevLoader) => {
return [
// 生产环境将css单独抽取成文件,开发环境直接只用 style-loader
isEnvProduction ? MiniCssExtractPlugin.loader : 'style-loader',
// 开发环境缓存css文件
!isEnvProduction && 'cache-loader',
'css-loader',
{
// 处理css兼容问题
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['postcss-preset-env']
}
}
},
prevLoader
].filter(Boolean)
}
module.exports = {
// ...
module: {
rules: [{
oneOf: [{
test: /\.css$/,
use: getStyleLoaders()
}, {
test: /\.less$/,
use: getStyleLoaders('less-loader')
}, {
test: /\.s[ac]ss$/,
use: getStyleLoaders('sass-loader')
},
// ...
]
}]
},
plugins: [
// ...
new MiniCssExtractPlugin({
filename: isEnvProduction ? 'css/[name].[contenthash:10].css' : 'css/[name].css',
chunkFilename: isEnvProduction ? 'css/[name].[contenthash:10].chunk.css' : 'css/[name].chunk.css',
}),
],
// ...
}
- 步骤3: 配置package.json,辅助postcss-lodaer做兼容处理:
{
// ...
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
}
3.3 优化其他配置
接下来我们针对生产环境优化其他相关配置。
3.3.1 修改输出文件名
在生产环境下,我们希望文件缓存更加持久,并且在被修改后浏览器也能及时获取最新的文件资源。此时需要为生产环境文件名增加 contenthash 值来达到上述目的。
// ...
module.exports = {
entry: resolvePath('../src/index.tsx'),
output: {
path: isEnvProduction ? resolvePath('../dist') : undefined,
filename: isEnvProduction ? 'scripts/[name].[contenthash:10].js' : 'scripts/[name].js',
chunkFilename: isEnvProduction ? 'scripts/[name].[contenthash:10].chunk.js' : 'scripts/[name].chunk.js',
// 静态资源输出位置
assetModuleFilename: 'assets/img/[hash:10][ext][query]',
clean: true
},
// ...
}
3.3.2 修改loader
tsx的热更新功能仅在开发环境保留,生产环境可以剔除。
module.exports = {
// ...
module: {
rules: [{
oneOf: [
//...
{
test: /\.(js|jsx|ts|tsx)$/,
// 只处理 src 下的文件,排除其他如 node_modules 的处理
include: resolvePath('../src'),
loader: 'babel-loader',
options: {
// 开启babel缓存
cacheDirectory: true,
// 关闭缓存压缩
cacheCompression: false,
// 开发环境激活TSX的HMR
plugins: [
!isEnvProduction && 'react-refresh/babel'
].filter(Boolean),
}
}
]
}]
},
// ...
}
3.3.3 修改plugin
生产环境和开发环境需要使用到的plugin也不相同,这里我们做一个区分,使用 isEnvProduction 标识配合 .filter(Boolean) 方法过滤生产环境不需要的plugin。
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: resolvePath('../public/index.html'),
}),
isEnvProduction && new MiniCssExtractPlugin({
filename: isEnvProduction ? 'css/[name].[contenthash:10].css' : 'css/[name].css',
chunkFilename: isEnvProduction ? 'css/[name].[contenthash:10].chunk.css' : 'css/[name].chunk.css',
}),
new EsLintWebpackPlugin({
context: resolvePath('../src'),
exclude: 'node_modules',
cache: true,
cacheLocation: resolvePath('../node_modules/.cache/.eslintCache')
}),
!isEnvProduction && new ReactRefreshWebpackPlugin()
].filter(Boolean),
// ...
}
3.3.4 添加optimization
生产模式下,需要对代码进行压缩和分割操作。先安装压缩css代码的依赖。
yarn add terser-webpack-plugin -D
配置optimization
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
// terser-webpack-plugin 在 webpack5 中已被内置,开箱即用无需安装
const TerserPlugin = require("terser-webpack-plugin")
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'all'
},
// 运行时的chunk文件
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`
},
// 是否需要开启压缩
minimize: isEnvProduction,
// 压缩css
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin()
],
},
// ...
}
3.3.5 其他配置优化
剩下将mode和devtool也改成适应环境的配置
module.exports = {
// ...
mode: isEnvProduction ? 'production' : 'development',
devtool: isEnvProduction ? false : 'cheap-module-source-map',
// ...
}
3.3.6 修改package.json
在 package.json 中添加打包命令 build。
{
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.js"
},
}
全部工作完成后终端输入 yarn build 打包代码查看打包结果。
运行index.html,打包结果正常运行。
3.3.7 代码分割
经过我们对webpack的配置后,项目已经能正常运行react的开发和生产环境代码了。但是打住!别着急开香槟,这里还有个问题需要我们优化,我们先看看打包后的各个文件的体积。
这个名为 343 的 bundle 体积是业务代码的百倍以上,它是所有 node_modules 打包后产生的 bundle 。如果以后我们的项目添加各种第三方依赖。,bundle 的体积会越来越大,从而影响用户体验。因此我们需要在这里做下代码分割处理。
配置 webpack.config.js 中的 optimization
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// react react-dom react-router-dom 一起打包
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
name:'chunk-react',
// 优先级,打包 react 相关依赖时,不会被打入 node_modules 中的chunk
priority: 10
},
// node_modules 单独打包
lib: {
test: /[\\/]node_modules[\\/]/,
name:'chunk-libs',
priority: 1
}
}
},
// ...
},
// ...
// 关闭性能分析,提升打包速度
performance: false
}
配置完成后重新打包查看打包结果,原来 343 的 bundle 变成了 chunk-react,chunk-lib :
运行打包后的页面,查看各资源大小:
这样原先相对较大的资源就被拆成了2个较小的资源。后续随着项目复杂程度不断增加,第三方依赖越来越多时,也能避免所有资源被打进一个bundle从而导致应用首页加载资源过慢的问题。
到这里所有的优化工作就配置完毕了。一个能承载你react项目的脚手架就这样搭建完成了。
四. 尾巴
赶在“银十”的尾巴,webpack实战篇终于正式上菜了。最近工作属实繁忙,9月底写完的文章拖到了现在才发布,咱们系列的最终章——— 原理篇 在后续工作不忙时自会新建文件夹,咱们有缘下次再见!
希望通过本次的实战,能让你把咱们webpack系列 基础篇 和 进阶篇 的知识点串联起来。如果本篇文章对你有帮助,欢迎点赞,评论,收藏,你们的支持是我创作的最大动力。
我是来蹭饭,一个会点儿吉他和编曲,绞尽脑汁想傍个富婆的摸鱼大师,希望本次的分享对你有帮助。

转载自:https://juejin.cn/post/7156431347316162573