一、Webpack处理图片与字体
前情提要:
0.准备工作
-
在
src目录下创建asset文件夹创建imgs文件夹和fonts文件夹存放图片和字体 -
在
src/asset/css/index.css使用图片和字体文件css@font-face { font-family: 'CustomFont'; /* 自定义字体名称(随意命名) */ src: url('../fonts/www.otf') format('opentype'); /* 路径根据项目结构调整,建议使用相对路径 */ } .content{ color: red; font-size: 40px; user-select:all; font-family: 'CustomFont'; } .bg{ min-width: 300px; min-height: 1300px; background-image: url('../asset/img/qd.jpg'); background-repeat: no-repeat; } -
format格式如下
字体文件后缀 format()参数适用场景 .woff2'woff2'现代浏览器首选,压缩率最高 .woff'woff'广泛兼容,压缩适中 .ttf'truetype'旧设备或 Android 兼容 .otf'opentype'部分高级排版需求 .eot'embedded-opentype'IE 兼容(已淘汰) -
在
src/components/cps.js文件中
js
import '@/css/index.css';
import w664 from '@/asset/img/w644.jpg';
const img = document.createElement('img');
img.src = w664;
document.body.appendChild(img);
const bg = document.createElement('div');
bg.classList.add('bg');
document.body.appendChild(bg);
1.Webpack4.x处理方式
webapck5.x之前处理图片和字体的时候是通过url-loader和file-loader来处理
图片资源处理
- Loader 组合
- url-loader :将小于指定大小的图片转换为
Base64 格式内嵌到代码中,减少 HTTP 请求。若文件超过限制,则自动调用file-loader处理 - file-loader :将文件复制到输出目录(如
dist),并返回文件路径。常用于处理大体积图片
- url-loader :将小于指定大小的图片转换为
- 配置示例:
js
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192, // 小于 8KB 转为 Base64
name: '[name].[hash:8].[ext]', // 文件名格式
outputPath: 'images/', // 输出目录
esModule: false // 解决路径引用问题(如 HTML 中 src 属性)
}
}
}
]
}
字体文件处理
- Loader 组合
- url-loader 或 file-loader :处理
.woff,.woff2,.eot,.ttf,.svg等字体文件,原理与图片处理类似
- url-loader 或 file-loader :处理
- 配置示例:
js
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: 'fonts/' // 输出到 fonts 目录
}
}
}
]
}
关键配置选项说明
limit(仅url-loader):设置文件大小阈值(单位:字节),超过阈值的文件由file-loader处理outputPath:指定资源输出目录(相对于output.path)name:控制输出文件名格式,支持哈希值(如[hash:8])避免缓存问题esModule:设为false可解决部分场景下资源路径的模块化问题(如CSS中引用字体)
两个loader之间的关系
-
功能继承
url-loader是file-loader的增强版,它在内部封装了file-loader的功能。当图片文件大小超过limit阈值时,url-loader会自动调用file-loader处理文件,无需显式配置file-loader
-
核心区别
url-loader可将小文件(如小于8KB)转为Base64格式内联到代码中,减少 HTTP 请求file-loader仅负责将文件复制到输出目录并返回路径,适用于大文件或无需内联的场景
为何图片配置中可能只看到 url-loader
- 隐式依赖
- 即使配置中仅使用
url-loader,当文件超过limit时,file-loader会被自动调用。因此,必须同时安装file-loader,否则会因依赖缺失导致打包失败
- 即使配置中仅使用
- 典型配置示例
- 此配置未显式调用
file-loader,但实际运行时,大文件会通过file-loader处理
- 此配置未显式调用
js
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192, // 超过 8KB 调用 file-loader
name: '[name].[hash:8].[ext]',
outputPath: 'images/'
}
}
}
- 依赖要求
url-loader并不直接包含file-loader的代码,而是通过调用其 API 实现功能。若未安装file-loader,当文件超过limit时会抛出错误
兼容性注意事项
- Webpack 版本 :
Webpack 4.x及更早版本需安装url-loader@1.x和file-loader@5.x
powershell
npm install url-loader@1.1.2 file-loader@5.0.2 --save-dev
图片路径错误
esModule配置 :Webpack 4.x中,若file-loader的esModule选项未设为false,可能导致图片路径输出为[object Module]。需在配置中显式关闭
js
options: {
esModule: false // 关闭 ES 模块语法
}
总结
- 在
Webpack 5.x之前,图片和字体文件主要依赖url-loader和file-loader,通过合理配置limit和输出路径,实现资源优化如Base64 内联)与文件管理。Webpack5.x后,可通过内置的asset/resource类型替代
2.Webpack5.x处理方式
1.为什么5的版本和4版本有不同处理方式?
Webpack5.x的asset模块通过 原生资源处理机制 替代了url-loader和file-loader,提供更简洁的配置、更高的性能- 完全无需依赖第三方 Loader 。根据文件类型选择
asset/resource(字体)或asset(图片自动优化),即可高效管理静态资源
2.资源模块类型
- 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换这些 loader:
1.asset/resource
-
asset/resource发送一个单独的文件并导出 URL5.x之前通过使用file-loader实现- 之前讲过了
file-loader不过多赘述了
- 之前讲过了
-
如何使用
asset/resource- 在我们的配置文件中
js// ... 省略其他配置 module: { rules: [ // ... 省略其他配置 { test: /.(jpg|png|gif|jpe?g|svg)$/, type: 'asset/resource', }, ], },- 如图我们会有一个疑问,我只配置了一个图片扩展名的配置 怎么字体文件,怎么也进入打包文件中呢?
- 因为我们之前准备工作的时候,进行引入字体文件, 通过
css语法 @font-face引入的字体文件,在使用css文件的时候,会将他放进webpack依赖图中 ,处理css的时候,我们用css-loader来处理转换css模块 ,让webpack识别- 我们用
asset/resource这个模式去解析图片文件,打包的时候会生成图片文件 ,根据生成效果并在使用图片的时候返回一个url
- 我们用

