Skip to content

Commit

Permalink
Merge pull request #2188 from embroider-build/module-request-cleanup
Browse files Browse the repository at this point in the history
Module request cleanup
  • Loading branch information
ef4 authored Nov 27, 2024
2 parents 8d218f9 + d4ee663 commit 35f48d4
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 493 deletions.
3 changes: 2 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export { default as Options, optionsWithDefaults } from './options';
export { default as WaitForTrees, OutputPaths } from './wait-for-trees';
export { compile as jsHandlebarsCompile } from './js-handlebars';
export { todo, unsupported, warn, debug, expectWarning, throwOnWarnings } from './messages';
export { Resolver, ModuleRequest, Resolution } from './module-resolver';
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';
Expand Down
150 changes: 150 additions & 0 deletions packages/core/src/module-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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 }

// used for requests that are special and don't represent real files that
// embroider can possibly do anything custom with.
//
// the motivating use case for introducing this is Vite's depscan which marks
// almost everything as "external" as a way to tell esbuild to stop traversing
// once it has been seen the first time.
| { type: 'ignored'; result: T }

// the important thing about this Resolution is that embroider should do its
// fallback behaviors here.
| { type: 'not_found'; err: E };

export type RequestAdapterCreate<Init, Res extends Resolution> = (
params: Init
) => { initialState: InitialRequestState; adapter: RequestAdapter<Res> } | undefined;

export interface RequestAdapter<Res extends Resolution> {
readonly debugType: string;
resolve(request: ModuleRequest<Res>): Promise<Res>;
}

export interface InitialRequestState {
readonly specifier: string;
readonly fromFile: string;
readonly meta: Record<string, unknown> | undefined;
}

export class ModuleRequest<Res extends Resolution = Resolution> implements ModuleRequest<Res> {
static create<Init, Res extends Resolution>(
createAdapter: RequestAdapterCreate<Init, Res>,
params: Init
): ModuleRequest<Res> | undefined {
let result = createAdapter(params);
if (result) {
return new ModuleRequest(result.adapter, result.initialState);
}
}

#adapter: RequestAdapter<Res>;
#specifier: string;
#fromFile: string;
#isVirtual: boolean;
#meta: Record<string, unknown> | undefined;
#isNotFound: boolean;
#resolvedTo: Res | undefined;

private constructor(
adapter: RequestAdapter<Res>,
initialize: InitialRequestState,
propagate?: { isVirtual: boolean; isNotFound: boolean; resolvedTo: Res | undefined }
) {
this.#adapter = adapter;
this.#specifier = initialize.specifier;
this.#fromFile = initialize.fromFile;
this.#meta = initialize.meta;
this.#isVirtual = propagate?.isVirtual ?? false;
this.#isNotFound = propagate?.isNotFound ?? false;
this.#resolvedTo = propagate?.resolvedTo;
}

get specifier(): string {
return this.#specifier;
}

get fromFile(): string {
return this.#fromFile;
}

get isVirtual(): boolean {
return this.#isVirtual;
}

get debugType(): string {
return this.#adapter.debugType;
}

get meta(): Record<string, unknown> | undefined {
return this.#meta;
}

get isNotFound(): boolean {
return this.#isNotFound;
}

get resolvedTo(): Res | undefined {
return this.#resolvedTo;
}

alias(newSpecifier: string): this {
if (this.#specifier === newSpecifier) {
return this;
}
let result = this.#clone();
result.#specifier = newSpecifier;
result.#isNotFound = false;
result.#resolvedTo = undefined;
return result;
}

rehome(newFromFile: string): this {
if (this.#fromFile === newFromFile) {
return this;
}
let result = this.#clone();
result.#fromFile = newFromFile;
result.#isNotFound = false;
result.#resolvedTo = undefined;
return result;
}

virtualize(virtualFileName: string): this {
let result = this.#clone();
result.#specifier = virtualFileName;
result.#isVirtual = true;
result.#isNotFound = false;
result.#resolvedTo = undefined;
return result;
}

withMeta(meta: Record<string, any> | undefined): this {
let result = this.#clone();
result.#meta = meta;
return result;
}

notFound(): this {
let result = this.#clone();
result.#isNotFound = true;
return result;
}

resolveTo(res: Res): this {
let result = this.#clone();
result.#resolvedTo = res;
return result;
}

defaultResolve(): Promise<Res> {
return this.#adapter.resolve(this);
}

#clone(): this {
return new ModuleRequest(this.#adapter, this, this) as this;
}
}
35 changes: 1 addition & 34 deletions packages/core/src/module-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { nodeResolve } from './node-resolve';
import { decodePublicRouteEntrypoint, encodeRouteEntrypoint } from './virtual-route-entrypoint';
import type { Options, EngineConfig } from './module-resolver-options';
import { satisfies } from 'semver';
import type { ModuleRequest, Resolution } from './module-request';

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

