From bafccf52aa567d744c156d599f957131633351a6 Mon Sep 17 00:00:00 2001
From: Kyle Herock
Date: Fri, 23 Sep 2022 11:31:18 -0400
Subject: [PATCH] feat(assets): allow `new URL` to resolve package assets
(#7837)
---
packages/vite/src/node/build.ts | 2 -
.../src/node/plugins/assetImportMetaUrl.ts | 52 ++++++++++++++++---
packages/vite/src/node/plugins/index.ts | 2 +
.../src/node/plugins/workerImportMetaUrl.ts | 41 ++++++++++-----
playground/assets/__tests__/assets.spec.ts | 10 ++++
playground/assets/index.html | 18 +++++++
.../worker/__tests__/es/es-worker.spec.ts | 10 ++++
.../worker/__tests__/iife/iife-worker.spec.ts | 8 +++
playground/worker/index.html | 6 +++
playground/worker/vite.config-es.js | 5 ++
playground/worker/vite.config-iife.js | 5 ++
.../worker/vite.config-relative-base.js | 5 ++
playground/worker/vite.config-sourcemap.js | 5 ++
playground/worker/worker/main-module.js | 9 ++++
14 files changed, 157 insertions(+), 21 deletions(-)
diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts
index 2d279bce3d8795..1d8b0e209680fe 100644
--- a/packages/vite/src/node/build.ts
+++ b/packages/vite/src/node/build.ts
@@ -42,7 +42,6 @@ import {
getDepsCacheDir,
initDepsOptimizer
} from './optimizer'
-import { assetImportMetaUrlPlugin } from './plugins/assetImportMetaUrl'
import { loadFallbackPlugin } from './plugins/loadFallback'
import type { PackageData } from './packages'
import { watchPackageDataPlugin } from './packages'
@@ -310,7 +309,6 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
watchPackageDataPlugin(config),
...(usePluginCommonjs ? [commonjsPlugin(options.commonjsOptions)] : []),
dataURIPlugin(),
- assetImportMetaUrlPlugin(config),
...(options.rollupOptions.plugins
? (options.rollupOptions.plugins.filter(Boolean) as Plugin[])
: [])
diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts
index 4b52a225a68458..de946614620866 100644
--- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts
+++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts
@@ -3,7 +3,13 @@ import MagicString from 'magic-string'
import { stripLiteral } from 'strip-literal'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
-import { transformStableResult } from '../utils'
+import type { ResolveFn } from '../'
+import {
+ isParentDirectory,
+ normalizePath,
+ slash,
+ transformStableResult
+} from '../utils'
import { fileToUrl } from './asset'
import { preloadHelperId } from './importAnalysisBuild'
@@ -18,6 +24,9 @@ import { preloadHelperId } from './importAnalysisBuild'
* ```
*/
export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
+ const normalizedPublicDir = normalizePath(config.publicDir)
+ let assetResolver: ResolveFn
+
return {
name: 'vite:asset-import-meta-url',
async transform(code, id, options) {
@@ -63,16 +72,45 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
}
const url = rawUrl.slice(1, -1)
- const file = path.resolve(path.dirname(id), url)
- // Get final asset URL. Catch error if the file does not exist,
- // in which we can resort to the initial URL and let it resolve in runtime
- const builtUrl = await fileToUrl(file, config, this).catch(() => {
+ let file: string | undefined
+ if (url.startsWith('.')) {
+ file = slash(path.resolve(path.dirname(id), url))
+ } else {
+ assetResolver ??= config.createResolver({
+ extensions: [],
+ mainFields: [],
+ tryIndex: false,
+ preferRelative: true
+ })
+ file = await assetResolver(url, id)
+ file ??= url.startsWith('/')
+ ? slash(path.join(config.publicDir, url))
+ : slash(path.resolve(path.dirname(id), url))
+ }
+
+ // Get final asset URL. If the file does not exist,
+ // we fall back to the initial URL and let it resolve in runtime
+ let builtUrl: string | undefined
+ if (file) {
+ try {
+ if (isParentDirectory(normalizedPublicDir, file)) {
+ const publicPath =
+ '/' + path.posix.relative(normalizedPublicDir, file)
+ builtUrl = await fileToUrl(publicPath, config, this)
+ } else {
+ builtUrl = await fileToUrl(file, config, this)
+ }
+ } catch {
+ // do nothing, we'll log a warning after this
+ }
+ }
+ if (!builtUrl) {
const rawExp = code.slice(index, index + exp.length)
config.logger.warnOnce(
`\n${rawExp} doesn't exist at build time, it will remain unchanged to be resolved at runtime`
)
- return url
- })
+ builtUrl = url
+ }
s.overwrite(
index,
index + exp.length,
diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts
index 75c8297bf26919..f5db400a7b7c7d 100644
--- a/packages/vite/src/node/plugins/index.ts
+++ b/packages/vite/src/node/plugins/index.ts
@@ -20,6 +20,7 @@ import { preAliasPlugin } from './preAlias'
import { definePlugin } from './define'
import { ssrRequireHookPlugin } from './ssrRequireHook'
import { workerImportMetaUrlPlugin } from './workerImportMetaUrl'
+import { assetImportMetaUrlPlugin } from './assetImportMetaUrl'
import { ensureWatchPlugin } from './ensureWatch'
import { metadataPlugin } from './metadata'
import { dynamicImportVarsPlugin } from './dynamicImportVars'
@@ -88,6 +89,7 @@ export async function resolvePlugins(
isBuild && config.build.ssr ? ssrRequireHookPlugin(config) : null,
isBuild && buildHtmlPlugin(config),
workerImportMetaUrlPlugin(config),
+ assetImportMetaUrlPlugin(config),
...buildPlugins.pre,
dynamicImportVarsPlugin(config),
importGlobPlugin(config),
diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts
index 6a0201a9a53707..8867924cb093c0 100644
--- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts
+++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts
@@ -8,11 +8,12 @@ import type { Plugin } from '../plugin'
import {
cleanUrl,
injectQuery,
- normalizePath,
parseRequest,
+ slash,
transformStableResult
} from '../utils'
import { getDepsOptimizer } from '../optimizer'
+import type { ResolveFn } from '..'
import type { WorkerType } from './worker'
import { WORKER_FILE_ID, workerFileToUrl } from './worker'
import { fileToUrl } from './asset'
@@ -75,6 +76,7 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType {
export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
const isBuild = config.command === 'build'
+ let workerResolver: ResolveFn
return {
name: 'vite:worker-import-meta-url',
@@ -116,22 +118,37 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
cleanString,
index + allExp.length
)
- const file = normalizePath(
- path.resolve(path.dirname(id), rawUrl.slice(1, -1))
- )
+ const url = rawUrl.slice(1, -1)
+ let file: string | undefined
+ if (url.startsWith('.')) {
+ file = path.resolve(path.dirname(id), url)
+ } else {
+ workerResolver ??= config.createResolver({
+ extensions: [],
+ tryIndex: false,
+ preferRelative: true
+ })
+ file = await workerResolver(url, id)
+ file ??= url.startsWith('/')
+ ? slash(path.join(config.publicDir, url))
+ : slash(path.resolve(path.dirname(id), url))
+ }
- let url: string
+ let builtUrl: string
if (isBuild) {
getDepsOptimizer(config, ssr)?.registerWorkersSource(id)
- url = await workerFileToUrl(config, file, query)
+ builtUrl = await workerFileToUrl(config, file, query)
} else {
- url = await fileToUrl(cleanUrl(file), config, this)
- url = injectQuery(url, WORKER_FILE_ID)
- url = injectQuery(url, `type=${workerType}`)
+ builtUrl = await fileToUrl(cleanUrl(file), config, this)
+ builtUrl = injectQuery(builtUrl, WORKER_FILE_ID)
+ builtUrl = injectQuery(builtUrl, `type=${workerType}`)
}
- s.overwrite(urlIndex, urlIndex + exp.length, JSON.stringify(url), {
- contentOnly: true
- })
+ s.overwrite(
+ urlIndex,
+ urlIndex + exp.length,
+ `new URL(${JSON.stringify(builtUrl)}, self.location)`,
+ { contentOnly: true }
+ )
}
if (s) {
diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts
index affe284fb62d75..768fadf7f35ce5 100644
--- a/playground/assets/__tests__/assets.spec.ts
+++ b/playground/assets/__tests__/assets.spec.ts
@@ -281,6 +281,16 @@ test('new URL(..., import.meta.url)', async () => {
expect(await page.textContent('.import-meta-url')).toMatch(assetMatch)
})
+test('new URL("@/...", import.meta.url)', async () => {
+ expect(await page.textContent('.import-meta-url-dep')).toMatch(assetMatch)
+})
+
+test('new URL("/...", import.meta.url)', async () => {
+ expect(await page.textContent('.import-meta-url-base-path')).toMatch(
+ iconMatch
+ )
+})
+
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'
diff --git a/playground/assets/index.html b/playground/assets/index.html
index 42fa5498f28b8c..f897d61a355ed0 100644
--- a/playground/assets/index.html
+++ b/playground/assets/index.html
@@ -182,6 +182,14 @@ new URL('...', import.meta.url)
+new URL('@/...', import.meta.url)
+
+
+
+new URL('/...', import.meta.url)
+
+
+
new URL('...', import.meta.url,) (with comma)
@@ -354,6 +362,16 @@ style in svg
text('.import-meta-url', metaUrl)
document.querySelector('.import-meta-url-img').src = metaUrl
+ const metaUrlDep = new URL('@/asset.png', import.meta.url)
+ text('.import-meta-url-dep', metaUrlDep)
+ document.querySelector('.import-meta-url-dep-img').src = metaUrlDep
+
+ // testing URLs for public assets served at the public base path
+ // equivalent to `new URL(`${import.meta.env.BASE_URL}/icon.png`, self.location)
+ const metaUrlBasePath = new URL('/icon.png', import.meta.url)
+ text('.import-meta-url-base-path', metaUrlBasePath)
+ document.querySelector('.import-meta-url-base-path-img').src = metaUrlBasePath
+
// prettier-ignore
const metaUrlWithComma = new URL('./nested/asset.png', import.meta.url,)
text('.import-meta-url-comma', metaUrlWithComma)
diff --git a/playground/worker/__tests__/es/es-worker.spec.ts b/playground/worker/__tests__/es/es-worker.spec.ts
index 5dd4d4c045d834..eb8b9f650c236f 100644
--- a/playground/worker/__tests__/es/es-worker.spec.ts
+++ b/playground/worker/__tests__/es/es-worker.spec.ts
@@ -92,6 +92,16 @@ describe.runIf(isBuild)('build', () => {
})
test('module worker', async () => {
+ await untilUpdated(
+ () => page.textContent('.worker-import-meta-url'),
+ 'A string',
+ true
+ )
+ await untilUpdated(
+ () => page.textContent('.worker-import-meta-url-resolve'),
+ 'A string',
+ true
+ )
await untilUpdated(
() => page.textContent('.shared-worker-import-meta-url'),
'A string',
diff --git a/playground/worker/__tests__/iife/iife-worker.spec.ts b/playground/worker/__tests__/iife/iife-worker.spec.ts
index b3c1ecfe28d632..d6a4e784de8380 100644
--- a/playground/worker/__tests__/iife/iife-worker.spec.ts
+++ b/playground/worker/__tests__/iife/iife-worker.spec.ts
@@ -79,6 +79,14 @@ describe.runIf(isBuild)('build', () => {
})
test('module worker', async () => {
+ await untilUpdated(
+ () => page.textContent('.worker-import-meta-url'),
+ 'A string'
+ )
+ await untilUpdated(
+ () => page.textContent('.worker-import-meta-url-resolve'),
+ 'A string'
+ )
await untilUpdated(
() => page.textContent('.shared-worker-import-meta-url'),
'A string'
diff --git a/playground/worker/index.html b/playground/worker/index.html
index 1b196e074d0678..5c9c85b4799fd5 100644
--- a/playground/worker/index.html
+++ b/playground/worker/index.html
@@ -44,6 +44,12 @@
+
+ new Worker(new URL('@/url-worker', import.meta.url), { type: 'module' })
+ .worker-import-meta-url-resolve
+
+
+
new SharedWorker(new URL('./url-shared-worker.js', import.meta.url), { type:
'module' })
diff --git a/playground/worker/vite.config-es.js b/playground/worker/vite.config-es.js
index 6d6704de0bc213..ecc2b4db846c6a 100644
--- a/playground/worker/vite.config-es.js
+++ b/playground/worker/vite.config-es.js
@@ -4,6 +4,11 @@ const vite = require('vite')
module.exports = vite.defineConfig({
base: '/es/',
enforce: 'pre',
+ resolve: {
+ alias: {
+ '@': __dirname
+ }
+ },
worker: {
format: 'es',
plugins: [vueJsx()],
diff --git a/playground/worker/vite.config-iife.js b/playground/worker/vite.config-iife.js
index 3a0578c329f8ea..0e1d59ba688564 100644
--- a/playground/worker/vite.config-iife.js
+++ b/playground/worker/vite.config-iife.js
@@ -3,6 +3,11 @@ const vite = require('vite')
module.exports = vite.defineConfig({
base: '/iife/',
+ resolve: {
+ alias: {
+ '@': __dirname
+ }
+ },
worker: {
format: 'iife',
plugins: [
diff --git a/playground/worker/vite.config-relative-base.js b/playground/worker/vite.config-relative-base.js
index 4c20940749eacc..16017c578a076c 100644
--- a/playground/worker/vite.config-relative-base.js
+++ b/playground/worker/vite.config-relative-base.js
@@ -4,6 +4,11 @@ const vite = require('vite')
module.exports = vite.defineConfig({
base: './',
+ resolve: {
+ alias: {
+ '@': __dirname
+ }
+ },
worker: {
format: 'es',
plugins: [vueJsx()],
diff --git a/playground/worker/vite.config-sourcemap.js b/playground/worker/vite.config-sourcemap.js
index c4cf61f6dea586..07d57bf4ec96f1 100644
--- a/playground/worker/vite.config-sourcemap.js
+++ b/playground/worker/vite.config-sourcemap.js
@@ -10,6 +10,11 @@ module.exports = vite.defineConfig((sourcemap) => {
base: `/iife-${
typeof sourcemap === 'boolean' ? 'sourcemap' : 'sourcemap-' + sourcemap
}/`,
+ resolve: {
+ alias: {
+ '@': __dirname
+ }
+ },
worker: {
format: 'iife',
plugins: [vueJsx()],
diff --git a/playground/worker/worker/main-module.js b/playground/worker/worker/main-module.js
index f83dce99011bca..adf1bea60b1a6b 100644
--- a/playground/worker/worker/main-module.js
+++ b/playground/worker/worker/main-module.js
@@ -65,6 +65,15 @@ w.addEventListener('message', (ev) =>
text('.worker-import-meta-url', JSON.stringify(ev.data))
)
+// url import worker with alias path
+const wResolve = new Worker(
+ new URL('@/url-worker.js', import.meta.url),
+ /* @vite-ignore */ workerOptions
+)
+wResolve.addEventListener('message', (ev) =>
+ text('.worker-import-meta-url-resolve', JSON.stringify(ev.data))
+)
+
const genWorkerName = () => 'module'
const w2 = new SharedWorker(
new URL('../url-shared-worker.js', import.meta.url),