diff --git a/packages/wxt/src/core/builders/vite/plugins/__tests__/resolveVirtualModules.test.ts b/packages/wxt/src/core/builders/vite/plugins/__tests__/resolveVirtualModules.test.ts new file mode 100644 index 000000000..00806c984 --- /dev/null +++ b/packages/wxt/src/core/builders/vite/plugins/__tests__/resolveVirtualModules.test.ts @@ -0,0 +1,86 @@ +import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; +import { resolveVirtualModules } from '../resolveVirtualModules'; +import { fakeResolvedConfig } from '../../../../utils/testing/fake-objects'; + +const tempDirs: string[] = []; + +afterEach(async () => { + await Promise.all( + tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })), + ); +}); + +describe('resolveVirtualModules', () => { + it.each([ + `import definition from 'virtual:user-background-entrypoint';`, + `import definition from "virtual:user-background-entrypoint";`, + ])( + 'should escape input paths with apostrophes when encountering: %s', + async (template) => { + const wxtModuleDir = await mkdtemp(join(tmpdir(), 'wxt-test-')); + tempDirs.push(wxtModuleDir); + + const filePath = join( + wxtModuleDir, + 'dist/virtual/background-entrypoint.mjs', + ); + await mkdir(join(wxtModuleDir, 'dist/virtual'), { recursive: true }); + await writeFile(filePath, template); + + const plugin = resolveVirtualModules( + fakeResolvedConfig({ wxtModuleDir }), + ).find( + (plugin) => plugin.name === 'wxt:resolve-virtual-background-entrypoint', + ); + + expect(plugin).toBeDefined(); + + const inputPath = `/tmp/foo'bar/background.ts`; + const code = await plugin!.load!.handler!( + '\0virtual:wxt-background-entrypoint?' + inputPath, + ); + + expect(code).toBe( + `import definition from "file:///tmp/foo'bar/background.ts";`, + ); + }, + ); + + it.each([ + `import definition from 'virtual:user-background-entrypoint';`, + `import definition from "virtual:user-background-entrypoint";`, + ])( + 'should escape input paths with double quotes when encountering: %s', + async (template) => { + const wxtModuleDir = await mkdtemp(join(tmpdir(), 'wxt-test-')); + tempDirs.push(wxtModuleDir); + + const filePath = join( + wxtModuleDir, + 'dist/virtual/background-entrypoint.mjs', + ); + await mkdir(join(wxtModuleDir, 'dist/virtual'), { recursive: true }); + await writeFile(filePath, template); + + const plugin = resolveVirtualModules( + fakeResolvedConfig({ wxtModuleDir }), + ).find( + (plugin) => plugin.name === 'wxt:resolve-virtual-background-entrypoint', + ); + + expect(plugin).toBeDefined(); + + const inputPath = `/tmp/foo"bar/background.ts`; + const code = await plugin!.load!.handler!( + '\0virtual:wxt-background-entrypoint?' + inputPath, + ); + + expect(code).toBe( + `import definition from "file:///tmp/foo%22bar/background.ts";`, + ); + }, + ); +}); diff --git a/packages/wxt/src/core/builders/vite/plugins/resolveVirtualModules.ts b/packages/wxt/src/core/builders/vite/plugins/resolveVirtualModules.ts index 12c2b9ce2..d465a7ea6 100644 --- a/packages/wxt/src/core/builders/vite/plugins/resolveVirtualModules.ts +++ b/packages/wxt/src/core/builders/vite/plugins/resolveVirtualModules.ts @@ -1,4 +1,5 @@ import { readFile } from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; import { resolve } from 'path'; import type { Plugin } from 'vite'; import { ResolvedConfig } from '../../../../types'; @@ -15,6 +16,7 @@ import { export function resolveVirtualModules(config: ResolvedConfig): Plugin[] { return virtualModuleNames.map((name) => { const virtualId: `${VirtualModuleId}?` = `virtual:wxt-${name}?`; + const userVirtualId = `virtual:user-${name}`; const resolvedVirtualId = '\0' + virtualId; return { @@ -40,7 +42,16 @@ export function resolveVirtualModules(config: ResolvedConfig): Plugin[] { resolve(config.wxtModuleDir, `dist/virtual/${name}.mjs`), 'utf-8', ); - return template.replace(`virtual:user-${name}`, inputPath); + const escapedPath = pathToFileURL(inputPath).href; + const code = template + .replace(`'${userVirtualId}'`, `"${escapedPath}"`) + .replace(`"${userVirtualId}"`, `"${escapedPath}"`); + if (code === template) { + throw Error( + `Failed to resolve virtual module "${name}": expected template import "${userVirtualId}"`, + ); + } + return code; }, }, };