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

fix #3874
This commit is contained in:
Evan You 2022-05-11 12:43:44 +08:00
parent fec12d7dcc
commit 57bb37bd64
8 changed files with 277 additions and 154 deletions

View File

@ -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",

View File

@ -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 */))
}"
`;

View File

@ -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\\"
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode(\\"img\\", {
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"/foo/logo.png\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"/foo/logo.png 2x\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_4 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"/foo/logo.png 2x\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_5 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"/foo/logo.png, /foo/logo.png 2x\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_6 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"/foo/logo.png 2x, /foo/logo.png\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_7 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"/foo/logo.png 2x, /foo/logo.png 3x\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_8 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"./logo.png\\",
srcset: \\"/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_9 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"/logo.png\\",
srcset: \\"/logo.png, /logo.png 2x\\"
}),
_createElementVNode(\\"img\\", {
}, 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\\"
}),
_createElementVNode(\\"img\\", {
}, null, -1 /* HOISTED */)
const _hoisted_11 = /*#__PURE__*/_createElementVNode(\\"img\\", {
src: \\"/logo.png\\",
srcset: \\"/logo.png, /foo/logo.png 2x\\"
}),
_createElementVNode(\\"img\\", {
}, 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, [
_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 */))
}"
`;

View File

@ -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()
})

View File

@ -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()
})

View File

@ -26,6 +26,7 @@ function compileWithSrcset(
? createSrcsetTransformWithOptions(normalizeOptions(options))
: transformSrcset
transform(ast, {
hoistStatic: true,
nodeTransforms: [srcsetTransform, transformElement],
directiveTransforms: {
bind: transformBind

View File

@ -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)
}

View File

@ -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
}