type NodeTransform,
type StructuralDirectiveTransform,
type DirectiveTransform,
+ type ImportItem,
} from './transform'
export {
generate,
) => void | (() => void)
export interface ImportItem {
- exp: string | ExpressionNode
+ exp: SimpleExpressionNode
path: string
}
} else {
return [
_createVNode("picture", null, [
- _createVNode("source", {
- srcset: _imports_1
- }),
+ _createVNode("source", { srcset: _imports_1 }),
_createVNode("img", { src: _imports_1 })
])
]
import path from 'path'
import {
ConstantTypes,
- type ExpressionNode,
type NodeTransform,
NodeTypes,
- type SimpleExpressionNode,
- createCompoundExpression,
createSimpleExpression,
} from '@vue/compiler-core'
import {
}
}
- const compoundExpression = createCompoundExpression([], attr.loc)
+ let content = ''
imageCandidates.forEach(({ url, descriptor }, index) => {
if (shouldProcessUrl(url)) {
const { path } = parseUrl(url)
- let exp: SimpleExpressionNode
if (path) {
+ let exp = ''
const existingImportsIndex = context.imports.findIndex(
i => i.path === path,
)
if (existingImportsIndex > -1) {
- exp = createSimpleExpression(
- `_imports_${existingImportsIndex}`,
- false,
- attr.loc,
- ConstantTypes.CAN_STRINGIFY,
- )
+ exp = `_imports_${existingImportsIndex}`
} else {
- exp = createSimpleExpression(
- `_imports_${context.imports.length}`,
- false,
- attr.loc,
- ConstantTypes.CAN_STRINGIFY,
- )
- context.imports.push({ exp, path })
+ exp = `_imports_${context.imports.length}`
+ context.imports.push({
+ exp: createSimpleExpression(
+ exp,
+ false,
+ attr.loc,
+ ConstantTypes.CAN_STRINGIFY,
+ ),
+ path,
+ })
}
- compoundExpression.children.push(exp)
+ content += exp
}
} else {
- const exp = createSimpleExpression(
- `"${url}"`,
- false,
- attr.loc,
- ConstantTypes.CAN_STRINGIFY,
- )
- compoundExpression.children.push(exp)
+ content += `"${url}"`
}
const isNotLast = imageCandidates.length - 1 > index
- if (descriptor && isNotLast) {
- compoundExpression.children.push(` + ' ${descriptor}, ' + `)
- } else if (descriptor) {
- compoundExpression.children.push(` + ' ${descriptor}'`)
+ if (descriptor) {
+ content += ` + ' ${descriptor}${isNotLast ? ', ' : ''}'${
+ isNotLast ? ' + ' : ''
+ }`
} else if (isNotLast) {
- compoundExpression.children.push(` + ', ' + `)
+ content += ` + ', ' + `
}
})
- let exp: ExpressionNode = compoundExpression
+ let exp = createSimpleExpression(
+ content,
+ false,
+ attr.loc,
+ ConstantTypes.CAN_STRINGIFY,
+ )
if (context.hoistStatic) {
- exp = context.hoist(compoundExpression)
+ exp = context.hoist(exp)
exp.constType = ConstantTypes.CAN_STRINGIFY
}
--- /dev/null
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler sfc: transform asset url > should allow for full base URLs, with paths 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("<img src=\\"http://localhost:3000/src/logo.png\\">", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler sfc: transform asset url > should allow for full base URLs, without paths 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("<img src=\\"http://localhost:3000/logo.png\\">", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler sfc: transform asset url > should allow for full base URLs, without port 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("<img src=\\"http://localhost/logo.png\\">", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler sfc: transform asset url > should allow for full base URLs, without protocol 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("<img src=\\"//localhost/logo.png\\">", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler sfc: transform asset url > support uri fragment 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from '@svg/file.svg';
+const t0 = _template("<use href=\\"" + _imports_0 + '#fragment' + "\\"></use>", false, 1)
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t0()
+ return [n0, n1]
+}"
+`;
+
+exports[`compiler sfc: transform asset url > support uri is empty 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("<use href=\\"\\"></use>", true, 1)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler sfc: transform asset url > transform assetUrls 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from './logo.png';
+import _imports_1 from 'fixtures/logo.png';
+import _imports_2 from '/fixtures/logo.png';
+const t0 = _template("<img src=\\"" + _imports_0 + "\\">")
+const t1 = _template("<img src=\\"" + _imports_1 + "\\">")
+const t2 = _template("<img src=\\"http://example.com/fixtures/logo.png\\">")
+const t3 = _template("<img src=\\"//example.com/fixtures/logo.png\\">")
+const t4 = _template("<img src=\\"" + _imports_2 + "\\">")
+const t5 = _template("<img src=\\"data:image/png;base64,i\\">")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t1()
+ const n2 = t1()
+ const n3 = t2()
+ const n4 = t3()
+ const n5 = t4()
+ const n6 = t5()
+ return [n0, n1, n2, n3, n4, n5, n6]
+}"
+`;
+
+exports[`compiler sfc: transform asset url > transform with stringify 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from './bar.png';
+import _imports_1 from '/bar.png';
+const t0 = _template("<div><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 + "\\"></div>", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
+exports[`compiler sfc: transform asset url > with explicit base 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from 'bar.png';
+import _imports_1 from '@theme/bar.png';
+const t0 = _template("<img src=\\"/foo/bar.png\\">")
+const t1 = _template("<img src=\\"" + _imports_0 + "\\">")
+const t2 = _template("<img src=\\"" + _imports_1 + "\\">")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t1()
+ const n2 = t1()
+ const n3 = t2()
+ return [n0, n1, n2, n3]
+}"
+`;
+
+exports[`compiler sfc: transform asset url > with includeAbsolute: true 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from './bar.png';
+import _imports_1 from '/bar.png';
+const t0 = _template("<img src=\\"" + _imports_0 + "\\">")
+const t1 = _template("<img src=\\"" + _imports_1 + "\\">")
+const t2 = _template("<img src=\\"https://foo.bar/baz.png\\">")
+const t3 = _template("<img src=\\"//foo.bar/baz.png\\">")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t1()
+ const n2 = t2()
+ const n3 = t3()
+ return [n0, n1, n2, n3]
+}"
+`;
--- /dev/null
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler sfc: transform srcset > srcset w/ explicit base option 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from '@/logo.png';
+import _imports_1 from '/foo/logo.png';
+const t0 = _template("<img srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x' + "\\">")
+const t1 = _template("<img srcset=\\"" + _imports_0 + ' 1x, ' + _imports_1 + ' 2x' + "\\">")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t1()
+ return [n0, n1]
+}"
+`;
+
+exports[`compiler sfc: transform srcset > transform srcset 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from './logo.png';
+import _imports_1 from '/logo.png';
+const t0 = _template("<img src=\\"" + _imports_0 + "\\" srcset>")
+const t1 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + "\\">")
+const t2 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x' + "\\">")
+const t3 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x' + "\\">")
+const t4 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x, ' + _imports_0 + "\\">")
+const t5 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' + "\\">")
+const t6 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' + "\\">")
+const t7 = _template("<img src=\\"" + _imports_1 + "\\" srcset=\\"" + _imports_1 + ', ' + _imports_1 + ' 2x' + "\\">")
+const t8 = _template("<img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\">")
+const t9 = _template("<img src=\\"" + _imports_1 + "\\" srcset=\\"" + _imports_1 + ', ' + _imports_0 + ' 2x' + "\\">")
+const t10 = _template("<img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t1()
+ const n2 = t2()
+ const n3 = t2()
+ const n4 = t3()
+ const n5 = t4()
+ const n6 = t5()
+ const n7 = t6()
+ const n8 = t7()
+ const n9 = t8()
+ const n10 = t9()
+ const n11 = t10()
+ return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11]
+}"
+`;
+
+exports[`compiler sfc: transform srcset > transform srcset w/ base 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from '/logo.png';
+import _imports_1 from '/foo/logo.png';
+const t0 = _template("<img src=\\"/foo/logo.png\\" srcset>")
+const t1 = _template("<img src=\\"/foo/logo.png\\" srcset=\\"/foo/logo.png\\">")
+const t2 = _template("<img src=\\"/foo/logo.png\\" srcset=\\"/foo/logo.png 2x\\">")
+const t3 = _template("<img src=\\"/foo/logo.png\\" srcset=\\"/foo/logo.png, /foo/logo.png 2x\\">")
+const t4 = _template("<img src=\\"/foo/logo.png\\" srcset=\\"/foo/logo.png 2x, /foo/logo.png\\">")
+const t5 = _template("<img src=\\"/foo/logo.png\\" srcset=\\"/foo/logo.png 2x, /foo/logo.png 3x\\">")
+const t6 = _template("<img src=\\"/foo/logo.png\\" srcset=\\"/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x\\">")
+const t7 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x' + "\\">")
+const t8 = _template("<img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\">")
+const t9 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_1 + ' 2x' + "\\">")
+const t10 = _template("<img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t1()
+ const n2 = t2()
+ const n3 = t2()
+ const n4 = t3()
+ const n5 = t4()
+ const n6 = t5()
+ const n7 = t6()
+ const n8 = t7()
+ const n9 = t8()
+ const n10 = t9()
+ const n11 = t10()
+ return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11]
+}"
+`;
+
+exports[`compiler sfc: transform srcset > transform srcset w/ includeAbsolute: true 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from './logo.png';
+import _imports_1 from '/logo.png';
+const t0 = _template("<img src=\\"" + _imports_0 + "\\" srcset>")
+const t1 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + "\\">")
+const t2 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x' + "\\">")
+const t3 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x' + "\\">")
+const t4 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x, ' + _imports_0 + "\\">")
+const t5 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' + "\\">")
+const t6 = _template("<img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' + "\\">")
+const t7 = _template("<img src=\\"" + _imports_1 + "\\" srcset=\\"" + _imports_1 + ', ' + _imports_1 + ' 2x' + "\\">")
+const t8 = _template("<img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\">")
+const t9 = _template("<img src=\\"" + _imports_1 + "\\" srcset=\\"" + _imports_1 + ', ' + _imports_0 + ' 2x' + "\\">")
+const t10 = _template("<img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">")
+
+export function render(_ctx) {
+ const n0 = t0()
+ const n1 = t1()
+ const n2 = t2()
+ const n3 = t2()
+ const n4 = t3()
+ const n5 = t4()
+ const n6 = t5()
+ const n7 = t6()
+ const n8 = t7()
+ const n9 = t8()
+ const n10 = t9()
+ const n11 = t10()
+ return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11]
+}"
+`;
+
+exports[`compiler sfc: transform srcset > transform srcset w/ stringify 1`] = `
+"import { template as _template } from 'vue';
+import _imports_0 from './logo.png';
+import _imports_1 from '/logo.png';
+const t0 = _template("<div><img src=\\"" + _imports_0 + "\\" srcset><img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x' + "\\"><img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x' + "\\"><img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x' + "\\"><img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x, ' + _imports_0 + "\\"><img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' + "\\"><img src=\\"" + _imports_0 + "\\" srcset=\\"" + _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x' + "\\"><img src=\\"" + _imports_1 + "\\" srcset=\\"" + _imports_1 + ', ' + _imports_1 + ' 2x' + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"" + _imports_1 + "\\" srcset=\\"" + _imports_1 + ', ' + _imports_0 + ' 2x' + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\"></div>", true)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
--- /dev/null
+import type { TransformOptions } from '@vue/compiler-core'
+import type { AssetURLOptions } from '../../../compiler-sfc/src/template/transformAssetUrl'
+import { stringifyStatic } from '../../../compiler-dom/src/transforms/stringifyStatic'
+import { compileTemplate } from '../../../compiler-sfc/src'
+
+function compileWithAssetUrls(
+ template: string,
+ options?: AssetURLOptions,
+ transformOptions?: TransformOptions,
+) {
+ return compileTemplate({
+ vapor: true,
+ id: 'test',
+ filename: 'test.vue',
+ source: template,
+ transformAssetUrls: {
+ includeAbsolute: true,
+ ...options,
+ },
+ })
+}
+
+describe('compiler sfc: transform asset url', () => {
+ test('transform assetUrls', () => {
+ const result = compileWithAssetUrls(`
+ <img src="./logo.png"/>
+ <img src="~fixtures/logo.png"/>
+ <img src="~/fixtures/logo.png"/>
+ <img src="http://example.com/fixtures/logo.png"/>
+ <img src="//example.com/fixtures/logo.png"/>
+ <img src="/fixtures/logo.png"/>
+ <img src="data:image/png;base64,i"/>
+ `)
+
+ expect(result.code).toMatchSnapshot()
+ })
+
+ /**
+ * vuejs/component-compiler-utils#22 Support uri fragment in transformed require
+ */
+ test('support uri fragment', () => {
+ const result = compileWithAssetUrls(
+ '<use href="~@svg/file.svg#fragment"></use>' +
+ '<use href="~@svg/file.svg#fragment"></use>',
+ {},
+ {
+ hoistStatic: true,
+ },
+ )
+ expect(result.code).toMatchSnapshot()
+ })
+
+ /**
+ * vuejs/component-compiler-utils#22 Support uri fragment in transformed require
+ */
+ test('support uri is empty', () => {
+ const result = compileWithAssetUrls('<use href="~"></use>')
+
+ expect(result.code).toMatchSnapshot()
+ })
+
+ test('with explicit base', () => {
+ const { code } = compileWithAssetUrls(
+ `<img src="./bar.png"></img>` + // -> /foo/bar.png
+ `<img src="bar.png"></img>` + // -> bar.png (untouched)
+ `<img src="~bar.png"></img>` + // -> still converts to import
+ `<img src="@theme/bar.png"></img>`, // -> still converts to import
+ {
+ base: '/foo',
+ },
+ )
+ expect(code).toMatch(`import _imports_0 from 'bar.png'`)
+ expect(code).toMatch(`import _imports_1 from '@theme/bar.png'`)
+ expect(code).toMatchSnapshot()
+ })
+
+ test('with includeAbsolute: true', () => {
+ const { code } = compileWithAssetUrls(
+ `<img src="./bar.png"/>` +
+ `<img src="/bar.png"/>` +
+ `<img src="https://foo.bar/baz.png"/>` +
+ `<img src="//foo.bar/baz.png"/>`,
+ {
+ includeAbsolute: true,
+ },
+ )
+ expect(code).toMatchSnapshot()
+ })
+
+ // vitejs/vite#298
+ test('should not transform hash fragments', () => {
+ const { code } = compileWithAssetUrls(
+ `<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="myCircle" cx="0" cy="0" r="5" />
+ </defs>
+ <use x="5" y="5" xlink:href="#myCircle" />
+ </svg>`,
+ )
+ // should not remove it
+ expect(code).toMatch(`xlink:href=\\"#myCircle\\"`) // compiled to template string, not object, so remove quotes
+ })
+
+ test('should allow for full base URLs, with paths', () => {
+ const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
+ base: 'http://localhost:3000/src/',
+ })
+
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should allow for full base URLs, without paths', () => {
+ const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
+ base: 'http://localhost:3000',
+ })
+
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should allow for full base URLs, without port', () => {
+ const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
+ base: 'http://localhost',
+ })
+
+ expect(code).toMatchSnapshot()
+ })
+
+ test('should allow for full base URLs, without protocol', () => {
+ const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
+ base: '//localhost',
+ })
+
+ expect(code).toMatchSnapshot()
+ })
+
+ test('transform with stringify', () => {
+ const { code } = compileWithAssetUrls(
+ `<div>` +
+ `<img src="./bar.png"/>` +
+ `<img src="/bar.png"/>` +
+ `<img src="https://foo.bar/baz.png"/>` +
+ `<img src="//foo.bar/baz.png"/>` +
+ `<img src="./bar.png"/>` +
+ `</div>`,
+ {
+ includeAbsolute: true,
+ },
+ {
+ hoistStatic: true,
+ transformHoist: stringifyStatic,
+ },
+ )
+ expect(code).toMatchSnapshot()
+ })
+
+ test('transform with stringify with space in absolute filename', () => {
+ const { code } = compileWithAssetUrls(
+ `<div><img src="/foo bar.png"/></div>`,
+ {
+ includeAbsolute: true,
+ },
+ {
+ hoistStatic: true,
+ transformHoist: stringifyStatic,
+ },
+ )
+ expect(code).toContain(`import _imports_0 from '/foo bar.png'`)
+ })
+
+ test('transform with stringify with space in relative filename', () => {
+ const { code } = compileWithAssetUrls(
+ `<div><img src="./foo bar.png"/></div>`,
+ {
+ includeAbsolute: true,
+ },
+ {
+ hoistStatic: true,
+ transformHoist: stringifyStatic,
+ },
+ )
+ expect(code).toContain(`import _imports_0 from './foo bar.png'`)
+ })
+})
--- /dev/null
+import type { TransformOptions } from '@vue/compiler-core'
+import type { AssetURLOptions } from '../../../compiler-sfc/src/template/transformAssetUrl'
+import { compileTemplate } from '../../../compiler-sfc/src/compileTemplate'
+import { stringifyStatic } from '../../../compiler-dom/src/transforms/stringifyStatic'
+
+function compileWithSrcset(
+ template: string,
+ options?: AssetURLOptions,
+ transformOptions?: TransformOptions,
+) {
+ return compileTemplate({
+ vapor: true,
+ id: 'test',
+ filename: 'test.vue',
+ source: template,
+ transformAssetUrls: {
+ includeAbsolute: true,
+ ...options,
+ },
+ })
+}
+
+const src = `
+<img src="./logo.png" srcset=""/>
+<img src="./logo.png" srcset="./logo.png"/>
+<img src="./logo.png" srcset="./logo.png 2x"/>
+<img src="./logo.png" srcset="./logo.png 2x"/>
+<img src="./logo.png" srcset="./logo.png, ./logo.png 2x"/>
+<img src="./logo.png" srcset="./logo.png 2x, ./logo.png"/>
+<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x"/>
+<img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x"/>
+<img src="/logo.png" srcset="/logo.png, /logo.png 2x"/>
+<img src="https://example.com/logo.png" srcset="https://example.com/logo.png, https://example.com/logo.png 2x"/>
+<img src="/logo.png" srcset="/logo.png, ./logo.png 2x"/>
+<img src="data:image/png;base64,i" srcset="data:image/png;base64,i 1x, data:image/png;base64,i 2x"/>
+`
+
+describe('compiler sfc: transform srcset', () => {
+ test('transform srcset', () => {
+ expect(compileWithSrcset(src).code).toMatchSnapshot()
+ })
+
+ test('transform srcset w/ base', () => {
+ expect(
+ compileWithSrcset(src, {
+ base: '/foo',
+ }).code,
+ ).toMatchSnapshot()
+ })
+
+ test('transform srcset w/ includeAbsolute: true', () => {
+ expect(
+ compileWithSrcset(src, {
+ includeAbsolute: true,
+ }).code,
+ ).toMatchSnapshot()
+ })
+
+ test('transform srcset w/ stringify', () => {
+ const code = compileWithSrcset(
+ `<div>${src}</div>`,
+ {
+ includeAbsolute: true,
+ },
+ {
+ hoistStatic: true,
+ transformHoist: stringifyStatic,
+ },
+ ).code
+ expect(code).toMatchSnapshot()
+ })
+
+ test('srcset w/ explicit base option', () => {
+ const code = compileWithSrcset(
+ `
+ <img srcset="@/logo.png, @/logo.png 2x"/>
+ <img srcset="@/logo.png 1x, ./logo.png 2x"/>
+ `,
+ { base: '/foo/' },
+ { hoistStatic: true },
+ ).code
+ expect(code).toMatchSnapshot()
+ })
+})
const delegates = genDelegates(context)
const templates = genTemplates(ir.template, ir.rootTemplateIndex, context)
- const imports = genHelperImports(context)
+ const imports = genHelperImports(context) + genAssetImports(context)
const preamble = imports + templates + delegates
const newlineCount = [...preamble].filter(c => c === '\n').length
}
return imports
}
+
+function genAssetImports({ ir }: CodegenContext) {
+ const assetImports = ir.node.imports
+ let imports = ''
+ for (const assetImport of assetImports) {
+ const exp = assetImport.exp
+ const name = exp.content
+ imports += `import ${name} from '${assetImport.path}';\n`
+ }
+ return imports
+}
} from '../ir'
import { genDirectivesForElement } from './directive'
import { genOperationWithInsertionState } from './operation'
-import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
+import {
+ type CodeFragment,
+ IMPORT_EXPR_RE,
+ NEWLINE,
+ buildCodeFragment,
+ genCall,
+} from './utils'
export function genTemplates(
templates: Map<string, number>,
result.push(
`const ${context.tName(i)} = ${context.helper('template')}(${JSON.stringify(
template,
+ ).replace(
+ // replace import expressions with string concatenation
+ IMPORT_EXPR_RE,
+ `" + $1 + "`,
)}${i === rootIndex ? ', true' : ns ? ', false' : ''}${ns ? `, ${ns}` : ''})\n`,
)
i++
import { isArray, isString } from '@vue/shared'
import type { CodegenContext } from '../generate'
+export const IMPORT_EXP_START = '__IMPORT_EXP_START__'
+export const IMPORT_EXP_END = '__IMPORT_EXP_END__'
+export const IMPORT_EXPR_RE: RegExp = new RegExp(
+ `${IMPORT_EXP_START}(.*?)${IMPORT_EXP_END}`,
+ 'g',
+)
+
export const NEWLINE: unique symbol = Symbol(__DEV__ ? `newline` : ``)
/** increase offset but don't push actual code */
export const LF: unique symbol = Symbol(__DEV__ ? `line feed` : ``)
} from './ir'
import { isConstantExpression, isStaticExpression } from './utils'
import { newBlock, newDynamic } from './transforms/utils'
+import type { ImportItem } from '@vue/compiler-core'
export type NodeTransform = (
node: RootNode | TemplateChildNode,
template: string = ''
childrenTemplate: (string | null)[] = []
dynamic: IRDynamicInfo = this.ir.block.dynamic
+ imports: ImportItem[] = []
inVOnce: boolean = false
inVFor: number = 0
transformNode(context)
+ ir.node.imports = context.imports
+
return ir
}
} from '../ir'
import { EMPTY_EXPRESSION } from './utils'
import { findProp, isBuiltInComponent } from '../utils'
+import { IMPORT_EXP_END, IMPORT_EXP_START } from '../generators/utils'
export const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap(
// the leading comma is intentional so empty string "" is also included
} else {
for (const prop of propsResult[1]) {
const { key, values } = prop
+ // handling asset imports
if (
+ context.imports.some(imported =>
+ values[0].content.includes(imported.exp.content),
+ )
+ ) {
+ // add start and end markers to the import expression, so it can be replaced
+ // with string concatenation in the generator, see genTemplates
+ template += ` ${key.content}="${IMPORT_EXP_START}${values[0].content}${IMPORT_EXP_END}"`
+ } else if (
key.isStatic &&
values.length === 1 &&
- values[0].isStatic &&
+ (values[0].isStatic || values[0].content === "''") &&
!dynamicKeys.includes(key.content)
) {
template += ` ${key.content}`
- if (values[0].content) template += `="${values[0].content}"`
+ if (values[0].content)
+ template += `="${values[0].content === "''" ? '' : values[0].content}"`
} else {
dynamicProps.push(key.content)
context.registerEffect(