html无构建步骤使用vue3加jsx加es模块化开发
此方案可以在无构建步骤的情况下使用jsx语法,实际上使用了js的完全编程能力 增强了模板功能,当然也缺失的vue的模板语法的性能优化 支持es模块化开发
单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>模块化使用
目录结构
project/
├── index.html
├── jsx-sw.js
├── App.js
└── HelloWorld.js<!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>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
}
}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');export default defineComponent(() => {
return () => <div>{{ default: () => 'HelloWorld' }}</div>;
})说明
在 Vue.js 中使用 JSX 时,注释 /* @jsx Vue.h */ 的作用是 指定 Babel 转换 JSX 语法时使用的创建元素函数。以下是详细解释:
核心作用:
覆盖默认的 JSX 转换函数
Babel 默认将 JSX 转换为React.createElement(),但该注释强制 Babel 改用Vue.h函数(Vue 的虚拟 DOM 创建函数)。适配 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函数:jsximport { h } from 'vue'; /* @jsx h */ // 直接指向导入的 h 函数
示例对比:
未指定 @jsx 时(默认 React 转换):
const element = <div>Hello</div>;
// 被转换为:
const element = React.createElement('div', null, 'Hello'); // 报错:Vue 中没有 React指定 /* @jsx Vue.h */ 后:
/* @jsx Vue.h */
const element = <div>Hello</div>;
// 被正确转换为:
const element = Vue.h('div', null, 'Hello'); // Vue 可识别