Skip to content

Commit 1ae4fe9

Browse files
committed
feat(vue-jsx-vapor): support SSR
1 parent cc10318 commit 1ae4fe9

File tree

9 files changed

+664
-62
lines changed

9 files changed

+664
-62
lines changed

Diff for: packages/vue-jsx-vapor/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,11 @@
221221
"@vue-jsx-vapor/babel": "workspace:*",
222222
"@vue-jsx-vapor/compiler": "workspace:*",
223223
"@vue-jsx-vapor/macros": "workspace:*",
224+
"@vue-macros/jsx-directive": "3.0.0-beta.7",
224225
"@vue-macros/volar": "catalog:",
226+
"@vue/babel-plugin-jsx": "^1.4.0",
225227
"hash-sum": "catalog:",
228+
"pathe": "^2.0.3",
226229
"ts-macro": "catalog:",
227230
"unplugin": "catalog:",
228231
"unplugin-utils": "catalog:"

Diff for: packages/vue-jsx-vapor/src/core/hmr.ts

+16-14
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
11
import { isFunctionalNode } from '@vue-jsx-vapor/macros/api'
22
import getHash from 'hash-sum'
3+
import { injectSSR } from './ssr'
34
import type { BabelFileResult, types } from '@babel/core'
45

5-
interface HotComponent {
6+
export interface HotComponent {
67
local: string
78
exported: string
89
id: string
910
}
1011

11-
export function registerHMR(
12+
export function injectHMRAndSSR(
1213
result: BabelFileResult,
1314
id: string,
14-
defineComponentNames = ['defineComponent', 'defineVaporComponent'],
15+
options?: {
16+
ssr?: boolean
17+
root?: string
18+
defineComponentNames?: string[]
19+
},
1520
) {
21+
const ssr = options?.ssr
22+
const defineComponentNames = options?.defineComponentNames ?? [
23+
'defineComponent',
24+
'defineVaporComponent',
25+
]
1626
const { ast } = result
1727

1828
// check for hmr injection
1929
const declaredComponents: string[] = []
2030
const hotComponents: HotComponent[] = []
2131
let hasDefaultExport = false
22-
const ssr = false
2332

2433
for (const node of ast!.program.body) {
2534
if (node.type === 'VariableDeclaration') {
@@ -112,16 +121,9 @@ if (import.meta.hot) {
112121
result.code = code
113122
}
114123

115-
// if (ssr) {
116-
// const normalizedId = normalizePath(path.relative(root, id))
117-
// let ssrInjectCode =
118-
// `\nimport { ssrRegisterHelper } from "${ssrRegisterHelperId}"` +
119-
// `\nconst __moduleId = ${JSON.stringify(normalizedId)}`
120-
// for (const { local } of hotComponents) {
121-
// ssrInjectCode += `\nssrRegisterHelper(${local}, __moduleId)`
122-
// }
123-
// result.code += ssrInjectCode
124-
// }
124+
if (ssr) {
125+
result.code += injectSSR(id, hotComponents, options?.root)
126+
}
125127
}
126128
}
127129

Diff for: packages/vue-jsx-vapor/src/core/index.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@ export function transformVueJsxVapor(
1010
code: string,
1111
id: string,
1212
options?: Options,
13+
needSourceMap = false,
1314
) {
1415
return transformSync(code, {
1516
plugins: [
1617
[jsx, { compile: options?.compile, interop: options?.interop }],
17-
id.endsWith('.tsx')
18-
? [babelTypescript, { isTSX: true, allowExtensions: true }]
19-
: null,
20-
].filter((i) => i !== null),
18+
...(id.endsWith('.tsx')
19+
? [[babelTypescript, { isTSX: true, allowExtensions: true }]]
20+
: []),
21+
],
2122
filename: id,
22-
sourceMaps: true,
23+
sourceMaps: needSourceMap,
2324
sourceFileName: id,
2425
babelrc: false,
2526
configFile: false,

Diff for: packages/vue-jsx-vapor/src/core/ssr.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { relative } from 'pathe'
2+
import { normalizePath } from 'unplugin-utils'
3+
import type { HotComponent } from './hmr'
4+
import type { ComponentOptions } from 'vue'
5+
6+
export const ssrRegisterHelperId = '/__vue-jsx-ssr-register-helper'
7+
export const ssrRegisterHelperCode =
8+
`import { useSSRContext } from "vue"\n` +
9+
// the const here is just to work around the Bun bug where
10+
// Function.toString() isn't working as intended
11+
// https://github.com/oven-sh/bun/issues/9543
12+
`export const ssrRegisterHelper = ${ssrRegisterHelper.toString()}`
13+
14+
/**
15+
* This function is serialized with toString() and evaluated as a virtual
16+
* module during SSR
17+
*/
18+
function ssrRegisterHelper(comp: ComponentOptions, filename: string) {
19+
const setup = comp.setup
20+
comp.setup = (props, ctx) => {
21+
// @ts-ignore
22+
const ssrContext = useSSRContext()
23+
;(ssrContext.modules || (ssrContext.modules = new Set())).add(filename)
24+
if (setup) {
25+
return setup(props, ctx)
26+
}
27+
}
28+
}
29+
30+
export function injectSSR(
31+
id: string,
32+
hotComponents: HotComponent[],
33+
root = '',
34+
) {
35+
const normalizedId = normalizePath(relative(root, id))
36+
let ssrInjectCode =
37+
`\nimport { ssrRegisterHelper } from "${ssrRegisterHelperId}"` +
38+
`\nconst __moduleId = ${JSON.stringify(normalizedId)}`
39+
for (const { local } of hotComponents) {
40+
ssrInjectCode += `\nssrRegisterHelper(${local}, __moduleId)`
41+
}
42+
return ssrInjectCode
43+
}

Diff for: packages/vue-jsx-vapor/src/core/vue-jsx.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { transformSync } from '@babel/core'
2+
// @ts-ignore missing type
3+
import babelTypescript from '@babel/plugin-transform-typescript'
4+
import jsx from '@vue/babel-plugin-jsx'
5+
6+
export function transformVueJsx(
7+
code: string,
8+
id: string,
9+
needSourceMap = false,
10+
) {
11+
const result = transformSync(code, {
12+
plugins: [
13+
jsx,
14+
...(id.endsWith('.tsx')
15+
? [[babelTypescript, { isTSX: true, allowExtensions: true }]]
16+
: []),
17+
],
18+
filename: id,
19+
sourceMaps: needSourceMap,
20+
sourceFileName: id,
21+
babelrc: false,
22+
configFile: false,
23+
})
24+
if (result?.code) {
25+
return {
26+
code: result.code,
27+
map: result.map,
28+
}
29+
}
30+
}

Diff for: packages/vue-jsx-vapor/src/raw.ts

+41-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
import Macros from '@vue-jsx-vapor/macros/raw'
1+
import macros from '@vue-jsx-vapor/macros/raw'
2+
import { transformJsxDirective } from '@vue-macros/jsx-directive/api'
23
import { createFilter, normalizePath } from 'unplugin-utils'
34
import { transformVueJsxVapor } from './core'
4-
import { registerHMR } from './core/hmr'
5+
import { injectHMRAndSSR } from './core/hmr'
56
import runtimeCode from './core/runtime?raw'
7+
import { ssrRegisterHelperCode, ssrRegisterHelperId } from './core/ssr'
8+
import { transformVueJsx } from './core/vue-jsx'
69
import type { Options } from './options'
710
import type { UnpluginOptions } from 'unplugin'
811

912
const plugin = (options: Options = {}): UnpluginOptions[] => {
1013
const transformInclude = createFilter(
11-
options?.include || /\.[cm]?[jt]sx?$/,
12-
options?.exclude,
14+
options?.include || /\.[cm]?[jt]sx$/,
15+
options?.exclude || /node_modules/,
1316
)
17+
let root = ''
1418
let needHMR = false
19+
let needSourceMap = false
1520
return [
1621
{
1722
name: 'vue-jsx-vapor',
@@ -33,34 +38,62 @@ const plugin = (options: Options = {}): UnpluginOptions[] => {
3338
}
3439
},
3540
configResolved(config) {
41+
root = config.root
3642
needHMR = config.command === 'serve'
43+
needSourceMap = config.command === 'serve' || !!config.build.sourcemap
3744
},
3845
},
3946
resolveId(id) {
47+
if (id === ssrRegisterHelperId) return id
4048
if (normalizePath(id) === 'vue-jsx-vapor/runtime') return id
4149
},
4250
loadInclude(id) {
51+
if (id === ssrRegisterHelperId) return true
4352
return normalizePath(id) === 'vue-jsx-vapor/runtime'
4453
},
4554
load(id) {
55+
if (id === ssrRegisterHelperId) return ssrRegisterHelperCode
4656
if (normalizePath(id) === 'vue-jsx-vapor/runtime') return runtimeCode
4757
},
4858
transformInclude,
49-
transform(code, id) {
50-
const result = transformVueJsxVapor(code, id, options)
59+
transform(code, id, opt?: { ssr?: boolean }) {
60+
const result = transformVueJsxVapor(code, id, options, needSourceMap)
5161
if (result?.code) {
52-
needHMR && registerHMR(result, id)
62+
;(needHMR || opt?.ssr) &&
63+
injectHMRAndSSR(result, id, { ssr: opt?.ssr, root })
5364
return {
5465
code: result.code,
5566
map: result.map,
5667
}
5768
}
5869
},
5970
},
71+
{
72+
name: '@vue-macros/jsx-directive',
73+
transformInclude,
74+
transform(code, id, opt?: { ssr?: boolean }) {
75+
if (options.interop || opt?.ssr) {
76+
return transformJsxDirective(code, id, {
77+
lib: 'vue',
78+
prefix: 'v-',
79+
version: 3.6,
80+
})
81+
}
82+
},
83+
},
84+
{
85+
name: '@vitejs/plugin-vue-jsx',
86+
transformInclude,
87+
transform(code, id, opt?: { ssr?: boolean }) {
88+
if (options.interop || opt?.ssr) {
89+
return transformVueJsx(code, id, needSourceMap)
90+
}
91+
},
92+
},
6093
...(options.macros === false
6194
? []
6295
: options.macros
63-
? [Macros(options.macros === true ? undefined : options.macros)]
96+
? [macros(options.macros === true ? undefined : options.macros)]
6497
: []),
6598
]
6699
}

Diff for: playground/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"dev": "node vite.js --port 5174"
7+
"dev": "node vite.js --port 5174",
8+
"build": "node vite.js build"
89
},
910
"devDependencies": {
1011
"@vitejs/plugin-vue-jsx": "^4.1.1",

0 commit comments

Comments
 (0)