Webpack迁到Vite第二节:JSX 到 Render 函数

Webpack迁到Vite第二节:JSX 到 Render 函数

本篇定位 :项目中仅有的两处 JSX 写法在 Vite 下无法保留(无兼容 Vite 7 的 Vue2 JSX 插件),本篇分析 $createElement 替代方案与优缺点评估------是迁移中改动面最小的专题。

思维导图

bash 复制代码
JSX 到 Render 函数
├── 1.1 背景:什么是 JSX
│   ├── JSX 语法示例
│   ├── JSX 需要编译工具
│   ├── JSX 和 createElement 是标准吗?(都不是)
│   └── 项目中 JSX 的使用场景
├── 1.2 为什么不能保留 JSX
│   ├── @vitejs/plugin-vue2-jsx 与 Vite 7 不兼容
│   ├── 尝试过的方案
│   │   ├── @rollup/plugin-babel
│   │   ├── rollup-plugin-esbuild
│   │   └── 自定义 Vite 插件
│   └── 为什么都不行
├── 1.3 $createElement 替代方案
│   ├── 什么是 $createElement
│   ├── JSX 和 $createElement 的对应关系
│   │   ├── 标签 → 第一个参数
│   │   ├── 属性 → 第二个参数
│   │   └── 子元素 → 第三个参数
│   ├── 实战:loginMixin.js 改造
│   └── 实战:selectAccount.vue 改造
└── 1.4 优缺点分析
    ├── 优势:零依赖、永远兼容
    └── 劣势:可读性略差(仅 2 处,可接受)

1.1 背景:什么是 JSX

JSX 是 JavaScript 的语法扩展,让你可以在 JS 中写类似 HTML 的代码:

jsx 复制代码
const element = (
  <label style="display: flex;">
    <input type="checkbox" id="check" />
    <span>30天内不再提示</span>
  </label>
)

这段代码不能直接在浏览器中运行 ------必须先被编译成普通的 JavaScript 函数调用。在 Vue 2 中,JSX 会被编译为 createElement 调用。

JSX 和 createElement 是标准吗?

都不是标准 。JSX 是 Facebook 为 React 设计的语法扩展,不是 ECMAScript 或 W3C 规范。Babel 的 @babel/plugin-transform-react-jsx 是事实上的编译器,但编译目标可以配置为任意函数名。

复制代码
JSX 语法      → 无标准,Facebook 发明
JSX 编译器    → Babel 插件是事实标准,esbuild/SWC/TypeScript 也都支持
编译产物      → 取决于配置,React.createElement / Vue h / Preact h 都可以

Vue 的 createElement($createElement) 是 Vue 框架自己定义的 API,用于创建 VNode(虚拟 DOM 节点)。VNode 本身也没有跨框架的标准------React 的 VNode 结构是 { type, props, key, ... },Vue 2 的是 { tag, data, children, elm, ... }。

整条链路,只有终点是标准:

复制代码
JSX(无标准)
  → Babel 编译(约定)
  → createElement / h(框架定义)
  → VNode(框架定义)
  → diff + patch(框架实现)
  → DOM API(W3C/WHATWG 标准 ✅)

标准化委员会(TC39、W3C)只管浏览器能直接执行的东西------import/export(ESM)、document.createElement(DOM)、fetch(WHATWG)。框架内部的 VNode 怎么设计、JSX 怎么编译,不在标准化的范围内。

项目中 JSX 的使用场景

项目中只有 2 处 使用了 JSX,都在 $confirm 弹窗的 content 参数中:

  1. src/views/pc/user/loginMixin.js --- 密码过期提醒弹窗
  2. src/views/pc/user/selectAccount.vue --- 同上
javascript 复制代码
// 原代码
this.$confirm({
  title: '为确保您安全使用大家签签署服务,建议您定期更改登录密码',
  content: (
    <label style="display: flex;align-items: center;">
      <input type="checkbox" id="check30DayNotHint" />
      <span style="margin-left: 5px">30天内不再提示</span>
    </label>
  ),
  // ...
})

1.2 为什么不能保留 JSX

核心问题:没有兼容的插件

@vitejs/plugin-vue2 不处理 JSX。它的职责仅限于 SFC 三件套:

xml 复制代码
@vitejs/plugin-vue2
  ├── 编译 <template>  → 调用 vue/compiler-sfc
  ├── 处理 <script>    → 交给 Vite 处理(esbuild 默认不处理 .vue 脚本块,且无兼容 Vite 7 的 Vue2 JSX 插件)
  └── 处理 <style>     → 交给 Less/Sass 编译器

JSX 需要单独的 @vitejs/plugin-vue2-jsx 插件处理。这是有意为之的职责分离------Vue 插件只管 SFC,JSX 是独立的语法扩展,需要独立插件。

版本冲突:

less 复制代码
@vitejs/plugin-vue2-jsx@1.1.1
  peerDependency: vite@^2.9.13 || ^3.0.0 || ^4.0.0 || ^5.0.0

它只支持 Vite 2~5,而项目使用的是 Vite 7。API 不兼容导致构建时 Rollup 无法解析 JSX 语法。

尝试过的方案

方案 工具 结果
1 @vitejs/plugin-vue2-jsx --force ❌ Dev 可用,Build 报错
2 @rollup/plugin-babel + @babel/plugin-transform-react-jsx ❌ babelHelpers 冲突
3 rollup-plugin-esbuild ❌ 虚拟模块不处理
4 自定义 Vite 插件 enforce: 'pre' ❌ 拦截不到 SFC 内部
5 自定义 Vite 插件 enforce: 'post' ❌ 同上

