Ver Fonte

fix(ssr): should not hoist transformed asset urls in ssr compile

fix #3874
Evan You há 4 anos atrás
pai
commit
57bb37bd64

+ 46 - 0
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(\`<!--[--><picture><source\${
+    _ssrRenderAttr(\\"srcset\\", _imports_0)
+  }><img\${
+    _ssrRenderAttr(\\"src\\", _imports_0)
+  }></picture>\`)
+  _push(_ssrRenderComponent(_component_router_link, null, {
+    default: _withCtx((_, _push, _parent, _scopeId) => {
+      if (_push) {
+        _push(\`<picture\${
+          _scopeId
+        }><source\${
+          _ssrRenderAttr(\\"srcset\\", _imports_1)
+        }\${
+          _scopeId
+        }><img\${
+          _ssrRenderAttr(\\"src\\", _imports_1)
+        }\${
+          _scopeId
+        }></picture>\`)
+      } 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",

+ 4 - 2
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 */))
 }"
 `;

+ 181 - 144
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 */))
 }"
 `;

+ 21 - 0
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: `
+    <picture>
+      <source srcset="./img/foo.svg"/>
+      <img src="./img/foo.svg"/>
+    </picture>
+    <router-link>
+      <picture>
+        <source srcset="./img/bar.svg"/>
+        <img src="./img/bar.svg"/>
+      </picture>
+    </router-link>
+    `,
+    ssr: true
+  })
+  expect(code).toMatchSnapshot()
+})

+ 5 - 2
packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts

@@ -54,9 +54,12 @@ describe('compiler sfc: transform asset url', () => {
   test('support uri fragment', () => {
     const result = compileWithAssetUrls(
       '<use href="~@svg/file.svg#fragment"></use>' +
-        '<use href="~@svg/file.svg#fragment"></use>'
+        '<use href="~@svg/file.svg#fragment"></use>',
+      {},
+      {
+        hoistStatic: true
+      }
     )
-
     expect(result.code).toMatchSnapshot()
   })
 

+ 1 - 0
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

+ 12 - 3
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)
   }

+ 7 - 3
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
           }