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: implement inheritAttrs #153

Merged
merged 34 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bb26087
feat(vapor): implement inheritAttrs
Doctor-wu Mar 17, 2024
7e1f592
feat(runtime-vapor): apply inheritAttrs to instance
Doctor-wu Mar 17, 2024
9d8c1e5
feat(runtime-vapor): extract apiSetup & init setup ctx
Doctor-wu Mar 17, 2024
20aa749
feat(runtime-vapor): add setup ctx type
Doctor-wu Mar 17, 2024
e4bc5ab
Revert "feat(runtime-vapor): add setup ctx type"
Doctor-wu Mar 17, 2024
2f8b9af
Revert "feat(runtime-vapor): extract apiSetup & init setup ctx"
Doctor-wu Mar 17, 2024
757aa74
feat(runtime-vapor): impl fallthrough attrs
Doctor-wu Mar 17, 2024
2db5e19
test(runtime-vapor): tweak props test case
Doctor-wu Mar 17, 2024
8498ba6
feat(runtime-vapor): update attrs when props update
Doctor-wu Mar 17, 2024
1742efe
feat(runtime-vapor): remove unecessary property & use proper way to s…
Doctor-wu Mar 18, 2024
e082299
feat(runtime-vapor, compiler-vapor): add withAttr in singleRoot Compo…
Doctor-wu Mar 18, 2024
5cab771
feat(runtime-vapor, compiler-vapor): resolve nest component attrs
Doctor-wu Mar 18, 2024
1482a20
feat(compiler-vapor): remove unecessary clone props
Doctor-wu Mar 18, 2024
b3b6622
feat(runtime-vapor): clean code
Doctor-wu Mar 18, 2024
791f533
chore(runtime-vapor): make lint happy
Doctor-wu Mar 18, 2024
f7d4c13
feat(runtime-vapor, compiler-vapor): make inheritAttrs reactive
Doctor-wu Mar 18, 2024
bdc1ee4
chore(runtime-vapor): remove unecessary code
Doctor-wu Mar 18, 2024
35499bd
feat(runtime-vapor): should not withAttrs when component set inheritA…
Doctor-wu Mar 18, 2024
a474ac4
feat(runtim-vapor, compiler-vapor): simplify implement
Doctor-wu Mar 18, 2024
24488bc
Merge branch 'main' into feature-inherit-attrs
Doctor-wu Mar 18, 2024
6044bf7
test(compiler-vapor): add generator component test case & snapshot
Doctor-wu Mar 18, 2024
8afe97d
Merge branch 'main' into feature-inherit-attrs
Doctor-wu Mar 18, 2024
c30a618
feat(runtime-vapor): simplify implement
Doctor-wu Mar 18, 2024
c085cda
feat(runtime-vapor): simplify implement
Doctor-wu Mar 18, 2024
0ea395c
refactor: optimize bundle size
sxzz Mar 18, 2024
b5879d3
refactor: check if component
sxzz Mar 18, 2024
322f0ba
refactor: remove type casting
sxzz Mar 18, 2024
34c1937
feat(compiler-vapor): optimize single root implemention
Doctor-wu Mar 18, 2024
56ff7c4
fix: type error
sxzz Mar 18, 2024
47754b7
feat(compiler-vapor): perf bundle size
Doctor-wu Mar 18, 2024
5270734
chore(compiler-vapor, runtime-vapor): bundle size & make lint happy
Doctor-wu Mar 18, 2024
c068506
refactor: simplify
sxzz Mar 18, 2024
61b572c
chore(compiler-vapor): remove unecessary import
Doctor-wu Mar 18, 2024
47f8b9a
test: undo
sxzz Mar 18, 2024
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
@@ -0,0 +1,44 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`generate component > generate multi root component 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("123")

export function render(_ctx) {
const n1 = t0()
const n0 = _createComponent(_resolveComponent("Comp"))
return [n0, n1]
}"
`;

