"children": Array [
Object {
"content": Object {
+ "constType": 0,
"content": "a < b",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
"children": Array [
Object {
"content": Object {
+ "constType": 0,
"content": "'</div>'",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
"props": Array [
Object {
"arg": Object {
+ "constType": 0,
"content": "se",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
"children": Array [
Object {
"content": Object {
+ "constType": 0,
"content": "",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
"props": Array [
Object {
"arg": Object {
+ "constType": 3,
"content": "class",
- "isConstant": true,
"isStatic": true,
"loc": Object {
"end": Object {
"type": 4,
},
"exp": Object {
+ "constType": 0,
"content": "{ some: condition }",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
"props": Array [
Object {
"arg": Object {
+ "constType": 3,
"content": "style",
- "isConstant": true,
"isStatic": true,
"loc": Object {
"end": Object {
"type": 4,
},
"exp": Object {
+ "constType": 0,
"content": "{ color: 'red' }",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
"props": Array [
Object {
"arg": Object {
+ "constType": 3,
"content": "style",
- "isConstant": true,
"isStatic": true,
"loc": Object {
"end": Object {
"type": 4,
},
"exp": Object {
+ "constType": 0,
"content": "{ color: 'red' }",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
"props": Array [
Object {
"arg": Object {
+ "constType": 3,
"content": "class",
- "isConstant": true,
"isStatic": true,
"loc": Object {
"end": Object {
"type": 4,
},
"exp": Object {
+ "constType": 0,
"content": "{ some: condition }",
- "isConstant": false,
"isStatic": false,
"loc": Object {
"end": Object {
IfConditionalExpression,
createVNodeCall,
VNodeCall,
- DirectiveArguments
+ DirectiveArguments,
+ ConstantTypes
} from '../src'
import {
CREATE_VNODE,
codegenNode: {
type: NodeTypes.FOR,
loc: locStub,
- source: createSimpleExpression('1 + 2', false, locStub, true),
+ source: createSimpleExpression(
+ '1 + 2',
+ false,
+ locStub,
+ ConstantTypes.CAN_STRINGIFY
+ ),
valueAlias: undefined,
keyAlias: undefined,
objectIndexAlias: undefined,
NodeTypes,
Position,
TextNode,
- InterpolationNode
+ InterpolationNode,
+ ConstantTypes
} from '../src/ast'
describe('compiler: parse', () => {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `message`,
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 2, line: 1, column: 3 },
end: { offset: 9, line: 1, column: 10 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a<b`,
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a<b`,
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 },
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
content: 'c>d',
loc: {
start: { offset: 12, line: 1, column: 13 },
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
- // The `isConstant` is the default value and will be determined in `transformExpression`.
- isConstant: false,
+ // The `constType` is the default value and will be determined in `transformExpression`.
+ constType: ConstantTypes.NOT_CONSTANT,
content: '"</div>"',
loc: {
start: { offset: 8, line: 1, column: 9 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: `msg`,
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 4, line: 1, column: 5 },
end: { offset: 7, line: 1, column: 8 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 11, line: 1, column: 12 },
end: { offset: 12, line: 1, column: 13 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'click',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'event',
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
source: '[event]',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'click',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'click',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a.b',
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
source: '[a.b]',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'a',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 8, line: 1, column: 9 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'a',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 13, line: 1, column: 14 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'a',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 8, line: 1, column: 9 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'a',
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'b',
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 14, line: 1, column: 15 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'a',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'a',
start: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: '{ b }',
isStatic: false,
- // The `isConstant` is the default value and will be determined in transformExpression
- isConstant: false,
+ // The `constType` is the default value and will be determined in transformExpression
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 10, line: 1, column: 11 },
end: { offset: 15, line: 1, column: 16 },
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo.bar',
isStatic: true,
- isConstant: true,
+ constType: ConstantTypes.CAN_STRINGIFY,
loc: {
source: 'foo.bar',
start: {
VNodeCall,
IfNode,
ElementNode,
- ForNode
+ ForNode,
+ ConstantTypes
} from '../../src'
import { FRAGMENT, RENDER_LIST, CREATE_TEXT } from '../../src/runtimeHelpers'
import { transformElement } from '../../src/transforms/transformElement'
content: {
content: `1`,
isStatic: false,
- isConstant: true
+ constType: ConstantTypes.CAN_STRINGIFY
}
}
}
{
key: {
content: `class`,
- isConstant: true,
- isStatic: true
+ isStatic: true,
+ constType: ConstantTypes.CAN_STRINGIFY
},
value: {
content: `{ foo: true }`,
- isConstant: true,
- isStatic: false
+ isStatic: false,
+ constType: ConstantTypes.CAN_STRINGIFY
}
}
]
type: NodeTypes.INTERPOLATION,
content: {
content: `_ctx.bar`,
- isConstant: false,
- isStatic: false
+ isStatic: false,
+ constType: ConstantTypes.NOT_CONSTANT
}
},
patchFlag: `1 /* TEXT */`
DirectiveNode,
NodeTypes,
CompilerOptions,
- InterpolationNode
+ InterpolationNode,
+ ConstantTypes
} from '../../src'
import { transformIf } from '../../src/transforms/vIf'
import { transformExpression } from '../../src/transforms/transformExpression'
type: NodeTypes.SIMPLE_EXPRESSION,
content: `13000n`,
isStatic: false,
- isConstant: true
+ constType: ConstantTypes.CAN_STRINGIFY
})
})
SimpleExpressionNode,
ElementNode,
InterpolationNode,
- ForCodegenNode
+ ForCodegenNode,
+ ConstantTypes
} from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src'
false /* disableTracking */
)
).toMatchObject({
- source: { content: `10`, isConstant: true },
+ source: { content: `10`, constType: ConstantTypes.CAN_STRINGIFY },
params: [{ content: `item` }],
innerVNodeCall: {
tag: `"p"`,
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
isStatic: false,
- isConstant: false
+ constType: ConstantTypes.NOT_CONSTANT
}
},
patchFlag: genFlagText(PatchFlags.TEXT)
parseResult?: ForParseResult
}
+/**
+ * Static types have several levels.
+ * Higher levels implies lower levels. e.g. a node that can be stringified
+ * can always be hoisted and skipped for patch.
+ */
+export const enum ConstantTypes {
+ NOT_CONSTANT = 0,
+ CAN_SKIP_PATCH,
+ CAN_HOIST,
+ CAN_STRINGIFY
+}
+
export interface SimpleExpressionNode extends Node {
type: NodeTypes.SIMPLE_EXPRESSION
content: string
isStatic: boolean
- isConstant: boolean
+ constType: ConstantTypes
/**
* Indicates this is an identifier for a hoist vnode call and points to the
* hoisted node.
* the identifiers declared inside the function body.
*/
identifiers?: string[]
- /**
- * some expressions (e.g. transformAssetUrls import identifiers) are constant,
- * but cannot be stringified because they must be first evaluated at runtime.
- */
- isRuntimeConstant?: boolean
}
export interface InterpolationNode extends Node {
content: SimpleExpressionNode['content'],
isStatic: SimpleExpressionNode['isStatic'],
loc: SourceLocation = locStub,
- isConstant: boolean = false
+ constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
): SimpleExpressionNode {
return {
type: NodeTypes.SIMPLE_EXPRESSION,
loc,
- isConstant,
content,
- isStatic
+ isStatic,
+ constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType
}
}
TextNode,
TemplateChildNode,
InterpolationNode,
- createRoot
+ createRoot,
+ ConstantTypes
} from './ast'
type OptionalOptions = 'isNativeTag' | 'isBuiltInComponent'
type: NodeTypes.SIMPLE_EXPRESSION,
content,
isStatic,
- isConstant: isStatic,
+ constType: isStatic
+ ? ConstantTypes.CAN_STRINGIFY
+ : ConstantTypes.NOT_CONSTANT,
loc
}
}
content: value.content,
isStatic: false,
// Treat as non-constant by default. This can be potentially set to
- // true by `transformExpression` to make it eligible for hoisting.
- isConstant: false,
+ // other values by `transformExpression` to make it eligible for hoisting.
+ constType: ConstantTypes.NOT_CONSTANT,
loc: value.loc
},
arg,
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
// Set `isConstant` to false by default and will decide in transformExpression
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
content,
loc: getSelection(context, innerStart, innerEnd)
},
CacheExpression,
createCacheExpression,
TemplateLiteral,
- createVNodeCall
+ createVNodeCall,
+ ConstantTypes
} from './ast'
import {
isString,
`_hoisted_${context.hoists.length}`,
false,
exp.loc,
- true
+ ConstantTypes.CAN_HOIST
)
identifier.hoisted = exp
return identifier
import {
+ ConstantTypes,
RootNode,
NodeTypes,
TemplateChildNode,
)
}
-const enum StaticType {
- NOT_STATIC = 0,
- FULL_STATIC,
- HAS_RUNTIME_CONSTANT
-}
-
function walk(
node: ParentNode,
context: TransformContext,
- resultCache: Map<TemplateChildNode, StaticType>,
+ resultCache: Map<TemplateChildNode, ConstantTypes>,
doNotHoistNode: boolean = false
) {
let hasHoistedNode = false
// @vue/compiler-dom), but doing it here allows us to perform only one full
// walk of the AST and allow `stringifyStatic` to stop walking as soon as its
// stringficiation threshold is met.
- let hasRuntimeConstant = false
+ let canStringify = true
const { children } = node
for (let i = 0; i < children.length; i++) {
child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.ELEMENT
) {
- let staticType
- if (
- !doNotHoistNode &&
- (staticType = getStaticType(child, resultCache)) > 0
- ) {
- if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
- hasRuntimeConstant = true
+ const constantType = doNotHoistNode
+ ? ConstantTypes.NOT_CONSTANT
+ : getConstantType(child, resultCache)
+ if (constantType > ConstantTypes.NOT_CONSTANT) {
+ if (constantType < ConstantTypes.CAN_STRINGIFY) {
+ canStringify = false
+ }
+ if (constantType >= ConstantTypes.CAN_HOIST) {
+ ;(child.codegenNode as VNodeCall).patchFlag =
+ PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
+ child.codegenNode = context.hoist(child.codegenNode!)
+ hasHoistedNode = true
+ continue
}
- // whole tree is static
- ;(child.codegenNode as VNodeCall).patchFlag =
- PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
- child.codegenNode = context.hoist(child.codegenNode!)
- hasHoistedNode = true
- continue
} else {
// node may contain dynamic children, but its props may be eligible for
// hoisting.
(!flag ||
flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) &&
- !hasNonHoistableProps(child)
+ getGeneratedPropsConstantType(child, resultCache) >=
+ ConstantTypes.CAN_HOIST
) {
const props = getNodeProps(child)
if (props) {
}
}
} else if (child.type === NodeTypes.TEXT_CALL) {
- const staticType = getStaticType(child.content, resultCache)
- if (staticType > 0) {
- if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
- hasRuntimeConstant = true
+ const contentType = getConstantType(child.content, resultCache)
+ if (contentType > 0) {
+ if (contentType < ConstantTypes.CAN_STRINGIFY) {
+ canStringify = false
+ }
+ if (contentType >= ConstantTypes.CAN_HOIST) {
+ child.codegenNode = context.hoist(child.codegenNode)
+ hasHoistedNode = true
}
- child.codegenNode = context.hoist(child.codegenNode)
- hasHoistedNode = true
}
}
}
}
- if (!hasRuntimeConstant && hasHoistedNode && context.transformHoist) {
+ if (canStringify && hasHoistedNode && context.transformHoist) {
context.transformHoist(children, context, node)
}
}
-export function getStaticType(
+export function getConstantType(
node: TemplateChildNode | SimpleExpressionNode,
- resultCache: Map<TemplateChildNode, StaticType> = new Map()
-): StaticType {
+ resultCache: Map<TemplateChildNode, ConstantTypes> = new Map()
+): ConstantTypes {
switch (node.type) {
case NodeTypes.ELEMENT:
if (node.tagType !== ElementTypes.ELEMENT) {
- return StaticType.NOT_STATIC
+ return ConstantTypes.NOT_CONSTANT
}
const cached = resultCache.get(node)
if (cached !== undefined) {
}
const codegenNode = node.codegenNode!
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
- return StaticType.NOT_STATIC
+ return ConstantTypes.NOT_CONSTANT
}
const flag = getPatchFlag(codegenNode)
- if (!flag && !hasNonHoistableProps(node)) {
- // element self is static. check its children.
- let returnType = StaticType.FULL_STATIC
+ if (!flag) {
+ let returnType = ConstantTypes.CAN_STRINGIFY
+
+ // Element itself has no patch flag. However we still need to check:
+
+ // 1. Even for a node with no patch flag, it is possible for it to contain
+ // non-hoistable expressions that refers to scope variables, e.g. compiler
+ // injected keys or cached event handlers. Therefore we need to always
+ // check the codegenNode's props to be sure.
+ const generatedPropsType = getGeneratedPropsConstantType(
+ node,
+ resultCache
+ )
+ if (generatedPropsType === ConstantTypes.NOT_CONSTANT) {
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+ return ConstantTypes.NOT_CONSTANT
+ }
+ if (generatedPropsType < returnType) {
+ returnType = generatedPropsType
+ }
+
+ // 2. its children.
for (let i = 0; i < node.children.length; i++) {
- const childType = getStaticType(node.children[i], resultCache)
- if (childType === StaticType.NOT_STATIC) {
- resultCache.set(node, StaticType.NOT_STATIC)
- return StaticType.NOT_STATIC
- } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) {
- returnType = StaticType.HAS_RUNTIME_CONSTANT
+ const childType = getConstantType(node.children[i], resultCache)
+ if (childType === ConstantTypes.NOT_CONSTANT) {
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+ return ConstantTypes.NOT_CONSTANT
+ }
+ if (childType < returnType) {
+ returnType = childType
}
}
- // check if any of the props contain runtime constants
- if (returnType !== StaticType.HAS_RUNTIME_CONSTANT) {
+ // 3. if the type is not already CAN_SKIP_PATCH which is the lowest non-0
+ // type, check if any of the props can cause the type to be lowered
+ // we can skip can_patch because it's guaranteed by the absence of a
+ // patchFlag.
+ if (returnType > ConstantTypes.CAN_SKIP_PATCH) {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
- if (
- p.type === NodeTypes.DIRECTIVE &&
- p.name === 'bind' &&
- p.exp &&
- (p.exp.type === NodeTypes.COMPOUND_EXPRESSION ||
- p.exp.isRuntimeConstant)
- ) {
- returnType = StaticType.HAS_RUNTIME_CONSTANT
+ if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind' && p.exp) {
+ const expType = getConstantType(p.exp, resultCache)
+ if (expType === ConstantTypes.NOT_CONSTANT) {
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+ return ConstantTypes.NOT_CONSTANT
+ }
+ if (expType < returnType) {
+ returnType = expType
+ }
}
}
}
// only svg/foreignObject could be block here, however if they are
- // stati then they don't need to be blocks since there will be no
+ // static then they don't need to be blocks since there will be no
// nested updates.
if (codegenNode.isBlock) {
codegenNode.isBlock = false
resultCache.set(node, returnType)
return returnType
} else {
- resultCache.set(node, StaticType.NOT_STATIC)
- return StaticType.NOT_STATIC
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
+ return ConstantTypes.NOT_CONSTANT
}
case NodeTypes.TEXT:
case NodeTypes.COMMENT:
- return StaticType.FULL_STATIC
+ return ConstantTypes.CAN_STRINGIFY
case NodeTypes.IF:
case NodeTypes.FOR:
case NodeTypes.IF_BRANCH:
- return StaticType.NOT_STATIC
+ return ConstantTypes.NOT_CONSTANT
case NodeTypes.INTERPOLATION:
case NodeTypes.TEXT_CALL:
- return getStaticType(node.content, resultCache)
+ return getConstantType(node.content, resultCache)
case NodeTypes.SIMPLE_EXPRESSION:
- return node.isRuntimeConstant
- ? StaticType.HAS_RUNTIME_CONSTANT
- : node.isConstant
- ? StaticType.FULL_STATIC
- : StaticType.NOT_STATIC
+ return node.constType
case NodeTypes.COMPOUND_EXPRESSION:
- let returnType = StaticType.FULL_STATIC
+ let returnType = ConstantTypes.CAN_STRINGIFY
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]
if (isString(child) || isSymbol(child)) {
continue
}
- const childType = getStaticType(child, resultCache)
- if (childType === StaticType.NOT_STATIC) {
- return StaticType.NOT_STATIC
- } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) {
- returnType = StaticType.HAS_RUNTIME_CONSTANT
+ const childType = getConstantType(child, resultCache)
+ if (childType === ConstantTypes.NOT_CONSTANT) {
+ return ConstantTypes.NOT_CONSTANT
+ } else if (childType < returnType) {
+ returnType = childType
}
}
return returnType
const exhaustiveCheck: never = node
exhaustiveCheck
}
- return StaticType.NOT_STATIC
+ return ConstantTypes.NOT_CONSTANT
}
}
-/**
- * Even for a node with no patch flag, it is possible for it to contain
- * non-hoistable expressions that refers to scope variables, e.g. compiler
- * injected keys or cached event handlers. Therefore we need to always check the
- * codegenNode's props to be sure.
- */
-function hasNonHoistableProps(node: PlainElementNode): boolean {
+function getGeneratedPropsConstantType(
+ node: PlainElementNode,
+ resultCache: Map<TemplateChildNode, ConstantTypes>
+): ConstantTypes {
+ let returnType = ConstantTypes.CAN_STRINGIFY
const props = getNodeProps(node)
if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
const { properties } = props
for (let i = 0; i < properties.length; i++) {
const { key, value } = properties[i]
- if (
- key.type !== NodeTypes.SIMPLE_EXPRESSION ||
- !key.isStatic ||
- (value.type !== NodeTypes.SIMPLE_EXPRESSION ||
- (!value.isStatic && !value.isConstant))
- ) {
- return true
+ const keyType = getConstantType(key, resultCache)
+ if (keyType === ConstantTypes.NOT_CONSTANT) {
+ return keyType
+ }
+ if (keyType < returnType) {
+ returnType = keyType
+ }
+ if (value.type !== NodeTypes.SIMPLE_EXPRESSION) {
+ return ConstantTypes.NOT_CONSTANT
+ }
+ const valueType = getConstantType(value, resultCache)
+ if (valueType === ConstantTypes.NOT_CONSTANT) {
+ return valueType
+ }
+ if (valueType < returnType) {
+ returnType = valueType
}
}
}
- return false
+ return returnType
}
function getNodeProps(node: PlainElementNode) {
VNodeCall,
TemplateTextChildNode,
DirectiveArguments,
- createVNodeCall
+ createVNodeCall,
+ ConstantTypes
} from '../ast'
import {
PatchFlags,
isStaticExp
} from '../utils'
import { buildSlots } from './vSlot'
-import { getStaticType } from './hoistStatic'
+import { getConstantType } from './hoistStatic'
import { BindingTypes } from '../options'
// some directive transforms (e.g. v-model) may return a symbol for runtime
const hasDynamicTextChild =
type === NodeTypes.INTERPOLATION ||
type === NodeTypes.COMPOUND_EXPRESSION
- if (hasDynamicTextChild && !getStaticType(child)) {
+ if (
+ hasDynamicTextChild &&
+ getConstantType(child) === ConstantTypes.NOT_CONSTANT
+ ) {
patchFlag |= PatchFlags.TEXT
}
// pass directly if the only child is a text node
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
- getStaticType(value) > 0)
+ getConstantType(value) > 0)
) {
// skip if the prop is a cached handler or has constant value
return
ExpressionNode,
SimpleExpressionNode,
CompoundExpressionNode,
- createCompoundExpression
+ createCompoundExpression,
+ ConstantTypes
} from '../ast'
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
import {
// fast path if expression is a simple identifier.
const rawExp = node.content
- // bail on parens to prevent any possible function invocations.
- const bailConstant = rawExp.indexOf(`(`) > -1
+ // bail constant on parens (function invocation) and dot (member access)
+ const bailConstant = rawExp.indexOf(`(`) > -1 || rawExp.indexOf('.') > 0
+
if (isSimpleIdentifier(rawExp)) {
- // const bindings exposed from setup - we know they never change
- // marking it as runtime constant will prevent it from being listed as
- // a dynamic prop.
- if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) {
- node.isRuntimeConstant = true
- }
- if (
- !asParams &&
- !context.identifiers[rawExp] &&
- !isGloballyWhitelisted(rawExp) &&
- !isLiteralWhitelisted(rawExp)
- ) {
+ const isScopeVarReference = context.identifiers[rawExp]
+ const isAllowedGlobal = isGloballyWhitelisted(rawExp)
+ const isLiteral = isLiteralWhitelisted(rawExp)
+ if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) {
+ // const bindings exposed from setup can be skipped for patching but
+ // cannot be hoisted to module scope
+ if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) {
+ node.constType = ConstantTypes.CAN_SKIP_PATCH
+ }
node.content = rewriteIdentifier(rawExp)
- } else if (!context.identifiers[rawExp] && !bailConstant) {
- // mark node constant for hoisting unless it's referring a scope variable
- node.isConstant = true
+ } else if (!isScopeVarReference) {
+ if (isLiteral) {
+ node.constType = ConstantTypes.CAN_STRINGIFY
+ } else {
+ node.constType = ConstantTypes.CAN_HOIST
+ }
}
return node
}
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end)
},
- id.isConstant /* isConstant */
+ id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
)
)
if (i === ids.length - 1 && end < rawExp.length) {
ret = createCompoundExpression(children, node.loc)
} else {
ret = node
- ret.isConstant = !bailConstant
+ ret.constType = bailConstant
+ ? ConstantTypes.NOT_CONSTANT
+ : ConstantTypes.CAN_STRINGIFY
}
ret.identifiers = Object.keys(knownIds)
return ret
CompoundExpressionNode,
createCallExpression,
CallExpression,
- ElementTypes
+ ElementTypes,
+ ConstantTypes
} from '../ast'
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
+import { getConstantType } from './hoistStatic'
// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
callArgs.push(child)
}
// mark dynamic text with flag so it gets patched inside a block
- if (!context.ssr && child.type !== NodeTypes.TEXT) {
+ if (
+ !context.ssr &&
+ getConstantType(child) === ConstantTypes.NOT_CONSTANT
+ ) {
callArgs.push(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`
)
const isStableFragment =
forNode.source.type === NodeTypes.SIMPLE_EXPRESSION &&
- forNode.source.isConstant
+ forNode.source.constType > 0
const fragmentFlag = isStableFragment
? PatchFlags.STABLE_FRAGMENT
: keyProp
createVNodeCall,
AttributeNode,
locStub,
- CacheExpression
+ CacheExpression,
+ ConstantTypes
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
const { helper } = context
const keyProperty = createObjectProperty(
`key`,
- createSimpleExpression(`${keyIndex}`, false, locStub, true)
+ createSimpleExpression(
+ `${keyIndex}`,
+ false,
+ locStub,
+ ConstantTypes.CAN_HOIST
+ )
)
const { children } = branch
const firstChild = children[0]
NodeTypes,
Property,
ElementTypes,
- ExpressionNode
+ ExpressionNode,
+ ConstantTypes
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import {
props.push(
createObjectProperty(
modifiersKey,
- createSimpleExpression(`{ ${modifiers} }`, false, dir.loc, true)
+ createSimpleExpression(
+ `{ ${modifiers} }`,
+ false,
+ dir.loc,
+ ConstantTypes.CAN_HOIST
+ )
)
)
}
context.cacheHandlers &&
// runtime constants don't need to be cached
// (this is analyzed by compileScript in SFC <script setup>)
- !(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.isRuntimeConstant) &&
+ !(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.constType > 0) &&
// #1541 bail if this is a member exp handler passed to a component -
// we need to use the original function to preserve arity,
// e.g. <transition> relies on checking cb.length to determine
ErrorCodes,
ElementTypes,
InterpolationNode,
- AttributeNode
+ AttributeNode,
+ ConstantTypes
} from '@vue/compiler-core'
import { parserOptions, DOMNamespaces } from '../src/parserOptions'
type: NodeTypes.SIMPLE_EXPRESSION,
content: `a < b`,
isStatic: false,
- isConstant: false,
+ constType: ConstantTypes.NOT_CONSTANT,
loc: {
start: { offset: 8, line: 1, column: 9 },
end: { offset: 16, line: 1, column: 17 },
compile,
NodeTypes,
CREATE_STATIC,
- createSimpleExpression
+ createSimpleExpression,
+ ConstantTypes
} from '../../src'
import {
stringifyStatic,
'_imports_0_',
false,
node.loc,
- true
+ ConstantTypes.CAN_HOIST
)
- exp.isRuntimeConstant = true
node.props[0] = {
type: NodeTypes.DIRECTIVE,
name: 'bind',
NodeTypes,
createSimpleExpression,
SimpleExpressionNode,
- SourceLocation
+ SourceLocation,
+ ConstantTypes
} from '@vue/compiler-core'
import { parseStringStyle } from '@vue/shared'
loc: SourceLocation
): SimpleExpressionNode => {
const normalized = parseStringStyle(cssText)
- return createSimpleExpression(JSON.stringify(normalized), false, loc, true)
+ return createSimpleExpression(
+ JSON.stringify(normalized),
+ false,
+ loc,
+ ConstantTypes.CAN_STRINGIFY
+ )
}
import path from 'path'
import {
+ ConstantTypes,
createSimpleExpression,
ExpressionNode,
NodeTransform,
return existing.exp as ExpressionNode
}
const name = `_imports_${importsArray.length}`
- const exp = createSimpleExpression(name, false, loc, true)
- exp.isRuntimeConstant = true
+ const exp = createSimpleExpression(
+ name,
+ false,
+ loc,
+ ConstantTypes.CAN_HOIST
+ )
context.imports.add({ exp, path })
if (hash && path) {
- const ret = context.hoist(
- createSimpleExpression(`${name} + '${hash}'`, false, loc, true)
+ return context.hoist(
+ createSimpleExpression(
+ `${name} + '${hash}'`,
+ false,
+ loc,
+ ConstantTypes.CAN_HOIST
+ )
)
- ret.isRuntimeConstant = true
- return ret
} else {
return exp
}
} else {
- return createSimpleExpression(`''`, false, loc, true)
+ return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_HOIST)
}
}
import path from 'path'
import {
+ ConstantTypes,
createCompoundExpression,
createSimpleExpression,
NodeTransform,
`_imports_${existingImportsIndex}`,
false,
attr.loc,
- true
+ ConstantTypes.CAN_HOIST
)
} else {
exp = createSimpleExpression(
`_imports_${importsArray.length}`,
false,
attr.loc,
- true
+ ConstantTypes.CAN_HOIST
)
context.imports.add({ exp, path })
}
`"${url}"`,
false,
attr.loc,
- true
+ ConstantTypes.CAN_HOIST
)
compoundExpression.children.push(exp)
}
})
const hoisted = context.hoist(compoundExpression)
- hoisted.isRuntimeConstant = true
+ hoisted.constType = ConstantTypes.CAN_HOIST
node.props[index] = {
type: NodeTypes.DIRECTIVE,