fix(compiler-dom): should bail stringification on runtime constant regardless of position
ref: vuejs/vite#157
This commit is contained in:
parent
45c96a06aa
commit
dd2bfb5a8f
@ -37,13 +37,29 @@ export function isSingleElementRoot(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enum StaticType {
|
||||||
|
NOT_STATIC = 0,
|
||||||
|
FULL_STATIC,
|
||||||
|
HAS_RUNTIME_CONSTANT
|
||||||
|
}
|
||||||
|
|
||||||
function walk(
|
function walk(
|
||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
context: TransformContext,
|
context: TransformContext,
|
||||||
resultCache: Map<TemplateChildNode, boolean>,
|
resultCache: Map<TemplateChildNode, StaticType>,
|
||||||
doNotHoistNode: boolean = false
|
doNotHoistNode: boolean = false
|
||||||
) {
|
) {
|
||||||
let hasHoistedNode = false
|
let hasHoistedNode = false
|
||||||
|
// Some transforms, e.g. trasnformAssetUrls from @vue/compiler-sfc, replaces
|
||||||
|
// static bindings with expressions. These expressions are guaranteed to be
|
||||||
|
// constant so they are still eligible for hoisting, but they are only
|
||||||
|
// available at runtime and therefore cannot be evaluated ahead of time.
|
||||||
|
// This is only a concern for pre-stringification (via transformHoist by
|
||||||
|
// @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
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const child = children[i]
|
const child = children[i]
|
||||||
// only plain elements & text calls are eligible for hoisting.
|
// only plain elements & text calls are eligible for hoisting.
|
||||||
@ -51,7 +67,14 @@ function walk(
|
|||||||
child.type === NodeTypes.ELEMENT &&
|
child.type === NodeTypes.ELEMENT &&
|
||||||
child.tagType === ElementTypes.ELEMENT
|
child.tagType === ElementTypes.ELEMENT
|
||||||
) {
|
) {
|
||||||
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
|
let staticType
|
||||||
|
if (
|
||||||
|
!doNotHoistNode &&
|
||||||
|
(staticType = getStaticType(child, resultCache)) > 0
|
||||||
|
) {
|
||||||
|
if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
|
||||||
|
hasRuntimeConstant = true
|
||||||
|
}
|
||||||
// whole tree is static
|
// whole tree is static
|
||||||
;(child.codegenNode as VNodeCall).patchFlag =
|
;(child.codegenNode as VNodeCall).patchFlag =
|
||||||
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
|
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
|
||||||
@ -78,12 +101,15 @@ function walk(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (child.type === NodeTypes.TEXT_CALL) {
|
||||||
child.type === NodeTypes.TEXT_CALL &&
|
const staticType = getStaticType(child.content, resultCache)
|
||||||
isStaticNode(child.content, resultCache)
|
if (staticType > 0) {
|
||||||
) {
|
if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
|
||||||
child.codegenNode = context.hoist(child.codegenNode)
|
hasRuntimeConstant = true
|
||||||
hasHoistedNode = true
|
}
|
||||||
|
child.codegenNode = context.hoist(child.codegenNode)
|
||||||
|
hasHoistedNode = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// walk further
|
// walk further
|
||||||
@ -101,19 +127,19 @@ function walk(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasHoistedNode && context.transformHoist) {
|
if (!hasRuntimeConstant && hasHoistedNode && context.transformHoist) {
|
||||||
context.transformHoist(children, context)
|
context.transformHoist(children, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isStaticNode(
|
export function getStaticType(
|
||||||
node: TemplateChildNode | SimpleExpressionNode,
|
node: TemplateChildNode | SimpleExpressionNode,
|
||||||
resultCache: Map<TemplateChildNode, boolean> = new Map()
|
resultCache: Map<TemplateChildNode, StaticType> = new Map()
|
||||||
): boolean {
|
): StaticType {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case NodeTypes.ELEMENT:
|
case NodeTypes.ELEMENT:
|
||||||
if (node.tagType !== ElementTypes.ELEMENT) {
|
if (node.tagType !== ElementTypes.ELEMENT) {
|
||||||
return false
|
return StaticType.NOT_STATIC
|
||||||
}
|
}
|
||||||
const cached = resultCache.get(node)
|
const cached = resultCache.get(node)
|
||||||
if (cached !== undefined) {
|
if (cached !== undefined) {
|
||||||
@ -121,53 +147,88 @@ export function isStaticNode(
|
|||||||
}
|
}
|
||||||
const codegenNode = node.codegenNode!
|
const codegenNode = node.codegenNode!
|
||||||
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
|
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
|
||||||
return false
|
return StaticType.NOT_STATIC
|
||||||
}
|
}
|
||||||
const flag = getPatchFlag(codegenNode)
|
const flag = getPatchFlag(codegenNode)
|
||||||
if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps(node)) {
|
if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps(node)) {
|
||||||
// element self is static. check its children.
|
// element self is static. check its children.
|
||||||
|
let returnType = StaticType.FULL_STATIC
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
if (!isStaticNode(node.children[i], resultCache)) {
|
const childType = getStaticType(node.children[i], resultCache)
|
||||||
resultCache.set(node, false)
|
if (childType === StaticType.NOT_STATIC) {
|
||||||
return false
|
resultCache.set(node, StaticType.NOT_STATIC)
|
||||||
|
return StaticType.NOT_STATIC
|
||||||
|
} else if (childType === StaticType.HAS_RUNTIME_CONSTANT) {
|
||||||
|
returnType = StaticType.HAS_RUNTIME_CONSTANT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// only svg/foreignObject could be block here, however if they are static
|
|
||||||
// then they don't need to be blocks since there will be no nested
|
// check if any of the props contain runtime constants
|
||||||
// updates.
|
if (returnType !== StaticType.HAS_RUNTIME_CONSTANT) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// nested updates.
|
||||||
if (codegenNode.isBlock) {
|
if (codegenNode.isBlock) {
|
||||||
codegenNode.isBlock = false
|
codegenNode.isBlock = false
|
||||||
}
|
}
|
||||||
resultCache.set(node, true)
|
|
||||||
return true
|
resultCache.set(node, returnType)
|
||||||
|
return returnType
|
||||||
} else {
|
} else {
|
||||||
resultCache.set(node, false)
|
resultCache.set(node, StaticType.NOT_STATIC)
|
||||||
return false
|
return StaticType.NOT_STATIC
|
||||||
}
|
}
|
||||||
case NodeTypes.TEXT:
|
case NodeTypes.TEXT:
|
||||||
case NodeTypes.COMMENT:
|
case NodeTypes.COMMENT:
|
||||||
return true
|
return StaticType.FULL_STATIC
|
||||||
case NodeTypes.IF:
|
case NodeTypes.IF:
|
||||||
case NodeTypes.FOR:
|
case NodeTypes.FOR:
|
||||||
case NodeTypes.IF_BRANCH:
|
case NodeTypes.IF_BRANCH:
|
||||||
return false
|
return StaticType.NOT_STATIC
|
||||||
case NodeTypes.INTERPOLATION:
|
case NodeTypes.INTERPOLATION:
|
||||||
case NodeTypes.TEXT_CALL:
|
case NodeTypes.TEXT_CALL:
|
||||||
return isStaticNode(node.content, resultCache)
|
return getStaticType(node.content, resultCache)
|
||||||
case NodeTypes.SIMPLE_EXPRESSION:
|
case NodeTypes.SIMPLE_EXPRESSION:
|
||||||
return node.isConstant
|
return node.isConstant
|
||||||
|
? node.isRuntimeConstant
|
||||||
|
? StaticType.HAS_RUNTIME_CONSTANT
|
||||||
|
: StaticType.FULL_STATIC
|
||||||
|
: StaticType.NOT_STATIC
|
||||||
case NodeTypes.COMPOUND_EXPRESSION:
|
case NodeTypes.COMPOUND_EXPRESSION:
|
||||||
return node.children.every(child => {
|
let returnType = StaticType.FULL_STATIC
|
||||||
return (
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
isString(child) || isSymbol(child) || isStaticNode(child, resultCache)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnType
|
||||||
default:
|
default:
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const exhaustiveCheck: never = node
|
const exhaustiveCheck: never = node
|
||||||
exhaustiveCheck
|
exhaustiveCheck
|
||||||
}
|
}
|
||||||
return false
|
return StaticType.NOT_STATIC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ import {
|
|||||||
findDir
|
findDir
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { buildSlots } from './vSlot'
|
import { buildSlots } from './vSlot'
|
||||||
import { isStaticNode } from './hoistStatic'
|
import { getStaticType } from './hoistStatic'
|
||||||
|
|
||||||
// some directive transforms (e.g. v-model) may return a symbol for runtime
|
// some directive transforms (e.g. v-model) may return a symbol for runtime
|
||||||
// import, which should be used instead of a resolveDirective call.
|
// import, which should be used instead of a resolveDirective call.
|
||||||
@ -156,7 +156,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
const hasDynamicTextChild =
|
const hasDynamicTextChild =
|
||||||
type === NodeTypes.INTERPOLATION ||
|
type === NodeTypes.INTERPOLATION ||
|
||||||
type === NodeTypes.COMPOUND_EXPRESSION
|
type === NodeTypes.COMPOUND_EXPRESSION
|
||||||
if (hasDynamicTextChild && !isStaticNode(child)) {
|
if (hasDynamicTextChild && !getStaticType(child)) {
|
||||||
patchFlag |= PatchFlags.TEXT
|
patchFlag |= PatchFlags.TEXT
|
||||||
}
|
}
|
||||||
// pass directly if the only child is a text node
|
// pass directly if the only child is a text node
|
||||||
@ -292,7 +292,7 @@ export function buildProps(
|
|||||||
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
|
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
|
||||||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
||||||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
|
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
|
||||||
isStaticNode(value))
|
getStaticType(value) > 0)
|
||||||
) {
|
) {
|
||||||
// skip if the prop is a cached handler or has constant value
|
// skip if the prop is a cached handler or has constant value
|
||||||
return
|
return
|
||||||
|
@ -161,10 +161,10 @@ describe('stringify static html', () => {
|
|||||||
|
|
||||||
test('should bail on runtime constant v-bind bindings', () => {
|
test('should bail on runtime constant v-bind bindings', () => {
|
||||||
const { ast } = compile(
|
const { ast } = compile(
|
||||||
`<div><div><img src="./foo" />${repeat(
|
`<div><div>${repeat(
|
||||||
`<span class="foo">foo</span>`,
|
`<span class="foo">foo</span>`,
|
||||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
|
||||||
)}</div></div>`,
|
)}<img src="./foo" /></div></div>`,
|
||||||
{
|
{
|
||||||
hoistStatic: true,
|
hoistStatic: true,
|
||||||
prefixIdentifiers: true,
|
prefixIdentifiers: true,
|
||||||
|
@ -185,17 +185,6 @@ function analyzeNode(node: StringiableNode): [number, number] | false {
|
|||||||
) {
|
) {
|
||||||
return bail()
|
return bail()
|
||||||
}
|
}
|
||||||
// some transforms, e.g. `transformAssetUrls` in `@vue/compiler-sfc` may
|
|
||||||
// convert static attributes into a v-bind with a constnat expresion.
|
|
||||||
// Such constant bindings are eligible for hoisting but not for static
|
|
||||||
// stringification because they cannot be pre-evaluated.
|
|
||||||
if (
|
|
||||||
p.exp &&
|
|
||||||
(p.exp.type === NodeTypes.COMPOUND_EXPRESSION ||
|
|
||||||
p.exp.isRuntimeConstant)
|
|
||||||
) {
|
|
||||||
return bail()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
Loading…
Reference in New Issue
Block a user