exports[`generate component > generate single root component (with props) 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';

export function render(_ctx) {
const n0 = _createComponent(_resolveComponent("Comp"), [{
foo: () => (foo)
}], true)
return n0
}"
`;

exports[`generate component > generate single root component (without props) 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';

export function render(_ctx) {
const n0 = _createComponent(_resolveComponent("Comp"), null, true)
return n0
}"
`;

exports[`generate component > should not generate withAttrs if component is not the root of the template 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, insert as _insert, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")

export function render(_ctx) {
const n1 = t0()
const n0 = _createComponent(_resolveComponent("Comp"))
_insert(n0, n1)
return n1
}"
`;
23 changes: 23 additions & 0 deletions packages/compiler-vapor/__tests__/generators/component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { compile } from '@vue/compiler-vapor'

describe('generate component', () => {
test('generate single root component (without props)', () => {
const { code } = compile(`<Comp/>`)
expect(code).toMatchSnapshot()
})

test('generate single root component (with props)', () => {
const { code } = compile(`<Comp :foo="foo"/>`)
expect(code).toMatchSnapshot()
})

test('generate multi root component', () => {
const { code } = compile(`<Comp/>123`)
expect(code).toMatchSnapshot()
})

test('should not generate withAttrs if component is not the root of the template', () => {
const { code } = compile(`<div><Comp/></div>`)
expect(code).toMatchSnapshot()
})
})
10 changes: 9 additions & 1 deletion packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ export function genCreateComponent(
? genCall(vaporHelper('resolveComponent'), JSON.stringify(oper.tag))
: [oper.tag]

const singleRoot = oper.isSingleRoot
const props = genProps()

return [
NEWLINE,
`const n${oper.id} = `,
...genCall(vaporHelper('createComponent'), tag, genProps()),
...genCall(
vaporHelper('createComponent'),
tag,
props || (singleRoot ? 'null' : false),
singleRoot ? 'true' : undefined,
),
]

function genProps() {
Expand Down
1 change: 1 addition & 0 deletions packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
// TODO slots

resolve: boolean
isSingleRoot: boolean
sxzz marked this conversation as resolved.
Show resolved Hide resolved
}

export type IRNode = OperationNode | RootIRNode
Expand Down
21 changes: 16 additions & 5 deletions packages/compiler-vapor/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
type AttributeNode,
type ComponentNode,
type ElementNode,
ElementTypes,
ErrorCodes,
Expand Down Expand Up @@ -48,29 +49,39 @@ export const transformElement: NodeTransform = (node, context) => {
context as TransformContext<ElementNode>,
)

;(isComponent ? transformComponentElement : transformNativeElement)(
tag,
propsResult,
context,
)
if (isComponent) {
transformComponentElement(
tag,
propsResult,
context,
node as ComponentNode,
)
} else {
transformNativeElement(tag, propsResult, context)
}
}
}

function transformComponentElement(
tag: string,
propsResult: PropsResult,
context: TransformContext,
node: ComponentNode,
) {
const { bindingMetadata } = context.options
const resolve = !bindingMetadata[tag]
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
const isSingleRoot =
context.root.node.children.length === 1 &&
context.root.node.children[0] === node

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

Expand Down
163 changes: 163 additions & 0 deletions packages/runtime-vapor/__tests__/componentAttrs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import {
createComponent,
getCurrentInstance,
nextTick,
ref,
setText,
template,
watchEffect,
} from '../src'
import { setCurrentInstance } from '../src/component'
import { makeRender } from './_utils'

const define = makeRender<any>()

describe('attribute fallthrough', () => {
it('should allow attrs to fallthrough', async () => {
const t0 = template('<div>')
const { component: Child } = define({
props: ['foo'],
render() {
const instance = getCurrentInstance()!
const n0 = t0()
watchEffect(() => setText(n0, instance.props.foo))
return n0
},
})

const foo = ref(1)
const id = ref('a')
const { instance, host } = define({
setup() {
return { foo, id }
},
render(_ctx: Record<string, any>) {
return createComponent(
Child,
[
{
foo: () => _ctx.foo,
id: () => _ctx.id,
},
],
true,
)
},
}).render()
const reset = setCurrentInstance(instance)
expect(host.innerHTML).toBe('<div id="a">1</div>')

foo.value++
await nextTick()
expect(host.innerHTML).toBe('<div id="a">2</div>')

id.value = 'b'
await nextTick()
expect(host.innerHTML).toBe('<div id="b">2</div>')
reset()
})

it('should not fallthrough if explicitly pass inheritAttrs: false', async () => {
const t0 = template('<div>')
const { component: Child } = define({
props: ['foo'],
inheritAttrs: false,
render() {
const instance = getCurrentInstance()!
const n0 = t0()
watchEffect(() => setText(n0, instance.props.foo))
return n0
},
})

const foo = ref(1)
const id = ref('a')
const { instance, host } = define({
setup() {
return { foo, id }
},
render(_ctx: Record<string, any>) {
return createComponent(
Child,
[
{
foo: () => _ctx.foo,
id: () => _ctx.id,
},
],
true,
)
},
}).render()
const reset = setCurrentInstance(instance)
expect(host.innerHTML).toBe('<div>1</div>')

foo.value++
await nextTick()
expect(host.innerHTML).toBe('<div>2</div>')

id.value = 'b'
await nextTick()
expect(host.innerHTML).toBe('<div>2</div>')
reset()
})

it('should pass through attrs in nested single root components', async () => {
const t0 = template('<div>')
const { component: Grandson } = define({
props: ['custom-attr'],
render() {
const instance = getCurrentInstance()!
const n0 = t0()
watchEffect(() => setText(n0, instance.attrs.foo))
return n0
},
})

const { component: Child } = define({
render() {
const n0 = createComponent(
Grandson,
[
{
'custom-attr': () => 'custom-attr',
},
],
true,
)
return n0
},
})

const foo = ref(1)
const id = ref('a')
const { instance, host } = define({
setup() {
return { foo, id }
},
render(_ctx: Record<string, any>) {
return createComponent(
Child,
[
{
foo: () => _ctx.foo,
id: () => _ctx.id,
},
],
true,
)
},
}).render()
const reset = setCurrentInstance(instance)
expect(host.innerHTML).toBe('<div foo="1" id="a">1</div>')

foo.value++
await nextTick()
expect(host.innerHTML).toBe('<div foo="2" id="a">2</div>')

id.value = 'b'
await nextTick()
expect(host.innerHTML).toBe('<div foo="2" id="b">2</div>')
reset()
})
})
5 changes: 2 additions & 3 deletions packages/runtime-vapor/__tests__/componentProps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ describe('component: props', () => {
test('optimized props updates', async () => {
const t0 = template('<div>')
const { component: Child } = define({
inheritAttrs: false,
props: ['foo'],
render() {
const instance = getCurrentInstance()!
Expand All @@ -245,17 +246,14 @@ describe('component: props', () => {
},
}).render()
const reset = setCurrentInstance(instance)
// expect(host.innerHTML).toBe('<div id="a">1</div>') // TODO: Fallthrough Attributes
expect(host.innerHTML).toBe('<div>1</div>')

foo.value++
await nextTick()
// expect(host.innerHTML).toBe('<div id="a">2</div>') // TODO: Fallthrough Attributes
expect(host.innerHTML).toBe('<div>2</div>')

id.value = 'b'
await nextTick()
// expect(host.innerHTML).toBe('<div id="b">2</div>') // TODO: Fallthrough Attributes
reset()
})

Expand Down Expand Up @@ -441,6 +439,7 @@ describe('component: props', () => {
// #5016
test('handling attr with undefined value', () => {
const { render, host } = define({
inheritAttrs: false,
render() {
const instance = getCurrentInstance()!
const t0 = template('<div></div>')
Expand Down
18 changes: 13 additions & 5 deletions packages/runtime-vapor/src/apiCreateComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import {
currentInstance,
} from './component'
import { setupComponent } from './apiRender'
import type { RawProps } from './componentProps'
import type { NormalizedRawProps } from './componentProps'
import { withAttrs } from './componentAttrs'

export function createComponent(comp: Component, rawProps: RawProps = null) {
export function createComponent(
comp: Component,
rawProps: NormalizedRawProps | null = null,
singleRoot: boolean = false,
) {
const current = currentInstance!
const instance = createComponentInstance(comp, rawProps)
setupComponent(instance)
const instance = createComponentInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
)
setupComponent(instance, singleRoot)

// register sub-component with current component for lifecycle management
current.comps.add(instance)

return instance.block
return instance
}
Loading
Loading