diff --git a/packages/core/src/module-resolver.ts b/packages/core/src/module-resolver.ts index 8f1c1a037..aa83cbf73 100644 --- a/packages/core/src/module-resolver.ts +++ b/packages/core/src/module-resolver.ts @@ -12,8 +12,6 @@ import makeDebug from 'debug'; import assertNever from 'assert-never'; import { externalName } from '@embroider/reverse-exports'; import { exports as resolveExports } from 'resolve.exports'; - -import { fastbootSwitch, decodeFastbootSwitch } from './virtual-content'; import { Memoize } from 'typescript-memoize'; import { describeExports } from './describe-exports'; import { readFileSync } from 'fs'; @@ -248,14 +246,10 @@ export class Resolver { configFile: false, }); let switchFile = fastbootSwitch(candidate, resolve(pkg.root, 'package.json'), names); - if (switchFile === request.fromFile) { + if (switchFile.specifier === request.fromFile) { return logTransition('internal lookup from fastbootSwitch', request); } else { - return logTransition( - 'shadowed app fastboot', - request, - request.virtualize({ type: 'fastboot-switch', specifier: switchFile }) - ); + return logTransition('shadowed app fastboot', request, request.virtualize(switchFile)); } } else { return logTransition( @@ -1325,10 +1319,7 @@ export class Resolver { ); } let { names } = describeExports(readFileSync(foundAppJS.filename, 'utf8'), { configFile: false }); - return request.virtualize({ - type: 'fastboot-switch', - specifier: fastbootSwitch(matched.matched, resolve(engine.root, 'package.json'), names), - }); + return request.virtualize(fastbootSwitch(matched.matched, resolve(engine.root, 'package.json'), names)); } } @@ -1425,3 +1416,28 @@ function engineRelativeName(pkg: Package, filename: string): string | undefined return '.' + outsideName.slice(pkg.name.length); } } + +const fastbootSwitchSuffix = '/embroider_fastboot_switch'; + +function fastbootSwitch(specifier: string, fromFile: string, names: Set) { + let filename = `${resolve(dirname(fromFile), specifier)}${fastbootSwitchSuffix}`; + let virtualSpecifier: string; + if (names.size > 0) { + virtualSpecifier = `${filename}?names=${[...names].join(',')}`; + } else { + virtualSpecifier = filename; + } + return { + type: 'fastboot-switch' as const, + specifier: virtualSpecifier, + names, + hasDefaultExport: 'x', + }; +} + +function decodeFastbootSwitch(filename: string) { + let index = filename.indexOf(fastbootSwitchSuffix); + if (index >= 0) { + return { filename: filename.slice(0, index) }; + } +} diff --git a/packages/core/src/virtual-content.ts b/packages/core/src/virtual-content.ts index 8ac19fb17..30726c2ac 100644 --- a/packages/core/src/virtual-content.ts +++ b/packages/core/src/virtual-content.ts @@ -1,24 +1,23 @@ -import { dirname, resolve, posix, sep, join } from 'path'; +import { posix, sep, join } from 'path'; import type { Resolver, AddonPackage, Package } from '.'; import { extensionsPattern } from '.'; import { compile } from './js-handlebars'; -import { renderImplicitTestScripts } from './virtual-test-support'; -import { renderTestSupportStyles } from './virtual-test-support-styles'; +import { renderImplicitTestScripts, type TestSupportResponse } from './virtual-test-support'; +import { renderTestSupportStyles, type TestSupportStylesResponse } from './virtual-test-support-styles'; import { renderVendor, type VirtualVendorResponse } from './virtual-vendor'; import { renderVendorStyles, type VirtualVendorStylesResponse } from './virtual-vendor-styles'; import { type EntrypointResponse, renderEntrypoint } from './virtual-entrypoint'; import { renderRouteEntrypoint, type RouteEntrypointResponse } from './virtual-route-entrypoint'; +import assertNever from 'assert-never'; export type VirtualResponse = { specifier: string } & ( - | { - type: 'fastboot-switch'; - } + | FastbootSwitchResponse | ImplicitModulesResponse | EntrypointResponse | RouteEntrypointResponse - | { type: 'test-support-js' } - | { type: 'test-support-css' } + | TestSupportResponse + | TestSupportStylesResponse | VirtualVendorResponse | VirtualVendorStylesResponse | VirtualPairResponse @@ -52,16 +51,11 @@ export function virtualContent(response: VirtualResponse, resolver: Resolver): V return renderImplicitModules(response, resolver); case 'route-entrypoint': return renderRouteEntrypoint(response, resolver); + case 'fastboot-switch': + return renderFastbootSwitchTemplate(response); + default: + throw assertNever(response); } - - let filename = response.specifier; - - let fb = decodeFastbootSwitch(filename); - if (fb) { - return renderFastbootSwitchTemplate(fb); - } - - throw new Error(`not an @embroider/core virtual file: ${filename}`); } interface PairedComponentShimParams { @@ -113,41 +107,17 @@ export interface VirtualPairResponse { debugName: string; } -const fastbootSwitchSuffix = '/embroider_fastboot_switch'; -const fastbootSwitchPattern = /(?.+)\/embroider_fastboot_switch(?:\?names=(?.+))?$/; -export function fastbootSwitch(specifier: string, fromFile: string, names: Set): string { - let filename = `${resolve(dirname(fromFile), specifier)}${fastbootSwitchSuffix}`; - if (names.size > 0) { - return `${filename}?names=${[...names].join(',')}`; - } else { - return filename; - } -} - -export function decodeFastbootSwitch(filename: string) { - // Performance: avoid paying regex exec cost unless needed - if (!filename.includes(fastbootSwitchSuffix)) { - return; - } - let match = fastbootSwitchPattern.exec(filename); - if (match) { - let names = match.groups?.names?.split(',') ?? []; - return { - names: names.filter(name => name !== 'default'), - hasDefaultExport: names.includes('default'), - filename: match.groups!.original, - }; - } -} - -interface FastbootSwitchParams { - names: string[]; - hasDefaultExport: boolean; +interface FastbootSwitchResponse { + type: 'fastboot-switch'; + names: Set; } -function renderFastbootSwitchTemplate(params: FastbootSwitchParams): VirtualContentResult { +function renderFastbootSwitchTemplate(params: FastbootSwitchResponse): VirtualContentResult { return { - src: fastbootSwitchTemplate(params), + src: fastbootSwitchTemplate({ + names: [...params.names].filter(name => name !== 'default'), + hasDefaultExport: params.names.has('default'), + }), watches: [], }; } @@ -166,7 +136,7 @@ export default mod.default; {{#each names as |name|}} export const {{name}} = mod.{{name}}; {{/each}} -`) as (params: FastbootSwitchParams) => string; +`) as (params: { names: string[]; hasDefaultExport: boolean }) => string; export interface ImplicitModulesResponse { type: 'implicit-modules' | 'implicit-test-modules';