Skip to content

Commit f9a3766

Browse files
committed
fix(compiler): bail strigification on runtime constant expressions
1 parent 3c3fe88 commit f9a3766

File tree

5 files changed

+80
-3
lines changed

5 files changed

+80
-3
lines changed

packages/compiler-core/src/ast.ts

+3
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ export interface SimpleExpressionNode extends Node {
195195
// an expression parsed as the params of a function will track
196196
// the identifiers declared inside the function body.
197197
identifiers?: string[]
198+
// some expressions (e.g. transformAssetUrls import identifiers) are constant,
199+
// but cannot be stringified because they must be first evaluated at runtime.
200+
isRuntimeConstant?: boolean
198201
}
199202

200203
export interface InterpolationNode extends Node {

packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts

+48-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { compile, NodeTypes, CREATE_STATIC } from '../../src'
1+
import {
2+
compile,
3+
NodeTypes,
4+
CREATE_STATIC,
5+
createSimpleExpression
6+
} from '../../src'
27
import {
38
stringifyStatic,
49
StringifyThresholds
@@ -121,4 +126,46 @@ describe('stringify static html', () => {
121126
]
122127
})
123128
})
129+
130+
test('should bail on runtime constant v-bind bindings', () => {
131+
const { ast } = compile(
132+
`<div><div><img src="./foo" />${repeat(
133+
`<span class="foo">foo</span>`,
134+
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
135+
)}</div></div>`,
136+
{
137+
hoistStatic: true,
138+
prefixIdentifiers: true,
139+
transformHoist: stringifyStatic,
140+
nodeTransforms: [
141+
node => {
142+
if (node.type === NodeTypes.ELEMENT && node.tag === 'img') {
143+
const exp = createSimpleExpression(
144+
'_imports_0_',
145+
false,
146+
node.loc,
147+
true
148+
)
149+
exp.isRuntimeConstant = true
150+
node.props[0] = {
151+
type: NodeTypes.DIRECTIVE,
152+
name: 'bind',
153+
arg: createSimpleExpression('src', true),
154+
exp,
155+
modifiers: [],
156+
loc: node.loc
157+
}
158+
}
159+
}
160+
]
161+
}
162+
)
163+
// the expression and the tree are still hoistable
164+
expect(ast.hoists.length).toBe(1)
165+
// ...but the hoisted tree should not be stringified
166+
expect(ast.hoists[0]).toMatchObject({
167+
// if it's stringified it will be NodeTypes.CALL_EXPRESSION
168+
type: NodeTypes.VNODE_CALL
169+
})
170+
})
124171
})

packages/compiler-dom/src/transforms/stringifyStatic.ts

+21
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,30 @@ export const enum StringifyThresholds {
4747
function shouldOptimize(node: ElementNode): boolean {
4848
let bindingThreshold = StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
4949
let nodeThreshold = StringifyThresholds.NODE_COUNT
50+
let bail = false
5051

5152
// TODO: check for cases where using innerHTML will result in different
5253
// output compared to imperative node insertions.
5354
// probably only need to check for most common case
5455
// i.e. non-phrasing-content tags inside `<p>`
5556
function walk(node: ElementNode) {
57+
// some transforms, e.g. `transformAssetUrls` in `@vue/compiler-sfc` may
58+
// convert static attributes into a v-bind with a constnat expresion.
59+
// Such constant bindings are eligible for hoisting but not for static
60+
// stringification because they cannot be pre-evaluated.
61+
for (let i = 0; i < node.props.length; i++) {
62+
const p = node.props[i]
63+
if (
64+
p.type === NodeTypes.DIRECTIVE &&
65+
p.name === 'bind' &&
66+
p.exp &&
67+
p.exp.type !== NodeTypes.COMPOUND_EXPRESSION &&
68+
p.exp.isRuntimeConstant
69+
) {
70+
bail = true
71+
return false
72+
}
73+
}
5674
for (let i = 0; i < node.children.length; i++) {
5775
if (--nodeThreshold === 0) {
5876
return true
@@ -65,6 +83,9 @@ function shouldOptimize(node: ElementNode): boolean {
6583
if (walk(child)) {
6684
return true
6785
}
86+
if (bail) {
87+
return false
88+
}
6889
}
6990
}
7091
return false

packages/compiler-sfc/src/templateTransformAssetUrl.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,14 @@ function getImportsExpressionExp(
126126
}
127127
const name = `_imports_${importsArray.length}`
128128
const exp = createSimpleExpression(name, false, loc, true)
129+
exp.isRuntimeConstant = true
129130
context.imports.add({ exp, path })
130131
if (hash && path) {
131-
return context.hoist(
132+
const ret = context.hoist(
132133
createSimpleExpression(`${name} + '${hash}'`, false, loc, true)
133134
)
135+
ret.isRuntimeConstant = true
136+
return ret
134137
} else {
135138
return exp
136139
}

packages/compiler-sfc/src/templateTransformSrcset.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,14 @@ export const transformSrcset: NodeTransform = (node, context) => {
8686
}
8787
})
8888

89+
const hoisted = context.hoist(compoundExpression)
90+
hoisted.isRuntimeConstant = true
91+
8992
node.props[index] = {
9093
type: NodeTypes.DIRECTIVE,
9194
name: 'bind',
9295
arg: createSimpleExpression('srcset', true, attr.loc),
93-
exp: context.hoist(compoundExpression),
96+
exp: hoisted,
9497
modifiers: [],
9598
loc: attr.loc
9699
}

0 commit comments

Comments
 (0)