Skip to content

html无构建步骤使用vue3加jsx加es模块化开发

此方案可以在无构建步骤的情况下使用jsx语法,实际上使用了js的完全编程能力 增强了模板功能,当然也缺失的vue的模板语法的性能优化 支持es模块化开发

单html使用

html
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue 3 JSX 内联方案</title>
    <script src="https://unpkg.com/vue/dist/vue.global.prod.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script src="https://unpkg.com/naive-ui/dist/index.prod.js"></script>
</head>

<body>
<div id="app"></div>

<script type="text/babel" data-type="module">
    /* @jsx Vue.h */
    
    // <script type="text/babel" data-type="module"> 中支持使用import导入 但是不会被babel处理
    // <script type="text/babel" data-type="module" src=""> 通过此方式引入会被babel处理
    
    const {createApp, ref, onMounted, defineComponent} = Vue;
    
    const app = createApp({
        setup() {

            const count = ref(190);
            return () => (
                    <div style={{padding: '10px'}}>
                        <h1>JSX + Vue 3 示例</h1>
                        <div>计数器: {count.value}</div>
                        <naive.NButton type="primary"
                                       onClick={() => count.value++}>{{default: () => '增加'}}</naive.NButton>

                    </div>
            );
        }
    });
    app.mount('#app');

</script>
</body>

</html>

模块化使用

目录结构

markdown
project/
├── index.html
├── jsx-sw.js
├── App.js
└── HelloWorld.js
html
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue3 jsx</title>
</head>

<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js?no-jsx"></script>
<script src="https://unpkg.com/vue-router@4.5.1/dist/vue-router.global.js?no-jsx"></script>
<script src="https://unpkg.com/naive-ui@2.42.0/dist/index.js?no-jsx"></script>

<script type="module">
    if (!('serviceWorker' in navigator)) {
        alert('不支持Service Worker')
        throw new Error('不支持Service Worker');
    }

    navigator.serviceWorker.register('./jsx-sw.js', {scope: './'}).then(reg => {
        navigator.serviceWorker.addEventListener('message', () => {
            console.log('[main] message')
            import('./App.js')
        });
        if (reg.active) {
            const key = 'jsx-sw-' + window.location.href
            if (!localStorage.getItem(key)) {
                localStorage.setItem(key, '1')
                console.log('[main] reload')
                window.location.reload()
            }
            console.log('[main] reg.active')
            import('./App.js')
        }
    })

</script>
</body>

</html>
js
const CONFIG = {
    // A 拦截所有js,要忽略的js需添加?no-jsx参数  B 只拦截参数中带有?jsx的js
    interceptor_mode: 'A',
    // babel/standalone的地址
    babel_url: 'https://unpkg.com/@babel/standalone@7.27.6/babel.min.js',
}

self.addEventListener('install', e => {
    console.log('[sw] install')
    // 强制跳过等待阶段
    e.waitUntil(
        getBabel().then(() => self.skipWaiting())
    );
});

// 新增activate事件处理
self.addEventListener('activate', e => {
    // 立即接管所有客户端
    e.waitUntil(Promise.all([self.clients.claim(), self.clients.matchAll().then((clients) => {
        clients.forEach((client) => {
            client.postMessage('Hello from Service Worker!');
        });
    })]));
});

self.addEventListener('fetch', event => {
    const url = new URL(event.request.url);
    if (!url.pathname.endsWith('.js')) {
        return;
    }
    if (CONFIG.interceptor_mode === 'A') {
        if (url.searchParams.has('no-jsx')) {
            // 参数标识no-jsx 不拦截
            return;
        }
    } else if (CONFIG.interceptor_mode === 'B') {
        if (!url.searchParams.has('jsx')) {
            // 参数没有标识jsx 不拦截
            return;
        }
    }
    event.respondWith(handleRequest(event.request))
})

async function getBabel() {
    const r = await fetch(CONFIG.babel_url)
    eval(await r.text())
}

async function handleRequest(request) {
    const url = new URL(request.url)
    const r = await fetch(request)
    if (r.status === 200 & url.host === location.host && url.pathname.endsWith('.js')) {
        const jsx = await r.text()
        const js = Babel.transform(jsx, {
            presets: ['react'],
            plugins: [
                ['transform-react-jsx', {
                    pragma: 'Vue.h',
                    pragmaFrag: 'Vue.Fragment'
                }]
            ]
        }).code
        return new Response(js, r)
    } else {
        return r
    }
}
js
const {createApp, ref} = Vue;
import HelloWorld from './component/HelloWorld.js'

const app = createApp({
    setup() {
        const count = ref(0);

        function increment() {
            count.value++
        }

        return () => <div>
            <naive.NButton onClick={increment}>{{default: () => `count ${count.value}`}}</naive.NButton>            
            <HelloWorld></HelloWorld>
        </div>
    }
});

app.mount('#app');
js
export default defineComponent(() => {
    return () => <div>{{ default: () => 'HelloWorld' }}</div>;
})

说明

在 Vue.js 中使用 JSX 时,注释 /* @jsx Vue.h */ 的作用是 指定 Babel 转换 JSX 语法时使用的创建元素函数。以下是详细解释:


核心作用:

  1. 覆盖默认的 JSX 转换函数
    Babel 默认将 JSX 转换为 React.createElement(),但该注释强制 Babel 改用 Vue.h 函数(Vue 的虚拟 DOM 创建函数)。

  2. 适配 Vue 的渲染机制
    Vue 使用 h() 函数(createElement 的别名)创建虚拟 DOM 节点。该注释确保 JSX 被正确转换为 Vue 兼容的格式。


技术原理:

  • Babel 的 JSX 转换规则
    当 Babel 处理 JSX 文件时(如 .jsx.tsx),会通过插件(如 @babel/plugin-transform-react-jsx)将 JSX 语法转换为函数调用。
    例如:<div>Hello</div>h('div', null, 'Hello')

  • @jsx 编译指示(Pragma)
    注释 /* @jsx Vue.h */ 是一种编译指示(pragma),明确告诉 Babel:

    “将本文件中的所有 JSX 转换为 Vue.h(...) 调用,而非默认的 React.createElement(...)。”


在 Vue 中的必要性:

  • Vue 2 场景
    Vue 2 的渲染函数使用 Vue.h(或 vm.$createElement)。若未指定此注释,Babel 会错误地使用 React 的函数导致报错。

  • Vue 3 场景
    通常直接从 vue 导入 h 函数:

    jsx
    import { h } from 'vue';
    /* @jsx h */  // 直接指向导入的 h 函数

示例对比:

未指定 @jsx 时(默认 React 转换)

jsx
const element = <div>Hello</div>;
// 被转换为:
const element = React.createElement('div', null, 'Hello'); // 报错:Vue 中没有 React

指定 /* @jsx Vue.h */

jsx
/* @jsx Vue.h */
const element = <div>Hello</div>;
// 被正确转换为:
const element = Vue.h('div', null, 'Hello'); // Vue 可识别
/src/technology/dateblog/2025/06/20250619-%E6%B5%8F%E8%A7%88%E5%99%A8html%E6%97%A0%E6%9E%84%E5%BB%BA%E6%AD%A5%E9%AA%A4%E4%BD%BF%E7%94%A8vue3%E5%8A%A0jsx%E7%A4%BA%E4%BE%8B.html