Skip to content

Commit 2a0a3dd

Browse files
committed
feat: basic render
1 parent ef9628c commit 2a0a3dd

30 files changed

+658
-43
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`basic 1`] = `
4+
"import { defineComponent as _defineComponent } from 'vue'
5+
import { template } from 'vue/vapor'
6+
const t0 = template(\`<h1 id=\\"title\\">Counter</h1>\`)
7+
export default /*#__PURE__*/_defineComponent({
8+
setup(__props) {
9+
10+
console.log('script')
11+
12+
return (() => {
13+
const root = t0()
14+
return root
15+
})();
16+
}
17+
18+
})"
19+
`;

Diff for: packages/compiler-vapor/__tests__/basic.test.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1-
test('basic', () => {
2-
//
1+
import * as CompilerVapor from '../src'
2+
// import * as CompilerDOM from '@vue/compiler-dom'
3+
import { parse, compileScript } from '@vue/compiler-sfc'
4+
import source from './fixtures/counter.vue?raw'
5+
6+
test('basic', async () => {
7+
const { descriptor } = parse(source, { compiler: CompilerVapor })
8+
const script = compileScript(descriptor, {
9+
id: 'counter.vue',
10+
inlineTemplate: true,
11+
templateOptions: { compiler: CompilerVapor }
12+
})
13+
expect(script.content).matchSnapshot()
314
})
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script setup lang="ts">
2+
console.log('script')
3+
</script>
4+
5+
<template>
6+
<h1 id="title">Counter</h1>
7+
</template>

Diff for: packages/compiler-vapor/package.json

+2-4
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@
2828
},
2929
"homepage": "https://github.com/vuejs/core-vapor/tree/dev/packages/compiler-vapor#readme",
3030
"dependencies": {
31-
"@babel/parser": "^7.23.0",
32-
"@vue/compiler-core": "3.3.8",
3331
"@vue/shared": "3.3.8",
34-
"estree-walker": "^2.0.2",
35-
"magic-string": "^0.30.5"
32+
"@vue/compiler-dom": "3.3.8",
33+
"ast-kit": "^0.11.2"
3634
},
3735
"devDependencies": {
3836
"@babel/types": "^7.23.0"

Diff for: packages/compiler-vapor/src/compile.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
CodegenResult,
3+
CompilerOptions,
4+
RootNode,
5+
baseParse
6+
} from '@vue/compiler-dom'
7+
import { isString } from '@vue/shared'
8+
import { transform } from './transform'
9+
import { generate } from './generate'
10+
11+
// code/AST -> IR -> JS codegen
12+
export function compile(
13+
template: string | RootNode,
14+
options: CompilerOptions
15+
): CodegenResult {
16+
const ast = isString(template) ? baseParse(template, options) : template
17+
const ir = transform(ast, options)
18+
return generate(ir, options)
19+
}

Diff for: packages/compiler-vapor/src/generate.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
CodegenContext,
3+
CodegenOptions,
4+
CodegenResult
5+
} from '@vue/compiler-dom'
6+
import { RootIRNode } from './transform'
7+
8+
// IR -> JS codegen
9+
export function generate(
10+
ast: RootIRNode,
11+
options: CodegenOptions & {
12+
onContextCreated?: (context: CodegenContext) => void
13+
} = {}
14+
): CodegenResult {
15+
let code = ''
16+
let preamble = "import { template } from 'vue/vapor'\n"
17+
18+
const isSetupInlined = !!options.inline
19+
20+
preamble += ast.template
21+
.map((template, i) => `const t${i} = template(\`${template.template}\`)`)
22+
.join('\n')
23+
24+
code += 'const root = t0()\n'
25+
code += 'return root'
26+
27+
const functionName = options.ssr ? `ssrRender` : `render`
28+
if (isSetupInlined) {
29+
code = `(() => {\n${code}\n})();`
30+
} else {
31+
code = `${preamble}\nexport function ${functionName}() {\n${code}\n}`
32+
}
33+
34+
return {
35+
code,
36+
ast: ast as any,
37+
preamble
38+
}
39+
}

Diff for: packages/compiler-vapor/src/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
export const foo = 'bar'
1+
export { parse } from '@vue/compiler-dom'
2+
export { transform } from './transform'
3+
export { generate } from './generate'
4+
export { compile } from './compile'

