diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 5b5e9e83ef4..7adfc8cba58 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -195,6 +195,9 @@ export interface SimpleExpressionNode extends Node { // 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 { diff --git a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts index 5e942c17af7..3dca19a0e8d 100644 --- a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts @@ -1,4 +1,9 @@ -import { compile, NodeTypes, CREATE_STATIC } from '../../src' +import { + compile, + NodeTypes, + CREATE_STATIC, + createSimpleExpression +} from '../../src' import { stringifyStatic, StringifyThresholds @@ -121,4 +126,46 @@ describe('stringify static html', () => { ] }) }) + + test('should bail on runtime constant v-bind bindings', () => { + const { ast } = compile( + `
` 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 @@ -65,6 +83,9 @@ function shouldOptimize(node: ElementNode): boolean { if (walk(child)) { return true } + if (bail) { + return false + } } } return false diff --git a/packages/compiler-sfc/src/templateTransformAssetUrl.ts b/packages/compiler-sfc/src/templateTransformAssetUrl.ts index 44ebe36af0a..40189be86af 100644 --- a/packages/compiler-sfc/src/templateTransformAssetUrl.ts +++ b/packages/compiler-sfc/src/templateTransformAssetUrl.ts @@ -126,11 +126,14 @@ function getImportsExpressionExp( } 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 } diff --git a/packages/compiler-sfc/src/templateTransformSrcset.ts b/packages/compiler-sfc/src/templateTransformSrcset.ts index dccebd9e5c6..06b16ffd67c 100644 --- a/packages/compiler-sfc/src/templateTransformSrcset.ts +++ b/packages/compiler-sfc/src/templateTransformSrcset.ts @@ -86,11 +86,14 @@ export const transformSrcset: NodeTransform = (node, context) => { } }) + 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 }