templateTransformAssetUrl.spec.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import {
  2. type TransformOptions,
  3. baseParse,
  4. generate,
  5. transform,
  6. } from '@vue/compiler-core'
  7. import {
  8. type AssetURLOptions,
  9. createAssetUrlTransformWithOptions,
  10. normalizeOptions,
  11. transformAssetUrl,
  12. } from '../src/template/transformAssetUrl'
  13. import { transformElement } from '../../compiler-core/src/transforms/transformElement'
  14. import { transformBind } from '../../compiler-core/src/transforms/vBind'
  15. import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic'
  16. function compileWithAssetUrls(
  17. template: string,
  18. options?: AssetURLOptions,
  19. transformOptions?: TransformOptions,
  20. ) {
  21. const ast = baseParse(template)
  22. const t = options
  23. ? createAssetUrlTransformWithOptions(normalizeOptions(options))
  24. : transformAssetUrl
  25. transform(ast, {
  26. nodeTransforms: [t, transformElement],
  27. directiveTransforms: {
  28. bind: transformBind,
  29. },
  30. ...transformOptions,
  31. })
  32. return generate(ast, { mode: 'module' })
  33. }
  34. describe('compiler sfc: transform asset url', () => {
  35. test('transform assetUrls', () => {
  36. const result = compileWithAssetUrls(`
  37. <img src="./logo.png"/>
  38. <img src="~fixtures/logo.png"/>
  39. <img src="~/fixtures/logo.png"/>
  40. <img src="http://example.com/fixtures/logo.png"/>
  41. <img src="//example.com/fixtures/logo.png"/>
  42. <img src="/fixtures/logo.png"/>
  43. <img src="data:image/png;base64,i"/>
  44. `)
  45. expect(result.code).toMatchSnapshot()
  46. })
  47. /**
  48. * vuejs/component-compiler-utils#22 Support uri fragment in transformed require
  49. */
  50. test('support uri fragment', () => {
  51. const result = compileWithAssetUrls(
  52. '<use href="~@svg/file.svg#fragment"></use>' +
  53. '<use href="~@svg/file.svg#fragment"></use>',
  54. {},
  55. {
  56. hoistStatic: true,
  57. },
  58. )
  59. expect(result.code).toMatchSnapshot()
  60. })
  61. /**
  62. * vuejs/component-compiler-utils#22 Support uri fragment in transformed require
  63. */
  64. test('support uri is empty', () => {
  65. const result = compileWithAssetUrls('<use href="~"></use>')
  66. expect(result.code).toMatchSnapshot()
  67. })
  68. test('with explicit base', () => {
  69. const { code } = compileWithAssetUrls(
  70. `<img src="./bar.png"></img>` + // -> /foo/bar.png
  71. `<img src="bar.png"></img>` + // -> bar.png (untouched)
  72. `<img src="~bar.png"></img>` + // -> still converts to import
  73. `<img src="@theme/bar.png"></img>`, // -> still converts to import
  74. {
  75. base: '/foo',
  76. },
  77. )
  78. expect(code).toMatch(`import _imports_0 from 'bar.png'`)
  79. expect(code).toMatch(`import _imports_1 from '@theme/bar.png'`)
  80. expect(code).toMatchSnapshot()
  81. })
  82. test('with includeAbsolute: true', () => {
  83. const { code } = compileWithAssetUrls(
  84. `<img src="./bar.png"/>` +
  85. `<img src="/bar.png"/>` +
  86. `<img src="https://foo.bar/baz.png"/>` +
  87. `<img src="//foo.bar/baz.png"/>`,
  88. {
  89. includeAbsolute: true,
  90. },
  91. )
  92. expect(code).toMatchSnapshot()
  93. })
  94. // vitejs/vite#298
  95. test('should not transform hash fragments', () => {
  96. const { code } = compileWithAssetUrls(
  97. `<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  98. <defs>
  99. <circle id="myCircle" cx="0" cy="0" r="5" />
  100. </defs>
  101. <use x="5" y="5" xlink:href="#myCircle" />
  102. </svg>`,
  103. )
  104. // should not remove it
  105. expect(code).toMatch(`"xlink:href": "#myCircle"`)
  106. })
  107. // #9919
  108. test('should transform subpath import paths', () => {
  109. const { code } = compileWithAssetUrls(
  110. `<img src="#src/assets/vue.svg" />` +
  111. `<img src="#/src/assets/vue.svg" />`,
  112. )
  113. expect(code).toContain(`_imports_0 from '#src/assets/vue.svg'`)
  114. expect(code).toContain(`_imports_1 from '#/src/assets/vue.svg'`)
  115. })
  116. test('should not transform pure hash values for custom asset URL tags', () => {
  117. const { code } = compileWithAssetUrls(
  118. `<foo bar="#fragment" />` +
  119. `<foo bar="#src/assets/vue.svg" />` +
  120. `<foo bar="#/src/assets/vue.svg" />`,
  121. {
  122. tags: {
  123. foo: ['bar'],
  124. },
  125. },
  126. )
  127. expect(code).toContain(`bar: "#fragment"`)
  128. expect(code).toContain(`bar: "#src/assets/vue.svg"`)
  129. expect(code).toContain(`bar: "#/src/assets/vue.svg"`)
  130. expect(code).not.toContain(`from '#fragment'`)
  131. expect(code).not.toContain(`from '#src/assets/vue.svg'`)
  132. expect(code).not.toContain(`from '#/src/assets/vue.svg'`)
  133. })
  134. test('should not throw for malformed percent-encoding in asset paths', () => {
  135. const { code } = compileWithAssetUrls(`<img src="./foo%.png" />`)
  136. expect(code).toContain(`import _imports_0 from './foo%.png'`)
  137. })
  138. test('should allow for full base URLs, with paths', () => {
  139. const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
  140. base: 'http://localhost:3000/src/',
  141. })
  142. expect(code).toMatchSnapshot()
  143. })
  144. test('should allow for full base URLs, without paths', () => {
  145. const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
  146. base: 'http://localhost:3000',
  147. })
  148. expect(code).toMatchSnapshot()
  149. })
  150. test('should allow for full base URLs, without port', () => {
  151. const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
  152. base: 'http://localhost',
  153. })
  154. expect(code).toMatchSnapshot()
  155. })
  156. test('should allow for full base URLs, without protocol', () => {
  157. const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
  158. base: '//localhost',
  159. })
  160. expect(code).toMatchSnapshot()
  161. })
  162. test('transform with stringify', () => {
  163. const { code } = compileWithAssetUrls(
  164. `<div>` +
  165. `<img src="./bar.png"/>` +
  166. `<img src="/bar.png"/>` +
  167. `<img src="https://foo.bar/baz.png"/>` +
  168. `<img src="//foo.bar/baz.png"/>` +
  169. `<img src="./bar.png"/>` +
  170. `</div>`,
  171. {
  172. includeAbsolute: true,
  173. },
  174. {
  175. hoistStatic: true,
  176. transformHoist: stringifyStatic,
  177. },
  178. )
  179. expect(code).toMatch(`_createStaticVNode`)
  180. expect(code).toMatchSnapshot()
  181. })
  182. test('transform with stringify with space in absolute filename', () => {
  183. const { code } = compileWithAssetUrls(
  184. `<div><img src="/foo bar.png"/></div>`,
  185. {
  186. includeAbsolute: true,
  187. },
  188. {
  189. hoistStatic: true,
  190. transformHoist: stringifyStatic,
  191. },
  192. )
  193. expect(code).toMatch(`_createElementVNode`)
  194. expect(code).toContain(`import _imports_0 from '/foo bar.png'`)
  195. })
  196. test('transform with stringify with space in relative filename', () => {
  197. const { code } = compileWithAssetUrls(
  198. `<div><img src="./foo bar.png"/></div>`,
  199. {
  200. includeAbsolute: true,
  201. },
  202. {
  203. hoistStatic: true,
  204. transformHoist: stringifyStatic,
  205. },
  206. )
  207. expect(code).toMatch(`_createElementVNode`)
  208. expect(code).toContain(`import _imports_0 from './foo bar.png'`)
  209. })
  210. })