Skip to content

Commit 87c73e9

Browse files
committed
fix(compiler-sfc): make asset url imports stringifiable
1 parent 3e5ed6c commit 87c73e9

7 files changed

+136
-16
lines changed

packages/compiler-dom/src/transforms/stringifyStatic.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ export const enum StringifyThresholds {
3838

3939
type StringifiableNode = PlainElementNode | TextCallNode
4040

41+
/**
42+
* Regex for replacing placeholders for embedded constant variables
43+
* (e.g. import URL string constants generated by compiler-sfc)
44+
*/
45+
const expReplaceRE = /__VUE_EXP_START__(.*?)__VUE_EXP_END__/g
46+
4147
/**
4248
* Turn eligible hoisted static trees into stringified static nodes, e.g.
4349
*
@@ -80,7 +86,7 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
8086
const staticCall = createCallExpression(context.helper(CREATE_STATIC), [
8187
JSON.stringify(
8288
currentChunk.map(node => stringifyNode(node, context)).join('')
83-
),
89+
).replace(expReplaceRE, `" + $1 + "`),
8490
// the 2nd argument indicates the number of DOM nodes this static vnode
8591
// will insert / hydrate
8692
String(currentChunk.length)
@@ -273,8 +279,17 @@ function stringifyElement(
273279
res += `="${escapeHtml(p.value.content)}"`
274280
}
275281
} else if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind') {
282+
const exp = p.exp as SimpleExpressionNode
283+
if (exp.content[0] === '_') {
284+
// internally generated string constant references
285+
// e.g. imported URL strings via compiler-sfc transformAssetUrl plugin
286+
res += ` ${(p.arg as SimpleExpressionNode).content}="__VUE_EXP_START__${
287+
exp.content
288+
}__VUE_EXP_END__"`
289+
continue
290+
}
276291
// constant v-bind, e.g. :foo="1"
277-
let evaluated = evaluateConstant(p.exp as SimpleExpressionNode)
292+
let evaluated = evaluateConstant(exp)
278293
if (evaluated != null) {
279294
const arg = p.arg && (p.arg as SimpleExpressionNode).content
280295
if (arg === 'class') {

packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap

+16
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ export function render(_ctx, _cache) {
7474
}"
7575
`;
7676

77+
exports[`compiler sfc: transform asset url transform with stringify 1`] = `
78+
"import { createElementVNode as _createElementVNode, createStaticVNode as _createStaticVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
79+
import _imports_0 from './bar.png'
80+
import _imports_1 from '/bar.png'
81+
82+
83+
const _hoisted_1 = /*#__PURE__*/_createStaticVNode(\\"<img src=\\\\\\"\\" + _imports_0 + \\"\\\\\\"><img src=\\\\\\"\\" + _imports_1 + \\"\\\\\\"><img src=\\\\\\"https://foo.bar/baz.png\\\\\\"><img src=\\\\\\"//foo.bar/baz.png\\\\\\"><img src=\\\\\\"\\" + _imports_0 + \\"\\\\\\">\\", 5)
84+
const _hoisted_6 = [
85+
_hoisted_1
86+
]
87+
88+
export function render(_ctx, _cache) {
89+
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_6))
90+
}"
91+
`;
92+
7793
exports[`compiler sfc: transform asset url with explicit base 1`] = `
7894
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
7995
import _imports_0 from 'bar.png'

packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap

