Skip to content

Commit

Permalink
Merge pull request #2198 from embroider-build/unify-virtual-namespace
Browse files Browse the repository at this point in the history
Virtual response namespace unification
  • Loading branch information
ef4 authored Dec 17, 2024
2 parents 01e21e6 + 8f2064f commit e6197c7
Show file tree
Hide file tree
Showing 16 changed files with 356 additions and 286 deletions.
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

0 comments on commit e6197c7

Please sign in to comment.