]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): preserve hash hrefs on `<image>` elements (#14756)
authorDaniel Roe <daniel@roe.dev>
Wed, 6 May 2026 06:42:47 +0000 (08:42 +0200)
committerGitHub <noreply@github.com>
Wed, 6 May 2026 06:42:47 +0000 (14:42 +0800)
packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
packages/compiler-sfc/src/template/transformAssetUrl.ts

index cd14f1057e85e97b7bfe0bd59fb6df9113f4d7f5..82984ad61475067c5c9228e64de38beb52b32f48 100644 (file)
@@ -150,6 +150,29 @@ describe('compiler sfc: transform asset url', () => {
     expect(code).toContain(`import _imports_0 from './foo%.png'`)
   })
 
+  test('should not transform hash fragments on <image>', () => {
+    // `<image href="#...">` is an in-document fragment reference to another
+    // SVG element (like `<use>`), not a Node.js subpath import specifier.
+    const { code } = compileWithAssetUrls(
+      `<svg><image href="#" /><image href="#myClip" /></svg>`,
+      { includeAbsolute: true },
+    )
+    expect(code).toContain(`href: "#"`)
+    expect(code).toContain(`href: "#myClip"`)
+    expect(code).not.toContain(`from '#'`)
+    expect(code).not.toContain(`from '#myClip'`)
+  })
+
+  test('should not transform bare `#` value into an import', () => {
+    // A bare `#` is never a valid module specifier and should always be left
+    // untouched, even on tags that otherwise support subpath imports.
+    const { code } = compileWithAssetUrls(`<img src="#" />`, {
+      includeAbsolute: true,
+    })
+    expect(code).toContain(`src: "#"`)
+    expect(code).not.toContain(`from '#'`)
+  })
+
   test('should allow for full base URLs, with paths', () => {
     const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
       base: 'http://localhost:3000/src/',
index 182e0201b104ed0abe466a7890162d2621eca1ee..0899d14846f947fb96ffd04f6015945846227a38 100644 (file)
@@ -35,13 +35,14 @@ export interface AssetURLOptions {
   tags?: AssetURLTagConfig
 }
 
-// Built-in attrs that always represent resource URLs. `use` is intentionally
-// omitted because its hash-only values may still be fragment references.
+// Built-in attrs that always represent resource URLs. `use` and `image` are
+// intentionally omitted because their hash-only values are fragment references
+// to in-document elements (e.g. `<image href="#myClip" />`), not module
+// specifiers.
 const resourceUrlTagConfig: AssetURLTagConfig = {
   video: ['src', 'poster'],
   source: ['src'],
   img: ['src'],
-  image: ['xlink:href', 'href'],
 }
 
 export const defaultAssetUrlOptions: Required<AssetURLOptions> = {
@@ -49,6 +50,7 @@ export const defaultAssetUrlOptions: Required<AssetURLOptions> = {
   includeAbsolute: false,
   tags: {
     ...resourceUrlTagConfig,
+    image: ['xlink:href', 'href'],
     use: ['xlink:href', 'href'],
   },
 }
@@ -125,6 +127,8 @@ export const transformAssetUrl: NodeTransform = (
       if (
         isExternalUrl(urlValue) ||
         isDataUrl(urlValue) ||
+        // a bare `#` is never a valid import specifier
+        urlValue === '#' ||
         (isHashOnlyValue && !canTransformHashImport(node.tag, attr.name)) ||
         (!options.includeAbsolute && !isRelativeUrl(urlValue))
       ) {