Skip to content

Commit 463b47e

Browse files
authored
feat: create component & component lifecycle/props/attrs (#151)
1 parent 5d15314 commit 463b47e

31 files changed

+917
-606
lines changed

packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap

+9-8
Original file line numberDiff line numberDiff line change
@@ -120,18 +120,19 @@ export function render(_ctx) {
120120
`;
121121
122122
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
123-
"import { createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
123+
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
124124
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
125-
const t1 = _template("<div><Comp></Comp></div>")
125+
const t1 = _template("<div></div>")
126126
127127
export function render(_ctx) {
128128
const n0 = t0()
129-
const n2 = t1()
130-
const n1 = _createTextNode()
131-
_insert(n1, n2)
132-
_renderEffect(() => _setText(n1, _ctx.bar))
133-
_renderEffect(() => _setDynamicProp(n2, "id", _ctx.foo))
134-
return [n0, n2]
129+
const n3 = t1()
130+
const n1 = _createComponent(_resolveComponent("Comp"))
131+
const n2 = _createTextNode()
132+
_insert([n1, n2], n3)
133+
_renderEffect(() => _setText(n2, _ctx.bar))
134+
_renderEffect(() => _setDynamicProp(n3, "id", _ctx.foo))
135+
return [n0, n3]
135136
}"
136137
`;
137138

packages/compiler-vapor/__tests__/compile.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ describe('compile', () => {
7272
expect(code).not.contains('effect')
7373
})
7474

75-
// TODO: support multiple root nodes and components
7675
test('should not affect siblings after it', () => {
7776
const code = compile(
7877
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { isArray } from '@vue/shared'
2+
import type { CodegenContext } from '../generate'
3+
import type { CreateComponentIRNode, IRProp } from '../ir'
4+
import {
5+
type CodeFragment,
6+
INDENT_END,
7+
INDENT_START,
8+
NEWLINE,
9+
genCall,
10+
genMulti,
11+
} from './utils'
12+
import { genExpression } from './expression'
13+
import { genPropKey } from './prop'
14+
15+
export function genCreateComponent(
16+
oper: CreateComponentIRNode,
17+
context: CodegenContext,
18+
): CodeFragment[] {
19+
const { vaporHelper } = context
20+
21+
const tag = oper.resolve
22+
? genCall(vaporHelper('resolveComponent'), JSON.stringify(oper.tag))
23+
: [oper.tag]
24+
25+
return [
26+
NEWLINE,
27+
`const n${oper.id} = `,
28+
...genCall(vaporHelper('createComponent'), tag, genProps()),
29+
]
30+
31+
function genProps() {
32+
const props = oper.props
33+
.map(props => {
34+
if (isArray(props)) {
35+
if (!props.length) return undefined
36+
return genStaticProps(props)
37+
} else {
38+
return ['() => (', ...genExpression(props, context), ')']
39+
}
40+
})
41+
.filter(Boolean)
42+
if (props.length) {
43+
return genMulti(['[', ']', ', '], ...props)
44+
}
45+
}
46+
47+
function genStaticProps(props: IRProp[]) {
48+
return genMulti(
49+
[
50+
['{', INDENT_START, NEWLINE],
51+
[INDENT_END, NEWLINE, '}'],
52+
[', ', NEWLINE],
53+
],
54+
...props.map(prop => {
55+
return [
56+
...genPropKey(prop, context),
57+
': () => (',
58+
...genExpression(prop.values[0], context),
59+
')',
60+
]
61+
}),
62+
)
63+
}
64+
}

packages/compiler-vapor/src/generators/operation.ts

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
NEWLINE,
1717
buildCodeFragment,
1818
} from './utils'
19+
import { genCreateComponent } from './component'
1920

2021
export function genOperations(opers: OperationNode[], context: CodegenContext) {
2122
const [frag, push] = buildCodeFragment()
@@ -56,6 +57,8 @@ export function genOperation(
5657
return genIf(oper, context)
5758
case IRNodeTypes.FOR:
5859
return genFor(oper, context)
60+
case IRNodeTypes.CREATE_COMPONENT_NODE:
61+
return genCreateComponent(oper, context)
5962
}
6063

6164
return []

packages/compiler-vapor/src/generators/prop.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ function genLiteralObjectProps(
7878
return genMulti(
7979
['{ ', ' }', ', '],
8080
...props.map(prop => [
81-
...genPropertyKey(prop, context),
81+
...genPropKey(prop, context),
8282
`: `,
8383
...genPropValue(prop.values, context),
8484
]),
8585
)
8686
}
8787

88-
function genPropertyKey(
88+
export function genPropKey(
8989
{ key: node, runtimeCamelize, modifier }: IRProp,
9090
context: CodegenContext,
9191
): CodeFragment[] {

packages/compiler-vapor/src/ir.ts

+12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export enum IRNodeTypes {
2929
INSERT_NODE,
3030
PREPEND_NODE,
3131
CREATE_TEXT_NODE,
32+
CREATE_COMPONENT_NODE,
3233

3334
WITH_DIRECTIVE,
3435

@@ -173,6 +174,16 @@ export interface WithDirectiveIRNode extends BaseIRNode {
173174
builtin?: VaporHelper
174175
}
175176

177+
export interface CreateComponentIRNode extends BaseIRNode {
178+
type: IRNodeTypes.CREATE_COMPONENT_NODE
179+
id: number
180+
tag: string
181+
props: IRProps[]
182+
// TODO slots
183+
184+
resolve: boolean
185+
}
186+
176187
export type IRNode = OperationNode | RootIRNode
177188
export type OperationNode =
178189
| SetPropIRNode
@@ -189,6 +200,7 @@ export type OperationNode =
189200
| WithDirectiveIRNode
190201
| IfIRNode
191202
| ForIRNode
203+
| CreateComponentIRNode
192204

193205
export enum DynamicFlag {
194206
NONE = 0,

packages/compiler-vapor/src/transforms/transformElement.ts

+85-48
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
TransformContext,
1616
} from '../transform'
1717
import {
18+
DynamicFlag,
1819
IRNodeTypes,
1920
type IRProp,
2021
type IRProps,
@@ -29,8 +30,7 @@ export const isReservedProp = /*#__PURE__*/ makeMap(
2930

3031
export const transformElement: NodeTransform = (node, context) => {
3132
return function postTransformElement() {
32-
node = context.node
33-
33+
;({ node } = context)
3434
if (
3535
!(
3636
node.type === NodeTypes.ELEMENT &&
@@ -41,37 +41,94 @@ export const transformElement: NodeTransform = (node, context) => {
4141
return
4242
}
4343

44-
const { tag, props } = node
45-
const isComponent = node.tagType === ElementTypes.COMPONENT
44+
const { tag, tagType } = node
45+
const isComponent = tagType === ElementTypes.COMPONENT
46+
const propsResult = buildProps(
47+
node,
48+
context as TransformContext<ElementNode>,
49+
)
50+
51+
;(isComponent ? transformComponentElement : transformNativeElement)(
52+
tag,
53+
propsResult,
54+
context,
55+
)
56+
}
57+
}
4658

47-
context.template += `<${tag}`
48-
if (props.length) {
49-
buildProps(
50-
node,
51-
context as TransformContext<ElementNode>,
52-
undefined,
53-
isComponent,
54-
)
55-
}
56-
const { scopeId } = context.options
57-
if (scopeId) {
58-
context.template += ` ${scopeId}`
59-
}
60-
context.template += `>` + context.childrenTemplate.join('')
59+
function transformComponentElement(
60+
tag: string,
61+
propsResult: PropsResult,
62+
context: TransformContext,
63+
) {
64+
const { bindingMetadata } = context.options
65+
const resolve = !bindingMetadata[tag]
66+
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
67+
68+
context.registerOperation({
69+
type: IRNodeTypes.CREATE_COMPONENT_NODE,
70+
id: context.reference(),
71+
tag,
72+
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
73+
resolve,
74+
})
75+
}
6176

62-
// TODO remove unnecessary close tag, e.g. if it's the last element of the template
63-
if (!isVoidTag(tag)) {
64-
context.template += `</${tag}>`
77+
function transformNativeElement(
78+
tag: string,
79+
propsResult: ReturnType<typeof buildProps>,
80+
context: TransformContext,
81+
) {
82+
const { scopeId } = context.options
83+
84+
context.template += `<${tag}`
85+
if (scopeId) context.template += ` ${scopeId}`
86+
87+
if (propsResult[0] /* dynamic props */) {
88+
const [, dynamicArgs, expressions] = propsResult
89+
context.registerEffect(expressions, [
90+
{
91+
type: IRNodeTypes.SET_DYNAMIC_PROPS,
92+
element: context.reference(),
93+
props: dynamicArgs,
94+
},
95+
])
96+
} else {
97+
for (const prop of propsResult[1]) {
98+
const { key, values } = prop
99+
if (key.isStatic && values.length === 1 && values[0].isStatic) {
100+
context.template += ` ${key.content}`
101+
if (values[0].content) context.template += `="${values[0].content}"`
102+
} else {
103+
context.registerEffect(values, [
104+
{
105+
type: IRNodeTypes.SET_PROP,
106+
element: context.reference(),
107+
prop,
108+
},
109+
])
110+
}
65111
}
66112
}
113+
114+
context.template += `>` + context.childrenTemplate.join('')
115+
// TODO remove unnecessary close tag, e.g. if it's the last element of the template
116+
if (!isVoidTag(tag)) {
117+
context.template += `</${tag}>`
118+
}
67119
}
68120

121+
export type PropsResult =
122+
| [dynamic: true, props: IRProps[], expressions: SimpleExpressionNode[]]
123+
| [dynamic: false, props: IRProp[]]
124+
69125
function buildProps(
70126
node: ElementNode,
71127
context: TransformContext<ElementNode>,
72-
props: (VaporDirectiveNode | AttributeNode)[] = node.props as any,
73-
isComponent: boolean,
74-
) {
128+
): PropsResult {
129+
const props = node.props as (VaporDirectiveNode | AttributeNode)[]
130+
if (props.length === 0) return [false, []]
131+
75132
const dynamicArgs: IRProps[] = []
76133
const dynamicExpr: SimpleExpressionNode[] = []
77134
let results: DirectiveTransformResult[] = []
@@ -112,31 +169,11 @@ function buildProps(
112169
if (dynamicArgs.length || results.some(({ key }) => !key.isStatic)) {
113170
// take rest of props as dynamic props
114171
pushMergeArg()
115-
context.registerEffect(dynamicExpr, [
116-
{
117-
type: IRNodeTypes.SET_DYNAMIC_PROPS,
118-
element: context.reference(),
119-
props: dynamicArgs,
120-
},
121-
])
122-
} else {
123-
const irProps = dedupeProperties(results)
124-
for (const prop of irProps) {
125-
const { key, values } = prop
126-
if (key.isStatic && values.length === 1 && values[0].isStatic) {
127-
context.template += ` ${key.content}`
128-
if (values[0].content) context.template += `="${values[0].content}"`
129-
} else {
130-
context.registerEffect(values, [
131-
{
132-
type: IRNodeTypes.SET_PROP,
133-
element: context.reference(),
134-
prop,
135-
},
136-
])
137-
}
138-
}
172+
return [true, dynamicArgs, dynamicExpr]
139173
}
174+
175+
const irProps = dedupeProperties(results)
176+
return [false, irProps]
140177
}
141178

142179
function transformProp(

packages/runtime-vapor/__tests__/_utils.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { Data } from '@vue/shared'
21
import {
2+
type App,
33
type ComponentInternalInstance,
44
type ObjectComponent,
55
type SetupFn,
6-
render as _render,
7-
createComponentInstance,
6+
createVaporApp,
87
defineComponent,
98
} from '../src'
9+
import type { RawProps } from '../src/componentProps'
1010

1111
export function makeRender<Component = ObjectComponent | SetupFn>(
1212
initHost = () => {
@@ -27,18 +27,20 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
2727
const define = (comp: Component) => {
2828
const component = defineComponent(comp as any)
2929
let instance: ComponentInternalInstance
30+
let app: App
3031
const render = (
31-
props: Data = {},
32+
props: RawProps = {},
3233
container: string | ParentNode = '#host',
3334
) => {
34-
instance = createComponentInstance(component, props)
35-
_render(instance, container)
35+
app = createVaporApp(component, props)
36+
instance = app.mount(container)
3637
return res()
3738
}
3839
const res = () => ({
3940
component,
4041
host,
4142
instance,
43+
app,
4244
render,
4345
})
4446

0 commit comments

Comments
 (0)