feat(v-on): cache handlers
This commit is contained in:
@@ -42,7 +42,8 @@ export const enum NodeTypes {
|
||||
JS_ARRAY_EXPRESSION,
|
||||
JS_FUNCTION_EXPRESSION,
|
||||
JS_SEQUENCE_EXPRESSION,
|
||||
JS_CONDITIONAL_EXPRESSION
|
||||
JS_CONDITIONAL_EXPRESSION,
|
||||
JS_CACHE_EXPRESSION
|
||||
}
|
||||
|
||||
export const enum ElementTypes {
|
||||
@@ -93,6 +94,7 @@ export interface RootNode extends Node {
|
||||
components: string[]
|
||||
directives: string[]
|
||||
hoists: JSChildNode[]
|
||||
cached: number
|
||||
codegenNode: TemplateChildNode | JSChildNode | undefined
|
||||
}
|
||||
|
||||
@@ -236,6 +238,7 @@ export type JSChildNode =
|
||||
| FunctionExpression
|
||||
| ConditionalExpression
|
||||
| SequenceExpression
|
||||
| CacheExpression
|
||||
|
||||
export interface CallExpression extends Node {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION
|
||||
@@ -283,6 +286,12 @@ export interface ConditionalExpression extends Node {
|
||||
alternate: JSChildNode
|
||||
}
|
||||
|
||||
export interface CacheExpression extends Node {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION
|
||||
index: number
|
||||
value: JSChildNode
|
||||
}
|
||||
|
||||
// Codegen Node Types ----------------------------------------------------------
|
||||
|
||||
// createVNode(...)
|
||||
@@ -605,3 +614,15 @@ export function createConditionalExpression(
|
||||
loc: locStub
|
||||
}
|
||||
}
|
||||
|
||||
export function createCacheExpression(
|
||||
index: number,
|
||||
value: JSChildNode
|
||||
): CacheExpression {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index,
|
||||
value,
|
||||
loc: locStub
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
SimpleExpressionNode,
|
||||
FunctionExpression,
|
||||
SequenceExpression,
|
||||
ConditionalExpression
|
||||
ConditionalExpression,
|
||||
CacheExpression
|
||||
} from './ast'
|
||||
import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||
import {
|
||||
@@ -218,6 +219,7 @@ export function generate(
|
||||
}
|
||||
}
|
||||
genHoists(ast.hoists, context)
|
||||
genCached(ast.cached, context)
|
||||
newline()
|
||||
push(`return `)
|
||||
} else {
|
||||
@@ -226,6 +228,7 @@ export function generate(
|
||||
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
|
||||
}
|
||||
genHoists(ast.hoists, context)
|
||||
genCached(ast.cached, context)
|
||||
newline()
|
||||
push(`export default `)
|
||||
}
|
||||
@@ -315,6 +318,18 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) {
|
||||
})
|
||||
}
|
||||
|
||||
function genCached(cached: number, context: CodegenContext) {
|
||||
if (cached > 0) {
|
||||
context.newline()
|
||||
context.push(`let `)
|
||||
for (let i = 0; i < cached; i++) {
|
||||
context.push(`_cached_${i + 1}`)
|
||||
if (i !== cached - 1) context.push(`, `)
|
||||
}
|
||||
context.newline()
|
||||
}
|
||||
}
|
||||
|
||||
function isText(n: string | CodegenNode) {
|
||||
return (
|
||||
isString(n) ||
|
||||
@@ -419,6 +434,9 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
||||
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
|
||||
genConditionalExpression(node, context)
|
||||
break
|
||||
case NodeTypes.JS_CACHE_EXPRESSION:
|
||||
genCacheExpression(node, context)
|
||||
break
|
||||
/* istanbul ignore next */
|
||||
default:
|
||||
if (__DEV__) {
|
||||
@@ -612,3 +630,9 @@ function genSequenceExpression(
|
||||
genNodeList(node.expressions, context)
|
||||
context.push(`)`)
|
||||
}
|
||||
|
||||
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||
context.push(`_cached_${node.index} || (_cached_${node.index} = `)
|
||||
genNode(node.value, context)
|
||||
context.push(`)`)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
ElementTypes,
|
||||
ElementCodegenNode,
|
||||
ComponentCodegenNode,
|
||||
createCallExpression
|
||||
createCallExpression,
|
||||
CacheExpression,
|
||||
createCacheExpression
|
||||
} from './ast'
|
||||
import { isString, isArray } from '@vue/shared'
|
||||
import { CompilerError, defaultOnError } from './errors'
|
||||
@@ -45,8 +47,13 @@ export type NodeTransform = (
|
||||
export type DirectiveTransform = (
|
||||
dir: DirectiveNode,
|
||||
node: ElementNode,
|
||||
context: TransformContext
|
||||
) => {
|
||||
context: TransformContext,
|
||||
// a platform specific compiler can import the base transform and augment
|
||||
// it by passing in this optional argument.
|
||||
augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult
|
||||
) => DirectiveTransformResult
|
||||
|
||||
export interface DirectiveTransformResult {
|
||||
props: Property[]
|
||||
needRuntime: boolean | symbol
|
||||
}
|
||||
@@ -64,6 +71,7 @@ export interface TransformOptions {
|
||||
directiveTransforms?: { [name: string]: DirectiveTransform }
|
||||
prefixIdentifiers?: boolean
|
||||
hoistStatic?: boolean
|
||||
cacheHandlers?: boolean
|
||||
onError?: (error: CompilerError) => void
|
||||
}
|
||||
|
||||
@@ -73,6 +81,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
components: Set<string>
|
||||
directives: Set<string>
|
||||
hoists: JSChildNode[]
|
||||
cached: number
|
||||
identifiers: { [name: string]: number | undefined }
|
||||
scopes: {
|
||||
vFor: number
|
||||
@@ -91,6 +100,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
addIdentifiers(exp: ExpressionNode | string): void
|
||||
removeIdentifiers(exp: ExpressionNode | string): void
|
||||
hoist(exp: JSChildNode): SimpleExpressionNode
|
||||
cache<T extends JSChildNode>(exp: T): CacheExpression | T
|
||||
}
|
||||
|
||||
function createTransformContext(
|
||||
@@ -98,6 +108,7 @@ function createTransformContext(
|
||||
{
|
||||
prefixIdentifiers = false,
|
||||
hoistStatic = false,
|
||||
cacheHandlers = false,
|
||||
nodeTransforms = [],
|
||||
directiveTransforms = {},
|
||||
onError = defaultOnError
|
||||
@@ -109,6 +120,7 @@ function createTransformContext(
|
||||
components: new Set(),
|
||||
directives: new Set(),
|
||||
hoists: [],
|
||||
cached: 0,
|
||||
identifiers: {},
|
||||
scopes: {
|
||||
vFor: 0,
|
||||
@@ -118,6 +130,7 @@ function createTransformContext(
|
||||
},
|
||||
prefixIdentifiers,
|
||||
hoistStatic,
|
||||
cacheHandlers,
|
||||
nodeTransforms,
|
||||
directiveTransforms,
|
||||
onError,
|
||||
@@ -204,6 +217,14 @@ function createTransformContext(
|
||||
false,
|
||||
exp.loc
|
||||
)
|
||||
},
|
||||
cache(exp) {
|
||||
if (cacheHandlers) {
|
||||
context.cached++
|
||||
return createCacheExpression(context.cached, exp)
|
||||
} else {
|
||||
return exp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,6 +294,7 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
|
||||
root.components = [...context.components]
|
||||
root.directives = [...context.directives]
|
||||
root.hoists = context.hoists
|
||||
root.cached = context.cached
|
||||
}
|
||||
|
||||
export function traverseChildren(
|
||||
|
||||
@@ -8,17 +8,14 @@ import {
|
||||
PlainElementNode,
|
||||
ComponentNode,
|
||||
TemplateNode,
|
||||
ElementNode
|
||||
ElementNode,
|
||||
PlainElementCodegenNode
|
||||
} from '../ast'
|
||||
import { TransformContext } from '../transform'
|
||||
import { WITH_DIRECTIVES } from '../runtimeHelpers'
|
||||
import { PatchFlags, isString, isSymbol } from '@vue/shared'
|
||||
import { isSlotOutlet, findProp } from '../utils'
|
||||
|
||||
function hasDynamicKeyOrRef(node: ElementNode) {
|
||||
return findProp(node, 'key', true) || findProp(node, 'ref', true)
|
||||
}
|
||||
|
||||
export function hoistStatic(root: RootNode, context: TransformContext) {
|
||||
walk(
|
||||
root.children,
|
||||
@@ -53,10 +50,11 @@ function walk(
|
||||
child.type === NodeTypes.ELEMENT &&
|
||||
child.tagType === ElementTypes.ELEMENT
|
||||
) {
|
||||
const hasBailoutProp = hasDynamicKeyOrRef(child) || hasCachedProps(child)
|
||||
if (
|
||||
!doNotHoistNode &&
|
||||
isStaticNode(child, resultCache) &&
|
||||
!hasDynamicKeyOrRef(child)
|
||||
!hasBailoutProp &&
|
||||
isStaticNode(child, resultCache)
|
||||
) {
|
||||
// whole tree is static
|
||||
child.codegenNode = context.hoist(child.codegenNode!)
|
||||
@@ -69,15 +67,11 @@ function walk(
|
||||
(!flag ||
|
||||
flag === PatchFlags.NEED_PATCH ||
|
||||
flag === PatchFlags.TEXT) &&
|
||||
!hasDynamicKeyOrRef(child)
|
||||
!hasBailoutProp
|
||||
) {
|
||||
let codegenNode = child.codegenNode as ElementCodegenNode
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode = codegenNode.arguments[0]
|
||||
}
|
||||
const props = codegenNode.arguments[1]
|
||||
const props = getNodeProps(child)
|
||||
if (props && props !== `null`) {
|
||||
codegenNode.arguments[1] = context.hoist(props)
|
||||
getVNodeCall(child).arguments[1] = context.hoist(props)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,15 +91,6 @@ function walk(
|
||||
}
|
||||
}
|
||||
|
||||
function getPatchFlag(node: PlainElementNode): number | undefined {
|
||||
let codegenNode = node.codegenNode as ElementCodegenNode
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode = codegenNode.arguments[0]
|
||||
}
|
||||
const flag = codegenNode.arguments[3]
|
||||
return flag ? parseInt(flag, 10) : undefined
|
||||
}
|
||||
|
||||
export function isStaticNode(
|
||||
node: TemplateChildNode | SimpleExpressionNode,
|
||||
resultCache: Map<TemplateChildNode, boolean> = new Map()
|
||||
@@ -157,3 +142,51 @@ export function isStaticNode(
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function hasDynamicKeyOrRef(node: ElementNode): boolean {
|
||||
return !!(findProp(node, 'key', true) || findProp(node, 'ref', true))
|
||||
}
|
||||
|
||||
function hasCachedProps(node: PlainElementNode): boolean {
|
||||
if (__BROWSER__) {
|
||||
return false
|
||||
}
|
||||
const props = getNodeProps(node)
|
||||
if (
|
||||
props &&
|
||||
props !== 'null' &&
|
||||
props.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||
) {
|
||||
const { properties } = props
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (properties[i].value.type === NodeTypes.JS_CACHE_EXPRESSION) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function getVNodeCall(node: PlainElementNode) {
|
||||
let codegenNode = node.codegenNode as ElementCodegenNode
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode = codegenNode.arguments[0]
|
||||
}
|
||||
return codegenNode
|
||||
}
|
||||
|
||||
function getVNodeArgAt(
|
||||
node: PlainElementNode,
|
||||
index: number
|
||||
): PlainElementCodegenNode['arguments'][number] {
|
||||
return getVNodeCall(node).arguments[index]
|
||||
}
|
||||
|
||||
function getPatchFlag(node: PlainElementNode): number | undefined {
|
||||
const flag = getVNodeArgAt(node, 3) as string
|
||||
return flag ? parseInt(flag, 10) : undefined
|
||||
}
|
||||
|
||||
function getNodeProps(node: PlainElementNode) {
|
||||
return getVNodeArgAt(node, 1) as PlainElementCodegenNode['arguments'][1]
|
||||
}
|
||||
|
||||
@@ -222,9 +222,10 @@ export function buildProps(
|
||||
const analyzePatchFlag = ({ key, value }: Property) => {
|
||||
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
||||
if (
|
||||
(value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
||||
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
|
||||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
||||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
|
||||
isStaticNode(value)
|
||||
isStaticNode(value))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { DirectiveTransform, TransformContext } from '../transform'
|
||||
import { DirectiveTransform } from '../transform'
|
||||
import {
|
||||
createSimpleExpression,
|
||||
createObjectProperty,
|
||||
createCompoundExpression,
|
||||
NodeTypes,
|
||||
Property,
|
||||
CompoundExpressionNode,
|
||||
createInterpolation,
|
||||
ElementTypes
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { isMemberExpression, isSimpleIdentifier } from '../utils'
|
||||
import { isObject } from '@vue/shared'
|
||||
import { isMemberExpression, isSimpleIdentifier, hasScopeRef } from '../utils'
|
||||
|
||||
export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
const { exp, arg } = dir
|
||||
@@ -54,16 +51,6 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
])
|
||||
: createSimpleExpression('onUpdate:modelValue', true)
|
||||
|
||||
let assignmentChildren =
|
||||
exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children
|
||||
// For a member expression used in assignment, it only needs to be updated
|
||||
// if the expression involves scope variables. Otherwise we can mark the
|
||||
// expression as constant to avoid it being included in `dynamicPropNames`
|
||||
// of the element. This optimization relies on `prefixIdentifiers: true`.
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
assignmentChildren = assignmentChildren.map(c => toConstant(c, context))
|
||||
}
|
||||
|
||||
const props = [
|
||||
// modelValue: foo
|
||||
createObjectProperty(propName, dir.exp!),
|
||||
@@ -72,12 +59,21 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
eventName,
|
||||
createCompoundExpression([
|
||||
`$event => (`,
|
||||
...assignmentChildren,
|
||||
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
|
||||
` = $event)`
|
||||
])
|
||||
)
|
||||
]
|
||||
|
||||
// cache v-model handler if applicable (when it doesn't refer any scope vars)
|
||||
if (
|
||||
!__BROWSER__ &&
|
||||
context.prefixIdentifiers &&
|
||||
!hasScopeRef(exp, context.identifiers)
|
||||
) {
|
||||
props[1].value = context.cache(props[1].value)
|
||||
}
|
||||
|
||||
// modelModifiers: { foo: true, "bar-baz": true }
|
||||
if (dir.modifiers.length && node.tagType === ElementTypes.COMPONENT) {
|
||||
const modifiers = dir.modifiers
|
||||
@@ -94,30 +90,6 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
return createTransformProps(props)
|
||||
}
|
||||
|
||||
function toConstant(
|
||||
exp: CompoundExpressionNode | CompoundExpressionNode['children'][0],
|
||||
context: TransformContext
|
||||
): any {
|
||||
if (!isObject(exp) || exp.type === NodeTypes.TEXT) {
|
||||
return exp
|
||||
}
|
||||
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
if (exp.isStatic || context.identifiers[exp.content]) {
|
||||
return exp
|
||||
}
|
||||
return {
|
||||
...exp,
|
||||
isConstant: true
|
||||
}
|
||||
} else if (exp.type === NodeTypes.COMPOUND_EXPRESSION) {
|
||||
return createCompoundExpression(
|
||||
exp.children.map(c => toConstant(c, context))
|
||||
)
|
||||
} else if (exp.type === NodeTypes.INTERPOLATION) {
|
||||
return createInterpolation(toConstant(exp.content, context), exp.loc)
|
||||
}
|
||||
}
|
||||
|
||||
function createTransformProps(props: Property[] = []) {
|
||||
return { props, needRuntime: false }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
import { DirectiveTransform, DirectiveTransformResult } from '../transform'
|
||||
import {
|
||||
DirectiveNode,
|
||||
createObjectProperty,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { capitalize } from '@vue/shared'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { processExpression } from './transformExpression'
|
||||
import { isMemberExpression } from '../utils'
|
||||
import { isMemberExpression, hasScopeRef } from '../utils'
|
||||
|
||||
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
|
||||
|
||||
@@ -28,7 +28,8 @@ export interface VOnDirectiveNode extends DirectiveNode {
|
||||
export const transformOn: DirectiveTransform = (
|
||||
dir: VOnDirectiveNode,
|
||||
node,
|
||||
context
|
||||
context,
|
||||
augmentor
|
||||
) => {
|
||||
const { loc, modifiers, arg } = dir
|
||||
if (!dir.exp && !modifiers.length) {
|
||||
@@ -51,22 +52,37 @@ export const transformOn: DirectiveTransform = (
|
||||
eventName.children.unshift(`"on" + (`)
|
||||
eventName.children.push(`)`)
|
||||
}
|
||||
// TODO .once modifier handling since it is platform agnostic
|
||||
// other modifiers are handled in compiler-dom
|
||||
|
||||
// handler processing
|
||||
let exp: ExpressionNode | undefined = dir.exp
|
||||
let isCacheable: boolean = !exp
|
||||
if (exp) {
|
||||
const isInlineStatement = !(
|
||||
isMemberExpression(exp.content) || fnExpRE.test(exp.content)
|
||||
)
|
||||
const isMemberExp = isMemberExpression(exp.content)
|
||||
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
|
||||
|
||||
// process the expression since it's been skipped
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
context.addIdentifiers(`$event`)
|
||||
exp = processExpression(exp, context)
|
||||
context.removeIdentifiers(`$event`)
|
||||
// with scope analysis, the function is hoistable if it has no reference
|
||||
// to scope variables.
|
||||
isCacheable =
|
||||
context.cacheHandlers && !hasScopeRef(exp, context.identifiers)
|
||||
// If the expression is optimizable and is a member expression pointing
|
||||
// to a function, turn it into invocation (and wrap in an arrow function
|
||||
// below) so that it always accesses the latest value when called - thus
|
||||
// avoiding the need to be patched.
|
||||
if (isCacheable && isMemberExp) {
|
||||
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
exp.content += `($event)`
|
||||
} else {
|
||||
exp.children.push(`($event)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isInlineStatement) {
|
||||
|
||||
if (isInlineStatement || (isCacheable && isMemberExp)) {
|
||||
// wrap inline statement in a function expression
|
||||
exp = createCompoundExpression([
|
||||
`$event => (`,
|
||||
@@ -76,7 +92,7 @@ export const transformOn: DirectiveTransform = (
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
let ret: DirectiveTransformResult = {
|
||||
props: [
|
||||
createObjectProperty(
|
||||
eventName,
|
||||
@@ -85,4 +101,18 @@ export const transformOn: DirectiveTransform = (
|
||||
],
|
||||
needRuntime: false
|
||||
}
|
||||
|
||||
// apply extended compiler augmentor
|
||||
if (augmentor) {
|
||||
ret = augmentor(ret)
|
||||
}
|
||||
|
||||
if (isCacheable) {
|
||||
// cache handlers so that it's always the same handler being passed down.
|
||||
// this avoids unnecessary re-renders when users use inline hanlders on
|
||||
// components.
|
||||
ret.props[0].value = context.cache(ret.props[0].value)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -19,21 +19,13 @@ import {
|
||||
FunctionExpression,
|
||||
CallExpression,
|
||||
createCallExpression,
|
||||
createArrayExpression,
|
||||
IfBranchNode
|
||||
createArrayExpression
|
||||
} from '../ast'
|
||||
import { TransformContext, NodeTransform } from '../transform'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import {
|
||||
findDir,
|
||||
isTemplateNode,
|
||||
assert,
|
||||
isVSlot,
|
||||
isSimpleIdentifier
|
||||
} from '../utils'
|
||||
import { findDir, isTemplateNode, assert, isVSlot, hasScopeRef } from '../utils'
|
||||
import { CREATE_SLOTS, RENDER_LIST } from '../runtimeHelpers'
|
||||
import { parseForExpression, createForLoopParams } from './vFor'
|
||||
import { isObject } from '@vue/shared'
|
||||
|
||||
const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
||||
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
||||
@@ -337,49 +329,3 @@ function buildDynamicSlot(
|
||||
createObjectProperty(`fn`, fn)
|
||||
])
|
||||
}
|
||||
|
||||
function hasScopeRef(
|
||||
node: TemplateChildNode | IfBranchNode | SimpleExpressionNode | undefined,
|
||||
ids: TransformContext['identifiers']
|
||||
): boolean {
|
||||
if (!node || Object.keys(ids).length === 0) {
|
||||
return false
|
||||
}
|
||||
switch (node.type) {
|
||||
case NodeTypes.ELEMENT:
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const p = node.props[i]
|
||||
if (
|
||||
p.type === NodeTypes.DIRECTIVE &&
|
||||
(hasScopeRef(p.arg, ids) || hasScopeRef(p.exp, ids))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return node.children.some(c => hasScopeRef(c, ids))
|
||||
case NodeTypes.FOR:
|
||||
if (hasScopeRef(node.source, ids)) {
|
||||
return true
|
||||
}
|
||||
return node.children.some(c => hasScopeRef(c, ids))
|
||||
case NodeTypes.IF:
|
||||
return node.branches.some(b => hasScopeRef(b, ids))
|
||||
case NodeTypes.IF_BRANCH:
|
||||
if (hasScopeRef(node.condition, ids)) {
|
||||
return true
|
||||
}
|
||||
return node.children.some(c => hasScopeRef(c, ids))
|
||||
case NodeTypes.SIMPLE_EXPRESSION:
|
||||
return (
|
||||
!node.isStatic &&
|
||||
isSimpleIdentifier(node.content) &&
|
||||
!!ids[node.content]
|
||||
)
|
||||
case NodeTypes.COMPOUND_EXPRESSION:
|
||||
return node.children.some(c => isObject(c) && hasScopeRef(c, ids))
|
||||
case NodeTypes.INTERPOLATION:
|
||||
return hasScopeRef(node.content, ids)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,14 @@ import {
|
||||
ElementCodegenNode,
|
||||
SlotOutletCodegenNode,
|
||||
ComponentCodegenNode,
|
||||
ExpressionNode
|
||||
ExpressionNode,
|
||||
IfBranchNode
|
||||
} from './ast'
|
||||
import { parse } from 'acorn'
|
||||
import { walk } from 'estree-walker'
|
||||
import { TransformContext } from './transform'
|
||||
import { OPEN_BLOCK, MERGE_PROPS, RENDER_SLOT } from './runtimeHelpers'
|
||||
import { isString, isFunction } from '@vue/shared'
|
||||
import { isString, isFunction, isObject } from '@vue/shared'
|
||||
|
||||
// cache node requires
|
||||
// lazy require dependencies so that they don't end up in rollup's dep graph
|
||||
@@ -250,3 +251,51 @@ export function toValidAssetId(
|
||||
export function isEmptyExpression(node: ExpressionNode) {
|
||||
return node.type === NodeTypes.SIMPLE_EXPRESSION && !node.content.trim()
|
||||
}
|
||||
|
||||
// Check if a node contains expressions that reference current context scope ids
|
||||
export function hasScopeRef(
|
||||
node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
|
||||
ids: TransformContext['identifiers']
|
||||
): boolean {
|
||||
if (!node || Object.keys(ids).length === 0) {
|
||||
return false
|
||||
}
|
||||
switch (node.type) {
|
||||
case NodeTypes.ELEMENT:
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const p = node.props[i]
|
||||
if (
|
||||
p.type === NodeTypes.DIRECTIVE &&
|
||||
(hasScopeRef(p.arg, ids) || hasScopeRef(p.exp, ids))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return node.children.some(c => hasScopeRef(c, ids))
|
||||
case NodeTypes.FOR:
|
||||
if (hasScopeRef(node.source, ids)) {
|
||||
return true
|
||||
}
|
||||
return node.children.some(c => hasScopeRef(c, ids))
|
||||
case NodeTypes.IF:
|
||||
return node.branches.some(b => hasScopeRef(b, ids))
|
||||
case NodeTypes.IF_BRANCH:
|
||||
if (hasScopeRef(node.condition, ids)) {
|
||||
return true
|
||||
}
|
||||
return node.children.some(c => hasScopeRef(c, ids))
|
||||
case NodeTypes.SIMPLE_EXPRESSION:
|
||||
return (
|
||||
!node.isStatic &&
|
||||
isSimpleIdentifier(node.content) &&
|
||||
!!ids[node.content]
|
||||
)
|
||||
case NodeTypes.COMPOUND_EXPRESSION:
|
||||
return node.children.some(c => isObject(c) && hasScopeRef(c, ids))
|
||||
case NodeTypes.INTERPOLATION:
|
||||
return hasScopeRef(node.content, ids)
|
||||
default:
|
||||
// TextNode or CommentNode
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user