Skip to content

feat: add generator #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/introduction/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ We assume you are already familiar with the basic usages of Vue before you conti
pnpm add vue-jsx-vapor

# runtime
pnpm add https://pkg.pr.new/vue@715b798
pnpm add https://pkg.pr.new/vue@51677cd
```

The Vue Vapor runtime is not release, so we use [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) to install.
Expand Down
2 changes: 1 addition & 1 deletion packages/babel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@babel/traverse": "catalog:",
"@babel/types": "catalog:",
"@vue-jsx-vapor/compiler": "workspace:*",
"source-map-js": "^1.2.1"
"source-map-js": "catalog:"
},
"devDependencies": {
"@types/babel__core": "catalog:",
Expand Down
24 changes: 13 additions & 11 deletions packages/babel/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ export type Options = {
filename: string
importSet: Set<string>
delegateEventSet: Set<string>
preambleMap: Map<string, string>
preambleIndex: number
templates: string[]
file: BabelFile
roots: {
node: JSXElement | JSXFragment
Expand Down Expand Up @@ -43,8 +42,7 @@ export default (): {
enter: (path, state) => {
state.importSet = new Set<string>()
state.delegateEventSet = new Set<string>()
state.preambleMap = new Map<string, string>()
state.preambleIndex = 0
state.templates = []
state.roots = []
const collectRoot: VisitNodeFunction<
Node,
Expand Down Expand Up @@ -78,20 +76,24 @@ export default (): {
})
},
exit: (path, state) => {
const { delegateEventSet, importSet, preambleMap } = state
const { delegateEventSet, importSet, templates } = state

const statements: string[] = []
if (delegateEventSet.size) {
statements.unshift(
`_delegateEvents(${Array.from(delegateEventSet).join(', ')});`,
`_delegateEvents("${Array.from(delegateEventSet).join('", "')}");`,
)
}

if (preambleMap.size) {
let preambleResult = ''
for (const [value, key] of preambleMap) {
preambleResult += `const ${key} = ${value}\n`
}
if (templates.length) {
let preambleResult = 'const '
const definedTemplates: Record<string, string> = {}
templates.forEach((template, index) => {
preambleResult += `t${index} = ${
definedTemplates[template] || template
}${templates.length - 1 === index ? ';' : ','}\n`
definedTemplates[template] = `t${index}`
})
statements.unshift(preambleResult)
}

Expand Down
27 changes: 4 additions & 23 deletions packages/babel/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,18 @@ export const transformJSX: VisitNodeFunction<
if (!root || !root.inVaporComponent) return

const isTS = state.filename?.endsWith('tsx')
let { code, helpers, preamble, map } = compile(root.node, {
const { code, map, helpers, templates, delegates } = compile(root.node, {
isTS,
filename: state.filename,
sourceMap: !!state.file.opts.sourceMaps,
source: ' '.repeat(root.node.start || 0) + root.source,
templates: state.templates.slice(),
...state.opts.compile,
})

helpers.forEach((helper) => state.importSet.add(helper))

preamble = preamble.replaceAll(
/(?<=const )t(?=(\d))/g,
`_t${state.preambleIndex}`,
)
code = code.replaceAll(/(?<== )t(?=\d)/g, `_t${state.preambleIndex}`)
state.preambleIndex++

for (const [, key, value] of preamble.matchAll(
/const (_t\d+) = (_template\(.*\))/g,
)) {
const result = state.preambleMap.get(value)
if (result) {
code = code.replaceAll(key, result)
} else {
state.preambleMap.set(value, key)
}
}

for (const [, events] of preamble.matchAll(/_delegateEvents\((.*)\)/g)) {
events.split(', ').forEach((event) => state.delegateEventSet.add(event))
}
delegates.forEach((delegate) => state.delegateEventSet.add(delegate))
state.templates.push(...templates.slice(state.templates.length))

const ast = parse(`(() => {${code}})()`, {
sourceFilename: state.filename,
Expand Down
26 changes: 26 additions & 0 deletions packages/babel/test/__snapshots__/interop.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`transform > transform multiple components 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<div></div>", true),
t1 = t0,
t2 = t1;
const A = defineComponent(() => {
defineVaporComponent(() => (() => {
const n0 = t0();
return n0;
})());
return () => <div />;
});
const B = defineVaporComponent(() => {
const C = defineComponent(() => <div />);
const D = (() => {
const n0 = t1();
return n0;
})();
return (() => {
const n0 = t2();
return n0;
})();
});"
`;
29 changes: 29 additions & 0 deletions packages/babel/test/__snapshots__/transform.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`transform > transform multiple components 1`] = `
"import { child as _child, delegateEvents as _delegateEvents, template as _template, createIf as _createIf } from 'vue';
import { setNodes as _setNodes } from 'vue-jsx-vapor/runtime';
const t0 = _template("<div> </div>", true),
t1 = _template("<div>Hello</div>"),
t2 = _template("<div>World</div>");
_delegateEvents("click", "dblclick");
const a = (() => {
const n0 = t0();
const x0 = _child(n0);
_setNodes(x0, () => Hello);
n0.$evtclick = e => onClick(e);
return n0;
})();
const b = (() => {
const n0 = _createIf(() => foo, () => {
const n2 = t1();
n2.$evtclick = e => onClick(e);
return n2;
}, () => {
const n4 = t2();
n4.$evtdblclick = e => onDblclick(e);
return n4;
});
return n0;
})();"
`;
23 changes: 1 addition & 22 deletions packages/babel/test/interop.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,6 @@ describe('transform', () => {
plugins: [[jsx, { interop: true }]],
},
)!
expect(code).toMatchInlineSnapshot(`
"import { template as _template } from 'vue';
const _t00 = _template("<div></div>", true);
const A = defineComponent(() => {
defineVaporComponent(() => (() => {
const n0 = _t00();
return n0;
})());
return () => <div />;
});
const B = defineVaporComponent(() => {
const C = defineComponent(() => <div />);
const D = (() => {
const n0 = _t00();
return n0;
})();
return (() => {
const n0 = _t00();
return n0;
})();
});"
`)
expect(code).matchSnapshot()
})
})
28 changes: 1 addition & 27 deletions packages/babel/test/transform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,6 @@ describe('transform', () => {
plugins: [[jsx]],
},
)!
expect(code).toMatchInlineSnapshot(`
"import { child as _child, delegateEvents as _delegateEvents, template as _template, createIf as _createIf } from 'vue';
import { setNodes as _setNodes } from 'vue-jsx-vapor/runtime';
const _t00 = _template("<div> </div>", true);
const _t10 = _template("<div>Hello</div>");
const _t11 = _template("<div>World</div>");
_delegateEvents("click", "dblclick");
const a = (() => {
const n0 = _t00();
const x0 = _child(n0);
_setNodes(x0, () => Hello);
n0.$evtclick = e => onClick(e);
return n0;
})();
const b = (() => {
const n0 = _createIf(() => foo, () => {
const n2 = _t10();
n2.$evtclick = e => onClick(e);
return n2;
}, () => {
const n4 = _t11();
n4.$evtdblclick = e => onDblclick(e);
return n4;
});
return n0;
})();"
`)
expect(code).matchSnapshot()
})
})
5 changes: 3 additions & 2 deletions packages/compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"@babel/parser": "catalog:",
"@babel/types": "catalog:",
"@vue/compiler-dom": "catalog:",
"@vue/compiler-vapor": "catalog:",
"@vue/shared": "catalog:"
"@vue/shared": "catalog:",
"ast-kit": "^2.1.1",
"source-map-js": "catalog:"
}
}
30 changes: 4 additions & 26 deletions packages/compiler/src/compile.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import { parse } from '@babel/parser'
import {
generate,
type VaporCodegenResult as BaseVaporCodegenResult,
} from '@vue/compiler-vapor'
import { extend, isString } from '@vue/shared'
import { customGenOperation } from './generate'

import {
IRNodeTypes,
type HackOptions,
type RootIRNode,
type RootNode,
} from './ir'
import { generate, type VaporCodegenResult } from './generate'
import { IRNodeTypes, type HackOptions, type RootNode } from './ir'
import {
transform,
type DirectiveTransform,
Expand All @@ -35,22 +25,12 @@ import { transformVText } from './transforms/vText'
import type { ExpressionStatement, JSXElement, JSXFragment } from '@babel/types'
import type { CompilerOptions as BaseCompilerOptions } from '@vue/compiler-dom'

export { generate }

export interface VaporCodegenResult
extends Omit<BaseVaporCodegenResult, 'ast'> {
ast: RootIRNode
customHelpers: Set<string>
}

// code/AST -> IR (transform) -> JS (generate)
export function compile(
source: JSXElement | JSXFragment | string,
options: CompilerOptions = {},
): VaporCodegenResult {
const resolvedOptions = extend({}, options, {
inline: true,
prefixIdentifiers: false,
expressionPlugins: options.expressionPlugins || ['jsx'],
})
if (!resolvedOptions.source && isString(source)) {
Expand Down Expand Up @@ -101,14 +81,12 @@ export function compile(
}),
)

return generate(ir as any, {
...resolvedOptions,
customGenOperation,
}) as unknown as VaporCodegenResult
return generate(ir, resolvedOptions)
}

export type CompilerOptions = HackOptions<BaseCompilerOptions> & {
source?: string
templates?: string[]
}
export type TransformPreset = [
NodeTransform[],
Expand Down
Loading
Loading