logo
blog
readme
Back to Blog
Back to Blog
Back to Blog

‌

‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌
‌

© 2025 linzhe. All rights reserved.

TODO:编辑

jsx本质

jsx是JavaScript的一个类似XML的扩展,虽然最早是由react提出的,但实际上 JSX 语法并没有定义运行时语义, 并且能被编译成各种不同的输出形式, jsx可以由很多工具进行转换为js,比如Babel和TypeScript,接下来就通过babel和ts创建属于自己的jsx

案例源码

1、TypeScript转换

安装typescript依赖,并进行配置相关的准备工作

bash
pnpm i -D typescript

编写tsconfig.json,输入如下的简单配置,其中jsx: preserve,表示不对jsx进行转换,而是直接保留jsx, 因为接下来我们可以通过Babel进行转换,为了让ts能够识别jsx,需要编写对应的类型

  • tsconfig.json

    json
    tsconfig.json
    {
      "compilerOptions": {
        "strict": true,
        "target": "ES5",
        "module": "ESNext",
        "jsx": "preserve", // 保留原始jsx,不进行转换
        "outDir": "dist",
        "types": ["./type.d.ts"]
      },
      "include": ["input.tsx"]
    }
    
  • type.d.ts:声明jsx类型,不然在编译jsx时会报错

    ts
    type.d.ts
    declare namespace JSX {
      interface IntrinsicElements {
        [elemName: string]: any
      }
    }
    
  • input.tsx: 入口文件,只是通过tsc编译js,不进行转换jsx,直接保留jsx,jsx的编译交给babel做。

    tsx
    input.tsx
    const x = (
      <div class='xxx' id='test'>
        <div class='list-1'>1111</div>
        <div class='list-2'>2222</div>
      </div>
    )
    
  • npx tsc编译input.tsx,输出如下

    jsx
    dist/input.jsx
    'use strict'
    var x = (
      <div class='xxx' id='test'>
        <div class='list-1'>1111</div>
        <div class='list-2'>2222</div>
      </div>
    )
    

2、通过Babel插件转换成自定义的jsx

参考react.createElement,假设存在一个函数Dom,用来创建属于自己的虚拟节点

ts
function Dom(tagName: string, props: Record<string, any>, ...children: CustomVNode[]): CustomVNode{
  ... // 实现不考虑,本文只考虑jsx的转换
}

安装babel相关依赖

bash
pnpm i -D @babel/cli @babel/core @babel/plugin-syntax-jsx
  • 编写自定义的babel插件

    js
    custom-plugin.js
    module.exports = function ({ types: t }) {
      return {
        visitor: {
          // 处理 JSXElement
          JSXElement(path) {
            // 得到当前 JSX的节点结构
            const node = path.node
            const { openingElement } = node
            // 获取这个JSX标签的名字
            const tagName = openingElement.name.name
            // {class:'xxx',id: 'test'}
            const propsList = openingElement.attributes.reduce((result, item) => {
              result.push(
                t.objectProperty(
                  t.identifier(item.name.name),
                  t.stringLiteral(item.value.value)
                )
              )
              return result
            }, [])
            const createIdentifier = t.identifier('Dom')
            const args = [t.stringLiteral(tagName), t.objectExpression(propsList)]
            // Dom('div', {class:'xxx',id: 'test'})
            const callRCExpression = t.callExpression(createIdentifier, args)
            // ...children
            callRCExpression.arguments = callRCExpression.arguments.concat(
              path.node.children
            )
            path.replaceWith(callRCExpression, path.node)
          },
          // 处理 JSXText 节点
          JSXText(path) {
            const nodeText = path.node.value // 直接用 string 替换 原来的节点
            path.replaceWith(t.stringLiteral(nodeText), path.node)
          },
        },
      }
    }
    
  • 配置babel,使用刚刚编写的自定义插件(custom-plugin.js)

    json
    .babelrc
    {
      "plugins": ["@babel/plugin-syntax-jsx", "./custom-plugin.js"]
    }
    
  • 对ts编译后的产物,再次进行转换,就可以得到自定义的jsx了

    bash
    npx babel dist/input.jsx --out-file dist/output.js
    
    js
    dist/output.js
    'use strict'
    var x = Dom(
      'div',
      { class: 'xxx', id: 'test' },
      '\n    ',
      Dom('div', { class: 'list-1' }, '1111'),
      '\n    ',
      Dom('div', { class: 'list-2' }, '2222'),
      '\n  '
    )