根本原因:Vite 7 的插件 API 发生了变化,Vue 2 JSX 插件没有跟进更新。Vue 2 已经进入维护期,插件作者不太可能为 Vite 7 更新。


1.3 $createElement 替代方案

什么是 $createElement

$createElement 是 Vue 2 实例上的方法,用于编程式创建 VNode (虚拟 DOM 节点)。JSX 本质上就是 createElement 的语法糖。

javascript 复制代码
// JSX
<label style="display: flex;">Hello</label>

// 等价于
this.$createElement('label', { style: { display: 'flex' } }, 'Hello')

参数说明

$createElement(tagName, options, children)

参数 类型 说明 示例
tagName String HTML 标签名 'label', 'input', 'span'
options Object 属性/样式/事件等 { style: {...}, attrs: {...} }
children `String Array` 子节点

JSX → $createElement 对照表

JSX $createElement
<div> h('div')
<div class="x"> h('div', { class: 'x' })
<div style="color:red"> h('div', { style: { color: 'red' } })
<input type="checkbox"> h('input', { attrs: { type: 'checkbox' } })
<div>text</div> h('div', {}, 'text')
<div><span>A</span></div> h('div', {}, [h('span', {}, 'A')])

关键规则:

  • HTML 属性(type, id, placeholder 等)放在 attrs 对象里
  • CSS 样式(style="...")放在 style 对象里,属性名用驼峰命名
  • 子元素用数组 [] 包裹

实战改造

项目中共 2 处 JSX,分别位于 src/views/pc/user/loginMixin.js(密码过期提醒弹窗)和 src/views/pc/user/selectAccount.vue(同上)。两处改造方式相同,以下示例两者通用。

原代码(JSX):

jsx 复制代码
<label style="display: flex;align-items: center;">
  <input type="checkbox" id="check30DayNotHint" />
  <span style="margin-left: 5px">30天内不再提示</span>
</label>

改造后($createElement):

javascript 复制代码
const h = this.$createElement

h('label', { style: { display: 'flex', 'align-items': 'center' } }, [
  h('input', { attrs: { type: 'checkbox', id: 'check30DayNotHint' } }),
  h('span', { style: { 'margin-left': '5px' } }, '30天内不再提示'),
])

逐行解释:

  1. const h = this.$createElement:把 $createElement 赋值给短变量 h,这是 Vue 社区的常见惯例(h 代表 hyperscript,即创建虚拟 DOM 的函数)

  2. 外层 h('label', { style: ... }, [...]):

    • 第一个参数 'label':创建 <label> 元素
    • 第二个参数 { style: { display: 'flex', 'align-items': 'center' } }:
      • style 对象对应 JSX 的 style="..." 属性
      • 'align-items' 加引号是因为包含连字符,不能直接写 align-items(JS 变量名不能有连字符)
    • 第三个参数 [...]:子元素数组
  3. 子元素 h('input', { attrs: { type: 'checkbox', id: 'check30DayNotHint' } }):

    • type、id 是 HTML 标签属性,必须放在 attrs 对象中
    • 这是 Vue 2 的规则:原生 HTML 属性走 attrs,Vue 特有的属性(如 style、class、on)走顶层
  4. 子元素 h('span', { style: { 'margin-left': '5px' } }, '30天内不再提示'):

    • 第三个参数是纯文本,直接写字符串即可

验证效果 :构建不再报 JSX 语法错误;密码过期提醒弹窗正常弹出,复选框与"30天内不再提示"文案正常渲染,勾选状态正确写入 passwordGoOnIsChecked(弹窗确认/取消时按该状态决定是否调用 goOn30Days())。


1.4 优缺点分析

优势

  1. 零依赖 :不需要任何额外插件,$createElement 是 Vue 2 内置 API
  2. 永远兼容:不依赖第三方插件的版本更新
  3. 改动量极小:项目只有 2 处 JSX,每处改动不到 10 行

劣势

  1. 可读性略差 :JSX 看起来像 HTML,更直观;$createElement 是纯 JS 函数调用
  2. 但在本项目场景下可接受:只有 2 处,且结构简单(一个 label + 一个 input + 一个 span)

为什么不用 vite-plugin-vue2-jsx 的 fork 版本

社区可能有 Vite 7 兼容的 fork 版本,但:

  • 不是官方维护,长期可靠性不确定
  • 增加依赖风险
  • 2 处 JSX 不值得引入一个依赖
相关推荐
Liu.7741 小时前
Vue3 中如何使用 GET 请求传递复杂参数
vue.js·vue3
定栓2 小时前
Webpack迁到Vite第一节:模块系统迁移:require → import
运维·前端·webpack
todoitbo2 小时前
本来只想给内网开个口子,Natapp 却多给了一道防护
前端·javascript·vue.js·内网穿透·网络防护·natapp
雪芽蓝域zzs3 小时前
JS 项目转 TS 项目(Vue3 + 若依这类后台项目)完整改造清单
前端·vue.js
定栓3 小时前
Webpack迁到Vite第四节:SVG 图标系统
前端·webpack·node.js
定栓3 小时前
Webpack迁到Vite第六节:构建配置详解(vite.config.js)
前端·javascript·webpack
鶴哥只手遮天15 小时前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 1 章 项目全景:monorepo 架构与工程化体系
前端·vue.js·开源
OpenTiny社区17 小时前
多会话 + 流式输出怎么落地?TinyRobot Kit 对话数据层深度解析
前端·vue.js
破烂三角裤20 小时前
Vue 3 自动生成汉字拼音:pinyin-pro、Ruby 标签
vue.js