Expand Down Expand Up @@ -110,40 +111,6 @@ type MergeMap = Map</* engine root dir */ string, Map</* withinEngineModuleName

const compatPattern = /@embroider\/virtual\/(?<type>[^\/]+)\/(?<rest>.*)/;

export interface ModuleRequest<Res extends Resolution = Resolution> {
readonly specifier: string;
readonly fromFile: string;
readonly isVirtual: boolean;
readonly meta: Record<string, unknown> | undefined;
readonly debugType: string;
readonly isNotFound: boolean;
readonly resolvedTo: Res | undefined;
alias(newSpecifier: string): this;
rehome(newFromFile: string): this;
virtualize(virtualFilename: string): this;
withMeta(meta: Record<string, any> | undefined): this;
notFound(): this;
defaultResolve(): Promise<Res>;
resolveTo(resolution: Res): this;
}

// 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 }

// used for requests that are special and don't represent real files that
// embroider can possibly do anything custom with.
//
// the motivating use case for introducing this is Vite's depscan which marks
// almost everything as "external" as a way to tell esbuild to stop traversing
// once it has been seen the first time.
| { type: 'ignored'; result: T }

// the important thing about this Resolution is that embroider should do its
// fallback behaviors here.
| { type: 'not_found'; err: E };

export class Resolver {
constructor(readonly options: Options) {}

Expand Down
87 changes: 22 additions & 65 deletions packages/core/src/node-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,31 @@ import { explicitRelative } from '@embroider/shared-internals';
import assertNever from 'assert-never';

// these would be circular, but they're type-only so it's fine
import type { ModuleRequest, Resolution, Resolver } from './module-resolver';

export class NodeModuleRequest implements ModuleRequest {
constructor(
private resolver: Resolver,
readonly specifier: string,
readonly fromFile: string,
readonly isVirtual: boolean,
readonly meta: Record<string, any> | undefined,
readonly isNotFound: boolean,
readonly resolvedTo: Resolution<NodeResolution, Error> | undefined
) {}
import { ModuleRequest, type RequestAdapter, type RequestAdapterCreate, type Resolution } from './module-request';
import type { Resolver } from './module-resolver';

export class NodeRequestAdapter implements RequestAdapter<Resolution<NodeResolution, Error>> {
static create: RequestAdapterCreate<
{ resolver: Resolver; specifier: string; fromFile: string },
Resolution<NodeResolution, Error>
> = ({ resolver, specifier, fromFile }) => {
return {
initialState: {
specifier,
fromFile,
meta: undefined,
},
adapter: new NodeRequestAdapter(resolver),
};
};

private constructor(private resolver: Resolver) {}

get debugType() {
return 'node';
}

alias(specifier: string): this {
return new NodeModuleRequest(this.resolver, specifier, this.fromFile, false, this.meta, false, undefined) as this;
}
rehome(fromFile: string): this {
if (this.fromFile === fromFile) {
return this;
} else {
return new NodeModuleRequest(this.resolver, this.specifier, fromFile, false, this.meta, false, undefined) as this;
}
}
virtualize(filename: string): this {
return new NodeModuleRequest(this.resolver, filename, this.fromFile, true, this.meta, false, undefined) as this;
}
withMeta(meta: Record<string, any> | undefined): this {
return new NodeModuleRequest(
this.resolver,
this.specifier,
this.fromFile,
this.isVirtual,
meta,
this.isNotFound,
this.resolvedTo
) as this;
}
notFound(): this {
return new NodeModuleRequest(
this.resolver,
this.specifier,
this.fromFile,
this.isVirtual,
this.meta,
true,
undefined
) as this;
}

resolveTo(resolution: Resolution<NodeResolution, Error>): this {
return new NodeModuleRequest(
this.resolver,
this.specifier,
this.fromFile,
this.isVirtual,
this.meta,
this.isNotFound,
resolution
) as this;
}

async defaultResolve(): Promise<Resolution<NodeResolution, Error>> {
const request = this;
async resolve(request: ModuleRequest<Resolution<NodeResolution, Error>>): Promise<Resolution<NodeResolution, Error>> {
if (request.isVirtual) {
return {
type: 'found',
Expand Down Expand Up @@ -156,9 +114,8 @@ export async function nodeResolve(
specifier: string,
fromFile: string
): Promise<NodeResolution | NodeResolutionError> {
let resolution = await resolver.resolve(
new NodeModuleRequest(resolver, specifier, fromFile, false, undefined, false, undefined)
);
let request = ModuleRequest.create(NodeRequestAdapter.create, { resolver, fromFile, specifier });
let resolution = await resolver.resolve(request!);
switch (resolution.type) {
case 'not_found':
return resolution;
Expand Down
Loading

0 comments on commit 35f48d4

Please sign in to comment.