Skip to content

feat(compiler-sfc): add preserveTilde option for asset URL and srcset transformations #13462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,17 @@ export function render(_ctx, _cache) {
], 64 /* STABLE_FRAGMENT */))
}"
`;

exports[`compiler sfc: transform asset url > with preserveTilde: true 1`] = `
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
import _imports_0 from '~/app/bar.png'
import _imports_1 from '~app/bar.png'


export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("img", { src: _imports_0 }),
_createElementVNode("img", { src: _imports_1 })
], 64 /* STABLE_FRAGMENT */))
}"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ export function render(_ctx, _cache) {
}"
`;

exports[`compiler sfc: transform srcset > srcset w/ preserveTilde: true 1`] = `
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
import _imports_0 from '~/app/logo.png'
import _imports_1 from '~app/logo.png'


const _hoisted_1 = _imports_0 + ', ' + _imports_1 + ' 2x'
const _hoisted_2 = _imports_1 + ' 1x, ' + _imports_0 + ' 2x'

export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */))
}"
`;

exports[`compiler sfc: transform srcset > transform srcset 1`] = `
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
import _imports_0 from './logo.png'
Expand Down
10 changes: 10 additions & 0 deletions packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ describe('compiler sfc: transform asset url', () => {
expect(code).toMatchSnapshot()
})

test('with preserveTilde: true', () => {
const { code } = compileWithAssetUrls(
`<img src="~/app/bar.png"/>` + `<img src="~app/bar.png"/>`,
{
preserveTilde: true,
},
)
expect(code).toMatchSnapshot()
})

// vitejs/vite#298
test('should not transform hash fragments', () => {
const { code } = compileWithAssetUrls(
Expand Down
11 changes: 11 additions & 0 deletions packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,15 @@ describe('compiler sfc: transform srcset', () => {
).code
expect(code).toMatchSnapshot()
})

test('srcset w/ preserveTilde: true', () => {
const code = compileWithSrcset(
`
<img srcset="~/app/logo.png, ~app/logo.png 2x"/>
<img srcset="~app/logo.png 1x, ~/app/logo.png 2x"/>
`,
{ preserveTilde: true },
).code
expect(code).toMatchSnapshot()
})
})
7 changes: 5 additions & 2 deletions packages/compiler-sfc/src/template/templateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ export function isDataUrl(url: string): boolean {
/**
* Parses string url into URL object.
*/
export function parseUrl(url: string): UrlWithStringQuery {
export function parseUrl(
url: string,
preserveTilde?: boolean,
): UrlWithStringQuery {
const firstChar = url.charAt(0)
if (firstChar === '~') {
if (firstChar === '~' && !preserveTilde) {
const secondChar = url.charAt(1)
url = url.slice(secondChar === '/' ? 2 : 1)
}
Expand Down
11 changes: 9 additions & 2 deletions packages/compiler-sfc/src/template/transformAssetUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,18 @@ export interface AssetURLOptions {
*/
includeAbsolute?: boolean
tags?: AssetURLTagConfig
/**
* Whether to preserve the tilde (~) in asset URLs.
* Nuxt uses ~ as alias for the /app directory.
* see #13460
*/
preserveTilde?: boolean
}

export const defaultAssetUrlOptions: Required<AssetURLOptions> = {
base: null,
includeAbsolute: false,
preserveTilde: false,
tags: {
video: ['src', 'poster'],
source: ['src'],
Expand Down Expand Up @@ -113,12 +120,12 @@ export const transformAssetUrl: NodeTransform = (
return
}

const url = parseUrl(attr.value.content)
const url = parseUrl(attr.value.content, options.preserveTilde)
if (options.base && attr.value.content[0] === '.') {
// explicit base - directly rewrite relative urls into absolute url
// to avoid generating extra imports
// Allow for full hostnames provided in options.base
const base = parseUrl(options.base)
const base = parseUrl(options.base, options.preserveTilde)
const protocol = base.protocol || ''
const host = base.host ? protocol + '//' + base.host : ''
const basePath = base.path || '/'
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-sfc/src/template/transformSrcset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const transformSrcset: NodeTransform = (
const compoundExpression = createCompoundExpression([], attr.loc)
imageCandidates.forEach(({ url, descriptor }, index) => {
if (shouldProcessUrl(url)) {
const { path } = parseUrl(url)
const { path } = parseUrl(url, options.preserveTilde)
let exp: SimpleExpressionNode
if (path) {
const existingImportsIndex = context.imports.findIndex(
Expand Down