Diff for: packages/compiler-vapor/src/transform.ts

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import {
2+
RootNode,
3+
TemplateChildNode,
4+
ElementNode,
5+
AttributeNode,
6+
SourceLocation,
7+
NodeTypes,
8+
InterpolationNode
9+
} from '@vue/compiler-dom'
10+
import { TransformOptions } from 'vite'
11+
12+
export const enum IRNodeTypes {
13+
ROOT,
14+
TEMPLATE_GENERATOR
15+
}
16+
17+
export interface IRNode {
18+
type: IRNodeTypes
19+
loc: SourceLocation
20+
}
21+
22+
export interface RootIRNode extends IRNode {
23+
type: IRNodeTypes.ROOT
24+
template: Array<TemplateGeneratorIRNode>
25+
helpers: Set<string>
26+
}
27+
28+
export interface TemplateGeneratorIRNode extends IRNode {
29+
type: IRNodeTypes.TEMPLATE_GENERATOR
30+
template: string
31+
}
32+
33+
// AST -> IR
34+
export function transform(
35+
root: RootNode,
36+
options: TransformOptions = {}
37+
): RootIRNode {
38+
const template = transformChildren(root.children)
39+
40+
return {
41+
type: IRNodeTypes.ROOT,
42+
loc: root.loc,
43+
template: [
44+
{
45+
type: IRNodeTypes.TEMPLATE_GENERATOR,
46+
template,
47+
loc: root.loc
48+
}
49+
],
50+
helpers: new Set(['template'])
51+
}
52+
}
53+
54+
function transformChildren(children: TemplateChildNode[]) {
55+
let template: string = ''
56+
children.forEach((child, i) => walkNode(child, children.length > i + 1))
57+
return template
58+
59+
function walkNode(node: TemplateChildNode, hasSibling: boolean) {
60+
switch (node.type) {
61+
case 1 satisfies NodeTypes.ELEMENT: {
62+
template += transformElement(node, hasSibling)
63+
break
64+
}
65+
case 2 satisfies NodeTypes.TEXT:
66+
template += node.content
67+
break
68+
case 5 satisfies NodeTypes.INTERPOLATION:
69+
template += transformInterpolation(node)
70+
break
71+
case 12 satisfies NodeTypes.TEXT_CALL:
72+
template += node.content
73+
}
74+
}
75+
}
76+
77+
function transformInterpolation(node: InterpolationNode) {
78+
// TODO
79+
if (node.content.type === (4 satisfies NodeTypes.SIMPLE_EXPRESSION)) {
80+
return `{{ ${node.content.content} }}`
81+
}
82+
return '[EXP]'
83+
// return `{{${node.content.content}}}`
84+
}
85+
86+
function transformElement(node: ElementNode, hasSibling: boolean) {
87+
const { tag, props, children } = node
88+
let template = `<${tag}`
89+
const propsTemplate = props
90+
.filter(
91+
(prop): prop is AttributeNode =>
92+
prop.type === (6 satisfies NodeTypes.ATTRIBUTE)
93+
)
94+
.map(prop => transformProp(prop))
95+
.join(' ')
96+
97+
if (propsTemplate) template += ' ' + propsTemplate
98+
template += `>`
99+
100+
if (children.length > 0) {
101+
template += transformChildren(children)
102+
}
103+
104+
template += `</${tag}>`
105+
106+
return template
107+
}
108+
109+
function transformProp(prop: AttributeNode) {
110+
const { name, value } = prop
111+
if (value) return `${name}="${value.content}"`
112+
return name
113+
}

Diff for: packages/runtime-vapor/package.json

