// an expression parsed as the params of a function will track
// 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 {
-import { compile, NodeTypes, CREATE_STATIC } from '../../src'
+import {
+ compile,
+ NodeTypes,
+ CREATE_STATIC,
+ createSimpleExpression
+} from '../../src'
import {
stringifyStatic,
StringifyThresholds
]
})
})
+
+ test('should bail on runtime constant v-bind bindings', () => {
+ const { ast } = compile(
+ `<div><div><img src="./foo" />${repeat(
+ `<span class="foo">foo</span>`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ )}</div></div>`,
+ {
+ hoistStatic: true,
+ prefixIdentifiers: true,
+ transformHoist: stringifyStatic,
+ nodeTransforms: [
+ node => {
+ if (node.type === NodeTypes.ELEMENT && node.tag === 'img') {
+ const exp = createSimpleExpression(
+ '_imports_0_',
+ false,
+ node.loc,
+ true
+ )
+ exp.isRuntimeConstant = true
+ node.props[0] = {
+ type: NodeTypes.DIRECTIVE,
+ name: 'bind',
+ arg: createSimpleExpression('src', true),
+ exp,
+ modifiers: [],
+ loc: node.loc
+ }
+ }
+ }
+ ]
+ }
+ )
+ // the expression and the tree are still hoistable
+ expect(ast.hoists.length).toBe(1)
+ // ...but the hoisted tree should not be stringified
+ expect(ast.hoists[0]).toMatchObject({
+ // if it's stringified it will be NodeTypes.CALL_EXPRESSION
+ type: NodeTypes.VNODE_CALL
+ })
+ })
})
function shouldOptimize(node: ElementNode): boolean {
let bindingThreshold = StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
let nodeThreshold = StringifyThresholds.NODE_COUNT
+ let bail = false
// TODO: check for cases where using innerHTML will result in different
// output compared to imperative node insertions.
// probably only need to check for most common case
// i.e. non-phrasing-content tags inside `<p>`
function walk(node: ElementNode) {
+ // 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.
+ 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
+ ) {
+ bail = true
+ return false
+ }
+ }
for (let i = 0; i < node.children.length; i++) {
if (--nodeThreshold === 0) {
return true
if (walk(child)) {
return true
}
+ if (bail) {
+ return false
+ }
}
}
return false
}
const name = `_imports_${importsArray.length}`
const exp = createSimpleExpression(name, false, loc, true)
+ exp.isRuntimeConstant = true
context.imports.add({ exp, path })
if (hash && path) {
- return context.hoist(
+ const ret = context.hoist(
createSimpleExpression(`${name} + '${hash}'`, false, loc, true)
)
+ ret.isRuntimeConstant = true
+ return ret
} else {
return exp
}
}
})
+ const hoisted = context.hoist(compoundExpression)
+ hoisted.isRuntimeConstant = true
+
node.props[index] = {
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: createSimpleExpression('srcset', true, attr.loc),
- exp: context.hoist(compoundExpression),
+ exp: hoisted,
modifiers: [],
loc: attr.loc
}