1.项目创建
这里将使用pnpm
pnpm
使用独特的方法来安装包,避免重复包,从而加快安装和更新时
间。特别是,当安装共享依赖的多个包时,
pnpm
只会为每个依赖安装一次,
除此之外它的优点还有:
更少的磁盘空间使用、更好地支持 monorepos、更好地支持对等依赖、更清晰的依赖树
# 初始化package.json文件
pnpm init
# 我的 pnpm 版本
pnpm -v
7.27.1
初始化完了会在根目录生成一个
package.json
文件
2.基本项目结构
在根目录新建基本
的项目结构:
├── build
| ├── webpack.base.ts # 公共配置
| ├── webpack.dev.ts # 开发环境配置
| └── webpack.prod.ts # 打包环境配置
├── public
│ └── index.html # html模板
├── src
| ├── App.tsx
| ├── App.css
│ └── index.tsx # react应用入口页面
└── package.json
index.html
内容:
<!DOCTYPE html>
<html lang="en">
<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>webpack5-react-ts</title>
</head>
<body>
<!-- 容器节点 -->
<div id="root"></div>
</body>
</html>
3.引入react
安装依赖:
pnpm i react react-dom
# 声明依赖
pnpm i @types/react @types/react-dom -D
先将入口文件
src/index.tsx
写好:
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
// const root = document.getElementById('root');
const root = document.querySelector('#root')
if(root) {
createRoot(root).render(<App />)
}
App.css
:
h2 {
color: red;
}
以及
App.tsx
:
import React from 'react'
import './App.css'
function App() {
return <h2>Hello East_White</h2>
}
export default App
4.引入typescript
为什么要使用
typescript
?
它的优点如下:
1、
更好的代码质量:
TypeScript
的静态类型系统可以帮助开发人员在编写代码时捕获错误,这可以
提高代码质量和稳定性。
2、
更好的可读性和可维护性:
TypeScript
的静态类型系统和类可以提高代码的可读性和可维护性,特别是在大型项目中。这可以使代码更易于理解和修改。
3、
更好的
IDE
支持:
TypeScript
具有出色的
IDE
支持,包括自动完成,语法突出显示和类型检查。这可以提高开发人员的生产力和准确性。
4、更好的可扩展性: TypeScript
支持面向对象编程,可以帮助开发人员创建复杂的数据类型和接
口,并使代码更易于扩展和维护。
5、更好的性能:由于
TypeScript
可以在编译时捕获错误,因此可以减少运行时错误并提高性能。
我们在项目中引入
typescript
,先安装依赖:
pnpm i typescript -D
pnpm i babel-loader ts-node @babel/core @babel/preset-react @babel/presettypescript @babel/preset-env core-js -D
初始化
tsconfig.json
:
npx tsc --init
# 如果全局安装了typescript,也可以通过下面的命令创建
tsc --init
初始化完了会在根目录生成一个
tsconfig.json
文件
5.webpack配置
安装依赖:
pnpm i webpack webpack-cli -D
配置
webpack.base.ts
文件:
import { Configuration } from 'webpack';
const path = require("path");
const baseConfig: Configuration = {
entry: path.join(__dirname, "../src/index.tsx"), // 入口文件
// 打包出口文件
output: {
filename: "static/js/[name].js", // 每个输出js的名称
path: path.join(__dirname, "../dist"), // 打包结果输出路径
clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了
publicPath: "/", // 打包后文件的公共前缀路径
},
// loader 配置
module: {
rules: [],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
// plugins 的配置
plugins: []
};
配置完了还会出现报红,那么我们就需要安装 @types/node 这个依赖:
pnpm i @types/node -D
另外因为我们在
App.tsx
中引入了
css
文件,所以还需要安装相关的
loader
:
pnpm i style-loader css-loader html-webpack-plugin -D
完善
webpack.base.ts
:
import { Configuration } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
const path = require("path");
const baseConfig: Configuration = {
entry: path.join(__dirname, "../src/index.tsx"), // 入口文件
// 打包出口文件
output: {
filename: "static/js/[name].js", // 每个输出js的名称
path: path.join(__dirname, "../dist"), // 打包结果输出路径
clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了
publicPath: "/", // 打包后文件的公共前缀路径
},
// loader 配置
module: {
rules: [
{
test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
use: {
loader: "babel-loader",
options: {
// 预设执行顺序由右往左,所以先处理ts,再处理tsx
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文件, babel- loader会自动寻找上面配置好的文件.browserslistrc
targets: {
browsers: ["> 1%", "last 2 versions", "not ie<= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime":"automatic" 添加到配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not
defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
},
},
},
{
test: /.css$/, //匹配 css 文件
use: ["style-loader", "css-loader"],
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
// plugins
plugins: [
new HtmlWebpackPlugin({
// 复制 'index.html' 文件,并自动引入打包输出的所有资源(js/css)
template: path.join(__dirname, "../public/index.html"),
// 压缩html资源
minify: {
collapseWhitespace: true, //去空格
removeComments: true, // 去注释
},
}),
],
};
export default baseConfig
因为
webpack.base.ts
文件承载了基本的配置,随着
webpack
做的事情越来越多,会逐渐变得很庞 大,我们可以将其中的 babel-loader
相关的配置抽离出来进行管理。在根目录新建
babel.config.js
:
module.exports = {
// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc
// "targets": {
// "chrome": 35,
// "ie": 9
// },
targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
};
然后在
webpack.base.ts
文件中,就可以将
babel-loader
配置简化成:
// ...
module: {
rules: [
{
test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
use: "babel-loader"
},
// ...
],
},
// ...
webpack.dev.ts
我们需要通过
webpack-dev-server
来启动我们的项目,所以需要安装相关的依赖:
pnpm i webpack-dev-server webpack-merge -D
配置开发环境配置:
webpack.dev.ts
import path from "path";
import { merge } from "webpack-merge";
import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from "webpack-devserver";
import baseConfig from "./webpack.base";
interface Configuration extends WebpackConfiguration {
devServer?: WebpackDevServerConfiguration;
}
const host = "127.0.0.1";
const port = "8082";
// 合并公共配置,并添加开发环境配置
const devConfig: Configuration = merge(baseConfig, {
mode: "development", // 开发模式,打包更加快速,省了代码优化步骤
devtool: "eval-cheap-module-source-map",
devServer: {
host,
port,
open: true, // 是否自动打开
compress: false, // gzip压缩,开发环境不开启,提升热更新速度
hot: true, // 开启热更新
historyApiFallback: true, // 解决history路由404问题
setupExitSignals: true, // 允许在 SIGINT 和 SIGTERM 信号时关闭开发服务器和退出进程。
static: {
directory: path.join(__dirname, "../public"), // 托管静态资源public文件夹
},
headers: { "Access-Control-Allow-Origin": "*" }, // HTTP响应头设置,允许任何
来源进行跨域请求
},
});
export default devConfig;
然后再
package.json
中添加启动脚本:
"scripts": {
"dev": "webpack serve -c build/webpack.dev.ts"
},
正当我们准备启动项目的时候,发现还有错误报红
这时候只需要在
tsconfig.json
中加入一行
"jsx": "react-jsx"
即可:
{
"compilerOptions": {
"target": "es2016",
"esModuleInterop": true,
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"jsx": "react-jsx" // 这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src"]
}
完事之后运行 pnpm run dev 脚本启动项目,就可以看到页面跑出来了!
webpack.prod.ts
配置 webpack.prod.ts :
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import baseConfig from "./webpack.base";
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
});
export default prodConfig;
在
package.json
中添加:
"scripts": {
// ...
"build": "webpack -c build/webpack.prod.ts"
},
运行
pnpm run build
,如果想看打包结果,可以通过一个小工具来查看:
# 如果之前使用npm,最简单的方法就是使用如下命令
npm i serve -g
# 如果是首次使用pnpm安装全局依赖,通过如下命令
pnpm setup
source ~/.zshrc
pnpm i serve -g
copy 静态资源
一般
public
文件夹都会放一些静态资源
,
可以直接根据绝对路径引入,比如图片、
css
、
js
文件等,不 需要 webpack
进行解析,只需要打包的时候把
public
下内容复制到构建出口文件夹中,可以借助
copy-webpack-plugin
插件,安装依赖:
pnpm i copy-webpack-plugin -D
修改
webpack.base.ts
:
// ...
const baseConfig: Configuration = {
// ...
plugins: [
new HtmlWebpackPlugin({
title: "webpack5-react-ts",
filename: "index.html",
// 复制 'index.html' 文件,并自动引入打包输出的所有资源(js/css)
template: path.join(__dirname, "../public/index.html"),
inject: true, // 自动注入静态资源
hash: true,
cache: false,
// 压缩html资源
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true, //去空格
removeComments: true, // 去注释
minifyJS: true, // 在脚本元素和事件属性中缩小JavaScript(使用UglifyJS)
minifyCSS: true, // 缩小CSS样式元素和样式属性
}
})
],
};
export default baseConfig;
开发环境已经在
devServer
中配置了
static
托管了
public
文件夹,在开发环境使用绝对路径可以访问 到 public
下的文件,但打包构建时不做处理会访问不到,所以现在需要在打包配置文件
webpack.prod.ts
中新增
copy
插件配置。
import path from "path";
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import CopyPlugin from "copy-webpack-plugin";
import baseConfig from "./webpack.base";
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"), // 复制public下文件
to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
filter: (source) => !source.includes("index.html"), // 忽略index.html
},
],
}),
],
});
export default prodConfig;
测试一下,在
public
中新增一个
favicon.ico
图标文件,在
index.html
中引入:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- 绝对路径引入图标文件 -->
<link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack5-react-ts</title>
</head>
<body>
<!-- 容器节点 -->
<div id="root"></div>
</body>
</html>
再执行
pnpm run build
打包,就可以看到
public
下的
favicon.ico
图标文件被复制到
dist
文件中
了。
有的人可能会遇到
favicon
不显示的问题,提供以下思路进行处理:
1.
在
head
添加了
favicon
没生效,但是新开标签页直接访问图片可以访问到。
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> 1
2.
改图片尺寸格式依然没显示,直接复制其他网站正常
favaicon.ico
,排除格式的原因。
3.
语法问题,尝试各种写法。
<link rel="icon" href="/favicon.ico"">
<link rel="shortcut" href="/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
4.
添加版本号,
favicon
正常显示,排除语法原因。
<link rel="shortcut icon" href="/favicon.ico?v=1.0" type="image/x-icon"> 1
5.
继续深入查找问题,
F12
查看
html
和
network
,发现
favicon.ico
的请求没有。
6.
没有
favicon
的请求,不会显示图片,怀疑是浏览器缓存问题,清除缓存后依然没有请求。
7.
打开其它浏览器,发现
favicon
正常显示,进一步确定是浏览器缓存。
8.
关闭标签页,重新打开浏览器,最后
favicon.ico
正常显示。
9.
chrome
浏览器,前端资源经常会产生缓存问题,清空或禁用缓存也不一定有效。可以尝试重启浏览器,或者重启电脑。
6.配置环境变量
corss-env + DefinePlugin
环境变量按作用分为两种:
1.
区分是开发模式还是打包构建模式
2.
区分项目业务环境,开发
/
测试
/
预测
/
正式环境
区分开发模式还是打包构建模式可以用
process.env.NODE_ENV
,因为很多第三方包里面判断都
是采用的这个环境变量。
区分项目接口环境可以自定义一个环境变量
process.env.BASE_ENV
,设置环境变量可以借助
cross-env
和
webpack.DefinePlugin
来设置。
cross-env
:运行跨平台设置和使用环境变量的脚本,兼容各系统的设置环境变量的包
webpack.DefinePlugin
:
webpack
内置的插件
,
可以为业务代码注入环境变量
安装
cross-env
:
pnpm i cross-env -D
修改
package.json
的
scripts
:
"scripts": {
"dev:dev": "cross-env NODE_ENV=development BASE_ENV=development webpack
serve - c build / webpack.dev.ts",
"dev:test": "cross-env NODE_ENV=development BASE_ENV=test webpack serve
- c build / webpack.dev.ts",
"dev:pre": "cross-env NODE_ENV=development BASE_ENV=pre webpack serve -c
build / webpack.dev.ts",
"dev:prod": "cross-env NODE_ENV=development BASE_ENV=production webpack
serve - c build / webpack.dev.ts",
"build:dev": "cross-env NODE_ENV=production BASE_ENV=development webpack
- c build / webpack.prod.ts",
"build:test": "cross-env NODE_ENV=production BASE_ENV=test webpack -c
build / webpack.prod.ts",
"build:pre": "cross-env NODE_ENV=production BASE_ENV=pre webpack -c
build / webpack.prod.ts",
"build:prod": "cross-env NODE_ENV=production BASE_ENV=production webpack
- c build / webpack.prod.ts"
},
process.env.NODE_ENV
环境变量
webpack
会自动根据设置的
mode
字段来给业务代码注入对应的 development 和
prodction
,这里在命令中再次设置环境变量
NODE_ENV
是为了在
webpack
和 babel 的配置文件中访问到。
在
webpack.base.ts
中打印一下设置的环境变量:
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
执行
pnpm run build:dev
,就可以在控制台打印出:
// NODE_ENV production
// BASE_ENV development
当前是打包模式,业务环境是开发环境,这里需要把
process.env.BASE_ENV
注入到业务代码里面,就可以通过该环境变量设置对应环境的接口地址和其他数据,要借助 webpack.DefinePlugin
插件
修改
webpack.base.ts
import { DefinePlugin } from 'webpack'
module.export = {
// ...
plugins: [
// ...
new DefinePlugin({
'process.env': JSON.stringify(process.env)
})
]
}
在根目录下新建
typings/global.d.ts
文件:
declare module 'process' {
global {
namespace NodeJS {
export interface ProcessEnv {
BASE_ENV: 'development' | 'test' | 'pre' | 'production'
NODE_ENV: 'development' | 'production'
}
}
}
}
并在
tsconfig.json
中配置:
{
"compilerOptions": {
"target": "es2016", // 编译输出的JavaScript版本为ES2016
"esModuleInterop": true, // 允许更好的兼容性与ECMAScript模块导入
"module": "commonjs", // 指定生成哪个模块系统代码,这里是CommonJS
"forceConsistentCasingInFileNames": true, // 确保文件名大小写一致,有助于跨平台开发
"strict": true, // 启用所有严格的类型检查选项
"skipLibCheck": true, // 跳过声明文件的类型检查,可以提高编译速度
"typeRoots": ["./typings/*.d.ts", "node_modules/@types"], // 指定类型声明文件的路径
"jsx": "react-jsx" // react18这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src", "./typings/*.d.ts"] // 指定哪些文件或目录应该被包含在编译范围内
}
配置后会把值注入到业务代码里面去,
webpack
解析代码匹配到
process.env.BASE_ENV
,就会设置到 对应的值。测试一下,在 src/index.tsx
打印一下两个环境变量:
需要注意的是,业务环境要能访问
process
,需要安装:
pnpm add @types/node -D
// src/index.tsx
// ...
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
执行
pnpm run dev:test
,可以在浏览器控制台看到打印的信息
// NODE_ENV development
// BASE_ENV test
7.配置多环境运行配置
安装依赖:
pnpm i dotenv
在根目录下新建一个多文件配置文件夹
env
:
├── env
├── .env.development # 开发环境
├── .env.test # 测试环境
├── .env.pre # 预发布环境
└── .env.production # 生产环境
文件中可以配置任意我们需要的变量:
// env/.env.development
REACT_APP_API_URL=https://api-dev.com
// env/.env.test
REACT_APP_API_URL=https://api-test.com
// env/.env.pre
REACT_APP_API_URL=https://api-pre.com
// env/.env.production
REACT_APP_API_URL=https://api-prod.com
然后再
webpack.base.ts
中引入,然后解析对应环境配置,最后通过
DefinePlugin
进行注入:
import path from "path";
import { Configuration, DefinePlugin } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import * as dotenv from "dotenv";
// 加载配置文件
const envConfig = dotenv.config({
path: path.resolve(__dirname, "../env/.env." + process.env.BASE_ENV),
});
// console.log("process.env", process.env);
// console.log("NODE_ENV", process.env.BASE_ENV);
// console.log("REACT_APP_API_URL", process.env.REACT_APP_API_URL);
const baseConfig: Configuration = {
// ...
plugins: [
// 注入到业务
new DefinePlugin({
"process.env": JSON.stringify(envConfig.parsed),
"process.env.BASE_ENV": JSON.stringify(process.env.BASE_ENV),
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
}),
].filter(Boolean), // // 去除数组中的假值(例如,如果某些插件的条件不满足而导致未定义)
};
export default baseConfig;
业务代码中使用:
import { createRoot } from 'react-dom/client';
import App from './App';
// const root = document.getElementById('root');
const root = document.querySelector('#root')
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
console.log("process.env", process.env);
if(root) {
createRoot(root).render(<App />)
}å
然后重启项目:
pnpm run dev:dev
,
8.文件别名
先在
webpack.base.ts
中配置:
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".less", ".css"],
// 别名需要配置两个地方,这里和 tsconfig.json
alias: {
"@": path.join(__dirname, "../src")
},
},
然后还需要在
tsconfig.json
中配置:
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
},
}
然后就可以在项目中使用了
~
import '@/App.css'
function App() {
return <h2>webpack5-react-ts</h2>
}
export default App
9.引入 less、sass(scss)、stylus
less
、
sass
(
scss
)
、
stylus
是三个比较流行的
CSS Modules
预处理库。在
React
中,使用
CSS
Modules
的好处在于:
1.
避免全局样式冲突:使用
CSS Modules
可以确保样式只应用于特定组件,避免全局样式冲突。
2.
更好的可维护性:
CSS Modules
使得样式与组件代码紧密关联,方便代码维护。
3.
提高代码可重用性:
CSS Modules
可以轻松地将样式从一个组件复制到另一个组件,提高代码可重用性。
4.
支持动态样式:使用
CSS Modules
可以轻松地生成动态样式,例如根据组件状态或属性更改样
式。
5.
更好的性能:
CSS Modules
使用模块化的方式加载样式,提高了页面加载速度和性能。
基本用法
先安装相关的依赖:
pnpm i less less-loader sass-loader sass stylus stylus-loader -D
在
webpack.base.ts
添加相关的
loader
:
// ...
const cssRegex = /\.css$/;
const sassRegex = /\.(scss|sass)$/;
const lessRegex = /\.less$/;
const stylRegex = /\.styl$/;
const styleLoadersArray = [
"style-loader",
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[path][name]__[local]--[hash:5]",
},
},
},
];
const baseConfig: Configuration = {
// ...
module: {
rules: [
// ...
{
test: cssRegex, // 匹配css文件
use: styleLoadersArray,
},
{
test: lessRegex,
use: [
...styleLoadersArray,
{
loader: 'less-loader',
options: {
lessOptions: {
// 如果要在less中写js的语法,需要加这一配置
javascriptEnabled: true
}
}
}
]
},
{
test: sassRegex,
use: [
...styleLoadersArray,
{
loader: 'sass-loader',
options: {
implementation: require('sass') // 使用dart-sass代替node-sass
}
}
]
},
{
test: stylRegex,
use: [
...styleLoadersArray,
'stylus-loader'
]
}
],
},
// ...
};
export default baseConfig;
webpack
配置说明
1.
localIdentName
:配置生成的
css
类名组成(
path
路径,
name
文件名,
local
原来的
css
类名
,
hash: base64:5
拼接生成
hash
值
5
位,具体位数可根据需要设置。
2.
如下的配置(
localIdentName: '[local]__[hash:base64:5]'
):生成的
css
类名类似
class="edit__275ih"
这种,既能达到
scoped
的效果,又保留原来的
css
类名
(
edit
)
。
然后就可以在业务中使用了:
/* src/app.less */
@color: red;
.lessBox {
.box {
color: @color;
background - color: lightblue;
transform: translateX(100);
&:before{
@arr: 'hello', 'world';
content: `@{arr}.join(' ').toUpperCase()`;
}
}
}
/* src/app.scss */
$blue: #1875e7;
$side: left;
.scssBox {
margin: 20px 0;
.box {
color: $blue;
background - color: rgb(226, 223, 223);
border: 1px solid grey;
margin -#{ $side }: 20px;
padding: (20px / 2);
}
}
/* src/app.styl */
.stylBox
.box
color: red;
background - color: yellow;
在
App.tsx
中引入:
import '@/App.css'
import lessStyles from './app.less'
import scssStyles from './app.scss'
import stylStyles from './app.styl'
function App() {
return <div>
<h2>webpack5-react-ts</h2>
<div className={lessStyles['lessBox']}>
<div className={lessStyles['box']}>lessBox</div>
</div>
<div className={scssStyles['scssBox']}>
<div className={scssStyles['box']}>scssBox</div>
</div>
<div className={stylStyles['stylBox']}>
<div className={stylStyles['box']}>stylBox</div>
</div>
</div>
}
export default App
重启项目,就会发现生成了带有
hash
值的
class
类名,且里面包含了我们自定义的类名,方便日后调试用
同时在验证打包是否成功,运行
pnpm run build:dev
,然后通过
serve -S dist
查看。 当然,如果
你不希望每次写
less
的时候,都在文件名上加一个
.module
,可以在
less-loader
中添加如下配置:
// ...
const baseConfig: Configuration = {
// ...
module: {
rules: [
// ...
{
test: lessRegex,
use: [
...styleLoadersArray,
{
loader: "less-loader",
options: {
lessOptions: {
importLoaders: 2,
// 可以加入modules: true,这样就不需要在less文件名加module了
modules: true,
// 如果要在less中写类型js的语法,需要加这一个配置
javascriptEnabled: true
},
},
},
],
},
// ...
],
},
// ...
};
export default baseConfig;
至此,我们就完成了
less
、
sass
(
scss
)
,
stylus
的引入。
Tips
:虽然我们在样式文件名上加一个
.module
的后缀,可以明确这是
css modules
,但也带
来了额外的码字开销。可以在
global.d.ts
加入样式文件的声明,就可以避免写
.module
后
缀。
// src/typings/global.d.ts
/* CSS MODULES */
declare module '*.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.scss' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.sass' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.less' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.styl' {
const classes: { [key: string]: string };
export default classes;
}
然后就可以删掉样式文件中的
.module
后缀了。
处理CSS3前缀在浏览器中的兼容
虽然
css3
现在浏览器支持率已经很高了
,
但有时候需要兼容一些低版本浏览器,需要给
css3
加前缀
,
可 以借助插件来自动加前缀,
postcss-loader
就是来给
css3
加浏览器前缀的,安装依赖:
pnpm i postcss-loader autoprefixer -D
为了避免
webpack.base.ts
文件过于庞大,我们将一些
loader
配置提取成单独的文件来进行管理,根目录新建 postcss.config.js
,作为
postcss-loader
的配置文件,会自动读取配置:
module.exports = {
ident: "postcss",
plugins: [require("autoprefixer")],
};
修改
webpack.base.ts
,在解析
css
和
less
的规则中添加配置:
// ...
const styleLoadersArray = [
"style-loader",
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[path][name]__[local]--[hash:5]",
},
},
},
// 添加 postcss-loader
'postcss-loader'
];
配置完成后,需要有一份要兼容浏览器的清单,让
postcss-loader
知道要加哪些浏览器的前缀,在根目录创建 .browserslistrc
文件:
IE 9 # 兼容IE 9
chrome 35 # 兼容chrome 35
以兼容到
ie9
和
chrome35
版本为例,配置好后,在
app.module.less
中加入一些
CSS3
的语法,重新启 动项目,就可以在浏览器的控制台-Elements
中看到配置成功了。
执行
pnpm run build:dev
打包,也可以看到打包后的
css
文件已经加上了
ie
和谷歌内核的前缀。
10.处理其他常用资源
处理图片
对于图片文件,
webpack4
使用
file-loader
和
url-loader
来处理的,但
webpack5
不使用这两个
loader
了
,
而是采用自带的
asset-module
来处理,修改
webpack.base.ts
,添加图片解析配置
{
output: {
// ... 这里自定义输出文件名的方式是,将某些资源发送到指定目录
assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
// ...
{
test: /\.(png|jpe?g|gif|svg)$/i, // 匹配图片文件
type: "asset", // type选择asset
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb转base64
}
},
generator: {
filename: 'static/images/[hash][ext][query]', // 文件输出目录和命名
},
},
]
}
}
将文件编译为
Data URI
使用,可以节省
HTTP
请求,是一个性能优化的点。但是将图片文件经
过
base64
编码转为
Data URI
,体积会增加大约
33%
。所以,我们一般只针对小图片做
Base64
的处理,对于一些比较大的文件来说,转为
Data URI
会明显增加打包后文件的体积,反而会加
大对带宽资源和流量的需求。
由于我们希望通过
ES6
的新语法
ESModule
的方式导入资源,为了使
TypeScript
可以识别图片模
块,需要在
src/typings/global.d.ts
中加入声明:
// ...
/* IMAGES */
declare module '*.svg' {
const ref: string;
export default ref;
}
declare module '*.bmp' {
const ref: string;
export default ref;
}
declare module '*.gif' {
const ref: string;
export default ref;
}
declare module '*.jpg' {
const ref: string;
export default ref;
}
declare module '*.jpeg' {
const ref: string;
export default ref;
}
declare module '*.png' {
const ref: string;
export default ref;
}
然后在 App.tsx 中引入图片资源:
import '@/App.css'
import lessStyles from '@/app.less'
import scssStyles from '@/app.scss'
import stylStyles from '@/app.styl'
import smallImg from '@/assets/images/5kb_img.jpeg'
import bigImg from '@/assets/images/10kb_img.png'
function App() {
return <div>
<h2>webpack5-react-ts</h2>
<div className={lessStyles['lessBox']}>
<div className={lessStyles['box']}>lessBox
<img src={smallImg} alt="小于10kb的图片" />
<img src={bigImg} alt="大于于10kb的图片" />
<div className={lessStyles['smallImg']}>小图片背景</div>
<div className={lessStyles['bigImg']}>大图片背景</div>
</div>
</div>
<div className={scssStyles['scssBox']}>
<div className={scssStyles['box']}>scssBox</div>
</div>
<div className={stylStyles['stylBox']}>
<div className={stylStyles['box']}>stylBox</div>
</div>
</div>
}
export default App
在
app.less
文件中加入背景图片:
@color: red;
.lessBox {
.box {
color: @color;
background - color: lightblue;
transform: translateX(100);
&:before{
@arr: 'hello', 'world';
content: `@{arr}.join(' ').toUpperCase()`;
}
}
.smallImg {
width: 69px;
height: 75px;
background: url('@/assets/imgs/5kb_img.jpeg') no - repeat;
}
.bigImg {
width: 232px;
height: 154px;
background: url('@/assets/imgs/10kb_img.png') no - repeat;
}
}
重启项目之后,就可以看到图片被展示出来了
然后测试打包
图片被打包进了我们指定的
static/images
文件里面。
处理字体和媒体
字体文件和媒体文件这两种资源处理方式和处理图片是一样的,只需要把匹配的路径和打包后放置的路径修改一下就可以了。修改 webpack.base.ts
文件:
module: {
rules: [
// ...
{
test: /.(woff2?|eot|ttf|otf)$/, // 匹配字体图标文件
type: "asset", // type选择asset
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb转base64
}
},
generator: {
filename: 'static/fonts/[hash][ext][query]', // 文件输出目录和命名
},
},
{
test: /.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件
type: "asset", // type选择asset
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb转base64
}
},
generator: {
filename: 'static/media/[hash][ext][query]', // 文件输出目录和命名
},
},
]
}
对于
fant-family
,可以去
google font
找一个字体,放入
assets/fonts
,然后通过下面的方式引
入:
@color: red;
@font-face {
font - family: "GoldmanBold";
src: local("GoldmanBold"),
url("@/assets/fonts/Phudu-Bold.ttf") format("truetype");
font - weight: bold;
}
.lessBox {
.box {
color: @color;
background - color: lightblue;
transform: translateX(100);
font - family: "GoldmanBold";
&:before{
@arr: 'hello', 'world';
content: `@{arr}.join(' ').toUpperCase()`;
}
}
}
处理json资源
// ...
{
// 匹配json文件
test: /\.json$/,
type: "json", // 模块资源类型为json模块
generator: {
// 这里专门针对json文件的处理
filename: "static/json/[name].[hash][ext][query]",
},
},
//
在
src
下增加一个
json
文件
test.json:
[
{
"name": "ian1",
"age": 18
},
{
"name": "ian2",
"age": 22
},
{
"name": "ian3",
"age": 23
}
]
然后在
App.tsx
中引入:
import memberList from './test.json'
这里可能会出现一个报错:
Cannot find module './test.json'. Consider using '--resolveJsonModule' to import
module with '.json' extension.
需要在
tsconfig.json
中加入一个配置:
{
"compilerOptions": {
// ...
"resolveJsonModule": true,
// ...
},
// ...
}
就可以在控制台打印出我们通过模块导入的
json
文件了:
11.babel处理js非标准语法
现在
react
主流开发都是函数组件和
react-hooks
,但有时也会用类组件,可以用装饰器简化代码。
新增
src/components/Cls.tsx
组件,在
App.tsx
中引入该组件使用
import React, { PureComponent } from "react";
// 装饰器为,组件添加age属性
function addAge(Target: Function) {
Target.prototype.age = 111
}
// 使用装饰器
@addAge
class Cls extends PureComponent {
age?: number
render() {
return (
<h2>我是类组件---{this.age}</h2>
)
}
}
export default Cls
需要开启一下
ts
装饰器支持,修改
tsconfig.json
文件
// tsconfig.json
{
"compilerOptions": {
// ...
// 开启装饰器使用
"experimentalDecorators": true
}
}
上面
Cls
组件代码中使用了装饰器目前
js
标准语法是不支持的,现在运行或者打包会报错,不识别装饰
器语法,需要借助
babel-loader
插件,安装依赖:
pnpm i @babel/plugin-proposal-decorators -D
在
babel.config.js
中添加插件:
const isDEV = process.env.NODE_ENV === "development"; // 是否是开发模式
module.exports = {
// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文件,babel-loader会自
动寻找上面配置好的文件.browserlistrc
// "targets": {
// "chrome": 35,
// "ie": 9
// },
targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入
polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
].filter(Boolean), // 过滤空值
};
现在项目就支持装饰器了。重启项目
12.webpack构建速度优化
webpack 进度条
webpackbar
这是一款个人感觉是个十分美观优雅的进度条,很多成名框架都用过他。而且使用起来也极其方便,也可以支持多个并发构建是个十分强大的进度插件。
pnpm i webpackbar -D
最常用的属性配置其实就是这些,注释里也写的很清楚了,我们在
webpack.base.ts
中引入:
// ...
import WebpackBar from 'webpackbar';
// ...
const baseConfig: Configuration = {
// ...
// plugins 的配置
plugins: [
// ...
new WebpackBar({
color: "#85d", // 默认green,进度条颜色支持HEX
basic: false, // 默认true,启用一个简单的日志报告器
profile: false, // 默认false,启用探查器。
})
],
};
export default baseConfig;
当然里面还有一个属性就是
reporters
还没有写上,可以在里面注册事件,也可以理解为各种钩子函 数。如下:
{ // 注册一个自定义数组
start(context) {
// 在(重新)编译开始时调用
const { start, progress, message, details, request, hasErrors } = context
},
change(context) {
// 在 watch 模式下文件更改时调用
},
update(context) {
// 在每次进度更新后调用
},
done(context) {
// 编译完成时调用
},
progress(context) {
// 构建进度更新时调用
},
allDone(context) {
// 当编译完成时调用
},
beforeAllDone(context) {
// 当编译完成前调用
},
afterAllDone(context) {
// 当编译完成后调用
},
}
构建耗时
当进行优化的时候,肯定要先知道时间都花费在哪些步骤上了,而
speed-measure-webpack-plugin
插件可以帮我们做到,安装依赖:
pnpm i speed-measure-webpack-plugin -D
使用的时候为了不影响到正常的开发
/
打包模式,我们选择新建一个配置文件,新增
webpack
构建分析配置文件 build/webpack.analy.ts
import { Configuration } from 'webpack'
import prodConfig from './webpack.prod' // 引入打包配置
import { merge } from 'webpack-merge' // 引入合并webpack配置方法
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); // 引入webpack
打包速度分析插件
const smp = new SpeedMeasurePlugin(); // 实例化分析插件
// 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
const analyConfig: Configuration = smp.wrap(merge(prodConfig, {
}))
export default analyConfig
修改
package.json
添加启动
webpack
打包分析脚本命令,在
scripts
新增:
{
// ...
"scripts": {
// ...
"build:analy": "cross-env NODE_ENV=production BASE_ENV=production webpack -c
build / webpack.analy.ts"
}
// ...
}
执行
pnpm build:analy
命令就能看见构建所耗时间
开启持久化存储缓存
在
webpack5
之前做缓存是使用
babel-loader
缓存解决
js
的解析结果,
cache-loader
缓存
css
等资 源的解析结果,还有模块缓存插件 hard-source-webpack-plugin
,配置好缓存后第二次打包,通过对 文件做哈希对比来验证文件前后是否一致,如果一致则采用上一次的缓存,可以极大地节省时间。
webpack5
较于
webpack4
,新增了持久化缓存、改进缓存算法等优化,通过配置
webpack
持久化缓 存
,来缓存生成的
webpack
模块和
chunk
,改善下一次打包的构建速度
,
可提速
90%
左右
,
配置也简 单,修改 webpack.base.ts
:
// webpack.base.ts
// ...
module.exports = {
// ...
cache: {
type: 'filesystem', // 使用文件缓存
},
}
通过开启
webpack5
持久化存储缓存,极大缩短了启动和打包的时间。缓存的存储位置在
node_modules/.cache/webpack
,里面又区分了
development
和
production
缓存。
运行在
Node.js
之上的
webpack
是单线程模式的,也就是说,
webpack
打包只能逐个文件处理,当 webpack 需要打包大量文件时,打包时间就会比较漫长。
多进程
/
多实例构建的方案比较知名的有以下三种:
thread-loader
parallel-webpack
HappyPack
webpack
的
loader
默认在单线程执行,现代电脑一般都有多核
cpu
,可以借助多核
cpu
开启多线程
loader 解析,可以极大地提升
loader
解析的速度,
thread-loader
就是用来开启多进程解析
loader
的,安装依赖
pnpm i thread-loader -D
使用时
,
需将此
loader
放置在其他
loader
之前。放置在此
loader
之后的
loader
会在一个独立的
worker
池中运行。
修改
webpack.base.ts
module: {
rules: [
{
test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
use: [
// 开启多进程打包。
// 进程启动大概为600ms,进程通信也有开销。
// 只有工作消耗时间比较长,才需要多进程打包
{
loader: 'thread-loader',
options: {
wokers: 4 // 进程数
}
},
'babel-loader']
},
]
}
由于
thread-loader
不支持抽离
css
插件
MiniCssExtractPlugin.loader
(
下面会讲
)
,所以这里只配
置了多进程解析
ts
。
缩小构建目标
一般第三库都是已经处理好的
,
不需要再次使用
loader
去解析,可以按照实际情况合理配置
loader
的作 用范围,来减少不必要的 loader
解析,节省时间,通过使用
include
和
exclude
两个配置项,可以实现这个功能,常见的例如
include
:只解析该选项配置的模块
exclude
:不解该选项配置的模块
,
优先级更高
修改
webpack.base.ts :
module: {
rules: [
{
test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
exclude: /node_modules/,
use: [
// 开启多进程打包。
// 进程启动大概为600ms,进程通信也有开销。
// 只有工作消耗时间比较长,才需要多进程打包
{
loader: 'thread-loader',
options: {
wokers: 4 // 进程数
}
},
'babel-loader']
},
]
}
其他
loader
也是相同的配置方式,如果除
src
文件外也还有需要解析的,就把对应的目录地址加上就可 以了,比如需要引入 antd
的
css
,可以把
antd
的文件目录路径添加解析
css
规则到
include
里面。
devtools 配置
开发过程中或者打包后的代码都是
webpack
处理后的代码,如果进行调试肯定希望看到源代码,而不是 编译后的代码, source map
就是用来做源码映射的,不同的映射模式会明显影响到构建和重新构建的速度, devtool
选项就是
webpack
提供的选择源码映射方式的配置。
devtool
的命名规则为:
^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$
开发环境推荐:
eval-cheap-module-source-map:
、本地开发首次打包慢点没关系,因为
eval
缓存的原因,热更新会很快
、开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上
cheap
、我们希望能够找到源代码的错误,而不是打包后的,所以需要加上
module
修改 webpack.dev.ts
// webpack.dev.ts
module.exports = {
// ...
devtool: 'eval-cheap-module-source-map'
}
打包环境推荐:
none(
就是不配置
devtool
选项了,不是配置
devtool: 'none')
// webpack.prod.ts
module.exports = {
// ...
// devtool: '', // 不用配置devtool此项
}
none
配置在调试的时候,只能看到编译后的代码,也不会泄露源代码,打包速度也会比较快。只
是不方便线上排查问题,但一般都可以根据报错信息在本地环境很快找出问题所在。
13.webpack构建产物优化
bundle 体积分析工具
webpack-bundle-analyzer
是分析
webpack
打包后文件的插件,使用交互式可缩放树形图可视化
webpack
输出文件的大小。通过该插件可以对打包后的文件进行观察和分析,可以方便我们对不完美的地方针对性的优化,安装依赖:
pnpm i webpack-bundle-analyzer -D
修改
webpack.analy.ts
:
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import prodConfig from "./webpack.prod";
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
// 引入webpack打包速度分析插件
const smp = new SpeedMeasurePlugin();
// 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
const analyConfig: Configuration = smp.wrap(merge(prodConfig, {
plugins: [
new BundleAnalyzerPlugin() // 配置分析打包结果插件
]
}))
export default analyConfig;
配置好后,执行
pnpm run build:analy
命令,打包完成后浏览器会自动打开窗口,可以看到打包文件的分析结果页面,可以看到各个文件所占的资源大小:
![](https://i-blog.csdnimg.cn/blog_migrate/65fcf129cf797605b37ebb2603df24b2.png)
然后,我们就可以根据这个图上给出的信息,来针对性优化产物体积。
样式提取
在开发环境我们希望
css
嵌入在
style
标签里面,方便样式热替换,但打包时我们希望把
css
单独抽离出来,
方便配置缓存策略。而插件
mini-css-extract-plugin
就是来帮我们做这件事的,安装依赖:
pnpm i mini-css-extract-plugin -D
修改
webpack.base.ts
,根据环境变量设置开发环境使用
style-looader
,打包模式抽离
css
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const isDev = process.env.NODE_ENV === 'development' // 是否是开发模式
const styleLoadersArray = [
isDev ? "style-loader" : MiniCssExtractPlugin.loader, // 开发环境使用stylelooader,打包模式抽离css
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[path][name]__[local]--[hash:5]",
},
},
},
'postcss-loader'
];
再修改
webpack.prod.ts
,打包时添加抽离
css
插件:
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const prodConfig: Configuration = merge(baseConfig, {
// ...
plugins: [
// ...
new MiniCssExtractPlugin({
filename: 'static/css/[name].css' // 抽离css的输出目录和名称
}),
],
});
export default prodConfig;
配置完成后,在开发模式
css
会嵌入到
style
标签里面,方便样式热替换,打包时会把
css
抽离成单独的 css
文件。
样式压缩
可以看到,上面配置了打包时把
css
抽离为单独
css
文件的配置,打开打包后的文件查看,可以看到默认 css
是没有压缩的,需要手动配置一下压缩
css
的插件。
可以借助
css-minimizer-webpack-plugin
来压缩
css
,安装依赖:
pnpm i css-minimizer-webpack-plugin -D
修改
webpack.prod.ts
文件, 需要在优化项
optimization
下的
minimizer
属性中配置:
// ...
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
module.exports = {
// ...
optimization: {
minimizer: [
new CssMinimizerPlugin(), // 压缩css
],
},
}
再次执行打包就可以看到
css
已经被压缩了
js 压缩
设置
mode
为
production
时
,webpack
会使用内置插件
terser-webpack-plugin
压缩
js
文件
,
该插件默认支持多线程压缩,
但是上面配置
optimization.minimizer
压缩
css
后
,js
压缩就失效了
,
需要手动再添加一
下
,webpack
内部安装了该插件
,
由于
pnpm
解决了幽灵依赖问题
,
如果用的
pnpm
的话
,
需要手动再安装一下依赖。:
pnpm i terser-webpack-plugin compression-webpack-plugin -D
修改
webpack.prod.ts
文件:
import path from "path";
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import CopyPlugin from "copy-webpack-plugin";
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin'
import CompressionPlugin from 'compression-webpack-plugin'
import baseConfig from "./webpack.base";
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
/**
* 打包环境推荐:none(就是不配置devtool选项了,不是配置devtool: 'none')
* ● none话调试只能看到编译后的代码,也不会泄露源代码,打包速度也会比较快。
* ● 只是不方便线上排查问题, 但一般都可以根据报错信息在本地环境很快找出问题所在。
*/
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"), // 复制public下文件
to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
filter: (source) => !source.includes("index.html"), // 忽略index.html
},
],
}),
new MiniCssExtractPlugin({
filename: 'static/css/[name].css' // 抽离css的输出目录和名称
}),
// 打包时生成gzip文件
new CompressionPlugin({
test: /\.(js|css)$/, // 只生成css,js压缩文件
filename: '[path][base].gz', // 文件命名
algorithm: 'gzip', // 压缩格式,默认是gzip
threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
minRatio: 0.8 // 压缩率,默认值是 0.8
})
],
optimization: {
// splitChunks: {
// chunks: "all",
// },
runtimeChunk: {
name: 'mainifels'
},
minimize: true,
minimizer: [
new CssMinimizerPlugin(), // 压缩css
new TerserPlugin({
parallel: true, // 开启多线程压缩
terserOptions: {
compress: {
pure_funcs: ['console.log'] // 删除console.log
}
}
})
],
},
performance: { // 配置与性能相关的选项的对象
hints: false, // 设置为false将关闭性能提示。默认情况下,Webpack会显示有关入口点和资产大小的警告和错误消息。将hints设置为false可以禁用这些消息。
maxAssetSize: 4000000, // 设置一个整数,表示以字节为单位的单个资源文件的最大允许大小。如果任何资源的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为4000000字节(约4MB)。
maxEntrypointSize: 5000000 // 设置一个整数,表示以字节为单位的入口点文件的最大允许大小。入口点是Webpack构建产生的主要JS文件,通常是应用程序的主要代码。如果入口点的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为5000000字节(约5MB)。
}
});
export default prodConfig;
配置完成后再打包,
css
和
js
就都可以被压缩了
tree-shaking清理未引用js
Tree Shaking
的字面意思是摇树,伴随着摇树这个动作,树上的枯枝败叶都会被摇下来,这里的
tree-shaking 在代码中摇掉的是未使用到的代码,也就是未引用的代码,最早是在
rollup
库中出现的,
webpack
在
v2
版本之后也开始支持。模式
mode
为
production
时就会默认开启
tree-shaking
功
能以此来标记未引入代码然后移除掉,测试一下。
在
src/components
目录下新增
Demo1.tsx
、
Demo2.tsx
两个组件
// src/components/Demo1.tsx
import React from "react";
function Demo1() {
return <h3>我是Demo1组件</h3>
}
export default Demo1
// src/components/Demo2.tsx
import React from "react";
function Demo2() {
return <h3>我是Demo2组件</h3>
}
export default Demo2
再在
src/components
目录下新增
index.ts
,把
Demo1
和
Demo2
组件引入进来再暴露出去:
// src/components/index.ts
export { default as Demo1 } from './Demo1'
export { default as Demo2 } from './Demo2'
在
App.tsx
中引入两个组件,但只使用
Demo1
组件:
// ...
import { Demo1, Demo2 } from '@/components'
function App() {
return <>
// ...
<Demo1 />
</>
}
export default App
执行打包
,
可以看到在
main.js
中搜索
Demo
,只搜索到了
Demo1
,代表
Demo2
组件被
tree
shaking
移除掉了
tree-shaking清理未使用css
js
中会有未使用到的代码,
css
中也会有未被页面使用到的样式,可以通过
purgecss-webpack-plugin
插件打包的时候移除未使用到的css
样式,这个插件是和
mini-css-extract-plugin
插件配合使用的
,
在上面已经安装过,还需要
glob-all
来选择要检测哪些文件里面的类名和
id
还有标签名称,安装依赖
:
pnpm i purgecss-webpack-plugin glob-all -D
修改
webpack.prod.ts
:
// ...
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
const globAll = require('glob-all')
const { PurgeCSSPlugin } = require('purgecss-webpack-plugin')
const prodConfig: Configuration = merge(baseConfig, {
// ...
plugins: [
// 抽离css插件
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css'
}),
// 清理无用css,检测src下所有tsx文件和public下index.html中使用的类名和id和标签名称
// 只打包这些文件中用到的样式
new PurgeCSSPlugin({
paths: globAll.sync(
[`${path.join(__dirname, '../src')}/**/*`, path.join(__dirname,
'../public/index.html')],
{
nodir: true
}
),
// 用 only 来指定 purgecss-webpack-plugin 的入口
// https://github.com/FullHuman/purgecss/tree/main/packages/purgecsswebpack-plugin
only: ["dist"],
safelist: {
standard: [/^ant-/] // 过滤以ant-开头的类名,哪怕没用到也不删除
}
}),
]
})
gzip 压缩
前端代码在浏览器运行,需要从服务器把
html
、
css
、
js
资源下载执行,下载的资源体积越小,页面加载速度就会越快。一般会采用 gzip
压缩,现在大部分浏览器和服务器都支持
gzip
,可以有效减少静态资源文件大小,压缩率在 70%
左右。
nginx
可以配置
gzip: on
来开启压缩,但是只在
nginx
层面开启,会在每次请求资源时都对资源进
行压缩,压缩文件会需要时间和占用服务器
cpu
资源,更好的方式是前端在打包的时候直接生成
gzip资源,服务器接收到请求,可以直接把对应压缩好的 gzip
文件返回给浏览器,节省时间和
cpu。
webpack
可以借助
compression-webpack-plugin
插件在打包时生成
gzip
文章,安装依赖:
pnpm i compression-webpack-plugin -D
添加配置,修改
webpack.prod.ts
:
const glob = require('glob')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
// ...
plugins: [
// ...
new CompressionPlugin({
test: /.(js|css)$/, // 只生成css,js压缩文件
filename: '[path][base].gz', // 文件命名
algorithm: 'gzip', // 压缩格式,默认是gzip
threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
minRatio: 0.8 // 压缩率,默认值是 0.8
})
]
}
配置完成后再打包,可以看到打包后
js
的目录下多了一个
.gz
结尾的文件
ok以上介绍了webpack的构建和主要的功能,如果你从头看到了这里,那么就请先休息一下吧~