From fc47029ed3849833a773a9f0d816228f8366a9bb Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 2 Oct 2019 23:10:41 -0400 Subject: [PATCH] feat(compiler): support v-for on named slots --- .../__snapshots__/compile.spec.ts.snap | 12 +- packages/compiler-core/__tests__/testUtils.ts | 13 +- .../transforms/__snapshots__/vIf.spec.ts.snap | 4 +- .../__snapshots__/vSlot.spec.ts.snap | 117 ++++-- .../__tests__/transforms/vSlot.spec.ts | 393 +++++++++++++----- packages/compiler-core/src/ast.ts | 7 +- packages/compiler-core/src/codegen.ts | 20 +- packages/compiler-core/src/index.ts | 11 +- .../compiler-core/src/runtimeConstants.ts | 1 + packages/compiler-core/src/transform.ts | 3 +- .../src/transforms/transformElement.ts | 6 +- .../src/transforms/transformExpression.ts | 10 +- packages/compiler-core/src/transforms/vFor.ts | 59 +-- packages/compiler-core/src/transforms/vIf.ts | 5 +- .../compiler-core/src/transforms/vSlot.ts | 203 +++++---- packages/compiler-core/src/utils.ts | 31 +- .../runtime-core/src/helpers/createSlots.ts | 26 ++ packages/runtime-core/src/index.ts | 1 + 18 files changed, 645 insertions(+), 277 deletions(-) create mode 100644 packages/runtime-core/src/helpers/createSlots.ts diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index c859f8b08..d9ade929b 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -14,9 +14,7 @@ return function render() { _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 */) @@ -39,9 +37,7 @@ return function render() { 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 */) @@ -63,9 +59,7 @@ export default function render() { _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 */) diff --git a/packages/compiler-core/__tests__/testUtils.ts b/packages/compiler-core/__tests__/testUtils.ts index f8c02f8e3..cc061d87d 100644 --- a/packages/compiler-core/__tests__/testUtils.ts +++ b/packages/compiler-core/__tests__/testUtils.ts @@ -7,6 +7,7 @@ import { ElementTypes } from '../src' import { CREATE_VNODE } from '../src/runtimeConstants' +import { isString } from '@vue/shared' const leadingBracketRE = /^\[/ const bracketsRE = /^\[|\]$/g @@ -26,11 +27,13 @@ export function createObjectMatcher(obj: any) { 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] })) } } diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap index b44c2eaef..3291e8dc2 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -71,9 +71,7 @@ return function render() { ? _createBlock(\\"div\\", { key: 0 }) : orNot ? _createBlock(\\"p\\", { key: 1 }) - : _createBlock(_Fragment, { key: 2 }, [ - \\"fine\\" - ])) + : _createBlock(_Fragment, { key: 2 }, [\\"fine\\"])) } }" `; diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index a43309a54..78142174a 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -8,14 +8,8 @@ return function render() { 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 */)) }" `; @@ -28,10 +22,7 @@ return function render() { 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)] })) }" `; @@ -51,6 +42,92 @@ return function render() { }" `; +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 @@ -59,14 +136,8 @@ return function render() { 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)] })) }" `; @@ -82,11 +153,7 @@ return function render() { 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), diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index f2f2ad817..a36e5388a 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -11,14 +11,20 @@ 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 { 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 ], @@ -314,118 +320,311 @@ describe('compiler: transform component slots', () => { expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() }) - test('error on extraneous children w/ named slots', () => { - const onError = jest.fn() - const source = `bar` - 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( + ` + + ` + ) + 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 = `` - 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( + ` + + `, + { 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 = `` - 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( + ` + + + + ` + ) + 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 = `
` - 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( + ` + + `, + { 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 = `foo` - 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 = `bar` + 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 = `` + 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 = `` + 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 = `
` + 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 = `foo` + 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 + } + } + }) }) }) }) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 5dbf853ac..4eff8ff83 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -1,4 +1,5 @@ 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 @@ -115,6 +116,8 @@ export interface DirectiveNode extends Node { 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 { @@ -249,13 +252,13 @@ export function createObjectExpression( } 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 } } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 143deee70..189e180b5 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -259,17 +259,23 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) { 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) @@ -435,6 +441,10 @@ function genCallExpression(node: CallExpression, context: CodegenContext) { 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__) && diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index e6f51c6ca..eb23a9e02 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -12,7 +12,7 @@ import { transformElement } from './transforms/transformElement' 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 @@ -45,7 +45,14 @@ export function baseCompile( nodeTransforms: [ transformIf, transformFor, - ...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []), + ...(prefixIdentifiers + ? [ + // order is important + trackVForSlotScopes, + transformExpression, + trackSlotScopes + ] + : []), optimizeText, transformStyle, transformSlotOutlet, diff --git a/packages/compiler-core/src/runtimeConstants.ts b/packages/compiler-core/src/runtimeConstants.ts index 6276f21b6..f67263bf1 100644 --- a/packages/compiler-core/src/runtimeConstants.ts +++ b/packages/compiler-core/src/runtimeConstants.ts @@ -14,6 +14,7 @@ export const RESOLVE_DIRECTIVE = `resolveDirective` 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` diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index d5585dd8a..e1d236253 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -15,8 +15,7 @@ import { 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: // diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 0f1c051dc..e4f37a22d 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -26,8 +26,8 @@ import { 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, '') @@ -418,7 +418,7 @@ function createDirectiveArgs( createObjectExpression( dir.modifiers.map(modifier => createObjectProperty( - createSimpleExpression(modifier, true, loc), + modifier, createSimpleExpression(`true`, false, loc) ) ), diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 0658ecc56..36c378339 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -35,11 +35,17 @@ export const transformExpression: NodeTransform = (node, context) => { // 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) diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index 8aec71ec6..fbcbdfa57 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -90,26 +90,6 @@ export const transformFor = createStructuralDirectiveTransform( } // 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) { //