]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-sfc): add preserveTilde option for asset URL and srcset transformations edison/fix/13460 13462/head
authordaiwei <daiwei521@126.com>
Wed, 11 Jun 2025 01:01:31 +0000 (09:01 +0800)
committerdaiwei <daiwei521@126.com>
Wed, 11 Jun 2025 01:01:31 +0000 (09:01 +0800)
packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap
packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap
packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
packages/compiler-sfc/src/template/templateUtils.ts
packages/compiler-sfc/src/template/transformAssetUrl.ts
packages/compiler-sfc/src/template/transformSrcset.ts

index 18ec05da2b574ad66d9c47a1cfdd02331f67dc69..6d95091ddb2a0f23cd67ed402c5f9f6750aaae7d 100644 (file)
@@ -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 */))
+}"
+`;
index 0469ffaba88655cd95429cc95d7b27883eed6fd4..ade723deac69f00cf7fc28d3f683f73c78cf930d 100644 (file)
@@ -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'
index a1f62f43a3c9dfa828554d88173a4279958a13e4..cfedf13fd7a70d70583ab9cd63c80950015e6ff5 100644 (file)
@@ -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(
index 491731f94e589640251612191609f7fb7a38dde6..55342002101e7f84cffcd42b3cdbe9cde5323552 100644 (file)
@@ -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()
+  })
 })
index c1414d1ecbdb3dc992943044d1d92fdfeee5eaab..7ac5e87278005216d651a3f394e032cf21ab158e 100644 (file)
@@ -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)
   }
index 6291e21bbba29c9a0bc30b85b5d9b767a91a24bf..1deb015cbae91ec2b18fc924864d9f051fe29990 100644 (file)
@@ -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'],
@@ -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 || '/'
index 8f00f86e3c3ac79d4becba78c1c8c7729219ff46..aefc4895bc559d9ef75c8291854e5d8de1afa7e4 100644 (file)
@@ -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(