Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtual response namespace unification #2198

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export { Resolver } from './module-resolver';
export { ModuleRequest, type Resolution, type RequestAdapter, type RequestAdapterCreate } from './module-request';
export type { Options as ResolverOptions } from './module-resolver-options';
export { ResolverLoader } from './resolver-loader';
export { virtualContent } from './virtual-content';
export { virtualContent, type VirtualResponse } from './virtual-content';
export type { Engine } from './app-files';

// this is reexported because we already make users manage a peerDep from some
Expand Down
10 changes: 6 additions & 4 deletions packages/core/src/module-request.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { VirtualResponse } from './virtual-content';

// This is generic because different build systems have different ways of
// representing a found module, and we just pass those values through.
export type Resolution<T = unknown, E = unknown> =
| { type: 'found'; filename: string; isVirtual: boolean; result: T }
| { type: 'found'; filename: string; virtual: VirtualResponse | false; result: T }

// the important thing about this Resolution is that embroider should do its
// fallback behaviors here.
Expand All @@ -19,7 +21,7 @@ export interface RequestAdapter<Res extends Resolution> {
// plugins are a pain in the butt. Integrators are encouraged to use the plain
// Response-returning variants in all sane build environments.
notFoundResponse(request: ModuleRequest<Res>): Res | (() => Promise<Res>);
virtualResponse(request: ModuleRequest<Res>, virtualFileName: string): Res | (() => Promise<Res>);
virtualResponse(request: ModuleRequest<Res>, response: VirtualResponse): Res | (() => Promise<Res>);
}

export interface InitialRequestState {
Expand Down Expand Up @@ -90,8 +92,8 @@ export class ModuleRequest<Res extends Resolution = Resolution> implements Modul
return result;
}

