refactor(compiler): split slot / slot outlet / slot scope handling into separate transforms
This commit is contained in:
parent
6377af483b
commit
6461b3853e
@ -8,7 +8,9 @@ return function render() {
|
|||||||
id: \\"foo\\",
|
id: \\"foo\\",
|
||||||
[prop]: bar,
|
[prop]: bar,
|
||||||
[foo + bar]: bar
|
[foo + bar]: bar
|
||||||
}, [createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })], [
|
}, [
|
||||||
|
createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })
|
||||||
|
], [
|
||||||
foo,
|
foo,
|
||||||
createVNode(\\"p\\")
|
createVNode(\\"p\\")
|
||||||
])
|
])
|
||||||
|
@ -14,7 +14,9 @@ return function render() {
|
|||||||
? _createVNode(\\"div\\", 0, \\"yes\\")
|
? _createVNode(\\"div\\", 0, \\"yes\\")
|
||||||
: \\"no\\",
|
: \\"no\\",
|
||||||
_renderList(list, (value, index) => {
|
_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\\")
|
? createVNode(\\"div\\", 0, \\"yes\\")
|
||||||
: \\"no\\",
|
: \\"no\\",
|
||||||
renderList(_ctx.list, (value, index) => {
|
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\\")
|
? createVNode(\\"div\\", 0, \\"yes\\")
|
||||||
: \\"no\\",
|
: \\"no\\",
|
||||||
_renderList(_ctx.list, (value, index) => {
|
_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))
|
||||||
|
])
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
}"
|
}"
|
||||||
|
@ -553,7 +553,9 @@ describe('compiler: codegen', () => {
|
|||||||
id: "foo",
|
id: "foo",
|
||||||
[prop]: bar,
|
[prop]: bar,
|
||||||
[foo + bar]: bar
|
[foo + bar]: bar
|
||||||
}, [${CREATE_VNODE}("p", { "some-key": "foo" })], [
|
}, [
|
||||||
|
${CREATE_VNODE}("p", { "some-key": "foo" })
|
||||||
|
], [
|
||||||
foo,
|
foo,
|
||||||
${CREATE_VNODE}("p")
|
${CREATE_VNODE}("p")
|
||||||
])`)
|
])`)
|
||||||
|
@ -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`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,23 +1,17 @@
|
|||||||
import {
|
import { CompilerOptions, parse, transform, generate } from '../../src'
|
||||||
CompilerOptions,
|
|
||||||
parse,
|
|
||||||
transform,
|
|
||||||
ElementNode,
|
|
||||||
NodeTypes,
|
|
||||||
generate
|
|
||||||
} from '../../src'
|
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
import { RENDER_SLOT } from '../../src/runtimeConstants'
|
import { trackSlotScopes } from '../../src/transforms/vSlot'
|
||||||
|
|
||||||
function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
||||||
const ast = parse(template)
|
const ast = parse(template)
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
...(options.prefixIdentifiers ? [transformExpression] : []),
|
...(options.prefixIdentifiers
|
||||||
// slot transform is part of transformElement
|
? [transformExpression, trackSlotScopes]
|
||||||
|
: []),
|
||||||
transformElement
|
transformElement
|
||||||
],
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
@ -29,302 +23,19 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
|
|||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('compiler: transform slots', () => {
|
describe('compiler: transform component 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`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('generate slot', () => {
|
test('generate slot', () => {
|
||||||
const ast = parseWithSlots(`<Comp><div/></Comp>`)
|
const ast = parseWithSlots(
|
||||||
const { code } = generate(ast)
|
`
|
||||||
|
<Comp>
|
||||||
|
<Comp v-slot="{ dur }">
|
||||||
|
hello {{ dur }}
|
||||||
|
</Comp>
|
||||||
|
</Comp>
|
||||||
|
`,
|
||||||
|
{ prefixIdentifiers: true }
|
||||||
|
)
|
||||||
|
const { code } = generate(ast, { prefixIdentifiers: true })
|
||||||
console.log(code)
|
console.log(code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -117,6 +117,9 @@ export interface SimpleExpressionNode extends Node {
|
|||||||
type: NodeTypes.SIMPLE_EXPRESSION
|
type: NodeTypes.SIMPLE_EXPRESSION
|
||||||
content: string
|
content: string
|
||||||
isStatic: boolean
|
isStatic: boolean
|
||||||
|
// an expression parsed as the params of a function will track
|
||||||
|
// the identifiers declared inside the function body.
|
||||||
|
identifiers?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InterpolationNode extends Node {
|
export interface InterpolationNode extends Node {
|
||||||
@ -128,6 +131,9 @@ export interface InterpolationNode extends Node {
|
|||||||
export interface CompoundExpressionNode extends Node {
|
export interface CompoundExpressionNode extends Node {
|
||||||
type: NodeTypes.COMPOUND_EXPRESSION
|
type: NodeTypes.COMPOUND_EXPRESSION
|
||||||
children: (SimpleExpressionNode | string)[]
|
children: (SimpleExpressionNode | string)[]
|
||||||
|
// an expression parsed as the params of a function will track
|
||||||
|
// the identifiers declared inside the function body.
|
||||||
|
identifiers?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IfNode extends Node {
|
export interface IfNode extends Node {
|
||||||
|
@ -5,12 +5,14 @@ import { RootNode } from './ast'
|
|||||||
import { isString } from '@vue/shared'
|
import { isString } from '@vue/shared'
|
||||||
import { transformIf } from './transforms/vIf'
|
import { transformIf } from './transforms/vIf'
|
||||||
import { transformFor } from './transforms/vFor'
|
import { transformFor } from './transforms/vFor'
|
||||||
|
import { transformExpression } from './transforms/transformExpression'
|
||||||
|
import { transformStyle } from './transforms/transformStyle'
|
||||||
|
import { transformSlotOutlet } from './transforms/transfromSlotOutlet'
|
||||||
import { transformElement } from './transforms/transformElement'
|
import { transformElement } from './transforms/transformElement'
|
||||||
import { transformOn } from './transforms/vOn'
|
import { transformOn } from './transforms/vOn'
|
||||||
import { transformBind } from './transforms/vBind'
|
import { transformBind } from './transforms/vBind'
|
||||||
import { transformExpression } from './transforms/transformExpression'
|
|
||||||
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
|
||||||
import { transformStyle } from './transforms/transformStyle'
|
import { trackSlotScopes } from './transforms/vSlot'
|
||||||
|
|
||||||
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
|
||||||
|
|
||||||
@ -41,8 +43,9 @@ export function baseCompile(
|
|||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
transformIf,
|
transformIf,
|
||||||
transformFor,
|
transformFor,
|
||||||
...(prefixIdentifiers ? [transformExpression] : []),
|
...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []),
|
||||||
transformStyle,
|
transformStyle,
|
||||||
|
transformSlotOutlet,
|
||||||
transformElement,
|
transformElement,
|
||||||
...(options.nodeTransforms || []) // user transforms
|
...(options.nodeTransforms || []) // user transforms
|
||||||
],
|
],
|
||||||
|
@ -8,8 +8,7 @@ import {
|
|||||||
Property,
|
Property,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
JSChildNode,
|
JSChildNode
|
||||||
SimpleExpressionNode
|
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { isString, isArray } from '@vue/shared'
|
import { isString, isArray } from '@vue/shared'
|
||||||
import { CompilerError, defaultOnError } from './errors'
|
import { CompilerError, defaultOnError } from './errors'
|
||||||
@ -64,8 +63,8 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
replaceNode(node: ChildNode): void
|
replaceNode(node: ChildNode): void
|
||||||
removeNode(node?: ChildNode): void
|
removeNode(node?: ChildNode): void
|
||||||
onNodeRemoved: () => void
|
onNodeRemoved: () => void
|
||||||
addIdentifier(exp: SimpleExpressionNode): void
|
addIdentifier(id: string): void
|
||||||
removeIdentifier(exp: SimpleExpressionNode): void
|
removeIdentifier(id: string): void
|
||||||
hoist(exp: JSChildNode): ExpressionNode
|
hoist(exp: JSChildNode): ExpressionNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,15 +126,15 @@ function createTransformContext(
|
|||||||
context.parent.children.splice(removalIndex, 1)
|
context.parent.children.splice(removalIndex, 1)
|
||||||
},
|
},
|
||||||
onNodeRemoved: () => {},
|
onNodeRemoved: () => {},
|
||||||
addIdentifier({ content }) {
|
addIdentifier(id) {
|
||||||
const { identifiers } = context
|
const { identifiers } = context
|
||||||
if (identifiers[content] === undefined) {
|
if (identifiers[id] === undefined) {
|
||||||
identifiers[content] = 0
|
identifiers[id] = 0
|
||||||
}
|
}
|
||||||
;(identifiers[content] as number)++
|
;(identifiers[id] as number)++
|
||||||
},
|
},
|
||||||
removeIdentifier({ content }) {
|
removeIdentifier(id) {
|
||||||
;(context.identifiers[content] as number)--
|
;(context.identifiers[id] as number)--
|
||||||
},
|
},
|
||||||
hoist(exp) {
|
hoist(exp) {
|
||||||
context.hoists.push(exp)
|
context.hoists.push(exp)
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
TO_HANDLERS
|
TO_HANDLERS
|
||||||
} from '../runtimeConstants'
|
} from '../runtimeConstants'
|
||||||
import { getInnerRange } from '../utils'
|
import { getInnerRange } from '../utils'
|
||||||
import { buildSlotOutlet, buildSlots } from './vSlot'
|
import { buildSlots } from './vSlot'
|
||||||
|
|
||||||
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
|
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
|
||||||
|
|
||||||
@ -95,10 +95,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
} else {
|
} else {
|
||||||
node.codegenNode = vnode
|
node.codegenNode = vnode
|
||||||
}
|
}
|
||||||
} else if (node.tagType === ElementTypes.SLOT) {
|
|
||||||
buildSlotOutlet(node, context)
|
|
||||||
}
|
}
|
||||||
// node.tagType can also be TEMPLATE, in which case nothing needs to be done
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,12 +87,12 @@ export function processExpression(
|
|||||||
_parseScript || (_parseScript = require('meriyah').parseScript)
|
_parseScript || (_parseScript = require('meriyah').parseScript)
|
||||||
const walk = _walk || (_walk = require('estree-walker').walk)
|
const walk = _walk || (_walk = require('estree-walker').walk)
|
||||||
|
|
||||||
let ast
|
let ast: any
|
||||||
// if the expression is supposed to be used in a function params position
|
// if the expression is supposed to be used in a function params position
|
||||||
// we need to parse it differently.
|
// we need to parse it differently.
|
||||||
const source = `(${node.content})${asParams ? `=>{}` : ``}`
|
const source = `(${node.content})${asParams ? `=>{}` : ``}`
|
||||||
try {
|
try {
|
||||||
ast = parseScript(source, { ranges: true }) as any
|
ast = parseScript(source, { ranges: true })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.onError(e)
|
context.onError(e)
|
||||||
return node
|
return node
|
||||||
@ -139,11 +139,22 @@ export function processExpression(
|
|||||||
parent.right === child
|
parent.right === child
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
knownIds[child.name] = true
|
const { name } = child
|
||||||
|
if (
|
||||||
|
(node as any)._scopeIds &&
|
||||||
|
(node as any)._scopeIds.has(name)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (name in knownIds) {
|
||||||
|
knownIds[name]++
|
||||||
|
} else {
|
||||||
|
knownIds[name] = 1
|
||||||
|
}
|
||||||
;(
|
;(
|
||||||
(node as any)._scopeIds ||
|
(node as any)._scopeIds ||
|
||||||
((node as any)._scopeIds = new Set())
|
((node as any)._scopeIds = new Set())
|
||||||
).add(child.name)
|
).add(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -151,9 +162,12 @@ export function processExpression(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
leave(node: any) {
|
leave(node: any) {
|
||||||
if (node._scopeIds) {
|
if (node !== ast.body[0].expression && node._scopeIds) {
|
||||||
node._scopeIds.forEach((id: string) => {
|
node._scopeIds.forEach((id: string) => {
|
||||||
delete knownIds[id]
|
knownIds[id]--
|
||||||
|
if (knownIds[id] === 0) {
|
||||||
|
delete knownIds[id]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,11 +199,14 @@ export function processExpression(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let ret
|
||||||
if (children.length) {
|
if (children.length) {
|
||||||
return createCompoundExpression(children, node.loc)
|
ret = createCompoundExpression(children, node.loc)
|
||||||
} else {
|
} else {
|
||||||
return node
|
ret = node
|
||||||
}
|
}
|
||||||
|
ret.identifiers = Object.keys(knownIds)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFunction = (node: Node): node is Function =>
|
const isFunction = (node: Node): node is Function =>
|
||||||
|
98
packages/compiler-core/src/transforms/transfromSlotOutlet.ts
Normal file
98
packages/compiler-core/src/transforms/transfromSlotOutlet.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { NodeTransform } from '../transform'
|
||||||
|
import {
|
||||||
|
NodeTypes,
|
||||||
|
ElementTypes,
|
||||||
|
CompoundExpressionNode,
|
||||||
|
createCompoundExpression,
|
||||||
|
CallExpression,
|
||||||
|
createCallExpression
|
||||||
|
} from '../ast'
|
||||||
|
import { isSimpleIdentifier } from '../utils'
|
||||||
|
import { buildProps } from './transformElement'
|
||||||
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
|
import { RENDER_SLOT } from '../runtimeConstants'
|
||||||
|
|
||||||
|
export const transformSlotOutlet: NodeTransform = (node, context) => {
|
||||||
|
if (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT) {
|
||||||
|
const { props, children, loc } = node
|
||||||
|
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
|
||||||
|
let slot: string | CompoundExpressionNode = $slots + `.default`
|
||||||
|
|
||||||
|
// check for <slot name="xxx" OR :name="xxx" />
|
||||||
|
let nameIndex: number = -1
|
||||||
|
for (let i = 0; i < props.length; i++) {
|
||||||
|
const prop = props[i]
|
||||||
|
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
if (prop.name === `name` && prop.value) {
|
||||||
|
// static name="xxx"
|
||||||
|
const name = prop.value.content
|
||||||
|
const accessor = isSimpleIdentifier(name)
|
||||||
|
? `.${name}`
|
||||||
|
: `[${JSON.stringify(name)}]`
|
||||||
|
slot = `${$slots}${accessor}`
|
||||||
|
nameIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if (prop.name === `bind`) {
|
||||||
|
const { arg, exp } = prop
|
||||||
|
if (
|
||||||
|
arg &&
|
||||||
|
exp &&
|
||||||
|
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
arg.isStatic &&
|
||||||
|
arg.content === `name`
|
||||||
|
) {
|
||||||
|
// dynamic :name="xxx"
|
||||||
|
slot = createCompoundExpression(
|
||||||
|
[
|
||||||
|
$slots + `[`,
|
||||||
|
...(exp.type === NodeTypes.SIMPLE_EXPRESSION
|
||||||
|
? [exp]
|
||||||
|
: exp.children),
|
||||||
|
`]`
|
||||||
|
],
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
nameIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const slotArgs: CallExpression['arguments'] = [slot]
|
||||||
|
const propsWithoutName =
|
||||||
|
nameIndex > -1
|
||||||
|
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
|
||||||
|
: props
|
||||||
|
const hasProps = propsWithoutName.length
|
||||||
|
if (hasProps) {
|
||||||
|
const { props: propsExpression, directives } = buildProps(
|
||||||
|
propsWithoutName,
|
||||||
|
loc,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
if (directives.length) {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(
|
||||||
|
ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
|
directives[0].loc
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
slotArgs.push(propsExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length) {
|
||||||
|
if (!hasProps) {
|
||||||
|
slotArgs.push(`{}`)
|
||||||
|
}
|
||||||
|
slotArgs.push(children)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.codegenNode = createCallExpression(
|
||||||
|
context.helper(RENDER_SLOT),
|
||||||
|
slotArgs,
|
||||||
|
loc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -43,15 +43,15 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||||||
const { addIdentifier, removeIdentifier } = context
|
const { addIdentifier, removeIdentifier } = context
|
||||||
|
|
||||||
// inject identifiers to context
|
// inject identifiers to context
|
||||||
value && addIdentifier(value)
|
value && addIdentifier(value.content)
|
||||||
key && addIdentifier(key)
|
key && addIdentifier(key.content)
|
||||||
index && addIdentifier(index)
|
index && addIdentifier(index.content)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// remove injected identifiers on exit
|
// remove injected identifiers on exit
|
||||||
value && removeIdentifier(value)
|
value && removeIdentifier(value.content)
|
||||||
key && removeIdentifier(key)
|
key && removeIdentifier(key.content)
|
||||||
index && removeIdentifier(index)
|
index && removeIdentifier(index.content)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
|
@ -3,10 +3,6 @@ import {
|
|||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
createCompoundExpression,
|
|
||||||
createCallExpression,
|
|
||||||
CompoundExpressionNode,
|
|
||||||
CallExpression,
|
|
||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createFunctionExpression,
|
createFunctionExpression,
|
||||||
@ -17,16 +13,37 @@ import {
|
|||||||
ChildNode,
|
ChildNode,
|
||||||
SourceLocation
|
SourceLocation
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { TransformContext } from '../transform'
|
import { TransformContext, NodeTransform } from '../transform'
|
||||||
import { buildProps } from './transformElement'
|
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { isSimpleIdentifier } from '../utils'
|
|
||||||
import { RENDER_SLOT } from '../runtimeConstants'
|
|
||||||
import { isString } from '@vue/shared'
|
import { isString } from '@vue/shared'
|
||||||
|
|
||||||
const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
|
const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
|
||||||
p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||||
|
|
||||||
|
// A NodeTransform that tracks scope identifiers for scoped slots so that they
|
||||||
|
// don't get prefixed by transformExpression. This transform is only applied
|
||||||
|
// in non-browser builds with { prefixIdentifiers: true }
|
||||||
|
export const trackSlotScopes: NodeTransform = (node, context) => {
|
||||||
|
if (
|
||||||
|
node.type === NodeTypes.ELEMENT &&
|
||||||
|
(node.tagType === ElementTypes.COMPONENT ||
|
||||||
|
node.tagType === ElementTypes.TEMPLATE)
|
||||||
|
) {
|
||||||
|
const vSlot = node.props.find(isVSlot)
|
||||||
|
if (vSlot && vSlot.exp) {
|
||||||
|
const { identifiers } = vSlot.exp
|
||||||
|
if (identifiers) {
|
||||||
|
identifiers.forEach(context.addIdentifier)
|
||||||
|
return () => {
|
||||||
|
identifiers.forEach(context.removeIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instead of being a DirectiveTransform, v-slot processing is called during
|
||||||
|
// transformElement to build the slots object for a component.
|
||||||
export function buildSlots(
|
export function buildSlots(
|
||||||
{ props, children, loc }: ElementNode,
|
{ props, children, loc }: ElementNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
@ -128,86 +145,3 @@ function buildSlot(
|
|||||||
loc
|
loc
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
|
|
||||||
const { props, children, loc } = node
|
|
||||||
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
|
|
||||||
let slot: string | CompoundExpressionNode = $slots + `.default`
|
|
||||||
|
|
||||||
// check for <slot name="xxx" OR :name="xxx" />
|
|
||||||
let nameIndex: number = -1
|
|
||||||
for (let i = 0; i < props.length; i++) {
|
|
||||||
const prop = props[i]
|
|
||||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
|
||||||
if (prop.name === `name` && prop.value) {
|
|
||||||
// static name="xxx"
|
|
||||||
const name = prop.value.content
|
|
||||||
const accessor = isSimpleIdentifier(name)
|
|
||||||
? `.${name}`
|
|
||||||
: `[${JSON.stringify(name)}]`
|
|
||||||
slot = `${$slots}${accessor}`
|
|
||||||
nameIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (prop.name === `bind`) {
|
|
||||||
const { arg, exp } = prop
|
|
||||||
if (
|
|
||||||
arg &&
|
|
||||||
exp &&
|
|
||||||
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
|
||||||
arg.isStatic &&
|
|
||||||
arg.content === `name`
|
|
||||||
) {
|
|
||||||
// dynamic :name="xxx"
|
|
||||||
slot = createCompoundExpression(
|
|
||||||
[
|
|
||||||
$slots + `[`,
|
|
||||||
...(exp.type === NodeTypes.SIMPLE_EXPRESSION
|
|
||||||
? [exp]
|
|
||||||
: exp.children),
|
|
||||||
`]`
|
|
||||||
],
|
|
||||||
loc
|
|
||||||
)
|
|
||||||
nameIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const slotArgs: CallExpression['arguments'] = [slot]
|
|
||||||
const propsWithoutName =
|
|
||||||
nameIndex > -1
|
|
||||||
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
|
|
||||||
: props
|
|
||||||
const hasProps = propsWithoutName.length
|
|
||||||
if (hasProps) {
|
|
||||||
const { props: propsExpression, directives } = buildProps(
|
|
||||||
propsWithoutName,
|
|
||||||
loc,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
if (directives.length) {
|
|
||||||
context.onError(
|
|
||||||
createCompilerError(
|
|
||||||
ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
|
||||||
directives[0].loc
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
slotArgs.push(propsExpression)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (children.length) {
|
|
||||||
if (!hasProps) {
|
|
||||||
slotArgs.push(`{}`)
|
|
||||||
}
|
|
||||||
slotArgs.push(children)
|
|
||||||
}
|
|
||||||
|
|
||||||
node.codegenNode = createCallExpression(
|
|
||||||
context.helper(RENDER_SLOT),
|
|
||||||
slotArgs,
|
|
||||||
loc
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user