id: \\"foo\\",
[prop]: bar,
[foo + bar]: bar
- }, [createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })], [
+ }, [
+ createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })
+ ], [
foo,
createVNode(\\"p\\")
])
? _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))
+ ])
})
])
}
? 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))
+ ])
})
])
}"
? 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))
+ ])
})
])
}"
id: "foo",
[prop]: bar,
[foo + bar]: bar
- }, [${CREATE_VNODE}("p", { "some-key": "foo" })], [
+ }, [
+ ${CREATE_VNODE}("p", { "some-key": "foo" })
+ ], [
foo,
${CREATE_VNODE}("p")
])`)
--- /dev/null
+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`
+ }
+ ]
+ ]
+ })
+ })
+})
-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: {
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)
})
})
type: NodeTypes.SIMPLE_EXPRESSION
content: string
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 CompoundExpressionNode extends Node {
type: NodeTypes.COMPOUND_EXPRESSION
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 {
import { isString } from '@vue/shared'
import { transformIf } from './transforms/vIf'
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 { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
-import { transformExpression } from './transforms/transformExpression'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
-import { transformStyle } from './transforms/transformStyle'
+import { trackSlotScopes } from './transforms/vSlot'
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
nodeTransforms: [
transformIf,
transformFor,
- ...(prefixIdentifiers ? [transformExpression] : []),
+ ...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []),
transformStyle,
+ transformSlotOutlet,
transformElement,
...(options.nodeTransforms || []) // user transforms
],
Property,
ExpressionNode,
createSimpleExpression,
- JSChildNode,
- SimpleExpressionNode
+ JSChildNode
} from './ast'
import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
replaceNode(node: ChildNode): void
removeNode(node?: ChildNode): void
onNodeRemoved: () => void
- addIdentifier(exp: SimpleExpressionNode): void
- removeIdentifier(exp: SimpleExpressionNode): void
+ addIdentifier(id: string): void
+ removeIdentifier(id: string): void
hoist(exp: JSChildNode): ExpressionNode
}
context.parent.children.splice(removalIndex, 1)
},
onNodeRemoved: () => {},
- addIdentifier({ content }) {
+ addIdentifier(id) {
const { identifiers } = context
- if (identifiers[content] === undefined) {
- identifiers[content] = 0
+ if (identifiers[id] === undefined) {
+ identifiers[id] = 0
}
- ;(identifiers[content] as number)++
+ ;(identifiers[id] as number)++
},
- removeIdentifier({ content }) {
- ;(context.identifiers[content] as number)--
+ removeIdentifier(id) {
+ ;(context.identifiers[id] as number)--
},
hoist(exp) {
context.hoists.push(exp)
TO_HANDLERS
} from '../runtimeConstants'
import { getInnerRange } from '../utils'
-import { buildSlotOutlet, buildSlots } from './vSlot'
+import { buildSlots } from './vSlot'
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
} else {
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
}
}
_parseScript || (_parseScript = require('meriyah').parseScript)
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
// we need to parse it differently.
const source = `(${node.content})${asParams ? `=>{}` : ``}`
try {
- ast = parseScript(source, { ranges: true }) as any
+ ast = parseScript(source, { ranges: true })
} catch (e) {
context.onError(e)
return node
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 = new Set())
- ).add(child.name)
+ ).add(name)
}
}
})
}
},
leave(node: any) {
- if (node._scopeIds) {
+ if (node !== ast.body[0].expression && node._scopeIds) {
node._scopeIds.forEach((id: string) => {
- delete knownIds[id]
+ knownIds[id]--
+ if (knownIds[id] === 0) {
+ delete knownIds[id]
+ }
})
}
}
}
})
+ let ret
if (children.length) {
- return createCompoundExpression(children, node.loc)
+ ret = createCompoundExpression(children, node.loc)
} else {
- return node
+ ret = node
}
+ ret.identifiers = Object.keys(knownIds)
+ return ret
}
const isFunction = (node: Node): node is Function =>
--- /dev/null
+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
+ )
+ }
+}
const { addIdentifier, removeIdentifier } = context
// inject identifiers to context
- value && addIdentifier(value)
- key && addIdentifier(key)
- index && addIdentifier(index)
+ value && addIdentifier(value.content)
+ key && addIdentifier(key.content)
+ index && addIdentifier(index.content)
return () => {
// remove injected identifiers on exit
- value && removeIdentifier(value)
- key && removeIdentifier(key)
- index && removeIdentifier(index)
+ value && removeIdentifier(value.content)
+ key && removeIdentifier(key.content)
+ index && removeIdentifier(index.content)
}
} else {
context.onError(
ObjectExpression,
createObjectExpression,
NodeTypes,
- createCompoundExpression,
- createCallExpression,
- CompoundExpressionNode,
- CallExpression,
createObjectProperty,
createSimpleExpression,
createFunctionExpression,
ChildNode,
SourceLocation
} from '../ast'
-import { TransformContext } from '../transform'
-import { buildProps } from './transformElement'
+import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
-import { isSimpleIdentifier } from '../utils'
-import { RENDER_SLOT } from '../runtimeConstants'
import { isString } from '@vue/shared'
const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
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(
{ props, children, loc }: ElementNode,
context: TransformContext
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
- )
-}