virtualize(virtualFileName: string): this {
return this.resolveTo(this.#adapter.virtualResponse(this, virtualFileName));
virtualize(virtualResponse: VirtualResponse): this {
return this.resolveTo(this.#adapter.virtualResponse(this, virtualResponse));
}

withMeta(meta: Record<string, any> | undefined): this {
Expand Down
113 changes: 40 additions & 73 deletions packages/core/src/module-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { decodePublicRouteEntrypoint, encodeRouteEntrypoint } from './virtual-ro
import type { Options, EngineConfig } from './module-resolver-options';
import { satisfies } from 'semver';
import type { ModuleRequest, Resolution } from './module-request';
import { virtualEntrypoint } from './virtual-entrypoint';

const debug = makeDebug('embroider:resolver');

Expand Down Expand Up @@ -252,7 +253,11 @@ export class Resolver {
if (switchFile === request.fromFile) {
return logTransition('internal lookup from fastbootSwitch', request);
} else {
return logTransition('shadowed app fastboot', request, request.virtualize(switchFile));
return logTransition(
'shadowed app fastboot',
request,
request.virtualize({ type: 'fastboot-switch', specifier: switchFile })
);
}
} else {
return logTransition(
Expand Down Expand Up @@ -347,13 +352,13 @@ export class Resolver {
return logTransition(
`dep's implicit modules`,
request,
request.virtualize(resolve(dep.root, `-embroider-${im.type}.js`))
request.virtualize({ type: im.type, specifier: resolve(dep.root, `-embroider-${im.type}.js`) })
);
} else {
return logTransition(
`own implicit modules`,
request,
request.virtualize(resolve(pkg.root, `-embroider-${im.type}.js`))
request.virtualize({ type: im.type, specifier: resolve(pkg.root, `-embroider-${im.type}.js`) })
);
}
}
Expand All @@ -363,48 +368,12 @@ export class Resolver {
return request;
}

//TODO move the extra forwardslash handling out into the vite plugin
const candidates = [
'@embroider/virtual/compat-modules',
'/@embroider/virtual/compat-modules',
'./@embroider/virtual/compat-modules',
];

if (!candidates.some(c => request.specifier.startsWith(c + '/') || request.specifier === c)) {
return request;
}

const result = /\.?\/?@embroider\/virtual\/compat-modules(?:\/(?<packageName>.*))?/.exec(request.specifier);

if (!result) {
// TODO make a better error
throw new Error('entrypoint does not match pattern' + request.specifier);
}

const { packageName } = result.groups!;

const requestingPkg = this.packageCache.ownerOfFile(request.fromFile);

if (!requestingPkg?.isV2Ember()) {
throw new Error(`bug: found entrypoint import in non-ember package at ${request.fromFile}`);
}

let pkg;

if (packageName) {
pkg = this.packageCache.resolve(packageName, requestingPkg);
let virtualResponse = virtualEntrypoint(request, this.packageCache);
if (virtualResponse) {
return logTransition('entrypoint', request, request.virtualize(virtualResponse));
} else {
pkg = requestingPkg;
return request;
}
let matched = resolveExports(pkg.packageJSON, '-embroider-entrypoint.js', {
browser: true,
conditions: ['default', 'imports'],
});
return logTransition(
'entrypoint',
request,
request.virtualize(resolve(pkg.root, matched?.[0] ?? '-embroider-entrypoint.js'))
);
}

private handleRouteEntrypoint<R extends ModuleRequest>(request: R): R {
Expand All @@ -424,15 +393,10 @@ export class Resolver {
throw new Error(`bug: found entrypoint import in non-ember package at ${request.fromFile}`);
}

let matched = resolveExports(pkg.packageJSON, '-embroider-route-entrypoint.js', {
browser: true,
conditions: ['default', 'imports'],
});

return logTransition(
'route entrypoint',
request,
request.virtualize(encodeRouteEntrypoint(pkg.root, matched?.[0], routeName))
request.virtualize({ type: 'route-entrypoint', specifier: encodeRouteEntrypoint(pkg, routeName) })
);
}

Expand All @@ -455,7 +419,11 @@ export class Resolver {
);
}

return logTransition('test-support', request, request.virtualize(resolve(pkg.root, '-embroider-test-support.js')));
return logTransition(
'test-support',
request,
request.virtualize({ type: 'test-support-js', specifier: resolve(pkg.root, '-embroider-test-support.js') })
);
}

private handleTestSupportStyles<R extends ModuleRequest>(request: R): R {
Expand All @@ -480,7 +448,10 @@ export class Resolver {
return logTransition(
'test-support-styles',
request,
request.virtualize(resolve(pkg.root, '-embroider-test-support-styles.css'))
request.virtualize({
type: 'test-support-css',
specifier: resolve(pkg.root, '-embroider-test-support-styles.css'),
})
);
}

Expand Down Expand Up @@ -535,7 +506,7 @@ export class Resolver {
return logTransition(
'vendor-styles',
request,
request.virtualize(resolve(pkg.root, '-embroider-vendor-styles.css'))
request.virtualize({ type: 'vendor-css', specifier: resolve(pkg.root, '-embroider-vendor-styles.css') })
);
}

Expand All @@ -562,11 +533,7 @@ export class Resolver {
for (let candidate of this.componentTemplateCandidates(target.packageName)) {
let candidateSpecifier = `${target.packageName}${candidate.prefix}${target.memberName}${candidate.suffix}`;

let resolution = await this.resolve(
request.alias(candidateSpecifier).rehome(target.from).withMeta({
runtimeFallback: false,
})
);
let resolution = await this.resolve(request.alias(candidateSpecifier).rehome(target.from));

if (resolution.type === 'found') {
hbsModule = resolution;
Expand All @@ -578,11 +545,7 @@ export class Resolver {
for (let candidate of this.componentJSCandidates(target.packageName)) {
let candidateSpecifier = `${target.packageName}${candidate.prefix}${target.memberName}${candidate.suffix}`;

let resolution = await this.resolve(
request.alias(candidateSpecifier).rehome(target.from).withMeta({
runtimeFallback: false,
})
);
let resolution = await this.resolve(request.alias(candidateSpecifier).rehome(target.from));

// .hbs is a resolvable extension for us, so we need to exclude it here.
// It matches as a priority lower than .js, so finding an .hbs means
Expand All @@ -602,7 +565,10 @@ export class Resolver {
return logTransition(
`resolveComponent found legacy HBS`,
request,
request.virtualize(virtualPairComponent(hbsModule.filename, jsModule?.filename))
request.virtualize({
type: 'component-pair',
specifier: virtualPairComponent(this.options.appRoot, hbsModule.filename, jsModule?.filename),
})
);
} else if (jsModule) {
return logTransition(`resolving to resolveComponent found only JS`, request, request.resolveTo(jsModule));
Expand All @@ -620,11 +586,7 @@ export class Resolver {
// component, so here to resolve the ambiguity we need to actually resolve
// that candidate to see if it works.
let helperCandidate = this.resolveHelper(path, inEngine, request);
let helperMatch = await this.resolve(
request.alias(helperCandidate.specifier).rehome(helperCandidate.fromFile).withMeta({
runtimeFallback: false,
})
);
let helperMatch = await this.resolve(request.alias(helperCandidate.specifier).rehome(helperCandidate.fromFile));

if (helperMatch.type === 'found') {
return logTransition('resolve to ambiguous case matched a helper', request, request.resolveTo(helperMatch));
Expand Down Expand Up @@ -1014,7 +976,11 @@ export class Resolver {
);
}

return logTransition('vendor', request, request.virtualize(resolve(pkg.root, '-embroider-vendor.js')));
return logTransition(
'vendor',
request,
request.virtualize({ type: 'vendor-js', specifier: resolve(pkg.root, '-embroider-vendor.js') })
);
}

private resolveWithinMovedPackage<R extends ModuleRequest>(request: R, pkg: Package): R {
Expand Down Expand Up @@ -1331,17 +1297,18 @@ export class Resolver {
return request.alias(matched.entry['fastboot-js'].specifier).rehome(matched.entry['fastboot-js'].fromFile);
case 'both':
let foundAppJS = await this.resolve(
request.alias(matched.entry['app-js'].specifier).rehome(matched.entry['app-js'].fromFile).withMeta({
runtimeFallback: false,
})
request.alias(matched.entry['app-js'].specifier).rehome(matched.entry['app-js'].fromFile)
);
if (foundAppJS.type !== 'found') {
throw new Error(
`${matched.entry['app-js'].fromPackageName} declared ${inEngineSpecifier} in packageJSON.ember-addon.app-js, but that module does not exist`
);
}
let { names } = describeExports(readFileSync(foundAppJS.filename, 'utf8'), { configFile: false });
return request.virtualize(fastbootSwitch(matched.matched, resolve(engine.root, 'package.json'), names));
return request.virtualize({
type: 'fastboot-switch',
specifier: fastbootSwitch(matched.matched, resolve(engine.root, 'package.json'), names),
});
}
}

Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/node-resolve.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { virtualContent } from './virtual-content';
import { virtualContent, type VirtualResponse } from './virtual-content';
import { dirname, resolve, isAbsolute } from 'path';
import { explicitRelative } from '@embroider/shared-internals';
import assertNever from 'assert-never';
Expand Down Expand Up @@ -39,16 +39,16 @@ export class NodeRequestAdapter implements RequestAdapter<Resolution<NodeResolut

virtualResponse(
_request: ModuleRequest<Resolution<NodeResolution, Error>>,
virtualFileName: string
virtual: VirtualResponse
): Resolution<NodeResolution, Error> {
return {
type: 'found',
filename: virtualFileName,
isVirtual: true,
filename: virtual.specifier,
virtual,
result: {
type: 'virtual' as 'virtual',
content: virtualContent(virtualFileName, this.resolver).src,
filename: virtualFileName,
type: 'virtual' as const,
content: virtualContent(virtual.specifier, this.resolver).src,
filename: virtual.specifier,
},
};
}
Expand Down Expand Up @@ -92,7 +92,7 @@ export class NodeRequestAdapter implements RequestAdapter<Resolution<NodeResolut

continue;
}
return { type: 'found', filename, result: { type: 'real' as 'real', filename }, isVirtual: false };
return { type: 'found', filename, result: { type: 'real' as 'real', filename }, virtual: false };
}

return { type: 'not_found', err: initialError };
Expand Down
Loading
Loading