From: Evan You Date: Sat, 28 Sep 2019 04:19:24 +0000 (-0400) Subject: refactor(compiler): split slot / slot outlet / slot scope handling into separate... X-Git-Tag: v3.0.0-alpha.0~675 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6461b3853e8027931782d71b0d9ea5314e8a8770;p=thirdparty%2Fvuejs%2Fcore.git refactor(compiler): split slot / slot outlet / slot scope handling into separate transforms --- diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index 8331ad7dcb..ffeada4ccb 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -8,7 +8,9 @@ return function render() { id: \\"foo\\", [prop]: bar, [foo + bar]: bar - }, [createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })], [ + }, [ + createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" }) + ], [ foo, createVNode(\\"p\\") ]) diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index 2a0a6c1dce..f73f3dd7a1 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -14,7 +14,9 @@ return function render() { ? _createVNode(\\"div\\", 0, \\"yes\\") : \\"no\\", _renderList(list, (value, index) => { - return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(value + index))]) + return _createVNode(\\"div\\", 0, [ + _createVNode(\\"span\\", 0, _toString(value + index)) + ]) }) ]) } @@ -35,7 +37,9 @@ return function render() { ? createVNode(\\"div\\", 0, \\"yes\\") : \\"no\\", renderList(_ctx.list, (value, index) => { - return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, toString(value + index))]) + return createVNode(\\"div\\", 0, [ + createVNode(\\"span\\", 0, toString(value + index)) + ]) }) ]) }" @@ -55,7 +59,9 @@ export default function render() { ? createVNode(\\"div\\", 0, \\"yes\\") : \\"no\\", _renderList(_ctx.list, (value, index) => { - return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, _toString(value + index))]) + return createVNode(\\"div\\", 0, [ + createVNode(\\"span\\", 0, _toString(value + index)) + ]) }) ]) }" diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index c4be89a1f0..1ab8afc66c 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -553,7 +553,9 @@ describe('compiler: codegen', () => { id: "foo", [prop]: bar, [foo + bar]: bar - }, [${CREATE_VNODE}("p", { "some-key": "foo" })], [ + }, [ + ${CREATE_VNODE}("p", { "some-key": "foo" }) + ], [ foo, ${CREATE_VNODE}("p") ])`) diff --git a/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts b/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts new file mode 100644 index 0000000000..f9fa59582f --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts @@ -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 outlets', () => { + test('default slot outlet', () => { + const ast = parseWithSlots(``) + 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(``) + 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(``) + 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(``) + 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(``, { + 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(``) + 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(``) + 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(``) + 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(`
`) + 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(`
`) + 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(`
`) + 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(`
`) + 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` + } + ] + ] + }) + }) +}) diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index d7a3619632..295d8d8d80 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -1,23 +1,17 @@ -import { - CompilerOptions, - parse, - transform, - ElementNode, - NodeTypes, - generate -} from '../../src' +import { CompilerOptions, parse, transform, generate } from '../../src' import { transformElement } from '../../src/transforms/transformElement' import { transformOn } from '../../src/transforms/vOn' import { transformBind } from '../../src/transforms/vBind' import { transformExpression } from '../../src/transforms/transformExpression' -import { RENDER_SLOT } from '../../src/runtimeConstants' +import { trackSlotScopes } from '../../src/transforms/vSlot' function parseWithSlots(template: string, options: CompilerOptions = {}) { const ast = parse(template) transform(ast, { nodeTransforms: [ - ...(options.prefixIdentifiers ? [transformExpression] : []), - // slot transform is part of transformElement + ...(options.prefixIdentifiers + ? [transformExpression, trackSlotScopes] + : []), transformElement ], directiveTransforms: { @@ -29,302 +23,19 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) { return ast } -describe('compiler: transform slots', () => { - test('default slot outlet', () => { - const ast = parseWithSlots(``) - 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(``) - 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(``) - 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(``) - 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(``, { - 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(``) - 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(``) - 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(``) - 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(`
`) - 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(`
`) - 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(`
`) - 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(`
`) - 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(`
`) - const { code } = generate(ast) + const ast = parseWithSlots( + ` + + + hello {{ dur }} + + +`, + { prefixIdentifiers: true } + ) + const { code } = generate(ast, { prefixIdentifiers: true }) console.log(code) }) }) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index d86b325ac3..edb924c8aa 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -117,6 +117,9 @@ export interface SimpleExpressionNode extends Node { 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 { @@ -128,6 +131,9 @@ 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 { diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 64a2d4be10..53badd58fb 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -5,12 +5,14 @@ import { RootNode } from './ast' 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 @@ -41,8 +43,9 @@ export function baseCompile( nodeTransforms: [ transformIf, transformFor, - ...(prefixIdentifiers ? [transformExpression] : []), + ...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []), transformStyle, + transformSlotOutlet, transformElement, ...(options.nodeTransforms || []) // user transforms ], diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index bf5fdf9170..0587434d65 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -8,8 +8,7 @@ import { Property, ExpressionNode, createSimpleExpression, - JSChildNode, - SimpleExpressionNode + JSChildNode } from './ast' import { isString, isArray } from '@vue/shared' import { CompilerError, defaultOnError } from './errors' @@ -64,8 +63,8 @@ export interface TransformContext extends Required { 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 } @@ -127,15 +126,15 @@ function createTransformContext( 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) diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index dab39b438a..a076328aff 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -27,7 +27,7 @@ import { 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, '') @@ -95,10 +95,7 @@ export const transformElement: NodeTransform = (node, context) => { } 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 } } diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 35a076cb42..7b90f87412 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -87,12 +87,12 @@ export function processExpression( _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 @@ -139,11 +139,22 @@ export function processExpression( 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) } } }) @@ -151,9 +162,12 @@ export function processExpression( } }, 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] + } }) } } @@ -185,11 +199,14 @@ export function processExpression( } }) + 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 => diff --git a/packages/compiler-core/src/transforms/transfromSlotOutlet.ts b/packages/compiler-core/src/transforms/transfromSlotOutlet.ts new file mode 100644 index 0000000000..99f7354cd6 --- /dev/null +++ b/packages/compiler-core/src/transforms/transfromSlotOutlet.ts @@ -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 + 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 + ) + } +} diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index a045f2270a..794f3968b2 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -43,15 +43,15 @@ export const transformFor = createStructuralDirectiveTransform( 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( diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 670371799e..7ba344f2c4 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -3,10 +3,6 @@ import { ObjectExpression, createObjectExpression, NodeTypes, - createCompoundExpression, - createCallExpression, - CompoundExpressionNode, - CallExpression, createObjectProperty, createSimpleExpression, createFunctionExpression, @@ -17,16 +13,37 @@ import { 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 @@ -128,86 +145,3 @@ function buildSlot( 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 - 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 - ) -}