refactor(compiler): split slot / slot outlet / slot scope handling into separate transforms

This commit is contained in:
Evan You
2019-09-28 00:19:24 -04:00
parent 6377af483b
commit 6461b3853e
13 changed files with 532 additions and 433 deletions

View File

@@ -8,7 +8,9 @@ return function render() {
id: \\"foo\\",
[prop]: bar,
[foo + bar]: bar
}, [createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })], [
}, [
createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })
], [
foo,
createVNode(\\"p\\")
])

View File

@@ -14,7 +14,9 @@ return function render() {
? _createVNode(\\"div\\", 0, \\"yes\\")
: \\"no\\",
_renderList(list, (value, index) => {
return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(value + index))])
return _createVNode(\\"div\\", 0, [
_createVNode(\\"span\\", 0, _toString(value + index))
])
})
])
}
@@ -35,7 +37,9 @@ return function render() {
? createVNode(\\"div\\", 0, \\"yes\\")
: \\"no\\",
renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, toString(value + index))])
return createVNode(\\"div\\", 0, [
createVNode(\\"span\\", 0, toString(value + index))
])
})
])
}"
@@ -55,7 +59,9 @@ export default function render() {
? createVNode(\\"div\\", 0, \\"yes\\")
: \\"no\\",
_renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, _toString(value + index))])
return createVNode(\\"div\\", 0, [
createVNode(\\"span\\", 0, _toString(value + index))
])
})
])
}"

View File

