feat(compiler): handle complex destructure expressions in v-for

This commit is contained in:
Evan You
2019-09-28 16:02:08 -04:00
parent 798a9cbe9b
commit 389a07835c
9 changed files with 388 additions and 278 deletions

View File

@@ -150,9 +150,9 @@ export interface IfBranchNode extends Node {
export interface ForNode extends Node {
type: NodeTypes.FOR
source: ExpressionNode
valueAlias: SimpleExpressionNode | undefined
keyAlias: SimpleExpressionNode | undefined
objectIndexAlias: SimpleExpressionNode | undefined
valueAlias: ExpressionNode | undefined
keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined
children: ChildNode[]
}

View File

@@ -509,14 +509,14 @@ function genFor(node: ForNode, context: CodegenContext) {
genNode(source, context)
push(`, (`)
if (valueAlias) {
genExpression(valueAlias, context)
genNode(valueAlias, context)
}
if (keyAlias) {
if (!valueAlias) {
push(`__value`)
}
push(`, `)
genExpression(keyAlias, context)
genNode(keyAlias, context)
}
if (objectIndexAlias) {
if (!keyAlias) {
@@ -527,7 +527,7 @@ function genFor(node: ForNode, context: CodegenContext) {
}
}
push(`, `)
genExpression(objectIndexAlias, context)
genNode(objectIndexAlias, context)
}
push(`) => {`)
indent()

View File

@@ -63,8 +63,8 @@ export interface TransformContext extends Required<TransformOptions> {
replaceNode(node: ChildNode): void
removeNode(node?: ChildNode): void
onNodeRemoved: () => void
addIdentifier(id: string): void
removeIdentifier(id: string): void
addIdentifiers(exp: ExpressionNode): void
removeIdentifiers(exp: ExpressionNode): void
hoist(exp: JSChildNode): ExpressionNode
}
@@ -126,15 +126,24 @@ function createTransformContext(
context.parent.children.splice(removalIndex, 1)
},
onNodeRemoved: () => {},
addIdentifier(id) {
const { identifiers } = context
if (identifiers[id] === undefined) {
identifiers[id] = 0
addIdentifiers(exp) {
// identifier tracking only happens in non-browser builds.
if (!__BROWSER__) {
if (exp.identifiers) {
exp.identifiers.forEach(addId)
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
addId(exp.content)
}
}
;(identifiers[id] as number)++
},
removeIdentifier(id) {
;(context.identifiers[id] as number)--
removeIdentifiers(exp) {
if (!__BROWSER__) {
if (exp.identifiers) {
exp.identifiers.forEach(removeId)
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
removeId(exp.content)
}
}
},
hoist(exp) {
context.hoists.push(exp)
@@ -145,6 +154,19 @@ function createTransformContext(
)
}
}
function addId(id: string) {
const { identifiers } = context
if (identifiers[id] === undefined) {
identifiers[id] = 0
}
;(identifiers[id] as number)++
}
function removeId(id: string) {
;(context.identifiers[id] as number)--
}
return context
}

View File

@@ -67,6 +67,8 @@ interface PrefixMeta {
export function processExpression(
node: SimpleExpressionNode,
context: TransformContext,
// some expressions like v-slot props & v-for aliases should be parsed as
// function params
asParams: boolean = false
): ExpressionNode {
if (!context.prefixIdentifiers) {
@@ -75,7 +77,7 @@ export function processExpression(
// fast path if expression is a simple identifier.
if (isSimpleIdentifier(node.content)) {
if (!context.identifiers[node.content]) {
if (!asParams && !context.identifiers[node.content]) {
node.content = `_ctx.${node.content}`
}
return node
@@ -107,17 +109,14 @@ export function processExpression(
if (node.type === 'Identifier') {
if (!ids.includes(node)) {
if (!knownIds[node.name] && shouldPrefix(node, parent)) {
if (
isPropertyKey(node, parent) &&
(parent as Property).value === node
) {
if (isPropertyShorthand(node, parent)) {
// property shorthand like { foo }, we need to add the key since we
// rewrite the value
node.prefix = `${node.name}: `
}
node.name = `_ctx.${node.name}`
ids.push(node)
} else if (!isPropertyKey(node, parent)) {
} else if (!isStaticPropertyKey(node, parent)) {
// also generate sub-expressioms for other identifiers for better
// source map support. (except for property keys which are static)
ids.push(node)
@@ -131,9 +130,11 @@ export function processExpression(
enter(child, parent) {
if (
child.type === 'Identifier' &&
!// do not keep as scope variable if this is a default value
// assignment of a param
(
// do not record as scope variable if is a destrcuture key
!isStaticPropertyKey(child, parent) &&
// do not record if this is a default value
// assignment of a destructured variable
!(
parent &&
parent.type === 'AssignmentPattern' &&
parent.right === child
@@ -213,7 +214,16 @@ const isFunction = (node: Node): node is Function =>
/Function(Expression|Declaration)$/.test(node.type)
const isPropertyKey = (node: Node, parent: Node) =>
parent.type === 'Property' && parent.key === node && !parent.computed
parent &&
parent.type === 'Property' &&
parent.key === node &&
!parent.computed
const isPropertyShorthand = (node: Node, parent: Node) =>
isPropertyKey(node, parent) && (parent as Property).value === node
const isStaticPropertyKey = (node: Node, parent: Node) =>
isPropertyKey(node, parent) && (parent as Property).value !== node
const globals = new Set(
(
@@ -236,11 +246,7 @@ function shouldPrefix(identifier: Identifier, parent: Node) {
parent.params.includes(identifier))
) &&
// not a key of Property
!(
isPropertyKey(identifier, parent) &&
// shorthand keys should be prefixed
!((parent as Property).value === identifier)
) &&
!isStaticPropertyKey(identifier, parent) &&
// not a property of a MemberExpression
!(
parent.type === 'MemberExpression' &&

View File

@@ -39,19 +39,20 @@ export const transformFor = createStructuralDirectiveTransform(
children: [node]
})
// scope management
const { addIdentifier, removeIdentifier } = context
if (!__BROWSER__) {
// scope management
const { addIdentifiers, removeIdentifiers } = context
// inject identifiers to context
value && addIdentifier(value.content)
key && addIdentifier(key.content)
index && addIdentifier(index.content)
// inject identifiers to context
value && addIdentifiers(value)
key && addIdentifiers(key)
index && addIdentifiers(index)
return () => {
// remove injected identifiers on exit
value && removeIdentifier(value.content)
key && removeIdentifier(key.content)
index && removeIdentifier(index.content)
return () => {
value && removeIdentifiers(value)
key && removeIdentifiers(key)
index && removeIdentifiers(index)
}
}
} else {
context.onError(
@@ -67,14 +68,16 @@ export const transformFor = createStructuralDirectiveTransform(
)
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
// This regex doesn't cover the case if key or index aliases have destructuring,
// but those do not make sense in the first place, so this works in practice.
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
interface ForParseResult {
source: ExpressionNode
value: SimpleExpressionNode | undefined
key: SimpleExpressionNode | undefined
index: SimpleExpressionNode | undefined
value: ExpressionNode | undefined
key: ExpressionNode | undefined
index: ExpressionNode | undefined
}
function parseForExpression(
@@ -88,21 +91,22 @@ function parseForExpression(
const [, LHS, RHS] = inMatch
let source: ExpressionNode = createAliasExpression(
loc,
RHS.trim(),
exp.indexOf(RHS, LHS.length)
)
if (!__BROWSER__ && context.prefixIdentifiers) {
source = processExpression(source, context)
}
const result: ForParseResult = {
source,
source: createAliasExpression(
loc,
RHS.trim(),
exp.indexOf(RHS, LHS.length)
),
value: undefined,
key: undefined,
index: undefined
}
if (!__BROWSER__ && context.prefixIdentifiers) {
result.source = processExpression(
result.source as SimpleExpressionNode,
context
)
}
let valueContent = LHS.trim()
.replace(stripParensRE, '')
@@ -118,6 +122,9 @@ function parseForExpression(
if (keyContent) {
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
result.key = createAliasExpression(loc, keyContent, keyOffset)
if (!__BROWSER__ && context.prefixIdentifiers) {
result.key = processExpression(result.key, context, true)
}
}
if (iteratorMatch[2]) {
@@ -134,12 +141,18 @@ function parseForExpression(
: trimmedOffset + valueContent.length
)
)
if (!__BROWSER__ && context.prefixIdentifiers) {
result.index = processExpression(result.index, context, true)
}
}
}
}
if (valueContent) {
result.value = createAliasExpression(loc, valueContent, trimmedOffset)
if (!__BROWSER__ && context.prefixIdentifiers) {
result.value = processExpression(result.value, context, true)
}
}
return result

View File

@@ -31,12 +31,9 @@ export const trackSlotScopes: NodeTransform = (node, context) => {
) {
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)
}
context.addIdentifiers(vSlot.exp)
return () => {
context.removeIdentifiers(vSlot.exp!)
}
}
}