2.asset/inline
-
asset/inline资源转换为Base64 格式内嵌到代码中 -
5.x之前通过使用url-loader实现- 之前讲过了
url-loader不过多赘述了
- 之前讲过了
-
如何使用
asset/inline- 在我们的配置文件中
js// ... 省略其他配置 module: { rules: [ // ... 省略其他配置 { test: /.(jpg|png|gif|jpe?g|svg)$/, type: 'asset/inline', }, ], },- 我们用
asset/inline这个模式去解析图片文件 我们打包出的文件没有生成图片文件 ,根据生成效果 看到内嵌在代码中,并且是base64

3.asset/source
-
asset/source导出资源的源代码 -
之前通过使用raw-loader 实现;
-
安装
raw-loaderpowershellnpm install raw-loader --save-dev -
4.x配置文件中jsmodule.exports = { module: { rules: [ { test: /\.(txt|svg|css)$/, // 匹配目标文件 use: 'raw-loader' // 无需复杂参数 } ] } }; -
将文件内容作为原始字符串导入 的 Loader。它的作用类似于直接读取文件的二进制内容,但以字符串形式暴露给 JavaScript 模块,适用于需要直接操作文件原始数据的场
-
功能定位
-
不进行任何转译(如 Babel)、不处理依赖(如 CSS 中的
@import),仅将文件内容转为字符串。 -
创建
data.txt
txtconst a = 123- 在
src/components/cps.js文件中
jsimport text from './data.txt'; console.log('text', text);
-
4.asset
-
asset在导出一个资源转换为Base64 格式内嵌到代码中和发送一个单独的文件之间自动选择 -
为什么要限制大小?
-
这是因为小的图片转换
base64之后可以和页面一起被请求,减少不必要的请求过程; -
而大的图片也进行转换
base64,反而会影响页面的请求速度; -
所以大的图片直接文件复制到输出目录,并返回路径
-
5.x之前通过使用url-loader,并且limit配置资源体积限制实现; -
5.x我们该如何限制大小呢?- 步骤一:将type修改为asset;
- 添加一个
parser属性,并且制定dataUrl的条件,添加maxSize属性 ;- 注意:无论是
url-loader还是maxSize他们**单位是字节(bytes)**我设置的的maxSize: 200 * 200实际是40000 字节(约 39KB),而非像素尺寸200×200px,记住不要搞混淆了
- 注意:无论是
jsmodule.exports = { // ....省略其他配置 { test: /.(jpg|png|gif|jpe?g|svg)$/, type: 'asset', parser: { dataUrlCondition: { maxSize: 200 * 200, }, }, } } -
如图所示:
-
第一图我们打包出来,文件输出目录就一张图片 因为就一张大图占有空间超出了设置大小
-
小图片我设置成背景图片 它的原图像素大小是
201*251空间占用大小是7kb,39>7 所以这个转换为base64 -
大图片我设置图片 它的原图像素大小是
647*825空间占用大小是280 Kb,39<280 直接文件 复制到输出目录,并返回http路径
-

3.资源模块如何设置文件名称和导出路径
1.全局配置所有输出规则
-
方式一:修改
output,添加assetModuleFilename属性;assetModuleFilename是 全局配置所有 Asset Modules 类型文件的输出规则
jsmodule.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'build'), filename: 'index.js', assetModuleFilename: 'img/[name].[hash:6].[ext]', } // .....省略其他配置 } -
可以看到字体和图片 都放到一个
img文件夹下了

适用文件类型
| 文件类型 | 常见扩展名 | 典型场景 |
|---|---|---|
| 图片资源 | .png、.jpg、.jpeg、.gif、.svg |
CSS 中的 background-image |
| 字体文件 | .otf、.ttf、.woff、.woff2 |
@font-face 引入的字体(如图像中的 www.otf) |
| 音视频文件 | .mp3、.mp4、.ogg |
多媒体资源引入 |
| 其他二进制文件 | .pdf、.zip |
直接导入的文档或压缩包 |
2.每个资源类型单独配置
- 方式二:在Rule中,添加一个
generator属性,并且设置filename
js
module: {
rules: [
// ...省略其他配置
{
test: /.(jpg|png|gif|jpe?g|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 200 * 200,
},
},
generator: {
filename: 'asset/imgs/[name].[hash:6].[ext]',
},
},
{
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
type: 'asset',
generator: {
filename: 'asset/fonts/[name].[hash:6].[ext]',
},
},
],
},
- 可以看到图片中我们打包文件成功为字体文件 和图片文件 分出单独目录进行管理

- 我们这里介绍几个最常用的placeholder:
- [ext]: 处理文件的扩展名;
- [name]:处理文件的名称;
- [hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
二、Babel处理 Vue、React、TS 、ES6+转ES5
1.为什么需要babel?
- 在开发中我们很少直接去接触babel ,但是babel对于前端开发来说,目前是不可缺少的一部分:
- 开发中,我们想要使用
ES6+的语法,想要使用TypeScript,开发React项目,它们都是离不开Babel的;
- 开发中,我们想要使用
- 那么,
Babel到底是什么呢?Babel是一个工具链 ,主要用于将ECMAScript 2015+代码转换为向后兼容版本的JavaScript;- 将
ReactTypeScript这种框架和静态语言转换为JavaScript
ES6语法用babel进行转换
js
const a = 10;
const b = ()=>{
console.log(2222)
}
class Person{
constructor(age){
this.age = age
}
}
const p1 = new Person('18')
- babel转换结果

更多详情查看 babel官网
2.babel命令行使用
1.介绍
babel本身可以作为一个独立的工具 (和postcss一样),可以和webpack等构建工具配置使用,也可以单独使用。
2.安装
-
如果我们希望在命令行尝试使用babel,需要安装如下库:
@babel/core:babel的核心代码,必须安装;@babel/cli:可以让我们在命令行使用babel;
powershellnpm install @babel/cli @babel/core -D
3.使用
-
使用babel来处理我们的源代码
-
src:是源文件的目录 -
--out-dir:指定要输出的文件夹dist;
powershellnpx babel src--out-dir dist- 此时我们发现并没有转换啊,仅复制文件,显示出来
-
4.没有转换成功的原因
-
这是因为我们没有安装
bable的插件和预设- 实际行为
- 对
.js文件- ES6+ 语法(如箭头函数、
class、const等) ❌ 不会转换,因为缺少@babel/preset-env或相关插件。Babel 默认无任何转换行为。 - JSX 语法(如
<div></div>) ❌ 不会转换,因为需要@babel/preset-react插件。
- ES6+ 语法(如箭头函数、
- 对
.ts/.tsx文件- ❌ 直接报错 ,因为 Babel 默认只处理
.js文件,且需要@babel/preset-typescript来解析TypeScript语法。
- ❌ 直接报错 ,因为 Babel 默认只处理
- 对
- 根本原因
- Babel 的行为完全由 插件和预设 决定。没有安装任何插件/预设时:
- 仅复制文件,不做任何语法转换
- 不支持
TypeScript或JSX解析
- Babel 的行为完全由 插件和预设 决定。没有安装任何插件/预设时:
- 实际行为
5.插件的使用
-
比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
@babel/plugin-transform-arrow-functionspowershellnpm install @babel/plugin-transform-arrow-functions -D- 然后使用命令 已经将箭头函数转成
ES5的函数
powershellnpx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions - 然后使用命令 已经将箭头函数转成
-
但是查看转换后的结果:我们会发现**
const并没有转成var 构造函数也没有转换**- 这是因为
plugin-transform-arrow-functions,并没有提供这样的功能,它只能做箭头函数的转换 - 我们需要使用
plugin-transform-block-scoping和``@babel/plugin-transform-classes` 来完成这样的功能;plugin-transform-block-scoping处理将const let转成var@babel/plugin-transform-classes将类转换成ES5的构造函数
- 安装
plugin-transform-block-scoping和``@babel/plugin-transform-classes`
bashnpm install @babel/plugin-transform-block-scoping -D- 执行命令
powershellnpx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping ,@babel/plugin-transform-arrow-functions,@babel/plugin-transform-classes - 这是因为
-
这样处理将
const let转成var,将类转换成ES5的构造函数 , 将箭头函数转成ES5的函数
3.Babel的预设preset-env
-
但是如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset):
- 安装
@babel/preset-env预设
powershellnpm install @babel/preset-env -D- 执行下面命令
- 他会把
src目录下所有js都会进行转换
- 他会把
powershellnpx babel src --out-dir dist --presets=@babel/preset-env注意:图片中我这里我用的
src1目录下进行转换的
- 安装
1.preset-env预设的参数
参数介绍
-
target: > 0.25%, not dead> 0.25%→ 代码要兼容"全球使用率超过 0.25% 的浏览器"(比如主流浏览器的最新几个版本)。not dead→ 排除那些已经"死掉"的浏览器(比如 IE 11、旧版 Safari 等)。- 效果:Babel 会按这个条件,只转换目标浏览器不支持的语法。
-
corejs-
这个是什么?
- Babel 可以将
ES6+的新语法 (如箭头函数、class)转成ES5,但**新的 API**(如Promise、Array.from)无法通过语法转换实现,必须用代码模拟。 core-js就是用来"模拟"这些新 API 的代码库。比如,在 IE11 中运行new Set([1, 2, 3]),如果没有core-js,就会直接报错;有了core-js,它会用ES5代码实现Set的功能。
- Babel 可以将
-
与 Babel 的关系
-
Babel 负责语法转换 (如
() => {}→function() {})。 -
core-js负责**API模拟**(如Promise、Array.includes)。 -
版本差异
-
core-js@2- 支持大部分 ES6+ API,但不覆盖实例方法 (如
[1, 2, 3].includes(1))。 - 已停止维护,不推荐使用。
- 支持大部分 ES6+ API,但不覆盖实例方法 (如
-
core-js@3- 支持 ES6-ES2022 的全部 API,包括实例方法(如
array.includes、string.padStart)。 - 持续更新,推荐使用。
- 支持 ES6-ES2022 的全部 API,包括实例方法(如
-
作用 :指定
core-js的版本(必须安装对应的版本)。- 为什么需要显式指定
corejs版本?
Babel 默认不处理新
API,必须通过corejs: 3明确告诉它使用core-js@3的Polyfill。jsnpm install core-js@3 --save - 为什么需要显式指定
-
-
useBuiltIns- 可选值
'usage'→ 按需添加(只加代码中用到的 API,推荐 )- 按需加载
Polyfill(比如Promise、Array.includes等新API) - 效果:Babel 会检查你的代码,只在你用到新 API 的地方,自动插入对应的兼容代码。
- 优点:生成的代码体积更小。
- 按需加载
'entry'→ 在入口文件手动导入import 'core-js',根据目标环境添加全部Polyfill。false→ 不自动添加Polyfill(需手动处理兼容性)。
- 可选值
-
modules- 作用 :是否将 ES6 模块语法(
import/export)转成其他模块格式。 - 可选值
'auto'→ 由 Webpack 等打包工具决定(默认)。false→ 保留 ES6 模块语法(推荐,便于 Webpack 做 Tree Shaking)。'commonjs'→ 转成CommonJS格式(适合Node.js)。
shippedProposals- 作用:是否启用浏览器已支持的提案特性(如某些 ES2022 特性)。
jsshippedProposals: true // 直接使用浏览器已实现的提案语法bugfixes- 作用:根据目标环境自动修复已知的语法 Bug(推荐开启)。
vbnetbugfixes: trueloose- 作用:以"宽松模式"生成代码(代码更简洁,但可能不符合标准)。
jsloose: true - 作用 :是否将 ES6 模块语法(
参数在命令行使用
powershell
npx babel src --out-dir dist--presets=@babel/preset-env,{"targets":"> 0.25%, not dead","useBuiltIns":"usage","corejs":3}
4.Babel-loader的使用
配置
-
在实际开发中,我们通常会在构建工具中通过配置babel来对其进行使用的,比如在
webpack中。 -
那么我们就需要去安装相关的依赖:
-
如果之前已经安装了
@babel/core,那么这里不需要再次安装;
powershell
npm install babel-loader @babel/core -D // 没有安装 @babel/core
npm install babel-loader -D // 安装了 @babel/core
- 我们可以设置在配置文件中设置规则,在加载
js文件时,使用我们的babel:
js
module: {
rules: [
// 省略其他配置
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
- 如果我们就这样设置了没有配置预设或者插件 ,还是跟刚刚一样 不会有任何转换变化如果遇到
ts和tsx还会报错 - 如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给
webpack提供一个preset,webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel - 如果之前没有安装
@babel/preset-env预设
powershell
npm install @babel/preset-env -D
- 然后在配置文件中新增预设
js
module: {
rules: [
// 省略其他配置
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
options: {
presets: ['@babel/preset-env'],
/** 这里可以配置插件 但是使用了预设可以不配插件
plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-transform-arrow-functions' // 转换箭头函数
]
**
},
},
}
}
]
配置项中使用预设参数
- 我现在想让预设配置能自动根据你指定的目标环境把
ES6+ 代码转换成兼容的 ES5 代码。
js
module: {
rules: [
// 省略其他配置
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
options: {
presets: [
['@babel/preset-env', {
targets: 'defaults', // 兼容主流浏览器最新两个版本
useBuiltIns: 'entry', // 在入口文件全局引入 Polyfill
corejs: 3
}],
['@babel/preset-react', { runtime: 'automatic' }]
],
},
},
}
}
]
- 目前配置文件太多东西了,我们直接把
babel配置项单独提取出去,我们创建一个babel.config.js
js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: 'defaults', // 兼容主流浏览器最新两个版本
useBuiltIns: 'usage', // 在入口文件全局引入 Polyfill
corejs: 3,
},
],
],
};
- 然后将
webpack配置文件里面的预设参数给移除 - 如果我们没有移除配置项,
babel.config.js和options选项那个优先级更高呢?babel.config.js优先级更高
js
module: {
rules: [
// 省略其他配置
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
}
]
5. 处理React
安装 react
- react 需要安装两个
react和react-dom
css
npm i react react-dom
初始化react文件
-
在
src目录下创建page/react/App.jsxjsximport React from 'react' export const ReactApp = () => { const [count, setCount] = React.useState(0) return ( <> <div>{count}</div> <button onClick={() => setCount(count + 1)}>+1</button> </> ) } -
在
src/index.js文件中添加以下代码
js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ReactApp } from 'page/react/App.jsx'; // react
// react
ReactDOM.createRoot(document.getElementById('root')).render(<ReactApp />);
- 在 根目录中
index.html中添加 react的根
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="./build/index.js"></script>
</body>
</html>
配置react预设
- 安装
@babel/preset-react
powershell
npm i @babel/preset-react -D
- 在
babel.config.js添加 这个预设
powershell
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: 'defaults', // 兼容主流浏览器最新两个版本
useBuiltIns: 'usage',
corejs: 3,
},
],
'@babel/preset-react',
],
};
npm run build查看效果

预设的参数
-
runtime:- 作用:控制 JSX 的转换方式。
- 可选值
'classic'→ 旧版模式,需手动导入 React(默认)。'automatic'→ 自动从react/jsx-runtime导入 JSX 函数(推荐,代码更简洁)。
jsruntime: 'automatic' // 无需手动写 `import React from 'react'` -
development- 作用:是否添加开发环境下的调试信息(如组件名称提示)。
jsdevelopment: process.env.NODE_ENV === 'development' // 根据环境自动开启 -
importSource- 作用 :指定
JSX运行时函数的导入路径(配合runtime: 'automatic'使用)
jsimportSource: '@emotion/react' // 使用 Emotion 库的 JSX 运行时 - 作用 :指定
-
throwIfNamespace- 作用 :是否禁止使用 XML 命名空间标签(如
<svg:circle>)
powershellthrowIfNamespace: false // 允许使用(默认是 true,遇到会报错) - 作用 :是否禁止使用 XML 命名空间标签(如
-
pure- 作用 :是否在编译时移除 JSX 中的纯注释(如
/*#__PURE__*/,用于 Tree Shaking)。
jspure: true // 默认开启 - 作用 :是否在编译时移除 JSX 中的纯注释(如
-
配置示例
js
// 在 Babel 配置或 Webpack 的 babel-loader 中
{
presets: [
[
'@babel/preset-env',
{
targets: '> 0.5%, not dead',
useBuiltIns: 'usage',
corejs: 3,
modules: false,
bugfixes: true
}
],
[
'@babel/preset-react',
{
runtime: 'automatic',
development: process.env.NODE_ENV === 'development'
}
]
]
}
6.处理TS或TSx
创建文件
- 在
src目录下创建一个index.tsx文件 路径page/react/App/index.tsx
tsx
import React from 'react';
interface Person {
name: string;
age: number;
gender: string;
}
const PersonComponent = () => {
const p1: Person = {
name: 'zhangsan',
age: 18,
gender: 'male',
};
return (
<div>
<h1>{p1.name}</h1>
<h1>{p1.age}</h1>
<h1>{p1.gender}</h1>
</div>
);
};
export default PersonComponent;
- 在
App.jsx进行引入
jsx
import React from 'react'
import PersonComponent from './index.tsx'
export const ReactApp = () => {
const [count, setCount] = React.useState(0)
return (
<>
<PersonComponent/>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
)
}
安装ts预设并配置
- 安装
powershell
npm i @babel/preset-typescript -D
- 配置在
babel.config.js进行配置
js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: 'defaults', // 兼容主流浏览器最新两个版本
useBuiltIns: 'usage', // 在入口文件全局引入 Polyfill
corejs: 3,
},
],
'@babel/preset-react',
'@babel/preset-typescript',
],
};
npm run build然后进行打包查看效果

预设参数
-
allExtensions -
作用 :将 所有文件 (如
.js、.jsx)当作 TypeScript 处理(默认只处理.ts、.tsx)。 -
场景 :混合
TypeScript和JavaScript的项目(慎用)。
js
presets: [
['@babel/preset-typescript', { allExtensions: true }]
]
-
isTSX -
作用 :强制将文件当作 TSX (TypeScript + JSX)处理(即使扩展名不是
.tsx)。 -
场景 :在
.ts文件中使用JSX语法。
js
presets: [
['@babel/preset-typescript', { isTSX: true }]
]
-
jsxPragma -
作用 :指定 JSX 转换后的函数名(默认是
React.createElement)。 -
场景 :配合非 React 的
JSX运行时(如 Vue 3 的h函数)。
js
presets: [
['@babel/preset-typescript', {
jsxPragma: 'h' // 转换 JSX 为 h('div')
}]
]
-
allowNamespaces -
作用 :是否保留 TypeScript 的 命名空间 (
namespace)语法(默认:false,转换为普通对象)。 -
场景:需要保留命名空间结构。
js
presets: [
['@babel/preset-typescript', { allowNamespaces: true }]
]
-
allowDeclareFields -
作用 :是否允许
TypeScript的 类属性声明 (declare class Foo { bar: string })(默认:false)。 -
场景:需要保留类属性的类型声明。
js
presets: [
['@babel/preset-typescript', { allowDeclareFields: true }]
]
-
onlyRemoveTypeImports- 作用 :仅删除 类型导入 (如
import type { Foo } from '...'),保留普通导入(默认:true)。 - 场景:避免误删普通导入。
jspresets: [ ['@babel/preset-typescript', { onlyRemoveTypeImports: false }] ] - 作用 :仅删除 类型导入 (如
-
optimizeConstEnums- 作用 :优化 常量枚举 (
const enum),直接替换为值(默认:false)。 - 场景:减少代码体积,但可能影响调试。
jspresets: [ ['@babel/preset-typescript', { optimizeConstEnums: true }] ] - 作用 :优化 常量枚举 (
-
rewriteImportExtensions- 作用 :将 TypeScript 的
.ts、.tsx导入扩展名改为.js(默认:false)。 - 场景 :用于
Node.js ESM或浏览器直接加载 ES 模块。
- 作用 :将 TypeScript 的
-
完整配置示例
js
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-typescript',
{
allExtensions: false, // 默认只处理 .ts/.tsx
isTSX: false, // 默认不强制 TSX 模式
jsxPragma: 'React.createElement', // 默认 React
allowNamespaces: false,
allowDeclareFields: false,
onlyRemoveTypeImports: true, // 默认删除类型导入
optimizeConstEnums: false,
rewriteImportExtensions: false,
},
],
],
};
注意事项
- 类型擦除 :Babel 只删除 TypeScript 类型,不进行类型检查 (需用
tsc单独检查)。 - 兼容性
- 部分高级
TypeScript语法(如装饰器@Decorator)需额外插件(如@babel/plugin-proposal-decorators)。
- 部分高级
7.处理Vue
安装vue
- 在
webpack环境下使用vue需要安装vue-loader
powershell
npm i vue
npm i vue-loader-D
初始化vue文件
- 在
src目录下创建page/vue/App.vue
vue
<template>
<h1>{{ title }}</h1>
<div>{{ context }}</div>
</template>
<script>
export default {
data() {
return {
title: 'vue',
context: 'vue',
};
},
};
</script>
<style lang="less" scoped>
h1 {
color: #09f185;
font-size: 40px;
}
div {
color: #f10962;
font-size: 20px;
}
</style>
- 然后在配置文件中添加
vue-loader配置
js
//
module: {
rules: [
// 省略其他配置
{
test: /.vue$/,
use: [
{
loader: 'vue-loader',
},
],
},
}
]
- 打包会报错,这是因为我们必须添加
@vue/compiler-sfc来对template进行解析
js
const { VueLoaderPlugin } = require('vue-loader/dist/index');
module.exports = {
// ...其余配置
module: {
rules: [
// 省略其他配置
{
test: /.vue$/,
use: [
{
loader: 'vue-loader',
},
],
},
}
]
}
plugins: [new VueLoaderPlugin()],
}
- 在
src/index.js文件中添加以下代码- 此时搭建出
Vue和React混合框架的雏形
- 此时搭建出
js
// import './components/cps.js';
import { createApp } from 'vue'; // vue
import App from 'page/vue/App'; // vue
import React from 'react'; // react
import ReactDOM from 'react-dom/client'; // react
import { ReactApp } from 'page/react/App'; // react
// vue
const app = createApp(App);
app.mount('#app');
// react
ReactDOM.createRoot(document.getElementById('root')).render(<ReactApp />);
- 执行
npm run build查看效果

8.React与Vue框架混合使用
- 有些项目会用到两个框架,我们就用
webpack简单搭建一下 框架的混用 - 现在我们有几个问题
- 在
vue中使用tsx语法 如何使用呢? - 在
vue中使用了tsx, 如何避免与react的tsx语法冲突呢? - 如果我是
vue父组件,我想引入React子组件 - 如果我是
React父组件,我想引入vue子组件
- 在
1.vue中使用tSX语法
-
首先我们需要安装一个
babel插件帮我去处理vue中的tsx语法- 安装
babel-plugin-jsx详细可以查看文档
jsnpm install @vue/babel-plugin-jsx -D- 如果你的框架中没有
react可以直接在babel.config.js中这样写
jsconst path = require('path'); module.exports = { presets: [ [ '@babel/preset-env', { targets: 'defaults', // 兼容主流浏览器最新两个版本 useBuiltIns: 'usage', // 在入口文件全局引入 Polyfill corejs: 3, }, ], [ '@babel/preset-typescript', { allExtensions: true, // 允许所有文件扩展名使用 TS isTSX: true, // 启用 TSX 支持 }, ], ], plugins: [ [ '@vue/babel-plugin-jsx', { optimize: true, transformOn: true, // 必须启用事件语法转换 }, ], ] }; - 安装
插件参数
-
transformOn -
类型 :
"boolean" -
默认值 :
"false" -
作用 :
on: { click: xx }转换为onClick: xxx- 未启用
transformOn(默认)
js// 输入(JSX) <button on={{ click: handleClick }}>Click</button> // 输出(编译后) h('button', { on: { click: handleClick } }, 'Click')- 启用
transformOn: true
js// 输入(JSX) <button onClick={handleClick}>Click</button> // 输出(编译后) h('button', { onClick: handleClick }, 'Click') - 未启用
-
optimize- 类型 :
boolean - 默认值 :
false - 作用:启用静态内容优化(类似Vue模板的静态节点提升),减少渲染开销。
- 类型 :
-
isCustomElement-
类型 :
(tag: string) => boolean -
默认值 :
undefined -
作用 :自定义元素检测函数,用于标记非
Vue组件的原生自定义元素(如Web Components)。- 配置方式(Babel 或 Vue CLI)
js// babel.config.js module.exports = { plugins: [ ["@vue/babel-plugin-jsx", { isCustomElement: (tag) => { // 匹配以 "ion-" 开头的标签(如 Ionic 框架组件) return tag.startsWith('ion-') // 或明确指定标签名 || ['my-web-component', 'vue-google-map'].includes(tag); } }] ] };- JSX使用
html// 输入(JSX) <div> <ion-button onClick={handleClick}>Click</ion-button> <my-web-component title="Hello" /> </div> // 输出(编译后) // 这些标签会被直接渲染为原生自定义元素,而非 Vue 组件
-
-
mergeProps -
类型:
boolean -
默认值:
true -
作用:自动合并分散的props(如
class、style、on*事件)。- 未启用
mergeProps(或设为false)
js// JSX 输入 <div class="header" style={{ color: 'red' }} onClick={handleClick} onCustomEvent={handleCustom} /> // 编译输出(Vue 3) h('div', { class: "header", style: { color: 'red' }, onClick: handleClick, onCustomEvent: handleCustom }) 问题 :如果父组件传递了额外的
class或style,需要手动合并(如class: [props.class, 'header'])。- 启用
mergeProps: true
js// JSX 输入 <div class="header" style={{ color: 'red' }} onClick={handleClick} onCustomEvent={handleCustom} /> // 编译输出(Vue 3) h('div', _mergeProps({ class: "header", style: { color: 'red' }, onClick: handleClick, onCustomEvent: handleCustom }, otherProps))优势 :自动合并外部传入的
class、style和事件(如父组件传递的className或onClick),无需手动处理。 - 未启用
-
enableObjectSlots-
类型:
boolean -
默认值:
true(Vue3中默认false) -
作用:启用对象形式的插槽语法(
Vue2兼容模式需要手动开启)。- 启用
enableObjectSlots: true
jsx// 父组件 JSX <Child v-slots={{ // 默认插槽 default: () => <div>默认内容</div>, // 具名插槽 footer: (props) => <span>{props.text}</span> }} /> // 编译输出(Vue 3) h(Child, null, { default: () => h("div", null, "默认内容"), footer: (props) => h("span", null, props.text) })- 禁用
enableObjectSlots: false
javascript// 父组件 JSX(Vue 3 原生写法) <Child> {{ default: () => <div>默认内容</div>, footer: (props) => <span>{props.text}</span> }} </Child> // 编译输出 h(Child, null, { default: () => h("div", null, "默认内容"), footer: (props) => h("span", null, props.text) }) - 启用
-
-
pragma-
类型:
string -
默认值:
createVNode(Vue3) /vueJsxCompat(Vue2) -
作用:自定义
JSX编译后的函数名(高级用法)。 - 默认行为(Vue 3)js// JSX 输入 <div>Hello</div> // 编译输出 import { createVNode as _createVNode } from 'vue' _createVNode('div', null, 'Hello')- 自定义
pragma
js// Babel 配置 { plugins: [ ["@vue/babel-plugin-jsx", { pragma: 'myCustomCreateElement' // 自定义函数名 }] ] } ------------- // JSX 输入 <div>Hello</div> // 编译输出 import { myCustomCreateElement as _createVNode } from './custom-renderer' _createVNode('div', null, 'Hello') - 自定义
-
扩展名为.vue
- 当我们配置好
babe-plugin-jsx这个插件就可以在在src/page/vue目录下创建 一个myComponet.vue
vue
<script lang="tsx">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
return () => (
<div>
<p>Count: {count.value}</p>
<button onClick={increment}>Increment</button>
</div>
);
},
});
</script>
- 使用
setup语法糖
vue
<template>
<jsx />
</template>
<script lang="tsx" setup>
import { ref } from 'vue';
const count = ref(0);
const props = defineProps({ num: Number });
console.log('props', props);
const increment = () => {
count.value++;
};
const jsx = () => (
<div>
<p>Count: {count.value}</p>
<button onClick={increment}>Increment</button>
</div>
);
</script>
- 将此组件放进
App.vue
vue
<template>
<h1>{{ title }}</h1>
<div>{{ context }}</div>
<MyComponet />
</template>
<script>
import MyComponet from './myComponet.vue';
export default {
components: { Hello },
data() {
return {
title: 'vue',
context: 'vue',
};
},
};
</script>
扩展名为.tsx
- 在
src/page/vue目录创建tsx/index.tsx
tsx
import { defineComponent, onMounted, ref, useTemplateRef, watch } from 'vue';
import ReactIndex from '../../react/test.tsx';
import { createRoot } from 'react-dom/client';
import React from 'react';
export default defineComponent({
name: 'VueHello',
setup() {
const count = ref(10001);
const increment = () => count.value++;
return () => (
<div className="vue-component">
<h1>Vue TSX Component</h1>
<h2 onClick={increment}>Count: {count.value}</h2>
</div>
);
},
});
2. 避免react的tsx语法冲突
冲突原因
- 目前如果我们这样写
babel.config.js配置
js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: 'defaults', // 兼容主流浏览器最新两个版本
useBuiltIns: 'usage', // 在入口文件全局引入 Polyfill
corejs: 3,
},
],
[
'@babel/preset-typescript',
{
allExtensions: true, // 允许所有文件扩展名使用 TS
isTSX: true, // 启用 TSX 支持
},
],
['@babel/preset-react', { runtime: 'automatic' }],
],
plugins: [
[
'@vue/babel-plugin-jsx',
{
optimize: true,
transformOn: true, // 必须启用事件语法转换
},
],
],
我们执行npm run build 然后运行index.html 出现了问题

-
错误原因
- 问题出在
Vue和React的JSX转换逻辑冲突。 - 混合使用 Vue 和 React 的 JSX 转换逻辑
@vue/babel-plugin-jsx会将JSX转换为Vue的h()函数(Vue 的虚拟 DOM 节点)。@babel/preset-react会将JSX转换为React.createElement()。
- 未隔离 Vue/React 的编译环境
- 所有文件(包括 React 组件)都应用了
Vue的JSX转换插件。
- 所有文件(包括 React 组件)都应用了
- 问题出在
解决方案一(vtsx)
-
为
vue的JSX文件创建一个另一个扩展名如:.vtsx-
将
vue中的tsx扩展名修改成.vtsx -
在
webpack配置文件中
js// webpack.config.js module.exports = { // 其余配置 module: { rules: [ // 其余配置 { test: /\.(vue)$/, // 处理 .vue 和 loader: 'vue-loader', }, { test: /\.(vtsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { plugins: ['@vue/babel-plugin-jsx'], }, }, }, { test: /\.(js|jsx|ts|tsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-react', { runtime: 'automatic' }, ], ], }, }, } ] } };babel.config.js配置项
jsmodule.exports = { presets: [ [ '@babel/preset-env', { targets: 'defaults', // 兼容主流浏览器最新两个版本 useBuiltIns: 'usage', // 在入口文件全局引入 Polyfill corejs: 3, }, ], [ '@babel/preset-typescript', { allExtensions: true, // 允许所有文件扩展名使用 TS isTSX: true, // 启用 TSX 支持 }, ], ] }执行
npm run build查看效果就没问题了 -

- 但是此时还有问题,我们虽然解决了
vue使用tsx的问题,但是我在.vue文件中直接写tsx语法就有问题- 这是我们只处理了
.vtsx并没有处理.vue里面的tsx语法
- 这是我们只处理了

- 现在我们直接把
babel-loader需要处理直接放到配置文件里面,更精细的去处理这些扩展文件- 在
babel-loader配置选项中有overrides属性 是一个用于针对特定文件或条件应用不同 Babel 配置的选项 。它允许你根据文件路径、环境变量、文件扩展名 等条件,为不同的文件覆盖或扩展配置。
- 在
- 在
babel.config.js进行配置
js
const path = require('path');
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: 'defaults', // 兼容主流浏览器最新两个版本
useBuiltIns: 'usage', // 在入口文件全局引入 Polyfill
corejs: 3,
},
],
[
'@babel/preset-typescript',
{
allExtensions: true, // 允许所有文件扩展名使用 TS
isTSX: true, // 启用 TSX 支持
},
],
],
overrides: [
{
test: /\.(vtsx|vue)$/, //单独进行配置
exclude: [
/node_modules/,
path.resolve(__dirname, 'src/page/react'), // ✅ 排除 React 目录
],
plugins: [
[
'@vue/babel-plugin-jsx',
{
optimize: true,
transformOn: true, // 必须启用事件语法转换
},
],
], // ✅ // Vue JSX 转换
},
// React 文件:使用 React 的 JSX 转换
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: [
/node_modules/,
path.resolve(__dirname, 'src/page/vue'), // ✅ 排除 Vue 目录
],
presets: [
[
'@babel/preset-react', // ✅ 仅 React JSX
{ runtime: 'automatic' },
],
],
},
],
};
- 修改
webpack.config.js配置
js
// webpack.config.js
module.exports = {
// 其余配置
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx|vtsx)$/, // 添加vtsx
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-react',
{ runtime: 'automatic' },
],
],
},
},
}
]
}
};
解决方案二(xxx.vue.tsx)
-
vue的tsx扩展名不进行更改,但是必须添加前缀xxxx.vue.tsx这样的格式 -
我们还是在
babel.config.js进行更改把vtsx的配置更改成.vue.tsx这样的配置
js
overrides: [
{
test: /\.(vue\.tsx|vue)$/, // 将vtsx删除
exclude: [
/node_modules/,
path.resolve(__dirname, 'src/page/react'), // ✅ 排除 React 目录
],
plugins: [
[
'@vue/babel-plugin-jsx',
{
optimize: true,
transformOn: true, // 必须启用事件语法转换
},
],
], // ✅ // Vue JSX 转换
},
// React 文件:使用 React 的 JSX 转换
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: [
/node_modules/,
path.resolve(__dirname, 'src/page/vue'), // ✅ 排除 Vue 目录
],
presets: [
[
'@babel/preset-react', // ✅ 仅 React JSX
{ runtime: 'automatic' },
],
],
},
],
- 最后将
webpack配置文件的vtsx删除就🆗了
3. Vue组件与React组件互相传参
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| vue createApp **React reactRoot ** | 少量的跨框架页面 | 简单易用 | 操作dom 易用性不高 |
| Web Components | 长期维护的跨框架项目 | 标准规范,无框架依赖 | 需处理 Shadow DOM 样式隔离 |
| 编译转换 | 小型混合功能 | 开发便捷 | 兼容性维护成本高 |
| 微前端 | 大型复杂应用 | 独立开发部署 | 通信和路由处理复杂 |
-
以上方案我们目前先练习第一个,在后面我们在慢慢了解其他几个方案
-
在
src/page/react创建test.tsx给vue页面使用
tsx
import React from 'react';
const TestReact = ({ count, onUpdate }) => {
return (
<>
<h1>react接收传参</h1>
<div onClick={onUpdate(1)}>{count}</div>
</>
);
};
export default TestReact;
- 在
src/page/vue/vue-tsx/创建test.vue.tsx给react页面使用
tsx
import { defineComponent, inject, onMounted } from 'vue';
const TextVue = defineComponent({
setup() {
const count = inject('count');
const setCount = inject('setCount');
return () => (
<div>
<h4>Vue 接收传参</h4>
<div onClick={() => setCount(count + 1)}>{count}</div>
</div>
);
},
});
export default TextVue;
- 在
App.tsx中使用vue组件test.vue.tsx
tsx
import React, { useEffect, useRef } from 'react'
import { createApp,h } from 'vue'
import VueWrapper from '../vue/vue-tsx/text.vue.tsx'
export const ReactApp = () => {
const [count, setCount] = React.useState(0)
const vueContainerRef = useRef(null)
const vueAppRef = useRef(null)
useEffect(() => {
if (vueContainerRef.current && !vueAppRef.current) {
// 创建 Vue 实例并挂载
vueAppRef.current = createApp({
render: () => h(VueWrapper),
provide: {
count,
setCount
}
})
vueAppRef.current.mount(vueContainerRef.current)
}else if (vueContainerRef.current) {
// 更新 Vue 实例
vueAppRef.current.$forceUpdate()
}
return () => {
// 卸载 Vue 实例
if (vueAppRef.current) {
vueAppRef.current.unmount()
vueAppRef.current = null
}
}
}, [count])
return (
<>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>+1</button>
<h1>
react
<div ref={vueContainerRef} />
</h1>
</>
)
}
- 在
src/page/vue创建index.vue.tsx然后使用React组件test.tsx
tsx
import { defineComponent, onMounted, ref, useTemplateRef, watch } from 'vue';
import ReactIndex from '../../react/test.tsx';
import { createRoot } from 'react-dom/client';
import React from 'react';
export default defineComponent({
name: 'VueHello',
setup() {
const count = ref(10001);
const increment = () => count.value++;
const reactContainerRef = useTemplateRef('reactContainerChild');
const PropsCount = (value: number) => {
return {
count: value,
onUpdate: (num: number) => () => {
count.value = value + num;
},
};
};
const createReactComponents = (value: number) => {
const reactElement = React.createElement(ReactIndex, PropsCount(value));
createRoot(reactContainerRef.value).render(reactElement);
};
onMounted(() => {
if (reactContainerRef.value) {
createReactComponents(count.value);
}
});
watch(
() => count.value,
(newValue, oldValue) => {
createReactComponents(newValue);
}
);
return () => (
<div className="vue-component">
<h3>
<div ref="reactContainerChild" />
</h3>
</div>
);
},
});
- 执行
npm run build查看效果 这样就没问题
