From 4cbb40d2f1a4050c9944e7e1d9e0f5b204b7e37b Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 22 Jun 2021 12:24:51 -0400 Subject: [PATCH] feat: support new URL(url, import.meta.url) usage --- docs/guide/assets.md | 26 +++++ .../assets/__tests__/assets.spec.ts | 13 +++ packages/playground/assets/index.html | 27 ++++++ packages/vite/src/node/build.ts | 2 + packages/vite/src/node/importGlob.ts | 8 +- .../src/node/plugins/assetImportMetaUrl.ts | 96 +++++++++++++++++++ 6 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 packages/vite/src/node/plugins/assetImportMetaUrl.ts diff --git a/docs/guide/assets.md b/docs/guide/assets.md index c60711cdadfa23..2d974ece37d049 100644 --- a/docs/guide/assets.md +++ b/docs/guide/assets.md @@ -82,3 +82,29 @@ Note that: - You should always reference `public` assets using root absolute path - for example, `public/icon.png` should be referenced in source code as `/icon.png`. - Assets in `public` cannot be imported from JavaScript. + +## new URL(url, import.meta.url) + +[import.meta.url](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import.meta) is a native ESM feature that exposes the current module's URL. Combining it with the native [URL constructor](https://developer.mozilla.org/en-US/docs/Web/API/URL), we can obtain the full, resolved URL of a static asset using relative path from a JavaScript module: + +```js +const imgUrl = new URL('./img.png', import.meta.url) + +document.getElementById('hero-img').src = imgUrl +``` + +This works natively in modern browsers - in fact, Vite doesn't need to process this code at all during development! + +This pattern also supports dynamic URLs via template literals: + +```js +function getImageUrl(name) { + return new URL(`./dir/${name}.png`, import.meta.url).href +} +``` + +During the production build, Vite will perform necessary transforms so that the URLs still point to the correct location even after bundling and asset hashing. + +::: warning Note: Does not work with SSR +This pattern does not work if you are using Vite for Server-Side Rendering, because `import.meta.url` have different semantics in browsers vs. Node.js. The server bundle also cannot determine the client host URL ahead of time. +::: diff --git a/packages/playground/assets/__tests__/assets.spec.ts b/packages/playground/assets/__tests__/assets.spec.ts index 1b38346e49c700..083148c59aff89 100644 --- a/packages/playground/assets/__tests__/assets.spec.ts +++ b/packages/playground/assets/__tests__/assets.spec.ts @@ -183,6 +183,19 @@ test('?url import', async () => { ) }) +test('new URL(..., import.meta.url)', async () => { + expect(await page.textContent('.import-meta-url')).toMatch(assetMatch) +}) + +test('new URL(`${dynamic}`, import.meta.url)', async () => { + expect(await page.textContent('.dynamic-import-meta-url-1')).toMatch( + isBuild ? 'data:image/png;base64' : '/foo/nested/icon.png' + ) + expect(await page.textContent('.dynamic-import-meta-url-2')).toMatch( + assetMatch + ) +}) + if (isBuild) { test('manifest', async () => { const manifest = readManifest('foo') diff --git a/packages/playground/assets/index.html b/packages/playground/assets/index.html index a578e64388b740..8668fa03b4f483 100644 --- a/packages/playground/assets/index.html +++ b/packages/playground/assets/index.html @@ -118,6 +118,20 @@

?raw import

?url import

+

new URL('...', import.meta.url)

+ + + +

new URL(`./${dynamic}`, import.meta.url)

+

+ + +

+

+ + +

+