diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts
index 920ba8d4a4b..215792ed403 100644
--- a/packages/compiler-core/src/transforms/hoistStatic.ts
+++ b/packages/compiler-core/src/transforms/hoistStatic.ts
@@ -52,16 +52,6 @@ function walk(
context: TransformContext,
doNotHoistNode: boolean = false
) {
- // Some transforms, e.g. transformAssetUrls 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
- // stringification threshold is met.
- let canStringify = true
-
const { children } = node
const originalCount = children.length
let hoistedCount = 0
@@ -77,9 +67,6 @@ function walk(
? ConstantTypes.NOT_CONSTANT
: getConstantType(child, context)
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 */` : ``)
@@ -110,17 +97,12 @@ function walk(
}
}
}
- } else if (child.type === NodeTypes.TEXT_CALL) {
- const contentType = getConstantType(child.content, context)
- if (contentType > 0) {
- if (contentType < ConstantTypes.CAN_STRINGIFY) {
- canStringify = false
- }
- if (contentType >= ConstantTypes.CAN_HOIST) {
- child.codegenNode = context.hoist(child.codegenNode)
- hoistedCount++
- }
- }
+ } else if (
+ child.type === NodeTypes.TEXT_CALL &&
+ getConstantType(child.content, context) >= ConstantTypes.CAN_HOIST
+ ) {
+ child.codegenNode = context.hoist(child.codegenNode)
+ hoistedCount++
}
// walk further
@@ -148,7 +130,7 @@ function walk(
}
}
- if (canStringify && hoistedCount && context.transformHoist) {
+ if (hoistedCount && context.transformHoist) {
context.transformHoist(children, context, node)
}
diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
new file mode 100644
index 00000000000..efa75df3f47
--- /dev/null
+++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`stringify static html should bail on bindings that are hoisted but not stringifiable 1`] = `
+"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"div\\", null, [
+ /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"foo\\" }, \\"foo\\"),
+ /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"foo\\" }, \\"foo\\"),
+ /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"foo\\" }, \\"foo\\"),
+ /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"foo\\" }, \\"foo\\"),
+ /*#__PURE__*/_createElementVNode(\\"span\\", { class: \\"foo\\" }, \\"foo\\"),
+ /*#__PURE__*/_createElementVNode(\\"img\\", { src: _imports_0_ })
+], -1 /* HOISTED */)
+const _hoisted_2 = [
+ _hoisted_1
+]
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
+}"
+`;
+
+exports[`stringify static html should work with bindings that are non-static but stringifiable 1`] = `
+"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
+
+const _hoisted_1 = /*#__PURE__*/_createStaticVNode(\\"
foofoofoofoofoo \\", 1)
+const _hoisted_2 = [
+ _hoisted_1
+]
+
+return function render(_ctx, _cache) {
+ return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_2))
+}"
+`;
diff --git a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
index 0923f0816c5..0beb42585b4 100644
--- a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
@@ -181,8 +181,8 @@ describe('stringify static html', () => {
])
})
- test('should bail on runtime constant v-bind bindings', () => {
- const { ast } = compile(
+ test('should bail on bindings that are hoisted but not stringifiable', () => {
+ const { ast, code } = compile(
`${repeat(
`
foo`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
@@ -216,13 +216,62 @@ describe('stringify static html', () => {
expect(ast.hoists).toMatchObject([
{
// the expression and the tree are still hoistable
- // but if it's stringified it will be NodeTypes.CALL_EXPRESSION
+ // but should stay NodeTypes.VNODE_CALL
+ // if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION
type: NodeTypes.VNODE_CALL
},
{
type: NodeTypes.JS_ARRAY_EXPRESSION
}
])
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should work with bindings that are non-static but stringifiable', () => {
+ // if a binding is non-static but marked as CAN_STRINGIFY, it means it's
+ // a known reference to a constant string.
+ const { ast, code } = compile(
+ `
${repeat(
+ `
foo`,
+ StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
+ )}
`,
+ {
+ hoistStatic: true,
+ prefixIdentifiers: true,
+ transformHoist: stringifyStatic,
+ nodeTransforms: [
+ node => {
+ if (node.type === NodeTypes.ELEMENT && node.tag === 'img') {
+ const exp = createSimpleExpression(
+ '_imports_0_',
+ false,
+ node.loc,
+ ConstantTypes.CAN_STRINGIFY
+ )
+ node.props[0] = {
+ type: NodeTypes.DIRECTIVE,
+ name: 'bind',
+ arg: createSimpleExpression('src', true),
+ exp,
+ modifiers: [],
+ loc: node.loc
+ }
+ }
+ }
+ ]
+ }
+ )
+ expect(ast.hoists).toMatchObject([
+ {
+ // the hoisted node should be NodeTypes.JS_CALL_EXPRESSION
+ // of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL
+ type: NodeTypes.JS_CALL_EXPRESSION
+ },
+ {
+ type: NodeTypes.JS_ARRAY_EXPRESSION
+ }
+ ])
+ expect(code).toMatchSnapshot()
})
// #1128
diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts
index 877c4d011c2..e257e3254fe 100644
--- a/packages/compiler-dom/src/transforms/stringifyStatic.ts
+++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts
@@ -14,7 +14,8 @@ import {
ElementTypes,
PlainElementNode,
JSChildNode,
- TextCallNode
+ TextCallNode,
+ ConstantTypes
} from '@vue/compiler-core'
import {
isVoidTag,
@@ -171,7 +172,7 @@ const isNonStringifiable = /*#__PURE__*/ makeMap(
/**
* for a hoisted node, analyze it and return:
- * - false: bailed (contains runtime constant)
+ * - false: bailed (contains non-stringifiable props or runtime constant)
* - [nc, ec] where
* - nc is the number of nodes inside
* - ec is the number of element with bindings inside
@@ -216,6 +217,13 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
) {
return bail()
}
+ if (
+ p.exp &&
+ (p.exp.type === NodeTypes.COMPOUND_EXPRESSION ||
+ p.exp.constType < ConstantTypes.CAN_STRINGIFY)
+ ) {
+ return bail()
+ }
}
}
for (let i = 0; i < node.children.length; i++) {
diff --git a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
index 15aff00bfea..44ef092aa22 100644
--- a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
+++ b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
@@ -160,7 +160,6 @@ describe('compiler sfc: transform asset url', () => {
transformHoist: stringifyStatic
}
)
- console.log(code)
expect(code).toMatch(`_createStaticVNode`)
expect(code).toMatchSnapshot()
})