_toString(world.burn()),
(_openBlock(), ok
? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
- : _createBlock(_Fragment, { key: 1 }, [
- \\"no\\"
- ])),
+ : _createBlock(_Fragment, { key: 1 }, [\\"no\\"])),
(_openBlock(), _createBlock(_Fragment, null, _renderList(list, (value, index) => {
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
- : createBlock(Fragment, { key: 1 }, [
- \\"no\\"
- ])),
+ : createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index), 1 /* TEXT */)
_toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok)
? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
- : createBlock(Fragment, { key: 1 }, [
- \\"no\\"
- ])),
+ : createBlock(Fragment, { key: 1 }, [\\"no\\"])),
(openBlock(), createBlock(Fragment, null, renderList(_ctx.list, (value, index) => {
return (openBlock(), createBlock(\\"div\\", null, [
createVNode(\\"span\\", null, _toString(value + index), 1 /* TEXT */)
ElementTypes
} from '../src'
import { CREATE_VNODE } from '../src/runtimeConstants'
+import { isString } from '@vue/shared'
const leadingBracketRE = /^\[/
const bracketsRE = /^\[|\]$/g
content: key.replace(bracketsRE, ''),
isStatic: !leadingBracketRE.test(key)
},
- value: {
- type: NodeTypes.SIMPLE_EXPRESSION,
- content: obj[key].replace(bracketsRE, ''),
- isStatic: !leadingBracketRE.test(obj[key])
- }
+ value: isString(obj[key])
+ ? {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: obj[key].replace(bracketsRE, ''),
+ isStatic: !leadingBracketRE.test(obj[key])
+ }
+ : obj[key]
}))
}
}
? _createBlock(\\"div\\", { key: 0 })
: orNot
? _createBlock(\\"p\\", { key: 1 })
- : _createBlock(_Fragment, { key: 2 }, [
- \\"fine\\"
- ]))
+ : _createBlock(_Fragment, { key: 2 }, [\\"fine\\"]))
}
}"
`;
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
- [_ctx.one]: ({ foo }) => [
- toString(foo),
- toString(_ctx.bar)
- ],
- [_ctx.two]: ({ bar }) => [
- toString(_ctx.foo),
- toString(bar)
- ]
+ [_ctx.one]: ({ foo }) => [toString(foo), toString(_ctx.bar)],
+ [_ctx.two]: ({ bar }) => [toString(_ctx.foo), toString(bar)]
}, 256 /* DYNAMIC_SLOTS */))
}"
`;
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
- default: ({ foo }) => [
- toString(foo),
- toString(_ctx.bar)
- ]
+ default: ({ foo }) => [toString(foo), toString(_ctx.bar)]
}))
}"
`;
}"
`;
+exports[`compiler: transform component slots named slot with v-for w/ prefixIdentifiers: true 1`] = `
+"const { toString, resolveComponent, renderList, createSlots, createVNode, openBlock, createBlock } = Vue
+
+return function render() {
+ const _ctx = this
+ const _component_Comp = resolveComponent(\\"Comp\\")
+
+ return (openBlock(), createBlock(_component_Comp, null, createSlots({}, [
+ renderList(_ctx.list, (name) => {
+ return {
+ name: name,
+ fn: () => [toString(name)]
+ }
+ })
+ ]), 256 /* DYNAMIC_SLOTS */))
+}"
+`;
+
+exports[`compiler: transform component slots named slot with v-if + prefixIdentifiers: true 1`] = `
+"const { toString, resolveComponent, createSlots, createVNode, openBlock, createBlock } = Vue
+
+return function render() {
+ const _ctx = this
+ const _component_Comp = resolveComponent(\\"Comp\\")
+
+ return (openBlock(), createBlock(_component_Comp, null, createSlots({}, [
+ (_ctx.ok)
+ ? {
+ name: \\"one\\",
+ fn: (props) => [toString(props)]
+ }
+ : undefined
+ ]), 256 /* DYNAMIC_SLOTS */))
+}"
+`;
+
+exports[`compiler: transform component slots named slot with v-if + v-else-if + v-else 1`] = `
+"const _Vue = Vue
+
+return function render() {
+ with (this) {
+ const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ const _component_Comp = _resolveComponent(\\"Comp\\")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({}, [
+ ok
+ ? {
+ name: \\"one\\",
+ fn: () => [\\"foo\\"]
+ }
+ : orNot
+ ? {
+ name: \\"two\\",
+ fn: (props) => [\\"bar\\"]
+ }
+ : {
+ name: \\"one\\",
+ fn: () => [\\"baz\\"]
+ }
+ ]), 256 /* DYNAMIC_SLOTS */))
+ }
+}"
+`;
+
+exports[`compiler: transform component slots named slot with v-if 1`] = `
+"const _Vue = Vue
+
+return function render() {
+ with (this) {
+ const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ const _component_Comp = _resolveComponent(\\"Comp\\")
+
+ return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({}, [
+ ok
+ ? {
+ name: \\"one\\",
+ fn: () => [\\"hello\\"]
+ }
+ : undefined
+ ]), 256 /* DYNAMIC_SLOTS */))
+ }
+}"
+`;
+
exports[`compiler: transform component slots named slots 1`] = `
"const { toString, resolveComponent, createVNode, openBlock, createBlock } = Vue
const _component_Comp = resolveComponent(\\"Comp\\")
return (openBlock(), createBlock(_component_Comp, null, {
- one: ({ foo }) => [
- toString(foo),
- toString(_ctx.bar)
- ],
- two: ({ bar }) => [
- toString(_ctx.foo),
- toString(bar)
- ]
+ one: ({ foo }) => [toString(foo), toString(_ctx.bar)],
+ two: ({ bar }) => [toString(_ctx.foo), toString(bar)]
}))
}"
`;
return (openBlock(), createBlock(_component_Comp, null, {
default: ({ foo }) => [
createVNode(_component_Inner, null, {
- default: ({ bar }) => [
- toString(foo),
- toString(bar),
- toString(_ctx.baz)
- ]
+ default: ({ bar }) => [toString(foo), toString(bar), toString(_ctx.baz)]
}),
toString(foo),
toString(_ctx.bar),
import { transformOn } from '../../src/transforms/vOn'
import { transformBind } from '../../src/transforms/vBind'
import { transformExpression } from '../../src/transforms/transformExpression'
-import { trackSlotScopes } from '../../src/transforms/vSlot'
+import {
+ trackSlotScopes,
+ trackVForSlotScopes
+} from '../../src/transforms/vSlot'
+import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeConstants'
+import { createObjectMatcher } from '../testUtils'
+import { PatchFlags } from '@vue/shared'
function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template)
transform(ast, {
nodeTransforms: [
...(options.prefixIdentifiers
- ? [transformExpression, trackSlotScopes]
+ ? [trackVForSlotScopes, transformExpression, trackSlotScopes]
: []),
transformElement
],
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
- test('error on extraneous children w/ named slots', () => {
- const onError = jest.fn()
- const source = `<Comp><template #default>foo</template>bar</Comp>`
- parseWithSlots(source, { onError })
- const index = source.indexOf('bar')
- expect(onError.mock.calls[0][0]).toMatchObject({
- code: ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN,
- loc: {
- source: `bar`,
- start: {
- offset: index,
- line: 1,
- column: index + 1
- },
- end: {
- offset: index + 3,
- line: 1,
- column: index + 4
+ test('named slot with v-if', () => {
+ const { root, slots } = parseWithSlots(
+ `<Comp>
+ <template #one v-if="ok">hello</template>
+ </Comp>`
+ )
+ expect(slots).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${CREATE_SLOTS}`,
+ arguments: [
+ createObjectMatcher({}),
+ {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ {
+ type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+ test: { content: `ok` },
+ consequent: createObjectMatcher({
+ name: `one`,
+ fn: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: [{ type: NodeTypes.TEXT, content: `hello` }]
+ }
+ }),
+ alternate: {
+ content: `undefined`,
+ isStatic: false
+ }
+ }
+ ]
}
- }
+ ]
})
+ expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
+ PatchFlags.DYNAMIC_SLOTS + ''
+ )
+ expect(generate(root).code).toMatchSnapshot()
})
- test('error on duplicated slot names', () => {
- const onError = jest.fn()
- const source = `<Comp><template #foo></template><template #foo></template></Comp>`
- parseWithSlots(source, { onError })
- const index = source.lastIndexOf('#foo')
- expect(onError.mock.calls[0][0]).toMatchObject({
- code: ErrorCodes.X_DUPLICATE_SLOT_NAMES,
- loc: {
- source: `#foo`,
- start: {
- offset: index,
- line: 1,
- column: index + 1
- },
- end: {
- offset: index + 4,
- line: 1,
- column: index + 5
+ test('named slot with v-if + prefixIdentifiers: true', () => {
+ const { root, slots } = parseWithSlots(
+ `<Comp>
+ <template #one="props" v-if="ok">{{ props }}</template>
+ </Comp>`,
+ { prefixIdentifiers: true }
+ )
+ expect(slots).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: CREATE_SLOTS,
+ arguments: [
+ createObjectMatcher({}),
+ {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ {
+ type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+ test: { content: `_ctx.ok` },
+ consequent: createObjectMatcher({
+ name: `one`,
+ fn: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ params: { content: `props` },
+ returns: [
+ {
+ type: NodeTypes.INTERPOLATION,
+ content: { content: `props` }
+ }
+ ]
+ }
+ }),
+ alternate: {
+ content: `undefined`,
+ isStatic: false
+ }
+ }
+ ]
}
- }
+ ]
})
+ expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
+ PatchFlags.DYNAMIC_SLOTS + ''
+ )
+ expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
- test('error on invalid mixed slot usage', () => {
- const onError = jest.fn()
- const source = `<Comp v-slot="foo"><template #foo></template></Comp>`
- parseWithSlots(source, { onError })
- const index = source.lastIndexOf('#foo')
- expect(onError.mock.calls[0][0]).toMatchObject({
- code: ErrorCodes.X_MIXED_SLOT_USAGE,
- loc: {
- source: `#foo`,
- start: {
- offset: index,
- line: 1,
- column: index + 1
- },
- end: {
- offset: index + 4,
- line: 1,
- column: index + 5
+ test('named slot with v-if + v-else-if + v-else', () => {
+ const { root, slots } = parseWithSlots(
+ `<Comp>
+ <template #one v-if="ok">foo</template>
+ <template #two="props" v-else-if="orNot">bar</template>
+ <template #one v-else>baz</template>
+ </Comp>`
+ )
+ expect(slots).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: `_${CREATE_SLOTS}`,
+ arguments: [
+ createObjectMatcher({}),
+ {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ {
+ type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+ test: { content: `ok` },
+ consequent: createObjectMatcher({
+ name: `one`,
+ fn: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ params: undefined,
+ returns: [{ type: NodeTypes.TEXT, content: `foo` }]
+ }
+ }),
+ alternate: {
+ type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
+ test: { content: `orNot` },
+ consequent: createObjectMatcher({
+ name: `two`,
+ fn: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ params: { content: `props` },
+ returns: [{ type: NodeTypes.TEXT, content: `bar` }]
+ }
+ }),
+ alternate: createObjectMatcher({
+ name: `one`,
+ fn: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ params: undefined,
+ returns: [{ type: NodeTypes.TEXT, content: `baz` }]
+ }
+ })
+ }
+ }
+ ]
}
- }
+ ]
})
+ expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
+ PatchFlags.DYNAMIC_SLOTS + ''
+ )
+ expect(generate(root).code).toMatchSnapshot()
})
- test('error on v-slot usage on plain elements', () => {
- const onError = jest.fn()
- const source = `<div v-slot/>`
- parseWithSlots(source, { onError })
- const index = source.indexOf('v-slot')
- expect(onError.mock.calls[0][0]).toMatchObject({
- code: ErrorCodes.X_MISPLACED_V_SLOT,
- loc: {
- source: `v-slot`,
- start: {
- offset: index,
- line: 1,
- column: index + 1
- },
- end: {
- offset: index + 6,
- line: 1,
- column: index + 7
+ test('named slot with v-for w/ prefixIdentifiers: true', () => {
+ const { root, slots } = parseWithSlots(
+ `<Comp>
+ <template v-for="name in list" #[name]>{{ name }}</template>
+ </Comp>`,
+ { prefixIdentifiers: true }
+ )
+ expect(slots).toMatchObject({
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: CREATE_SLOTS,
+ arguments: [
+ createObjectMatcher({}),
+ {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ {
+ type: NodeTypes.JS_CALL_EXPRESSION,
+ callee: RENDER_LIST,
+ arguments: [
+ { content: `_ctx.list` },
+ {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ params: [{ content: `name` }],
+ returns: createObjectMatcher({
+ name: `[name]`,
+ fn: {
+ type: NodeTypes.JS_FUNCTION_EXPRESSION,
+ returns: [
+ {
+ type: NodeTypes.INTERPOLATION,
+ content: { content: `name`, isStatic: false }
+ }
+ ]
+ }
+ })
+ }
+ ]
+ }
+ ]
}
- }
+ ]
})
+ expect((root as any).children[0].codegenNode.arguments[3]).toMatch(
+ PatchFlags.DYNAMIC_SLOTS + ''
+ )
+ expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
- test('error on named slot on component', () => {
- const onError = jest.fn()
- const source = `<Comp v-slot:foo>foo</Comp>`
- parseWithSlots(source, { onError })
- const index = source.indexOf('v-slot')
- expect(onError.mock.calls[0][0]).toMatchObject({
- code: ErrorCodes.X_NAMED_SLOT_ON_COMPONENT,
- loc: {
- source: `v-slot:foo`,
- start: {
- offset: index,
- line: 1,
- column: index + 1
- },
- end: {
- offset: index + 10,
- line: 1,
- column: index + 11
+ describe('errors', () => {
+ test('error on extraneous children w/ named slots', () => {
+ const onError = jest.fn()
+ const source = `<Comp><template #default>foo</template>bar</Comp>`
+ parseWithSlots(source, { onError })
+ const index = source.indexOf('bar')
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_EXTRANEOUS_NON_SLOT_CHILDREN,
+ loc: {
+ source: `bar`,
+ start: {
+ offset: index,
+ line: 1,
+ column: index + 1
+ },
+ end: {
+ offset: index + 3,
+ line: 1,
+ column: index + 4
+ }
}
- }
+ })
+ })
+
+ test('error on duplicated slot names', () => {
+ const onError = jest.fn()
+ const source = `<Comp><template #foo></template><template #foo></template></Comp>`
+ parseWithSlots(source, { onError })
+ const index = source.lastIndexOf('#foo')
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_DUPLICATE_SLOT_NAMES,
+ loc: {
+ source: `#foo`,
+ start: {
+ offset: index,
+ line: 1,
+ column: index + 1
+ },
+ end: {
+ offset: index + 4,
+ line: 1,
+ column: index + 5
+ }
+ }
+ })
+ })
+
+ test('error on invalid mixed slot usage', () => {
+ const onError = jest.fn()
+ const source = `<Comp v-slot="foo"><template #foo></template></Comp>`
+ parseWithSlots(source, { onError })
+ const index = source.lastIndexOf('#foo')
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_MIXED_SLOT_USAGE,
+ loc: {
+ source: `#foo`,
+ start: {
+ offset: index,
+ line: 1,
+ column: index + 1
+ },
+ end: {
+ offset: index + 4,
+ line: 1,
+ column: index + 5
+ }
+ }
+ })
+ })
+
+ test('error on v-slot usage on plain elements', () => {
+ const onError = jest.fn()
+ const source = `<div v-slot/>`
+ parseWithSlots(source, { onError })
+ const index = source.indexOf('v-slot')
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_MISPLACED_V_SLOT,
+ loc: {
+ source: `v-slot`,
+ start: {
+ offset: index,
+ line: 1,
+ column: index + 1
+ },
+ end: {
+ offset: index + 6,
+ line: 1,
+ column: index + 7
+ }
+ }
+ })
+ })
+
+ test('error on named slot on component', () => {
+ const onError = jest.fn()
+ const source = `<Comp v-slot:foo>foo</Comp>`
+ parseWithSlots(source, { onError })
+ const index = source.indexOf('v-slot')
+ expect(onError.mock.calls[0][0]).toMatchObject({
+ code: ErrorCodes.X_NAMED_SLOT_ON_COMPONENT,
+ loc: {
+ source: `v-slot:foo`,
+ start: {
+ offset: index,
+ line: 1,
+ column: index + 1
+ },
+ end: {
+ offset: index + 10,
+ line: 1,
+ column: index + 11
+ }
+ }
+ })
})
})
})
import { isString } from '@vue/shared'
+import { ForParseResult } from './transforms/vFor'
// Vue template is a platform-agnostic superset of HTML (syntax only).
// More namespaces like SVG and MathML are declared by platform specific
exp: ExpressionNode | undefined
arg: ExpressionNode | undefined
modifiers: string[]
+ // optional property to cache the expression parse result for v-for
+ parseResult?: ForParseResult
}
export interface SimpleExpressionNode extends Node {
}
export function createObjectProperty(
- key: Property['key'],
+ key: Property['key'] | string,
value: Property['value']
): Property {
return {
type: NodeTypes.JS_PROPERTY,
loc: locStub,
- key,
+ key: isString(key) ? createSimpleExpression(key, true) : key,
value
}
}
context.newline()
}
+function isText(n: string | CodegenNode) {
+ return (
+ isString(n) ||
+ n.type === NodeTypes.SIMPLE_EXPRESSION ||
+ n.type === NodeTypes.TEXT ||
+ n.type === NodeTypes.INTERPOLATION ||
+ n.type === NodeTypes.COMPOUND_EXPRESSION
+ )
+}
+
function genNodeListAsArray(
nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext
) {
const multilines =
nodes.length > 3 ||
- ((!__BROWSER__ || __DEV__) &&
- nodes.some(
- n =>
- isArray(n) || (!isString(n) && n.type !== NodeTypes.SIMPLE_EXPRESSION)
- ))
+ ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
context.push(`[`)
multilines && context.indent()
genNodeList(nodes, context, multilines)
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline, resetMapping } = context
const { properties } = node
+ if (!properties.length) {
+ push(`{}`, node)
+ return
+ }
const multilines =
properties.length > 1 ||
((!__BROWSER__ || __DEV__) &&
import { transformOn } from './transforms/vOn'
import { transformBind } from './transforms/vBind'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
-import { trackSlotScopes } from './transforms/vSlot'
+import { trackSlotScopes, trackVForSlotScopes } from './transforms/vSlot'
import { optimizeText } from './transforms/optimizeText'
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
nodeTransforms: [
transformIf,
transformFor,
- ...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []),
+ ...(prefixIdentifiers
+ ? [
+ // order is important
+ trackVForSlotScopes,
+ transformExpression,
+ trackSlotScopes
+ ]
+ : []),
optimizeText,
transformStyle,
transformSlotOutlet,
export const APPLY_DIRECTIVES = `applyDirectives`
export const RENDER_LIST = `renderList`
export const RENDER_SLOT = `renderSlot`
+export const CREATE_SLOTS = `createSlots`
export const TO_STRING = `toString`
export const MERGE_PROPS = `mergeProps`
export const TO_HANDLERS = `toHandlers`
import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
import { TO_STRING, COMMENT, CREATE_VNODE, FRAGMENT } from './runtimeConstants'
-import { createBlockExpression } from './utils'
-import { isVSlot } from './transforms/vSlot'
+import { isVSlot, createBlockExpression } from './utils'
// There are two types of transforms:
//
MERGE_PROPS,
TO_HANDLERS
} from '../runtimeConstants'
-import { getInnerRange } from '../utils'
-import { buildSlots, isVSlot } from './vSlot'
+import { getInnerRange, isVSlot } from '../utils'
+import { buildSlots } from './vSlot'
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
createObjectExpression(
dir.modifiers.map(modifier =>
createObjectProperty(
- createSimpleExpression(modifier, true, loc),
+ modifier,
createSimpleExpression(`true`, false, loc)
)
),
// handle directives on element
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
- if (dir.type === NodeTypes.DIRECTIVE) {
+ // do not process for v-for since it's special handled
+ if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp as SimpleExpressionNode | undefined
const arg = dir.arg as SimpleExpressionNode | undefined
if (exp) {
- dir.exp = processExpression(exp, context, dir.name === 'slot')
+ dir.exp = processExpression(
+ exp,
+ context,
+ // slot args must be processed as function params
+ dir.name === 'slot'
+ )
}
if (arg && !arg.isStatic) {
dir.arg = processExpression(arg, context)
}
// finish the codegen now that all children have been traversed
- const params: ExpressionNode[] = []
- if (value) {
- params.push(value)
- }
- if (key) {
- if (!value) {
- params.push(createSimpleExpression(`_`, false))
- }
- params.push(key)
- }
- if (index) {
- if (!key) {
- if (!value) {
- params.push(createSimpleExpression(`_`, false))
- }
- params.push(createSimpleExpression(`__`, false))
- }
- params.push(index)
- }
-
let childBlock
if (node.tagType === ElementTypes.TEMPLATE) {
// <template v-for="...">
if (keyProp) {
childBlockProps = createObjectExpression([
createObjectProperty(
- createSimpleExpression(`key`, true),
+ `key`,
keyProp.type === NodeTypes.ATTRIBUTE
? createSimpleExpression(keyProp.value!.content, true)
: keyProp.exp!
renderExp.arguments.push(
createFunctionExpression(
- params,
+ createForLoopParams(parseResult),
childBlock,
true /* force newline */
)
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
-interface ForParseResult {
+export interface ForParseResult {
source: ExpressionNode
value: ExpressionNode | undefined
key: ExpressionNode | undefined
index: ExpressionNode | undefined
}
-function parseForExpression(
+export function parseForExpression(
input: SimpleExpressionNode,
context: TransformContext
-): ForParseResult | null {
+): ForParseResult | undefined {
const loc = input.loc
const exp = input.content
const inMatch = exp.match(forAliasRE)
- if (!inMatch) return null
+ if (!inMatch) return
const [, LHS, RHS] = inMatch
getInnerRange(range, offset, content.length)
)
}
+
+export function createForLoopParams({
+ value,
+ key,
+ index
+}: ForParseResult): ExpressionNode[] {
+ const params: ExpressionNode[] = []
+ if (value) {
+ params.push(value)
+ }
+ if (key) {
+ if (!value) {
+ params.push(createSimpleExpression(`_`, false))
+ }
+ params.push(key)
+ }
+ if (index) {
+ if (!key) {
+ if (!value) {
+ params.push(createSimpleExpression(`_`, false))
+ }
+ params.push(createSimpleExpression(`__`, false))
+ }
+ params.push(index)
+ }
+ return params
+}
}
function createKeyProperty(index: number): Property {
- return createObjectProperty(
- createSimpleExpression(`key`, true),
- createSimpleExpression(index + '', false)
- )
+ return createObjectProperty(`key`, createSimpleExpression(index + '', false))
}
createConditionalExpression,
ConditionalExpression,
JSChildNode,
- SimpleExpressionNode
+ SimpleExpressionNode,
+ FunctionExpression,
+ CallExpression,
+ createCallExpression,
+ createArrayExpression
} from '../ast'
import { TransformContext, NodeTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
-import { mergeExpressions, findDir } from '../utils'
-
-export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
- p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
+import { findDir, isTemplateNode, assert, isVSlot } from '../utils'
+import { CREATE_SLOTS, RENDER_LIST } from '../runtimeConstants'
+import { parseForExpression, createForLoopParams } from './vFor'
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
const defaultFallback = createSimpleExpression(`undefined`, false)
-const hasSameName = (slot: Property, name: string): boolean =>
- isStaticExp(slot.key) && slot.key.content === name
-
// 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 }
(node.tagType === ElementTypes.COMPONENT ||
node.tagType === ElementTypes.TEMPLATE)
) {
- const vSlot = node.props.find(isVSlot)
- if (vSlot && vSlot.exp) {
- context.addIdentifiers(vSlot.exp)
+ const vSlot = findDir(node, 'slot')
+ if (vSlot) {
+ const { addIdentifiers, removeIdentifiers } = context
+ const slotProps = vSlot.exp
+ slotProps && addIdentifiers(slotProps)
return () => {
- context.removeIdentifiers(vSlot.exp!)
+ slotProps && removeIdentifiers(slotProps)
+ }
+ }
+ }
+}
+
+// A NodeTransform that tracks scope identifiers for scoped slots with v-for.
+// This transform is only applied in non-browser builds with { prefixIdentifiers: true }
+export const trackVForSlotScopes: NodeTransform = (node, context) => {
+ let vFor
+ if (
+ isTemplateNode(node) &&
+ node.props.some(isVSlot) &&
+ (vFor = findDir(node, 'for'))
+ ) {
+ const result = (vFor.parseResult = parseForExpression(
+ vFor.exp as SimpleExpressionNode,
+ context
+ ))
+ if (result) {
+ const { value, key, index } = result
+ const { addIdentifiers, removeIdentifiers } = context
+ value && addIdentifiers(value)
+ key && addIdentifiers(key)
+ index && addIdentifiers(index)
+
+ return () => {
+ value && removeIdentifiers(value)
+ key && removeIdentifiers(key)
+ index && removeIdentifiers(index)
}
}
}
// 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,
+ node: ElementNode,
context: TransformContext
): {
- slots: ObjectExpression
+ slots: ObjectExpression | CallExpression
hasDynamicSlots: boolean
} {
- const slots: Property[] = []
+ const { children, loc } = node
+ const slotsProperties: Property[] = []
+ const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
let hasDynamicSlots = false
// 1. Check for default slot with slotProps on component itself.
// <Comp v-slot="{ prop }"/>
- const explicitDefaultSlot = props.find(isVSlot)
+ const explicitDefaultSlot = findDir(node, 'slot', true)
if (explicitDefaultSlot) {
const { arg, exp, loc } = explicitDefaultSlot
if (arg) {
createCompilerError(ErrorCodes.X_NAMED_SLOT_ON_COMPONENT, loc)
)
}
- slots.push(buildDefaultSlot(exp, children, loc))
+ slotsProperties.push(buildDefaultSlot(exp, children, loc))
}
// 2. Iterate through children and check for template slots
let slotDir
if (
- slotElement.type !== NodeTypes.ELEMENT ||
- slotElement.tagType !== ElementTypes.TEMPLATE ||
- !(slotDir = slotElement.props.find(isVSlot))
+ !isTemplateNode(slotElement) ||
+ !(slotDir = findDir(slotElement, 'slot', true))
) {
// not a <template v-slot>, skip.
- extraneousChild = extraneousChild || slotElement
+ if (slotElement.type !== NodeTypes.COMMENT && !extraneousChild) {
+ extraneousChild = slotElement
+ }
continue
}
slotChildren.length ? slotChildren[0].loc : slotLoc
)
- // check if this slot is conditional (v-if/else/else-if)
+ // check if this slot is conditional (v-if/v-for)
let vIf: DirectiveNode | undefined
let vElse: DirectiveNode | undefined
+ let vFor: DirectiveNode | undefined
if ((vIf = findDir(slotElement, 'if'))) {
hasDynamicSlots = true
- slots.push(
- createObjectProperty(
- slotName,
- createConditionalExpression(vIf.exp!, slotFunction, defaultFallback)
+ dynamicSlots.push(
+ createConditionalExpression(
+ vIf.exp!,
+ buildDynamicSlot(slotName, slotFunction),
+ defaultFallback
)
)
} else if (
- (vElse = findDir(slotElement, /^else(-if)?$/, true /* allow empty */))
+ (vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
) {
- hasDynamicSlots = true
-
- // find adjacent v-if slot
- let baseIfSlot: Property | undefined
- let baseIfSlotWithSameName: Property | undefined
- let i = slots.length
- while (i--) {
- if (slots[i].value.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {
- baseIfSlot = slots[i]
- if (staticSlotName && hasSameName(baseIfSlot, staticSlotName)) {
- baseIfSlotWithSameName = baseIfSlot
- break
- }
+ // find adjacent v-if
+ let j = i
+ let prev
+ while (j--) {
+ prev = children[j]
+ if (prev.type !== NodeTypes.COMMENT) {
+ break
}
}
- if (!baseIfSlot) {
- context.onError(
- createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, vElse.loc)
- )
- continue
- }
-
- if (baseIfSlotWithSameName) {
- // v-else branch has same slot name with base v-if branch
- let conditional = baseIfSlotWithSameName.value as ConditionalExpression
- // locate the deepest conditional in case we have nested ones
+ if (prev && isTemplateNode(prev) && findDir(prev, 'if')) {
+ // remove node
+ children.splice(i, 1)
+ i--
+ __DEV__ && assert(dynamicSlots.length > 0)
+ // attach this slot to previous conditional
+ let conditional = dynamicSlots[
+ dynamicSlots.length - 1
+ ] as ConditionalExpression
while (
conditional.alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
) {
conditional = conditional.alternate
}
- // attach the v-else branch to the base v-if's conditional expression
conditional.alternate = vElse.exp
? createConditionalExpression(
vElse.exp,
- slotFunction,
+ buildDynamicSlot(slotName, slotFunction),
defaultFallback
)
- : slotFunction
+ : buildDynamicSlot(slotName, slotFunction)
} else {
- // not the same slot name. generate a separate property.
- slots.push(
- createObjectProperty(
- slotName,
- createConditionalExpression(
- // negate base branch condition
- mergeExpressions(
- `!(`,
- (baseIfSlot.value as ConditionalExpression).test,
- `)`,
- ...(vElse.exp ? [` && (`, vElse.exp, `)`] : [])
- ),
- slotFunction,
- defaultFallback
+ context.onError(
+ createCompilerError(ErrorCodes.X_ELSE_NO_ADJACENT_IF, vElse.loc)
+ )
+ }
+ } else if ((vFor = findDir(slotElement, 'for'))) {
+ hasDynamicSlots = true
+ const parseResult =
+ vFor.parseResult ||
+ parseForExpression(vFor.exp as SimpleExpressionNode, context)
+ if (parseResult) {
+ // Render the dynamic slots as an array and add it to the createSlot()
+ // args. The runtime knows how to handle it appropriately.
+ dynamicSlots.push(
+ createCallExpression(context.helper(RENDER_LIST), [
+ parseResult.source,
+ createFunctionExpression(
+ createForLoopParams(parseResult),
+ buildDynamicSlot(slotName, slotFunction),
+ true
)
- )
+ ])
+ )
+ } else {
+ context.onError(
+ createCompilerError(ErrorCodes.X_FOR_MALFORMED_EXPRESSION, vFor.loc)
)
}
} else {
}
seenSlotNames.add(staticSlotName)
}
- slots.push(createObjectProperty(slotName, slotFunction))
+ slotsProperties.push(createObjectProperty(slotName, slotFunction))
}
}
if (!explicitDefaultSlot && !hasTemplateSlots) {
// implicit default slot.
- slots.push(buildDefaultSlot(undefined, children, loc))
+ slotsProperties.push(buildDefaultSlot(undefined, children, loc))
+ }
+
+ let slots: ObjectExpression | CallExpression = createObjectExpression(
+ slotsProperties,
+ loc
+ )
+ if (dynamicSlots.length) {
+ slots = createCallExpression(context.helper(CREATE_SLOTS), [
+ slots,
+ createArrayExpression(dynamicSlots)
+ ])
}
return {
- slots: createObjectExpression(slots, loc),
+ slots,
hasDynamicSlots
}
}
loc: SourceLocation
): Property {
return createObjectProperty(
- createSimpleExpression(`default`, true),
+ `default`,
createFunctionExpression(
slotProps,
children,
)
)
}
+
+function buildDynamicSlot(
+ name: ExpressionNode,
+ fn: FunctionExpression
+): ObjectExpression {
+ return createObjectExpression([
+ createObjectProperty(`name`, name),
+ createObjectProperty(`fn`, fn)
+ ])
+}
SequenceExpression,
createSequenceExpression,
createCallExpression,
- ExpressionNode,
- CompoundExpressionNode,
- createCompoundExpression,
- DirectiveNode
+ DirectiveNode,
+ ElementTypes,
+ TemplateChildNode,
+ RootNode
} from './ast'
import { parse } from 'acorn'
import { walk } from 'estree-walker'
if (
p.type === NodeTypes.DIRECTIVE &&
(allowEmpty || p.exp) &&
- p.name.match(name)
+ (isString(name) ? p.name === name : name.test(p.name))
) {
return p
}
])
}
-export function mergeExpressions(
- ...args: (string | ExpressionNode)[]
-): CompoundExpressionNode {
- const children: CompoundExpressionNode['children'] = []
- for (let i = 0; i < args.length; i++) {
- const exp = args[i]
- if (isString(exp) || exp.type === NodeTypes.SIMPLE_EXPRESSION) {
- children.push(exp)
- } else {
- children.push(...exp.children)
- }
- }
- return createCompoundExpression(children)
-}
+export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
+ p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
+
+export const isTemplateNode = (
+ node: RootNode | TemplateChildNode
+): node is ElementNode =>
+ node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE
--- /dev/null
+import { Slot } from '../componentSlots'
+import { isArray } from '@vue/shared'
+
+interface CompiledSlotDescriptor {
+ name: string
+ fn: Slot
+}
+
+export function createSlots(
+ slots: Record<string, Slot>,
+ dynamicSlots: (CompiledSlotDescriptor | CompiledSlotDescriptor[])[]
+): Record<string, Slot> {
+ for (let i = 0; i < dynamicSlots.length; i++) {
+ const slot = dynamicSlots[i]
+ // array of dynamic slot generated by <template v-for="..." #[...]>
+ if (isArray(slot)) {
+ for (let j = 0; j < slot.length; j++) {
+ slots[slot[i].name] = slot[i].fn
+ }
+ } else {
+ // conditional single slot generated by <template v-if="..." #foo>
+ slots[slot.name] = slot.fn
+ }
+ }
+ return slots
+}
export { toString } from './helpers/toString'
export { toHandlers } from './helpers/toHandlers'
export { renderSlot } from './helpers/renderSlot'
+export { createSlots } from './helpers/createSlots'
export { capitalize, camelize } from '@vue/shared'
// Internal, for integration with runtime compiler