一篇带你速通Webpack如何处理框架中的难点

一、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-loaderfile-loader来处理
图片资源处理
  • Loader 组合
    • url-loader :将小于指定大小的图片转换为 Base64 格式内嵌到代码中,减少 HTTP 请求。若文件超过限制,则自动调用 file-loader 处理
    • file-loader :将文件复制到输出目录(如 dist),并返回文件路径。常用于处理大体积图片
  • 配置示例
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-loaderfile-loader :处理 .woff, .woff2, .eot, .ttf, .svg 等字体文件,原理与图片处理类似
  • 配置示例
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-loaderfile-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 时会抛出错误
兼容性注意事项
powershell 复制代码
npm install [email protected] [email protected] --save-dev
图片路径错误
  • esModule 配置Webpack 4.x 中,若 file-loaderesModule 选项未设为 false,可能导致图片路径输出为 [object Module]。需在配置中显式关闭
js 复制代码
options: {
  esModule: false  // 关闭 ES 模块语法
}
总结
  • Webpack 5.x 之前,图片和字体文件主要依赖 url-loaderfile-loader ,通过合理配置 limit 和输出路径,实现资源优化如 Base64 内联)与文件管理。Webpack5.x 后,可通过内置的 asset/resource 类型替代

2.Webpack5.x处理方式

1.为什么5的版本和4版本有不同处理方式?
  • Webpack5.xasset 模块通过 原生资源处理机制 替代了 url-loaderfile-loader,提供更简洁的配置、更高的性能
  • 完全无需依赖第三方 Loader 。根据文件类型选择 asset/resource(字体)或 asset(图片自动优化),即可高效管理静态资源
2.资源模块类型
  • 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换这些 loader:
1.asset/resource
  • asset/resource 发送一个单独的文件并导出 URL

    • 5.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-loader

      powershell 复制代码
      npm install raw-loader --save-dev
    • 4.x配置文件中

      js 复制代码
      module.exports = {
      module: {
          rules: [
            {
              test: /\.(txt|svg|css)$/,  // 匹配目标文件
              use: 'raw-loader'          // 无需复杂参数
            }
          ]
      }
      };
    • 将文件内容作为原始字符串导入 的 Loader。它的作用类似于直接读取文件的二进制内容,但以字符串形式暴露给 JavaScript 模块,适用于需要直接操作文件原始数据的场

    • 功能定位

    • 不进行任何转译(如 Babel)、不处理依赖(如 CSS 中的 @import),仅将文件内容转为字符串

    • 创建data.txt

    txt 复制代码
    const a = 123
    • src/components/cps.js文件中
    js 复制代码
    import 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记住不要搞混淆了
    js 复制代码
    module.exports = {
        // ....省略其他配置
        {
            test: /.(jpg|png|gif|jpe?g|svg)$/,
            type: 'asset',
            parser: {
              dataUrlCondition: {
                maxSize: 200 * 200,
              },
            },
        }
    }     
  • 如图所示:

    • 第一图我们打包出来,文件输出目录就一张图片 因为就一张大图占有空间超出了设置大小

    • 小图片我设置成背景图片 它的原图像素大小是201*251 空间占用大小是 7kb39>7 所以这个转换为base64

    • 大图片我设置图片 它的原图像素大小是647*825 空间占用大小是 280 Kb,39<280 直接文件 复制到输出目录,并返回http路径

3.资源模块如何设置文件名称和导出路径
1.全局配置所有输出规则
  • 方式一:修改output ,添加assetModuleFilename属性;

    • assetModuleFilename全局配置所有 Asset Modules 类型文件的输出规则
    js 复制代码
    module.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处理 VueReactTS ES6+转ES5

1.为什么需要babel?

  • 在开发中我们很少直接去接触babel ,但是babel对于前端开发来说,目前是不可缺少的一部分:
    • 开发中,我们想要使用ES6+的语法,想要使用TypeScript,开发React项目,它们都是离不开Babel的;
  • 那么,Babel到底是什么呢?
    • Babel是一个工具链 ,主要用于将ECMAScript 2015+代码转换为向后兼容版本的JavaScript;
    • React TypeScript 这种框架和静态语言转换为 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/corebabel的核心代码,必须安装;
    • @babel/cli:可以让我们在命令行使用babel;
    powershell 复制代码
    npm install @babel/cli @babel/core -D