+25
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,28 @@ export function render(_ctx, _cache) {
194194
], 64 /* STABLE_FRAGMENT */))
195195
}"
196196
`;
197+
198+
exports[`compiler sfc: transform srcset transform srcset w/ stringify 1`] = `
199+
"import { createElementVNode as _createElementVNode, createStaticVNode as _createStaticVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
200+
import _imports_0 from './logo.png'
201+
import _imports_1 from '/logo.png'
202+
203+
204+
const _hoisted_1 = _imports_0
205+
const _hoisted_2 = _imports_0 + ' 2x'
206+
const _hoisted_3 = _imports_0 + ' 2x'
207+
const _hoisted_4 = _imports_0 + ', ' + _imports_0 + ' 2x'
208+
const _hoisted_5 = _imports_0 + ' 2x, ' + _imports_0
209+
const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
210+
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
211+
const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
212+
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
213+
const _hoisted_10 = /*#__PURE__*/_createStaticVNode(\\"<img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\\\\\"><img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_1 + \\"\\\\\\"><img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_2 + \\"\\\\\\"><img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_3 + \\"\\\\\\"><img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_4 + \\"\\\\\\"><img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_5 + \\"\\\\\\"><img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_6 + \\"\\\\\\"><img src=\\\\\\"./logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_7 + \\"\\\\\\"><img src=\\\\\\"/logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_8 + \\"\\\\\\"><img src=\\\\\\"https://example.com/logo.png\\\\\\" srcset=\\\\\\"https://example.com/logo.png, https://example.com/logo.png 2x\\\\\\"><img src=\\\\\\"/logo.png\\\\\\" srcset=\\\\\\"\\" + _hoisted_9 + \\"\\\\\\"><img src=\\\\\\"data:image/png;base64,i\\\\\\" srcset=\\\\\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\\\\\">\\", 12)
214+
const _hoisted_22 = [
215+
_hoisted_10
216+
]
217+
218+
export function render(_ctx, _cache) {
219+
return (_openBlock(), _createElementBlock(\\"div\\", null, _hoisted_22))
220+
}"
221+
`;

packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { generate, baseParse, transform } from '@vue/compiler-core'
1+
import {
2+
generate,
3+
baseParse,
4+
transform,
5+
TransformOptions
6+
} from '@vue/compiler-core'
27
import {
38
transformAssetUrl,
49
createAssetUrlTransformWithOptions,
@@ -7,8 +12,13 @@ import {
712
} from '../src/templateTransformAssetUrl'
813
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
914
import { transformBind } from '../../compiler-core/src/transforms/vBind'
15+
import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic'
1016

11-
function compileWithAssetUrls(template: string, options?: AssetURLOptions) {
17+
function compileWithAssetUrls(
18+
template: string,
19+
options?: AssetURLOptions,
20+
transformOptions?: TransformOptions
21+
) {
1222
const ast = baseParse(template)
1323
const t = options
1424
? createAssetUrlTransformWithOptions(normalizeOptions(options))
@@ -17,7 +27,8 @@ function compileWithAssetUrls(template: string, options?: AssetURLOptions) {
1727
nodeTransforms: [t, transformElement],
1828
directiveTransforms: {
1929
bind: transformBind
20-
}
30+
},
31+
...transformOptions
2132
})
2233
return generate(ast, { mode: 'module' })
2334
}
@@ -131,4 +142,26 @@ describe('compiler sfc: transform asset url', () => {
131142

132143
expect(code).toMatchSnapshot()
133144
})
145+
146+
test('transform with stringify', () => {
147+
const { code } = compileWithAssetUrls(
148+
`<div>` +
149+
`<img src="./bar.png"/>` +
150+
`<img src="/bar.png"/>` +
151+
`<img src="https://foo.bar/baz.png"/>` +
152+
`<img src="//foo.bar/baz.png"/>` +
153+
`<img src="./bar.png"/>` +
154+
`</div>`,
155+
{
156+
includeAbsolute: true
157+
},
158+
{
159+
hoistStatic: true,
160+
transformHoist: stringifyStatic
161+
}
162+
)
163+
console.log(code)
164+
expect(code).toMatch(`_createStaticVNode`)
165+
expect(code).toMatchSnapshot()
166+
})
134167
})

packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts

+29-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { generate, baseParse, transform } from '@vue/compiler-core'
1+
import {
2+
generate,
3+
baseParse,
4+
transform,
5+
TransformOptions
6+
} from '@vue/compiler-core'
27
import {
38
transformSrcset,
49
createSrcsetTransformWithOptions
@@ -9,8 +14,13 @@ import {
914
AssetURLOptions,
1015
normalizeOptions
1116
} from '../src/templateTransformAssetUrl'
17+
import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic'
1218

13-
function compileWithSrcset(template: string, options?: AssetURLOptions) {
19+
function compileWithSrcset(
20+
template: string,
21+
options?: AssetURLOptions,
22+
transformOptions?: TransformOptions
23+
) {
1424
const ast = baseParse(template)
1525
const srcsetTransform = options
1626
? createSrcsetTransformWithOptions(normalizeOptions(options))
@@ -19,7 +29,8 @@ function compileWithSrcset(template: string, options?: AssetURLOptions) {
1929
nodeTransforms: [srcsetTransform, transformElement],
2030
directiveTransforms: {
2131
bind: transformBind
22-
}
32+
},
33+
...transformOptions
2334
})
2435
return generate(ast, { mode: 'module' })
2536
}
@@ -59,4 +70,19 @@ describe('compiler sfc: transform srcset', () => {
5970
}).code
6071
).toMatchSnapshot()
6172
})
73+
74+
test('transform srcset w/ stringify', () => {
75+
const code = compileWithSrcset(
76+
`<div>${src}</div>`,
77+
{
78+
includeAbsolute: true
79+
},
80+
{
81+
hoistStatic: true,
82+
transformHoist: stringifyStatic
83+
}
84+
).code
85+
expect(code).toMatch(`_createStaticVNode`)
86+
expect(code).toMatchSnapshot()
87+
})
6288
})

packages/compiler-sfc/src/templateTransformAssetUrl.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,12 @@ function getImportsExpressionExp(
162162
exp = context.imports[existingIndex].exp as SimpleExpressionNode
163163
} else {
164164
name = `_imports_${context.imports.length}`
165-
exp = createSimpleExpression(name, false, loc, ConstantTypes.CAN_HOIST)
165+
exp = createSimpleExpression(
166+
name,
167+
false,
168+
loc,
169+
ConstantTypes.CAN_STRINGIFY
170+
)
166171
context.imports.push({ exp, path })
167172
}
168173

@@ -184,13 +189,13 @@ function getImportsExpressionExp(
184189
`_hoisted_${existingHoistIndex + 1}`,
185190
false,
186191
loc,
187-
ConstantTypes.CAN_HOIST
192+
ConstantTypes.CAN_STRINGIFY
188193
)
189194
}
190195
return context.hoist(
191-
createSimpleExpression(hashExp, false, loc, ConstantTypes.CAN_HOIST)
196+
createSimpleExpression(hashExp, false, loc, ConstantTypes.CAN_STRINGIFY)
192197
)
193198
} else {
194-
return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_HOIST)
199+
return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_STRINGIFY)
195200
}
196201
}

packages/compiler-sfc/src/templateTransformSrcset.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ export const transformSrcset: NodeTransform = (
113113
`_imports_${existingImportsIndex}`,
114114
false,
115115
attr.loc,
116-
ConstantTypes.CAN_HOIST
116+
ConstantTypes.CAN_STRINGIFY
117117
)
118118
} else {
119119
exp = createSimpleExpression(
120120
`_imports_${context.imports.length}`,
121121
false,
122122
attr.loc,
123-
ConstantTypes.CAN_HOIST
123+
ConstantTypes.CAN_STRINGIFY
124124
)
125125
context.imports.push({ exp, path })
126126
}
@@ -131,7 +131,7 @@ export const transformSrcset: NodeTransform = (
131131
`"${url}"`,
132132
false,
133133
attr.loc,
134-
ConstantTypes.CAN_HOIST
134+
ConstantTypes.CAN_STRINGIFY
135135
)
136136
compoundExpression.children.push(exp)
137137
}
@@ -146,7 +146,7 @@ export const transformSrcset: NodeTransform = (
146146
})
147147

148148
const hoisted = context.hoist(compoundExpression)
149-
hoisted.constType = ConstantTypes.CAN_HOIST
149+
hoisted.constType = ConstantTypes.CAN_STRINGIFY
150150

151151
node.props[index] = {
152152
type: NodeTypes.DIRECTIVE,

0 commit comments

Comments
 (0)