diff --git a/scripts/build.ts b/scripts/build.ts index c4530464..d95b5dc6 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -8,6 +8,10 @@ import { consola } from 'consola'; import pMap from 'p-map'; import os from 'node:os'; import path from 'node:path'; +import { + virtualModuleNames, + virtualEntrypointModuleNames, +} from '~/core/utils/virtual-modules'; const spinnerText = 'Building WXT'; const spinner = ora(spinnerText).start(); @@ -20,12 +24,7 @@ const preset = { dts: true, silent: true, sourcemap: false, - external: [ - 'virtual:user-unlisted-script', - 'virtual:user-content-script-isolated-world', - 'virtual:user-content-script-main-world', - 'virtual:user-background', - ], + external: virtualEntrypointModuleNames.map((name) => `virtual:user-${name}`), } satisfies tsup.Options; function spinnerPMap(configs: tsup.Options[]) { @@ -74,17 +73,13 @@ const config: tsup.Options[] = [ }, { ...preset, - entry: { - 'virtual/background-entrypoint': 'src/virtual/background-entrypoint.ts', - 'virtual/content-script-isolated-world-entrypoint': - 'src/virtual/content-script-isolated-world-entrypoint.ts', - 'virtual/content-script-main-world-entrypoint': - 'src/virtual/content-script-main-world-entrypoint.ts', - 'virtual/mock-browser': 'src/virtual/mock-browser.ts', - 'virtual/reload-html': 'src/virtual/reload-html.ts', - 'virtual/unlisted-script-entrypoint': - 'src/virtual/unlisted-script-entrypoint.ts', - }, + entry: virtualModuleNames.reduce>( + (acc, moduleName) => { + acc[`virtual/${moduleName}`] = `src/virtual/${moduleName}.ts`; + return acc; + }, + {}, + ), format: ['esm'], splitting: false, dts: false, diff --git a/src/core/builders/vite/index.ts b/src/core/builders/vite/index.ts index 837a0ce3..101596bc 100644 --- a/src/core/builders/vite/index.ts +++ b/src/core/builders/vite/index.ts @@ -3,7 +3,6 @@ import { BuildStepOutput, Entrypoint, ResolvedConfig, - VirtualEntrypointType, WxtBuilder, WxtBuilderServer, WxtDevServer, @@ -13,6 +12,10 @@ import { getEntrypointBundlePath, isHtmlEntrypoint, } from '~/core/utils/entrypoints'; +import { + VirtualEntrypointType, + VirtualModuleId, +} from '~/core/utils/virtual-modules'; export async function createViteBuilder( wxtConfig: ResolvedConfig, @@ -48,10 +51,7 @@ export async function createViteBuilder( wxtPlugins.download(wxtConfig), wxtPlugins.devHtmlPrerender(wxtConfig, server), wxtPlugins.unimport(wxtConfig), - wxtPlugins.virtualEntrypoint('background', wxtConfig), - wxtPlugins.virtualEntrypoint('content-script-isolated-world', wxtConfig), - wxtPlugins.virtualEntrypoint('content-script-main-world', wxtConfig), - wxtPlugins.virtualEntrypoint('unlisted-script', wxtConfig), + wxtPlugins.resolveVirtualModules(wxtConfig), wxtPlugins.devServerGlobals(wxtConfig, server), wxtPlugins.tsconfigPaths(wxtConfig), wxtPlugins.noopBackground(), @@ -268,7 +268,10 @@ function getRollupEntry(entrypoint: Entrypoint): string { : 'content-script-isolated-world'; break; } - return virtualEntrypointType - ? `virtual:wxt-${virtualEntrypointType}?${entrypoint.inputPath}` - : entrypoint.inputPath; + + if (virtualEntrypointType) { + const moduleId: VirtualModuleId = `virtual:wxt-${virtualEntrypointType}-entrypoint`; + return `${moduleId}?${entrypoint.inputPath}`; + } + return entrypoint.inputPath; } diff --git a/src/core/builders/vite/plugins/index.ts b/src/core/builders/vite/plugins/index.ts index 5f7973be..ae5390ba 100644 --- a/src/core/builders/vite/plugins/index.ts +++ b/src/core/builders/vite/plugins/index.ts @@ -3,7 +3,7 @@ export * from './devServerGlobals'; export * from './download'; export * from './multipageMove'; export * from './unimport'; -export * from './virtualEntrypoint'; +export * from './resolveVirtualModules'; export * from './tsconfigPaths'; export * from './noopBackground'; export * from './cssEntrypoints'; diff --git a/src/core/builders/vite/plugins/resolveVirtualModules.ts b/src/core/builders/vite/plugins/resolveVirtualModules.ts new file mode 100644 index 00000000..600ba7d4 --- /dev/null +++ b/src/core/builders/vite/plugins/resolveVirtualModules.ts @@ -0,0 +1,41 @@ +import { Plugin } from 'vite'; +import { ResolvedConfig } from '~/types'; +import { normalizePath } from '~/core/utils/paths'; +import { + VirtualModuleId, + virtualModuleNames, +} from '~/core/utils/virtual-modules'; +import fs from 'fs-extra'; +import { resolve } from 'path'; + +/** + * Resolve all the virtual modules to the `node_modules/wxt/dist/virtual` directory. + */ +export function resolveVirtualModules(config: ResolvedConfig): Plugin[] { + return virtualModuleNames.map((name) => { + const virtualId: `${VirtualModuleId}?` = `virtual:wxt-${name}?`; + const resolvedVirtualId = '\0' + virtualId; + return { + name: `wxt:resolve-virtual-${name}`, + resolveId(id) { + // Id doesn't start with prefix, it looks like this: + // /path/to/project/virtual:wxt-background?/path/to/project/entrypoints/background.ts + const index = id.indexOf(virtualId); + if (index === -1) return; + + const inputPath = normalizePath(id.substring(index + virtualId.length)); + return resolvedVirtualId + inputPath; + }, + async load(id) { + if (!id.startsWith(resolvedVirtualId)) return; + + const inputPath = id.replace(resolvedVirtualId, ''); + const template = await fs.readFile( + resolve(config.wxtModuleDir, `dist/virtual/${name}.js`), + 'utf-8', + ); + return template.replace(`virtual:user-${name}`, inputPath); + }, + }; + }); +} diff --git a/src/core/builders/vite/plugins/virtualEntrypoint.ts b/src/core/builders/vite/plugins/virtualEntrypoint.ts deleted file mode 100644 index 12a9ab5a..00000000 --- a/src/core/builders/vite/plugins/virtualEntrypoint.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Plugin } from 'vite'; -import { ResolvedConfig, VirtualEntrypointType } from '~/types'; -import fs from 'fs-extra'; -import { resolve } from 'path'; -import { normalizePath } from '~/core/utils/paths'; - -/** - * Wraps a user's entrypoint with a vitual version with additional logic. - */ -export function virtualEntrypoint( - type: VirtualEntrypointType, - config: Omit, -): Plugin { - const virtualId = `virtual:wxt-${type}?`; - const resolvedVirtualId = `\0${virtualId}`; - - return { - name: `wxt:virtual-entrypoint`, - resolveId(id) { - // Id doesn't start with prefix, it looks like this: - // /path/to/project/virtual:background?/path/to/project/entrypoints/background.ts - const index = id.indexOf(virtualId); - if (index === -1) return; - - const inputPath = normalizePath(id.substring(index + virtualId.length)); - return resolvedVirtualId + inputPath; - }, - async load(id) { - if (!id.startsWith(resolvedVirtualId)) return; - - const inputPath = id.replace(resolvedVirtualId, ''); - const template = await fs.readFile( - resolve(config.wxtModuleDir, `dist/virtual/${type}-entrypoint.js`), - 'utf-8', - ); - return template.replace(`virtual:user-${type}`, inputPath); - }, - }; -} diff --git a/src/core/utils/__tests__/virtual-modules.test.ts b/src/core/utils/__tests__/virtual-modules.test.ts new file mode 100644 index 00000000..6b4e462a --- /dev/null +++ b/src/core/utils/__tests__/virtual-modules.test.ts @@ -0,0 +1,20 @@ +import { describe, it } from 'vitest'; +import { + VirtualModuleId, + VirtualModuleName, + VirtualEntrypointType, + VirtualEntrypointModuleName, +} from '../virtual-modules'; + +describe('Virtual Modules', () => { + it('should resolve types to litteral values, not string', () => { + // @ts-expect-error + const _c: VirtualEntrypointType = ''; + // @ts-expect-error + const _d: VirtualEntrypointModuleName = ''; + // @ts-expect-error + const _b: VirtualModuleName = ''; + // @ts-expect-error + const _a: VirtualModuleId = ''; + }); +}); diff --git a/src/core/utils/virtual-modules.ts b/src/core/utils/virtual-modules.ts new file mode 100644 index 00000000..35653edc --- /dev/null +++ b/src/core/utils/virtual-modules.ts @@ -0,0 +1,37 @@ +export const virtualEntrypointTypes = [ + 'content-script-main-world' as const, + 'content-script-isolated-world' as const, + 'background' as const, + 'unlisted-script' as const, +]; +export type VirtualEntrypointType = (typeof virtualEntrypointTypes)[0]; + +/** + * All the names of entrypoint files in the `src/virtual/` and `dist/virtual/` directories, minus the extension. + */ +export const virtualEntrypointModuleNames = virtualEntrypointTypes.map( + (name) => `${name}-entrypoint` as const, +); +/** + * Name of entrypoint files in the `src/virtual/` and `dist/virtual/` directories, minus the extension. + */ +export type VirtualEntrypointModuleName = + (typeof virtualEntrypointModuleNames)[0]; + +/** + * All the names of files in the `src/virtual/` and `dist/virtual/` directories, minus the extension. + */ +export const virtualModuleNames = [ + ...virtualEntrypointModuleNames, + 'mock-browser' as const, + 'reload-html' as const, +]; +/** + * Name of files in the `src/virtual/` and `dist/virtual/` directories, minus the extension. + */ +export type VirtualModuleName = (typeof virtualModuleNames)[0]; + +/** + * Import alias used for importing a virtual module + */ +export type VirtualModuleId = `virtual:wxt-${VirtualModuleName}`; diff --git a/src/types/index.ts b/src/types/index.ts index b1768416..5579dc26 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1103,12 +1103,6 @@ export interface ExtensionRunner { closeBrowser(): Promise; } -export type VirtualEntrypointType = - | 'content-script-main-world' - | 'content-script-isolated-world' - | 'background' - | 'unlisted-script'; - export type EslintGlobalsPropValue = | boolean | 'readonly' diff --git a/src/virtual/background-entrypoint.ts b/src/virtual/background-entrypoint.ts index df195889..a921c020 100644 --- a/src/virtual/background-entrypoint.ts +++ b/src/virtual/background-entrypoint.ts @@ -1,4 +1,4 @@ -import definition from 'virtual:user-background'; +import definition from 'virtual:user-background-entrypoint'; import { setupWebSocket } from './utils/setup-web-socket'; import { logger } from '../sandbox/utils/logger'; import { browser } from 'wxt/browser'; diff --git a/src/virtual/content-script-isolated-world-entrypoint.ts b/src/virtual/content-script-isolated-world-entrypoint.ts index e8a0ebb9..c2caa200 100644 --- a/src/virtual/content-script-isolated-world-entrypoint.ts +++ b/src/virtual/content-script-isolated-world-entrypoint.ts @@ -1,4 +1,4 @@ -import definition from 'virtual:user-content-script-isolated-world'; +import definition from 'virtual:user-content-script-isolated-world-entrypoint'; import { logger } from '../sandbox/utils/logger'; import { ContentScriptContext } from 'wxt/client'; diff --git a/src/virtual/content-script-main-world-entrypoint.ts b/src/virtual/content-script-main-world-entrypoint.ts index ad1248d7..52616afb 100644 --- a/src/virtual/content-script-main-world-entrypoint.ts +++ b/src/virtual/content-script-main-world-entrypoint.ts @@ -1,4 +1,4 @@ -import definition from 'virtual:user-content-script-main-world'; +import definition from 'virtual:user-content-script-main-world-entrypoint'; import { logger } from '../sandbox/utils/logger'; (async () => { diff --git a/src/virtual/unlisted-script-entrypoint.ts b/src/virtual/unlisted-script-entrypoint.ts index 9665ee62..035b7e38 100644 --- a/src/virtual/unlisted-script-entrypoint.ts +++ b/src/virtual/unlisted-script-entrypoint.ts @@ -1,4 +1,4 @@ -import definition from 'virtual:user-unlisted-script'; +import definition from 'virtual:user-unlisted-script-entrypoint'; import { logger } from '../sandbox/utils/logger'; (async () => { diff --git a/src/virtual/virtual-module-globals.d.ts b/src/virtual/virtual-module-globals.d.ts index c324452f..41f9fb30 100644 --- a/src/virtual/virtual-module-globals.d.ts +++ b/src/virtual/virtual-module-globals.d.ts @@ -1,11 +1,11 @@ // Types required to make the virtual modules happy. -declare module 'virtual:user-background' { +declare module 'virtual:user-background-entrypoint' { const definition: { main: () => void }; export default definition; } -declare module 'virtual:user-content-script-isolated-world' { +declare module 'virtual:user-content-script-isolated-world-entrypoint' { const definition: { main: ( ctx: import('wxt/client').ContentScriptContext, @@ -14,12 +14,12 @@ declare module 'virtual:user-content-script-isolated-world' { export default definition; } -declare module 'virtual:user-content-script-main-world' { +declare module 'virtual:user-content-script-main-world-entrypoint' { const definition: { main: () => void | Promise }; export default definition; } -declare module 'virtual:user-unlisted-script' { +declare module 'virtual:user-unlisted-script-entrypoint' { const definition: { main: () => void | Promise }; export default definition; }