@@ -553,7 +553,9 @@ describe('compiler: codegen', () => {
id: "foo",
[prop]: bar,
[foo + bar]: bar
}, [${CREATE_VNODE}("p", { "some-key": "foo" })], [
}, [
${CREATE_VNODE}("p", { "some-key": "foo" })
], [
foo,
${CREATE_VNODE}("p")
])`)

View File

@@ -0,0 +1,324 @@
import {
CompilerOptions,
parse,
transform,
ElementNode,
NodeTypes
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
import { transformBind } from '../../src/transforms/vBind'
import { transformExpression } from '../../src/transforms/transformExpression'
import { RENDER_SLOT } from '../../src/runtimeConstants'
import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet,
transformElement
],
directiveTransforms: {
on: transformOn,
bind: transformBind
},
...options
})
return ast
}
describe('compiler: transform <slot> outlets', () => {
test('default slot outlet', () => {
const ast = parseWithSlots(`<slot/>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [`$slots.default`]
})
})
test('statically named slot outlet', () => {
const ast = parseWithSlots(`<slot name="foo" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [`$slots.foo`]
})
})
test('statically named slot outlet w/ name that needs quotes', () => {
const ast = parseWithSlots(`<slot name="foo-bar" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [`$slots["foo-bar"]`]
})
})
test('dynamically named slot outlet', () => {
const ast = parseWithSlots(`<slot :name="foo" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$slots[`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
isStatic: false
},
`]`
]
}
]
})
})
test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
prefixIdentifiers: true
})
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
arguments: [
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`_ctx.$slots[`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`,
isStatic: false
},
` + `,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.bar`,
isStatic: false
},
`]`
]
}
]
})
})
test('default slot outlet with props', () => {
const ast = parseWithSlots(`<slot foo="bar" :baz="qux" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.default`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: true
}
},
{
key: {
content: `baz`,
isStatic: true
},
value: {
content: `qux`,
isStatic: false
}
}
]
}
]
})
})
test('statically named slot outlet with props', () => {
const ast = parseWithSlots(`<slot name="foo" foo="bar" :baz="qux" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.foo`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
// props should not include name
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: true
}
},
{
key: {
content: `baz`,
isStatic: true
},
value: {
content: `qux`,
isStatic: false
}
}
]
}
]
})
})
test('dynamically named slot outlet with props', () => {
const ast = parseWithSlots(`<slot :name="foo" foo="bar" :baz="qux" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$slots[`, { content: `foo` }, `]`]
},
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
// props should not include name
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: true
}
},
{
key: {
content: `baz`,
isStatic: true
},
value: {
content: `qux`,
isStatic: false
}
}
]
}
]
})
})
test('default slot outlet with fallback', () => {
const ast = parseWithSlots(`<slot><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.default`,
`{}`,
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
test('named slot outlet with fallback', () => {
const ast = parseWithSlots(`<slot name="foo"><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.foo`,
`{}`,
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
test('default slot outlet with props & fallback', () => {
const ast = parseWithSlots(`<slot :foo="bar"><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.default`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: false
}
}
]
},
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
test('named slot outlet with props & fallback', () => {
const ast = parseWithSlots(`<slot name="foo" :foo="bar"><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.foo`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: false
}
}
]
},
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
})

View File

@@ -1,23 +1,17 @@
import {
CompilerOptions,
parse,
transform,
ElementNode,
NodeTypes,
generate
} from '../../src'
import { CompilerOptions, parse, transform, generate } from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
import { transformBind } from '../../src/transforms/vBind'
import { transformExpression } from '../../src/transforms/transformExpression'
import { RENDER_SLOT } from '../../src/runtimeConstants'
import { trackSlotScopes } from '../../src/transforms/vSlot'
function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []),
// slot transform is part of transformElement
...(options.prefixIdentifiers
? [transformExpression, trackSlotScopes]
: []),
transformElement
],
directiveTransforms: {
@@ -29,302 +23,19 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
return ast
}
describe('compiler: transform slots', () => {
test('default slot outlet', () => {
const ast = parseWithSlots(`<slot/>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [`$slots.default`]
})
})
test('statically named slot outlet', () => {
const ast = parseWithSlots(`<slot name="foo" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [`$slots.foo`]
})
})
test('statically named slot outlet w/ name that needs quotes', () => {
const ast = parseWithSlots(`<slot name="foo-bar" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [`$slots["foo-bar"]`]
})
})
test('dynamically named slot outlet', () => {
const ast = parseWithSlots(`<slot :name="foo" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$slots[`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`,
isStatic: false
},
`]`
]
}
]
})
})
test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
prefixIdentifiers: true
})
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT,
arguments: [
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`_ctx.$slots[`,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`,
isStatic: false
},
` + `,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.bar`,
isStatic: false
},
`]`
]
}
]
})
})
test('default slot outlet with props', () => {
const ast = parseWithSlots(`<slot foo="bar" :baz="qux" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.default`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: true
}
},
{
key: {
content: `baz`,
isStatic: true
},
value: {
content: `qux`,
isStatic: false
}
}
]
}
]
})
})
test('statically named slot outlet with props', () => {
const ast = parseWithSlots(`<slot name="foo" foo="bar" :baz="qux" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.foo`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
// props should not include name
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: true
}
},
{
key: {
content: `baz`,
isStatic: true
},
value: {
content: `qux`,
isStatic: false
}
}
]
}
]
})
})
test('dynamically named slot outlet with props', () => {
const ast = parseWithSlots(`<slot :name="foo" foo="bar" :baz="qux" />`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
{
type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$slots[`, { content: `foo` }, `]`]
},
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
// props should not include name
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: true
}
},
{
key: {
content: `baz`,
isStatic: true
},
value: {
content: `qux`,
isStatic: false
}
}
]
}
]
})
})
test('default slot outlet with fallback', () => {
const ast = parseWithSlots(`<slot><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.default`,
`{}`,
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
test('named slot outlet with fallback', () => {
const ast = parseWithSlots(`<slot name="foo"><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.foo`,
`{}`,
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
test('default slot outlet with props & fallback', () => {
const ast = parseWithSlots(`<slot :foo="bar"><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.default`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: false
}
}
]
},
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
test('named slot outlet with props & fallback', () => {
const ast = parseWithSlots(`<slot name="foo" :foo="bar"><div/></slot>`)
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${RENDER_SLOT}`,
arguments: [
`$slots.foo`,
{
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties: [
{
key: {
content: `foo`,
isStatic: true
},
value: {
content: `bar`,
isStatic: false
}
}
]
},
[
{
type: NodeTypes.ELEMENT,
tag: `div`
}
]
]
})
})
describe('compiler: transform component slots', () => {
test('generate slot', () => {
const ast = parseWithSlots(`<Comp><div/></Comp>`)
const { code } = generate(ast)
const ast = parseWithSlots(
`
<Comp>
<Comp v-slot="{ dur }">
hello {{ dur }}
</Comp>
</Comp>
`,
{ prefixIdentifiers: true }
)
const { code } = generate(ast, { prefixIdentifiers: true })
console.log(code)
})
})