3.使用
  • 使用babel来处理我们的源代码

    • src:是源文件的目录

    • --out-dir:指定要输出的文件夹dist;

    powershell 复制代码
    npx babel src--out-dir dist
    • 此时我们发现并没有转换啊,仅复制文件,显示出来
4.没有转换成功的原因
  • 这是因为我们没有安装bable的插件和预设

    • 实际行为
      • .js 文件
        • ES6+ 语法(如箭头函数、classconst 等) ❌ 不会转换,因为缺少 @babel/preset-env 或相关插件。Babel 默认无任何转换行为。
        • JSX 语法(如 <div></div> ❌ 不会转换,因为需要 @babel/preset-react 插件。
      • .ts / .tsx 文件
        • 直接报错 ,因为 Babel 默认只处理 .js 文件,且需要 @babel/preset-typescript 来解析 TypeScript 语法。
    • 根本原因
      • Babel 的行为完全由 插件和预设 决定。没有安装任何插件/预设时:
        • 仅复制文件,不做任何语法转换
        • 不支持 TypeScriptJSX 解析
5.插件的使用
  • 比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件: @babel/plugin-transform-arrow-functions

    powershell 复制代码
    npm install @babel/plugin-transform-arrow-functions -D
    • 然后使用命令 已经将箭头函数转成ES5的函数
    powershell 复制代码
    npx 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`
    bash 复制代码
    npm install @babel/plugin-transform-block-scoping -D 
    • 执行命令
    powershell 复制代码
    npx 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预设
    powershell 复制代码
    npm install @babel/preset-env -D
    • 执行下面命令
      • 他会把 src 目录下所有js都会进行转换
    powershell 复制代码
    npx 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**(如 PromiseArray.from)无法通过语法转换实现,必须用代码模拟。
      • core-js 就是用来"模拟"这些新 API 的代码库。比如,在 IE11 中运行 new Set([1, 2, 3]),如果没有 core-js,就会直接报错;有了 core-js,它会用 ES5 代码实现 Set 的功能。
    • 与 Babel 的关系

    • Babel 负责语法转换 (如 () => {}function() {})。

    • core-js 负责**API 模拟**(如 PromiseArray.includes)。

    • 版本差异

    • core-js@2

      • 支持大部分 ES6+ API,但不覆盖实例方法 (如 [1, 2, 3].includes(1))。
      • 已停止维护,不推荐使用
    • core-js@3

      • 支持 ES6-ES2022 的全部 API,包括实例方法(如 array.includesstring.padStart)。
      • 持续更新,推荐使用
    • 作用 :指定 core-js 的版本(必须安装对应的版本)。

      • 为什么需要显式指定 corejs 版本?

      Babel 默认不处理新 API,必须通过 corejs: 3 明确告诉它使用 core-js@3Polyfill

      js 复制代码
      npm install core-js@3 --save
  • useBuiltIns

    • 可选值
      • 'usage' → 按需添加(只加代码中用到的 API,推荐
        • 按需加载 Polyfill(比如 PromiseArray.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 特性)。
    js 复制代码
    shippedProposals: true  // 直接使用浏览器已实现的提案语法
    • bugfixes
      • 作用:根据目标环境自动修复已知的语法 Bug(推荐开启)。
    vbnet 复制代码
    bugfixes: true
    • loose
      • 作用:以"宽松模式"生成代码(代码更简洁,但可能不符合标准)。
    js 复制代码
    loose: true
参数在命令行使用
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"
				}
			}
		]
	},
  • 如果我们就这样设置了没有配置预设或者插件 ,还是跟刚刚一样 不会有任何转换变化如果遇到tstsx还会报错
  • 如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个presetwebpack 会根据我们的预设来加载对应的插件列表,并且将其传递给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 需要安装两个 reactreact-dom
css 复制代码
npm i react react-dom
初始化react文件
  • src目录下创建 page/react/App.jsx

    jsx 复制代码
    import 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 函数(推荐,代码更简洁)。
    js 复制代码
    runtime: 'automatic'  // 无需手动写 `import React from 'react'`
  • development

    • 作用:是否添加开发环境下的调试信息(如组件名称提示)。
    js 复制代码
    development: process.env.NODE_ENV === 'development'  // 根据环境自动开启
  • importSource

    • 作用 :指定 JSX 运行时函数的导入路径(配合 runtime: 'automatic' 使用)
    js 复制代码
    importSource: '@emotion/react'  // 使用 Emotion 库的 JSX 运行时
  • throwIfNamespace

    • 作用 :是否禁止使用 XML 命名空间标签(如 <svg:circle>
    powershell 复制代码
    throwIfNamespace: false  // 允许使用(默认是 true,遇到会报错)
  • pure

    • 作用 :是否在编译时移除 JSX 中的纯注释(如 /*#__PURE__*/,用于 Tree Shaking)。
    js 复制代码
    pure: true  // 默认开启
  • 配置示例

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)。
    • 场景:避免误删普通导入。
    js 复制代码
    presets: [
      ['@babel/preset-typescript', { onlyRemoveTypeImports: false }]
    ]
  • optimizeConstEnums

    • 作用 :优化 常量枚举const enum),直接替换为值(默认:false)。
    • 场景:减少代码体积,但可能影响调试。
    js 复制代码
    presets: [
      ['@babel/preset-typescript', { optimizeConstEnums: true }]
    ]
  • rewriteImportExtensions

    • 作用 :将 TypeScript 的 .ts.tsx 导入扩展名改为 .js(默认:false)。
    • 场景 :用于 Node.js ESM 或浏览器直接加载 ES 模块。
  • 完整配置示例

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,
      },
    ],
  ],
};

注意事项

  1. 类型擦除 :Babel 只删除 TypeScript 类型,不进行类型检查 (需用 tsc 单独检查)。
  2. 兼容性
    • 部分高级 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文件中添加以下代码
    • 此时搭建出VueReact 混合框架的雏形
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.ReactVue框架混合使用

  • 有些项目会用到两个框架,我们就用webpack 简单搭建一下 框架的混用
  • 现在我们有几个问题
    • vue中使用tsx语法 如何使用呢?
    • vue中使用了tsx, 如何避免与reacttsx语法冲突呢?
    • 如果我是vue 父组件,我想引入React 子组件
    • 如果我是React 父组件,我想引入vue 子组件
1.vue中使用tSX语法
  • 首先我们需要安装一个babel插件帮我去处理 vue 中的tsx语法

    js 复制代码
    npm install @vue/babel-plugin-jsx -D
    • 如果你的框架中没有react 可以直接在 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 支持
          },
        ],
      ],
      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(如classstyleon*事件)。

    • 未启用 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
    })

    问题 :如果父组件传递了额外的 classstyle,需要手动合并(如 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))

    优势 :自动合并外部传入的 classstyle 和事件(如父组件传递的 classNameonClick),无需手动处理。

  • 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. 避免reacttsx语法冲突
冲突原因
  • 目前如果我们这样写 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 出现了问题

  • 错误原因

    • 问题出在 VueReactJSX 转换逻辑冲突。
    • 混合使用 Vue 和 React 的 JSX 转换逻辑
      • @vue/babel-plugin-jsx 会将 JSX 转换为 Vueh() 函数(Vue 的虚拟 DOM 节点)。
      • @babel/preset-react 会将 JSX 转换为 React.createElement()
    • 未隔离 Vue/React 的编译环境
      • 所有文件(包括 React 组件)都应用了 VueJSX转换插件。
解决方案一(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配置项
    js 复制代码
    module.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)
  • vuetsx扩展名不进行更改,但是必须添加前缀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.tsxvue页面使用

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.tsxreact页面使用
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 查看效果 这样就没问题
相关推荐
去旅行、在路上15 分钟前
chrome使用手机调试触屏web
前端·chrome
Aphasia31144 分钟前
模式验证库——zod
前端·react.js
lexiangqicheng1 小时前
es6+和css3新增的特性有哪些
前端·es6·css3
拉不动的猪2 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
烛阴2 小时前
Python枚举类Enum超详细入门与进阶全攻略
前端·python
代码搬运媛2 小时前
“packageManager“: “[email protected]“ 配置如何正确启动项目?
windows·webpack
孟孟~2 小时前
npm run dev 报错:Error: error:0308010C:digital envelope routines::unsupported
前端·npm·node.js
孟孟~2 小时前
npm install 报错:npm error: ...node_modules\deasync npm error command failed
前端·npm·node.js
狂炫一碗大米饭2 小时前
一文打通TypeScript 泛型
前端·javascript·typescript
wh_xia_jun3 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot