Skip to content
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

feat: create component & component lifecycle/props/attrs #151

Merged
merged 13 commits into from
Mar 16, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,19 @@ export function render(_ctx) {
`;

exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
"import { createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
"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';
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
const t1 = _template("<div><Comp></Comp></div>")
const t1 = _template("<div></div>")

export function render(_ctx) {
const n0 = t0()
const n2 = t1()
const n1 = _createTextNode()
_insert(n1, n2)
_renderEffect(() => _setText(n1, _ctx.bar))
_renderEffect(() => _setDynamicProp(n2, "id", _ctx.foo))
return [n0, n2]
const n3 = t1()
const n1 = _createComponent(_resolveComponent("Comp"))
const n2 = _createTextNode()
_insert([n1, n2], n3)
_renderEffect(() => _setText(n2, _ctx.bar))
_renderEffect(() => _setDynamicProp(n3, "id", _ctx.foo))
return [n0, n3]
}"
`;

Expand Down
1 change: 0 additions & 1 deletion packages/compiler-vapor/__tests__/compile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ describe('compile', () => {
expect(code).not.contains('effect')
})

// TODO: support multiple root nodes and components
test('should not affect siblings after it', () => {
const code = compile(
`<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
Expand Down
64 changes: 64 additions & 0 deletions packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { isArray } from '@vue/shared'
import type { CodegenContext } from '../generate'
import type { CreateComponentIRNode, IRProp } from '../ir'
import {
type CodeFragment,
INDENT_END,
INDENT_START,
NEWLINE,
genCall,
genMulti,
} from './utils'
import { genExpression } from './expression'
import { genPropKey } from './prop'

export function genCreateComponent(
oper: CreateComponentIRNode,
context: CodegenContext,
): CodeFragment[] {
const { vaporHelper } = context

const tag = oper.resolve
? genCall(vaporHelper('resolveComponent'), JSON.stringify(oper.tag))
: [oper.tag]

return [
NEWLINE,
`const n${oper.id} = `,
...genCall(vaporHelper('createComponent'), tag, genProps()),
]

function genProps() {
const props = oper.props
.map(props => {
if (isArray(props)) {
if (!props.length) return undefined
return genStaticProps(props)
} else {
return ['() => (', ...genExpression(props, context), ')']
}
})
.filter(Boolean)
if (props.length) {
return genMulti(['[', ']', ', '], ...props)
}
}

function genStaticProps(props: IRProp[]) {
return genMulti(
[
['{', INDENT_START, NEWLINE],
[INDENT_END, NEWLINE, '}'],
[', ', NEWLINE],
],
...props.map(prop => {
return [
...genPropKey(prop, context),
': () => (',
...genExpression(prop.values[0], context),
')',
]
}),
)
}
}
3 changes: 3 additions & 0 deletions packages/compiler-vapor/src/generators/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
NEWLINE,
buildCodeFragment,
} from './utils'
import { genCreateComponent } from './component'

export function genOperations(opers: OperationNode[], context: CodegenContext) {
const [frag, push] = buildCodeFragment()
Expand Down Expand Up @@ -56,6 +57,8 @@ export function genOperation(
return genIf(oper, context)
case IRNodeTypes.FOR:
return genFor(oper, context)
case IRNodeTypes.CREATE_COMPONENT_NODE:
return genCreateComponent(oper, context)
}

return []
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler-vapor/src/generators/prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ function genLiteralObjectProps(
return genMulti(
['{ ', ' }', ', '],
...props.map(prop => [
...genPropertyKey(prop, context),
...genPropKey(prop, context),
`: `,
...genPropValue(prop.values, context),
]),
)
}

function genPropertyKey(
export function genPropKey(
{ key: node, runtimeCamelize, modifier }: IRProp,
context: CodegenContext,
): CodeFragment[] {
Expand Down
12 changes: 12 additions & 0 deletions packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum IRNodeTypes {
INSERT_NODE,
PREPEND_NODE,
CREATE_TEXT_NODE,
CREATE_COMPONENT_NODE,

WITH_DIRECTIVE,

Expand Down Expand Up @@ -173,6 +174,16 @@ export interface WithDirectiveIRNode extends BaseIRNode {
builtin?: VaporHelper
}

export interface CreateComponentIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_COMPONENT_NODE
id: number
tag: string
props: IRProps[]
// TODO slots

resolve: boolean
}

export type IRNode = OperationNode | RootIRNode
export type OperationNode =
| SetPropIRNode
Expand All @@ -189,6 +200,7 @@ export type OperationNode =
| WithDirectiveIRNode
| IfIRNode
| ForIRNode
| CreateComponentIRNode

export enum DynamicFlag {
NONE = 0,
Expand Down
133 changes: 85 additions & 48 deletions packages/compiler-vapor/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
TransformContext,
} from '../transform'
import {
DynamicFlag,
IRNodeTypes,
type IRProp,
type IRProps,
Expand All @@ -29,8 +30,7 @@ export const isReservedProp = /*#__PURE__*/ makeMap(

export const transformElement: NodeTransform = (node, context) => {
return function postTransformElement() {
node = context.node

;({ node } = context)
if (
!(
node.type === NodeTypes.ELEMENT &&
Expand All @@ -41,37 +41,94 @@ export const transformElement: NodeTransform = (node, context) => {
return
}

const { tag, props } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
const { tag, tagType } = node
const isComponent = tagType === ElementTypes.COMPONENT
const propsResult = buildProps(
node,
context as TransformContext<ElementNode>,
)

;(isComponent ? transformComponentElement : transformNativeElement)(
tag,
propsResult,
context,
)
}
}

context.template += `<${tag}`
if (props.length) {
buildProps(
node,
context as TransformContext<ElementNode>,
undefined,
isComponent,
)
}
const { scopeId } = context.options
if (scopeId) {
context.template += ` ${scopeId}`
}
context.template += `>` + context.childrenTemplate.join('')
function transformComponentElement(
tag: string,
propsResult: PropsResult,
context: TransformContext,
) {
const { bindingMetadata } = context.options
const resolve = !bindingMetadata[tag]
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT

context.registerOperation({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: context.reference(),
tag,
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
resolve,
})
}

// TODO remove unnecessary close tag, e.g. if it's the last element of the template
if (!isVoidTag(tag)) {
context.template += `</${tag}>`
function transformNativeElement(
tag: string,
propsResult: ReturnType<typeof buildProps>,
context: TransformContext,
) {
const { scopeId } = context.options

context.template += `<${tag}`
if (scopeId) context.template += ` ${scopeId}`

if (propsResult[0] /* dynamic props */) {
const [, dynamicArgs, expressions] = propsResult
context.registerEffect(expressions, [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: context.reference(),
props: dynamicArgs,
},
])
} else {
for (const prop of propsResult[1]) {
const { key, values } = prop
if (key.isStatic && values.length === 1 && values[0].isStatic) {
context.template += ` ${key.content}`
if (values[0].content) context.template += `="${values[0].content}"`
} else {
context.registerEffect(values, [
{
type: IRNodeTypes.SET_PROP,
element: context.reference(),
prop,
},
])
}
}
}

context.template += `>` + context.childrenTemplate.join('')
// TODO remove unnecessary close tag, e.g. if it's the last element of the template
if (!isVoidTag(tag)) {
context.template += `</${tag}>`
}
}

export type PropsResult =
| [dynamic: true, props: IRProps[], expressions: SimpleExpressionNode[]]
| [dynamic: false, props: IRProp[]]

function buildProps(
node: ElementNode,
context: TransformContext<ElementNode>,
props: (VaporDirectiveNode | AttributeNode)[] = node.props as any,
isComponent: boolean,
) {
): PropsResult {
const props = node.props as (VaporDirectiveNode | AttributeNode)[]
if (props.length === 0) return [false, []]

const dynamicArgs: IRProps[] = []
const dynamicExpr: SimpleExpressionNode[] = []
let results: DirectiveTransformResult[] = []
Expand Down Expand Up @@ -112,31 +169,11 @@ function buildProps(
if (dynamicArgs.length || results.some(({ key }) => !key.isStatic)) {
// take rest of props as dynamic props
pushMergeArg()
context.registerEffect(dynamicExpr, [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: context.reference(),
props: dynamicArgs,
},
])
} else {
const irProps = dedupeProperties(results)
for (const prop of irProps) {
const { key, values } = prop
if (key.isStatic && values.length === 1 && values[0].isStatic) {
context.template += ` ${key.content}`
if (values[0].content) context.template += `="${values[0].content}"`
} else {
context.registerEffect(values, [
{
type: IRNodeTypes.SET_PROP,
element: context.reference(),
prop,
},
])
}
}
return [true, dynamicArgs, dynamicExpr]
}

const irProps = dedupeProperties(results)
return [false, irProps]
}

function transformProp(
Expand Down
14 changes: 8 additions & 6 deletions packages/runtime-vapor/__tests__/_utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Data } from '@vue/shared'
import {
type App,
type ComponentInternalInstance,
type ObjectComponent,
type SetupFn,
render as _render,
createComponentInstance,
createVaporApp,
defineComponent,
} from '../src'
import type { RawProps } from '../src/componentProps'

export function makeRender<Component = ObjectComponent | SetupFn>(
initHost = () => {
Expand All @@ -27,18 +27,20 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
const define = (comp: Component) => {
const component = defineComponent(comp as any)
let instance: ComponentInternalInstance
let app: App
const render = (
props: Data = {},
props: RawProps = {},
container: string | ParentNode = '#host',
) => {
instance = createComponentInstance(component, props)
_render(instance, container)
app = createVaporApp(component, props)
instance = app.mount(container)
return res()
}
const res = () => ({
component,
host,
instance,
app,
render,
})

Expand Down
Loading
Loading