From 57bb37bd64cc6b2aa3099d91585441db6d43e2a2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 11 May 2022 12:43:44 +0800 Subject: [PATCH] fix(ssr): should not hoist transformed asset urls in ssr compile fix #3874 --- .../compileTemplate.spec.ts.snap | 46 +++ .../templateTransformAssetUrl.spec.ts.snap | 6 +- .../templateTransformSrcset.spec.ts.snap | 325 ++++++++++-------- .../__tests__/compileTemplate.spec.ts | 21 ++ .../templateTransformAssetUrl.spec.ts | 7 +- .../__tests__/templateTransformSrcset.spec.ts | 1 + .../src/templateTransformAssetUrl.ts | 15 +- .../src/templateTransformSrcset.ts | 10 +- 8 files changed, 277 insertions(+), 154 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap index 0af6bed6..4dfc212e 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap @@ -1,5 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should not hoist srcset URLs in SSR mode 1`] = ` +"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\" +import { ssrRenderAttr as _ssrRenderAttr, ssrRenderComponent as _ssrRenderComponent } from \\"vue/server-renderer\\" +import _imports_0 from './img/foo.svg' +import _imports_1 from './img/bar.svg' + + +export function ssrRender(_ctx, _push, _parent, _attrs) { + const _component_router_link = _resolveComponent(\\"router-link\\") + + _push(\`\`) + _push(_ssrRenderComponent(_component_router_link, null, { + default: _withCtx((_, _push, _parent, _scopeId) => { + if (_push) { + _push(\`\`) + } else { + return [ + _createVNode(\\"picture\\", null, [ + _createVNode(\\"source\\", { + srcset: _imports_1 + }), + _createVNode(\\"img\\", { src: _imports_1 }) + ]) + ] + } + }), + _: 1 /* STABLE */ + }, _parent)) + _push(\`\`) +}" +`; + exports[`source map 1`] = ` Object { "mappings": ";;;wBACE,oBAA8B;IAAzB,oBAAmB,4BAAbA,WAAM", diff --git a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap index c0fceece..ac09f57e 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap @@ -38,11 +38,13 @@ import _imports_0 from '@svg/file.svg' const _hoisted_1 = _imports_0 + '#fragment' +const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"use\\", { href: _hoisted_1 }, null, -1 /* HOISTED */) +const _hoisted_3 = /*#__PURE__*/_createElementVNode(\\"use\\", { href: _hoisted_1 }, null, -1 /* HOISTED */) export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock(_Fragment, null, [ - _createElementVNode(\\"use\\", { href: _hoisted_1 }), - _createElementVNode(\\"use\\", { href: _hoisted_1 }) + _hoisted_2, + _hoisted_3 ], 64 /* STABLE_FRAGMENT */)) }" `; diff --git a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap index 89e487f0..1d9be468 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap @@ -13,57 +13,69 @@ const _hoisted_5 = _imports_0 + ' 2x, ' + _imports_0 const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x' const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' const _hoisted_8 = \\"/logo.png\\" + ', ' + _imports_0 + ' 2x' +const _hoisted_9 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"\\" +}, null, -1 /* HOISTED */) +const _hoisted_10 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_1 +}, null, -1 /* HOISTED */) +const _hoisted_11 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_2 +}, null, -1 /* HOISTED */) +const _hoisted_12 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_3 +}, null, -1 /* HOISTED */) +const _hoisted_13 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_4 +}, null, -1 /* HOISTED */) +const _hoisted_14 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_5 +}, null, -1 /* HOISTED */) +const _hoisted_15 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_6 +}, null, -1 /* HOISTED */) +const _hoisted_16 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_7 +}, null, -1 /* HOISTED */) +const _hoisted_17 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"/logo.png\\", + srcset: \\"/logo.png, /logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_18 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"https://example.com/logo.png\\", + srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_19 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"/logo.png\\", + srcset: _hoisted_8 +}, null, -1 /* HOISTED */) +const _hoisted_20 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"data:image/png;base64,i\\", + srcset: \\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\" +}, null, -1 /* HOISTED */) export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock(_Fragment, null, [ - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_1 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_2 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_3 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_4 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_5 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_6 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_7 - }), - _createElementVNode(\\"img\\", { - src: \\"/logo.png\\", - srcset: \\"/logo.png, /logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"https://example.com/logo.png\\", - srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"/logo.png\\", - srcset: _hoisted_8 - }), - _createElementVNode(\\"img\\", { - src: \\"data:image/png;base64,i\\", - srcset: \\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\" - }) + _hoisted_9, + _hoisted_10, + _hoisted_11, + _hoisted_12, + _hoisted_13, + _hoisted_14, + _hoisted_15, + _hoisted_16, + _hoisted_17, + _hoisted_18, + _hoisted_19, + _hoisted_20 ], 64 /* STABLE_FRAGMENT */)) }" `; @@ -71,56 +83,69 @@ export function render(_ctx, _cache) { exports[`compiler sfc: transform srcset transform srcset w/ base 1`] = ` "import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\" +const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"\\" +}, null, -1 /* HOISTED */) +const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"/foo/logo.png\\" +}, null, -1 /* HOISTED */) +const _hoisted_3 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"/foo/logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_4 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"/foo/logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_5 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"/foo/logo.png, /foo/logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_6 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"/foo/logo.png 2x, /foo/logo.png\\" +}, null, -1 /* HOISTED */) +const _hoisted_7 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"/foo/logo.png 2x, /foo/logo.png 3x\\" +}, null, -1 /* HOISTED */) +const _hoisted_8 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x\\" +}, null, -1 /* HOISTED */) +const _hoisted_9 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"/logo.png\\", + srcset: \\"/logo.png, /logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_10 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"https://example.com/logo.png\\", + srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_11 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"/logo.png\\", + srcset: \\"/logo.png, /foo/logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_12 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"data:image/png;base64,i\\", + srcset: \\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\" +}, null, -1 /* HOISTED */) + export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock(_Fragment, null, [ - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"/foo/logo.png\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"/foo/logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"/foo/logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"/foo/logo.png, /foo/logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"/foo/logo.png 2x, /foo/logo.png\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"/foo/logo.png 2x, /foo/logo.png 3x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"/logo.png\\", - srcset: \\"/logo.png, /logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"https://example.com/logo.png\\", - srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"/logo.png\\", - srcset: \\"/logo.png, /foo/logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"data:image/png;base64,i\\", - srcset: \\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\" - }) + _hoisted_1, + _hoisted_2, + _hoisted_3, + _hoisted_4, + _hoisted_5, + _hoisted_6, + _hoisted_7, + _hoisted_8, + _hoisted_9, + _hoisted_10, + _hoisted_11, + _hoisted_12 ], 64 /* STABLE_FRAGMENT */)) }" `; @@ -140,57 +165,69 @@ const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x' const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x' const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x' +const _hoisted_10 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: \\"\\" +}, null, -1 /* HOISTED */) +const _hoisted_11 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_1 +}, null, -1 /* HOISTED */) +const _hoisted_12 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_2 +}, null, -1 /* HOISTED */) +const _hoisted_13 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_3 +}, null, -1 /* HOISTED */) +const _hoisted_14 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_4 +}, null, -1 /* HOISTED */) +const _hoisted_15 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_5 +}, null, -1 /* HOISTED */) +const _hoisted_16 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_6 +}, null, -1 /* HOISTED */) +const _hoisted_17 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"./logo.png\\", + srcset: _hoisted_7 +}, null, -1 /* HOISTED */) +const _hoisted_18 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"/logo.png\\", + srcset: _hoisted_8 +}, null, -1 /* HOISTED */) +const _hoisted_19 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"https://example.com/logo.png\\", + srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\" +}, null, -1 /* HOISTED */) +const _hoisted_20 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"/logo.png\\", + srcset: _hoisted_9 +}, null, -1 /* HOISTED */) +const _hoisted_21 = /*#__PURE__*/_createElementVNode(\\"img\\", { + src: \\"data:image/png;base64,i\\", + srcset: \\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\" +}, null, -1 /* HOISTED */) export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock(_Fragment, null, [ - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: \\"\\" - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_1 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_2 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_3 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_4 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_5 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_6 - }), - _createElementVNode(\\"img\\", { - src: \\"./logo.png\\", - srcset: _hoisted_7 - }), - _createElementVNode(\\"img\\", { - src: \\"/logo.png\\", - srcset: _hoisted_8 - }), - _createElementVNode(\\"img\\", { - src: \\"https://example.com/logo.png\\", - srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\" - }), - _createElementVNode(\\"img\\", { - src: \\"/logo.png\\", - srcset: _hoisted_9 - }), - _createElementVNode(\\"img\\", { - src: \\"data:image/png;base64,i\\", - srcset: \\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\" - }) + _hoisted_10, + _hoisted_11, + _hoisted_12, + _hoisted_13, + _hoisted_14, + _hoisted_15, + _hoisted_16, + _hoisted_17, + _hoisted_18, + _hoisted_19, + _hoisted_20, + _hoisted_21 ], 64 /* STABLE_FRAGMENT */)) }" `; diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts index af0f6600..2beda880 100644 --- a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts +++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts @@ -153,3 +153,24 @@ test('should generate the correct imports expression', () => { expect(code).toMatch(`_ssrRenderAttr(\"src\", _imports_1)`) expect(code).toMatch(`_createVNode(\"img\", { src: _imports_1 })`) }) + +// #3874 +test('should not hoist srcset URLs in SSR mode', () => { + const { code } = compile({ + filename: 'example.vue', + source: ` + + + + + + + + + + + `, + ssr: true + }) + expect(code).toMatchSnapshot() +}) diff --git a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts index 44ef092a..0b0f138b 100644 --- a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts +++ b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts @@ -54,9 +54,12 @@ describe('compiler sfc: transform asset url', () => { test('support uri fragment', () => { const result = compileWithAssetUrls( '' + - '' + '', + {}, + { + hoistStatic: true + } ) - expect(result.code).toMatchSnapshot() }) diff --git a/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts b/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts index f88706c4..c2a61460 100644 --- a/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts +++ b/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts @@ -26,6 +26,7 @@ function compileWithSrcset( ? createSrcsetTransformWithOptions(normalizeOptions(options)) : transformSrcset transform(ast, { + hoistStatic: true, nodeTransforms: [srcsetTransform, transformElement], directiveTransforms: { bind: transformBind diff --git a/packages/compiler-sfc/src/templateTransformAssetUrl.ts b/packages/compiler-sfc/src/templateTransformAssetUrl.ts index 2f6152b8..32bf33bc 100644 --- a/packages/compiler-sfc/src/templateTransformAssetUrl.ts +++ b/packages/compiler-sfc/src/templateTransformAssetUrl.ts @@ -176,6 +176,17 @@ function getImportsExpressionExp( } const hashExp = `${name} + '${hash}'` + const finalExp = createSimpleExpression( + hashExp, + false, + loc, + ConstantTypes.CAN_STRINGIFY + ) + + if (!context.hoistStatic) { + return finalExp + } + const existingHoistIndex = context.hoists.findIndex(h => { return ( h && @@ -192,9 +203,7 @@ function getImportsExpressionExp( ConstantTypes.CAN_STRINGIFY ) } - return context.hoist( - createSimpleExpression(hashExp, false, loc, ConstantTypes.CAN_STRINGIFY) - ) + return context.hoist(finalExp) } else { return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_STRINGIFY) } diff --git a/packages/compiler-sfc/src/templateTransformSrcset.ts b/packages/compiler-sfc/src/templateTransformSrcset.ts index ccdcc721..a2c36b2d 100644 --- a/packages/compiler-sfc/src/templateTransformSrcset.ts +++ b/packages/compiler-sfc/src/templateTransformSrcset.ts @@ -3,6 +3,7 @@ import { ConstantTypes, createCompoundExpression, createSimpleExpression, + ExpressionNode, NodeTransform, NodeTypes, SimpleExpressionNode @@ -145,14 +146,17 @@ export const transformSrcset: NodeTransform = ( } }) - const hoisted = context.hoist(compoundExpression) - hoisted.constType = ConstantTypes.CAN_STRINGIFY + let exp: ExpressionNode = compoundExpression + if (context.hoistStatic) { + exp = context.hoist(compoundExpression) + exp.constType = ConstantTypes.CAN_STRINGIFY + } node.props[index] = { type: NodeTypes.DIRECTIVE, name: 'bind', arg: createSimpleExpression('srcset', true, attr.loc), - exp: hoisted, + exp, modifiers: [], loc: attr.loc }