+12-5
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
"name": "@vue/runtime-vapor",
33
"version": "0.0.0",
44
"description": "@vue/runtime-vapor",
5-
"main": "dist/runtime-vapor.cjs.js",
5+
"main": "index.js",
6+
"module": "dist/runtime-vapor.esm-bundler.js",
7+
"types": "dist/runtime-vapor.d.ts",
8+
"unpkg": "dist/runtime-vapor.global.js",
69
"files": [
10+
"index.js",
711
"dist"
812
],
13+
"sideEffects": false,
914
"buildOptions": {
15+
"name": "VueRuntimeVapor",
1016
"formats": [
11-
"cjs"
12-
],
13-
"prod": false
17+
"esm-bundler",
18+
"esm-browser",
19+
"cjs",
20+
"global"
21+
]
1422
},
15-
"types": "dist/runtime-vapor.d.ts",
1623
"repository": {
1724
"type": "git",
1825
"url": "git+https://github.com/vuejs/core-vapor.git",

Diff for: packages/runtime-vapor/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { template } from './template'
2+
export { render } from './render'

Diff for: packages/runtime-vapor/src/render.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {
2+
effectScope,
3+
normalizeClass,
4+
normalizeStyle,
5+
toDisplayString
6+
} from 'vue'
7+
import { isArray } from '@vue/shared'
8+
9+
export type Block = Node | Fragment | Block[]
10+
export type Fragment = { nodes: Block; anchor?: Node }
11+
export type BlockFn = (props?: any) => Block
12+
13+
export function render(
14+
comp: BlockFn,
15+
container: string | ParentNode
16+
): () => void {
17+
const scope = effectScope()
18+
const block = scope.run(() => comp())!
19+
insert(block, (container = normalizeContainer(container)))
20+
return () => {
21+
scope.stop()
22+
remove(block, container as ParentNode)
23+
}
24+
}
25+
26+
export function normalizeContainer(container: string | ParentNode): ParentNode {
27+
return typeof container === 'string'
28+
? (document.querySelector(container) as ParentNode)
29+
: container
30+
}
31+
32+
export function insert(
33+
block: Block,
34+
parent: ParentNode,
35+
anchor: Node | null = null
36+
) {
37+
// if (!isHydrating) {
38+
if (block instanceof Node) {
39+
parent.insertBefore(block, anchor)
40+
} else if (isArray(block)) {
41+
for (const child of block) insert(child, parent, anchor)
42+
} else {
43+
insert(block.nodes, parent, anchor)
44+
block.anchor && parent.insertBefore(block.anchor, anchor)
45+
}
46+
// }
47+
}
48+
49+
export function remove(block: Block, parent: ParentNode) {
50+
if (block instanceof Node) {
51+
parent.removeChild(block)
52+
} else if (isArray(block)) {
53+
for (const child of block) remove(child, parent)
54+
} else {
55+
remove(block.nodes, parent)
56+
block.anchor && parent.removeChild(block.anchor)
57+
}
58+
}
59+
60+
export function setText(el: Element, oldVal: any, newVal: any) {
61+
if ((newVal = toDisplayString(newVal)) !== oldVal) {
62+
el.textContent = newVal
63+
}
64+
}
65+
66+
export function setClass(el: Element, oldVal: any, newVal: any) {
67+
if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) {
68+
el.className = newVal
69+
}
70+
}
71+
72+
export function setStyle(el: HTMLElement, oldVal: any, newVal: any) {
73+
if ((newVal = normalizeStyle(newVal)) !== oldVal && (newVal || oldVal)) {
74+
if (typeof newVal === 'string') {
75+
el.style.cssText = newVal
76+
} else {
77+
// TODO
78+
}
79+
}
80+
}
81+
82+
export function setAttr(el: Element, key: string, oldVal: any, newVal: any) {
83+
if (newVal !== oldVal) {
84+
if (newVal != null) {
85+
el.setAttribute(key, newVal)
86+
} else {
87+
el.removeAttribute(key)
88+
}
89+
}
90+
}
91+
92+
export function setDynamicProp(el: Element, key: string, val: any) {
93+
if (key === 'class') {
94+
setClass(el, void 0, val)
95+
} else if (key === 'style') {
96+
setStyle(el as HTMLElement, void 0, val)
97+
} else if (key in el) {
98+
;(el as any)[key] = val
99+
} else {
100+
// TODO special checks
101+
setAttr(el, key, void 0, val)
102+
}
103+
}

Diff for: packages/vue/compiler-vapor/index.d.mts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@vue/compiler-vapor'

Diff for: packages/vue/compiler-vapor/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@vue/compiler-vapor'

Diff for: packages/vue/compiler-vapor/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('@vue/compiler-vapor')

Diff for: packages/vue/compiler-vapor/index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@vue/compiler-vapor'

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

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"main": "index.js",
3+
"module": "index.mjs"
4+
}

0 commit comments

Comments
 (0)