|
| 1 | +--- |
| 2 | +title: Vue 使用 JSX 开发配置 |
| 3 | +date: 2025-06-30 12:08 |
| 4 | +updated: 2025-06-30T12:08:34+08:00 |
| 5 | +permalink: |
| 6 | +top: 0 |
| 7 | +comments: |
| 8 | +copyright: true |
| 9 | +tags: |
| 10 | +categories: |
| 11 | +keywords: |
| 12 | +description: |
| 13 | +--- |
| 14 | +vue3 使用 ts 开发,天然支持 tsx 开发.本文记录下如何在 bun + vitejs 环境下配置 tsx |
| 15 | + |
| 16 | +1、添加依赖 |
| 17 | + |
| 18 | +```sh |
| 19 | +bun add vue vue-router vuetify |
| 20 | +bun add -D @mdi/font @types/node @vitejs/plugin-vue @vitejs/plugin-vue-jsx typescript vite vue-tsc |
| 21 | +``` |
| 22 | + |
| 23 | +2、配置 tsconfig.json |
| 24 | + |
| 25 | +主要是打开 jsx 支持 |
| 26 | +```json |
| 27 | +{ |
| 28 | + "jsx": "preserve", |
| 29 | + "jsxImportSource": "vue", |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +完整配置 |
| 34 | +```json |
| 35 | +{ |
| 36 | + "compilerOptions": { |
| 37 | + "target": "ES2020", |
| 38 | + "useDefineForClassFields": true, |
| 39 | + "module": "ESNext", |
| 40 | + "lib": [ |
| 41 | + "ES2020", |
| 42 | + "DOM", |
| 43 | + "DOM.Iterable" |
| 44 | + ], |
| 45 | + "skipLibCheck": true, |
| 46 | + /* Bundler mode */ |
| 47 | + "moduleResolution": "bundler", |
| 48 | + "allowImportingTsExtensions": true, |
| 49 | + "allowSyntheticDefaultImports": true, |
| 50 | + "resolveJsonModule": true, |
| 51 | + "isolatedModules": true, |
| 52 | + "noEmit": true, |
| 53 | + /* Linting */ |
| 54 | + "strict": true, |
| 55 | + "noUnusedLocals": true, |
| 56 | + "noUnusedParameters": true, |
| 57 | + "noFallthroughCasesInSwitch": true, |
| 58 | + "jsx": "preserve", |
| 59 | + "jsxImportSource": "vue", |
| 60 | + "paths": { |
| 61 | + "@/*": [ |
| 62 | + "./src/*" |
| 63 | + ], |
| 64 | + "~/*": [ |
| 65 | + "./public/*" |
| 66 | + ] |
| 67 | + } |
| 68 | + }, |
| 69 | + "include": [ |
| 70 | + "src/**/*.ts", |
| 71 | + "src/**/*.d.ts", |
| 72 | + "src/**/*.tsx", |
| 73 | + "src/**/*.vue", |
| 74 | + "vite.config.ts", |
| 75 | + ] |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +3、配置 vite.config.ts |
| 80 | + |
| 81 | +```diff |
| 82 | +++ import vue from "@vitejs/plugin-vue"; |
| 83 | +++ import vueJsx from '@vitejs/plugin-vue-jsx' |
| 84 | +export default defineConfig(async () => ({ |
| 85 | +++ plugins: [vue(), vueJsx())], |
| 86 | +``` |
| 87 | + |
| 88 | +如此就可以直接使用 tsx 文件进行开发. |
| 89 | + |
| 90 | +4、由于 vscode 插件对 tsx 支持不完善, 导致 onClick 等事件识别不了而报 lint 错误,所以手动添加下类型说明 |
| 91 | +```ts |
| 92 | +import { VNodeProps } from 'vue' |
| 93 | +// 定义修饰符类型 |
| 94 | +type EventModifiers = { |
| 95 | + capture?: boolean |
| 96 | + once?: boolean |
| 97 | + passive?: boolean |
| 98 | + stop?: boolean |
| 99 | + prevent?: boolean |
| 100 | + self?: boolean |
| 101 | +} |
| 102 | + |
| 103 | +// 扩展事件处理器类型 |
| 104 | +type EventHandler<T = Event> = ((event: T) => void) | { |
| 105 | + handler: (event: T) => void |
| 106 | + modifiers: EventModifiers |
| 107 | +} |
| 108 | + |
| 109 | +export type ModifierKey = 'stop' | 'prevent' | 'capture' | 'self' | 'once' | 'passive' |
| 110 | + |
| 111 | +export type WithModifiers<T> = T & { |
| 112 | + [key in `_${ModifierKey}`]: WithModifiers<T> |
| 113 | +} |
| 114 | + |
| 115 | +export type EventHandlerWithModifiers<T extends Function> = WithModifiers<T> |
| 116 | + |
| 117 | +declare module 'vue' { |
| 118 | + interface HTMLAttributes { |
| 119 | + // 鼠标事件 |
| 120 | + onClick?: EventHandler<MouseEvent> |
| 121 | + onDblclick?: EventHandler<MouseEvent> |
| 122 | + onMousedown?: EventHandler<MouseEvent> |
| 123 | + onMouseup?: EventHandler<MouseEvent> |
| 124 | + onMouseover?: EventHandler<MouseEvent> |
| 125 | + onMousemove?: EventHandler<MouseEvent> |
| 126 | + onMouseout?: EventHandler<MouseEvent> |
| 127 | + onContextmenu?: EventHandler<MouseEvent> |
| 128 | + |
| 129 | + // 键盘事件 |
| 130 | + onKeydown?: EventHandler<KeyboardEvent> |
| 131 | + onKeypress?: EventHandler<KeyboardEvent> |
| 132 | + onKeyup?: EventHandler<KeyboardEvent> |
| 133 | + |
| 134 | + // 表单事件 |
| 135 | + onChange?: EventHandler<Event> |
| 136 | + onInput?: EventHandler<Event> |
| 137 | + onSubmit?: EventHandler<Event> |
| 138 | + onReset?: EventHandler<Event> |
| 139 | + |
| 140 | + // 焦点事件 |
| 141 | + onFocus?: EventHandler<FocusEvent> |
| 142 | + onBlur?: EventHandler<FocusEvent> |
| 143 | + |
| 144 | + // 其他事件 |
| 145 | + onScroll?: EventHandler<UIEvent> |
| 146 | + onWheel?: EventHandler<WheelEvent> |
| 147 | + onDrag?: EventHandler<DragEvent> |
| 148 | + onDragstart?: EventHandler<DragEvent> |
| 149 | + onDragend?: EventHandler<DragEvent> |
| 150 | + onDragenter?: EventHandler<DragEvent> |
| 151 | + onDragleave?: EventHandler<DragEvent> |
| 152 | + onDragover?: EventHandler<DragEvent> |
| 153 | + onDrop?: EventHandler<DragEvent> |
| 154 | + } |
| 155 | + |
| 156 | + interface ComponentCustomProps extends HTMLAttributes { |
| 157 | + // 这里可以添加组件特定的props类型 |
| 158 | + // [event: string]: ((any) => any | { |
| 159 | + // [string]?: (any) => any |
| 160 | + // modifiers: EventModifiers |
| 161 | + // }); |
| 162 | + // or this |
| 163 | + [event: `on${string}`]: (...args: any[]) => void |
| 164 | + } |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +--- |
| 169 | +另外如果想在项目中使用 mdx, 需要如下配置 |
| 170 | + |
| 171 | +1、添加依赖 |
| 172 | +```sh |
| 173 | +bun add @mdx-js/vue |
| 174 | +bun add -D @mdx-js/rollup @mdx-js/vue @types/mdx |
| 175 | +``` |
| 176 | +2、配置 vite.config.ts |
| 177 | +```diff |
| 178 | +++ import mdx from '@mdx-js/rollup' |
| 179 | +export default defineConfig(async () => ({ |
| 180 | + plugins: [vue(), vueJsx(), |
| 181 | +++ mdx({ |
| 182 | +++ jsxImportSource: 'vue', |
| 183 | +++ jsxRuntime: 'automatic', // 或 'automatic' |
| 184 | +++ providerImportSource: '@mdx-js/vue', // 使用 Vue 运行时 |
| 185 | +++ remarkPlugins: [], |
| 186 | +++ rehypePlugins: [] |
| 187 | + }) |
| 188 | + ], |
| 189 | +``` |
| 190 | + |
| 191 | +主要配置 mdx 插件,使用 `@mdx-js/vue` 来渲染生成 mdx 文件对应的 VNode. 否则就会使用内置的 `preact` 生成 Component 而报错 |
| 192 | + |
| 193 | +```ts |
| 194 | +import { defineConfig } from "vite"; |
| 195 | +import vue from "@vitejs/plugin-vue"; |
| 196 | +import vueJsx from '@vitejs/plugin-vue-jsx' |
| 197 | +import mdx from '@mdx-js/rollup' |
| 198 | +import path from "path" |
| 199 | +const host = process.env.TAURI_DEV_HOST; |
| 200 | + |
| 201 | +// https://vitejs.dev/config/ |
| 202 | +export default defineConfig(async () => ({ |
| 203 | + plugins: [vue(), vueJsx(), mdx({ |
| 204 | + jsxImportSource: 'vue', |
| 205 | + jsxRuntime: 'automatic', // 或 'automatic' |
| 206 | + providerImportSource: '@mdx-js/vue', // 使用 Vue 运行时 |
| 207 | + remarkPlugins: [], |
| 208 | + rehypePlugins: [] |
| 209 | + })], |
| 210 | + resolve: { |
| 211 | + alias: { |
| 212 | + "@": path.resolve(__dirname, "./src"), |
| 213 | + "~": path.resolve(__dirname, "./public"), |
| 214 | + }, |
| 215 | + }, |
| 216 | + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` |
| 217 | + // |
| 218 | + // 1. prevent vite from obscuring rust errors |
| 219 | + clearScreen: false, |
| 220 | + // 2. tauri expects a fixed port, fail if that port is not available |
| 221 | + server: { |
| 222 | + port: 1420, |
| 223 | + strictPort: true, |
| 224 | + host: host || false, |
| 225 | + hmr: host |
| 226 | + ? { |
| 227 | + protocol: "ws", |
| 228 | + host, |
| 229 | + port: 1421, |
| 230 | + } |
| 231 | + : undefined, |
| 232 | + watch: { |
| 233 | + // 3. tell vite to ignore watching `src-tauri` |
| 234 | + ignored: ["**/src-tauri/**"], |
| 235 | + }, |
| 236 | + }, |
| 237 | +})); |
| 238 | + |
| 239 | +``` |
| 240 | + |
| 241 | + |
| 242 | +```tsx |
| 243 | +import { defineComponent } from "vue"; |
| 244 | +import { VCard, VContainer, VSheet } from "vuetify/components"; |
| 245 | +import Earth from '~/mdx/Earth.mdx'; |
| 246 | + |
| 247 | +export default defineComponent({ |
| 248 | + setup() { |
| 249 | + return () => ( |
| 250 | + <VContainer> |
| 251 | + <VSheet rounded="lg"></VSheet> |
| 252 | + <VCard> |
| 253 | + <Earth/> |
| 254 | + </VCard> |
| 255 | + </VContainer> |
| 256 | + ) |
| 257 | + } |
| 258 | +}) |
| 259 | +``` |
0 commit comments