refactor(compiler): split slot / slot outlet / slot scope handling into separate transforms

This commit is contained in:
Evan You
2019-09-28 00:19:24 -04:00
parent 6377af483b
commit 6461b3853e
13 changed files with 532 additions and 433 deletions

View File

@@ -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
}
}

View File

@@ -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 =>

View File

@@ -0,0 +1,98 @@
import { NodeTransform } from '../transform'
import {
NodeTypes,
ElementTypes,
CompoundExpressionNode,
createCompoundExpression,
CallExpression,
createCallExpression
} from '../ast'
import { isSimpleIdentifier } from '../utils'
import { buildProps } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors'
import { RENDER_SLOT } from '../runtimeConstants'
export const transformSlotOutlet: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT) {
const { props, children, loc } = node
const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
let slot: string | CompoundExpressionNode = $slots + `.default`
// check for <slot name="xxx" OR :name="xxx" />
let nameIndex: number = -1
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.ATTRIBUTE) {
if (prop.name === `name` && prop.value) {
// static name="xxx"
const name = prop.value.content
const accessor = isSimpleIdentifier(name)
? `.${name}`
: `[${JSON.stringify(name)}]`
slot = `${$slots}${accessor}`
nameIndex = i
break
}
} else if (prop.name === `bind`) {
const { arg, exp } = prop
if (
arg &&
exp &&
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
arg.isStatic &&
arg.content === `name`
) {
// dynamic :name="xxx"
slot = createCompoundExpression(
[
$slots + `[`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION
? [exp]
: exp.children),
`]`
],
loc
)
nameIndex = i
break
}
}
}
const slotArgs: CallExpression['arguments'] = [slot]
const propsWithoutName =
nameIndex > -1
? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
: props
const hasProps = propsWithoutName.length
if (hasProps) {
const { props: propsExpression, directives } = buildProps(
propsWithoutName,
loc,
context
)
if (directives.length) {
context.onError(
createCompilerError(
ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
directives[0].loc
)
)
}
slotArgs.push(propsExpression)
}
if (children.length) {
if (!hasProps) {
slotArgs.push(`{}`)
}
slotArgs.push(children)
}
node.codegenNode = createCallExpression(
context.helper(RENDER_SLOT),
slotArgs,
loc
)
}
}

View File

@@ -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(

View File

@@ -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 <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
)
}