Skip to content

Commit

Permalink
Merge pull request #1331 from embroider-build/refactor-resolving2
Browse files Browse the repository at this point in the history
Move resolving into dedicated plugins
  • Loading branch information
ef4 committed Jan 26, 2023
2 parents 8db1b27 + f840a23 commit d94051e
Show file tree
Hide file tree
Showing 19 changed files with 655 additions and 571 deletions.
87 changes: 72 additions & 15 deletions packages/compat/src/audit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFileSync, readJSONSync } from 'fs-extra';
import { dirname, join, resolve as resolvePath } from 'path';
import resolveModule from 'resolve';
import { AppMeta, explicitRelative, hbsToJS } from '@embroider/core';
import { AppMeta, explicitRelative, hbsToJS, Resolution, Resolver, ResolverOptions } from '@embroider/core';
import { Memoize } from 'typescript-memoize';
import chalk from 'chalk';
import jsdom from 'jsdom';
Expand All @@ -18,6 +18,7 @@ import {
import { AuditBuildOptions, AuditOptions } from './audit/options';
import { buildApp, BuildError, isBuildError } from './audit/build';
import { AuditMessage } from './resolver';
import assertNever from 'assert-never';

const { JSDOM } = jsdom;

Expand Down Expand Up @@ -83,6 +84,11 @@ function isLinked(module: InternalModule | undefined): module is LinkedInternalM
return Boolean(module?.parsed && module.resolved && module.linked);
}

interface Request {
specifier: string;
fromFile: string;
}

export interface Import {
source: string;
specifiers: {
Expand Down Expand Up @@ -249,6 +255,15 @@ export class Audit {
return config;
}

@Memoize()
private get resolverParams(): ResolverOptions {
let config = {
...readJSONSync(join(this.appDir, '_adjust_imports.json')),
...readJSONSync(join(this.appDir, '_relocated_files.json')),
};
return config;
}

private debug(message: string, ...args: any[]) {
if (this.options.debug) {
console.log(message, ...args);
Expand Down Expand Up @@ -294,8 +309,9 @@ export class Audit {
} else {
module.parsed = visitResult;
let resolved = new Map() as NonNullable<InternalModule['resolved']>;
let resolver = new Resolver(this.resolverParams);
for (let dep of visitResult.dependencies) {
let depFilename = await this.resolve(dep, filename);
let depFilename = await this.resolve({ specifier: dep, fromFile: filename }, resolver);
if (depFilename) {
resolved.set(dep, depFilename);
if (!isResolutionFailure(depFilename)) {
Expand Down Expand Up @@ -520,20 +536,61 @@ export class Audit {
return this.visitJS(filename, js);
}

private async resolve(specifier: string, fromPath: string): Promise<string | ResolutionFailure | undefined> {
if (['@embroider/macros', '@ember/template-factory'].includes(specifier)) {
return;
private nextRequest(
prevRequest: { specifier: string; fromFile: string },
resolution: Resolution
): { specifier: string; fromFile: string } | undefined {
switch (resolution.result) {
case 'virtual':
// nothing to audit
return undefined;
case 'alias':
// follow the redirect
let specifier = resolution.specifier;
let fromFile = resolution.fromFile ?? prevRequest.fromFile;
return { specifier, fromFile };
case 'rehome':
return { specifier: prevRequest.specifier, fromFile: resolution.fromFile };
case 'continue':
return prevRequest;
default:
throw assertNever(resolution);
}
try {
return resolveModule.sync(specifier, {
basedir: dirname(fromPath),
extensions: this.meta['resolvable-extensions'],
});
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
return { isResolutionFailure: true };
} else {
throw err;
}

private async resolve(request: Request, resolver: Resolver): Promise<string | ResolutionFailure | undefined> {
let current: Request | undefined = request;

while (true) {
current = this.nextRequest(current, resolver.beforeResolve(current.specifier, current.fromFile));
if (!current) {
return;
}

if (['@embroider/macros', '@ember/template-factory'].includes(current.specifier)) {
// the audit process deliberately removes the @embroider/macros babel
// plugins, so the imports are still present and should be left alone.
return;
}
try {
return resolveModule.sync(current.specifier, {
basedir: dirname(current.fromFile),
extensions: this.meta['resolvable-extensions'],
});
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
let retry = this.nextRequest(current, resolver.fallbackResolve(current.specifier, current.fromFile));
if (!retry) {
// the request got virtualized
return;
}
if (retry === current) {
// the fallback has nothing new to offer, so this is a real resolution failure
return { isResolutionFailure: true };
}
current = retry;
}
}
}
Expand Down
88 changes: 60 additions & 28 deletions packages/compat/src/audit/build.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,19 @@
import chalk from 'chalk';
import resolveModule from 'resolve';
import { AuditBuildOptions } from '../audit';
import { CaptureStream } from './capture';
import { spawn } from 'child_process';

export async function buildApp(options: AuditBuildOptions): Promise<void> {
let { default: cli } = await import(resolveModule.sync('ember-cli', { basedir: options.app }));
process.env.STAGE2_ONLY = 'true';
let capture = new CaptureStream();
let orig = { cwd: process.cwd(), log: console.log, error: console.error, warn: console.warn };
process.chdir(options.app);
// this is icky, but too many things in the build don't respect the
// `outputStream`, etc, options we pass below.
console.log = console.warn = console.error = capture.log;
try {
let result = await cli({
cliArgs: ['build'],
outputStream: capture,
errorStream: capture,
});
let exitCode = typeof result === 'object' ? result.exitCode : result;
// an undefined exit code means success, because of course it does.
if (exitCode != null && exitCode !== 0) {
throw new BuildError(
`${chalk.yellow('Unable to begin audit')} because the build failed. Build output follows:\n${capture.output}`
);
}
} finally {
process.chdir(orig.cwd);
console.log = orig.log;
console.warn = orig.warn;
console.error = orig.error;
let result = await execute(`node node_modules/ember-cli/bin/ember build`, {
pwd: options.app,
env: {
STAGE2_ONLY: 'true',
},
});

if (result.exitCode !== 0) {
throw new BuildError(
`${chalk.yellow('Unable to begin audit')} because the build failed. Build output follows:\n${result.output}`
);
}
}

Expand All @@ -43,3 +27,51 @@ export class BuildError extends Error {
export function isBuildError(err: any): err is BuildError {
return err?.isBuildError;
}

async function execute(
shellCommand: string,
opts?: { env?: Record<string, string>; pwd?: string }
): Promise<{
exitCode: number;
stderr: string;
stdout: string;
output: string;
}> {
let env: Record<string, string | undefined> | undefined;
if (opts?.env) {
env = { ...process.env, ...opts.env };
}
let child = spawn(shellCommand, {
stdio: ['inherit', 'pipe', 'pipe'],
cwd: opts?.pwd,
shell: true,
env,
});
let stderrBuffer: string[] = [];
let stdoutBuffer: string[] = [];
let combinedBuffer: string[] = [];
child.stderr.on('data', data => {
stderrBuffer.push(data);
combinedBuffer.push(data);
});
child.stdout.on('data', data => {
stdoutBuffer.push(data);
combinedBuffer.push(data);
});
return new Promise(resolve => {
child.on('close', (exitCode: number) => {
resolve({
exitCode,
get stdout() {
return stdoutBuffer.join('');
},
get stderr() {
return stderrBuffer.join('');
},
get output() {
return combinedBuffer.join('');
},
});
});
});
}
28 changes: 0 additions & 28 deletions packages/compat/src/audit/capture.ts

This file was deleted.

11 changes: 5 additions & 6 deletions packages/compat/src/compat-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ import { sync as resolveSync } from 'resolve';
import { MacrosConfig } from '@embroider/macros/src/node';
import bind from 'bind-decorator';
import { pathExistsSync } from 'fs-extra';
import { tmpdir } from '@embroider/core';
import { Options as AdjustImportsOptions } from '@embroider/core/src/babel-plugin-adjust-imports';
import { tmpdir, ResolverOptions } from '@embroider/core';
import type { Transform } from 'babel-plugin-ember-template-compilation';

interface TreeNames {
Expand Down Expand Up @@ -337,21 +336,21 @@ class CompatAppAdapter implements AppAdapter<TreeNames> {
@Memoize()
adjustImportsOptionsPath(): string {
let file = join(this.root, '_adjust_imports.json');
writeFileSync(file, JSON.stringify(this.adjustImportsOptions()));
writeFileSync(file, JSON.stringify(this.resolverConfig()));
return file;
}

@Memoize()
adjustImportsOptions(): AdjustImportsOptions {
resolverConfig(): ResolverOptions {
return this.makeAdjustImportOptions(true);
}

// this gets serialized out by babel plugin and ast plugin
private makeAdjustImportOptions(outer: boolean): AdjustImportsOptions {
private makeAdjustImportOptions(outer: boolean): ResolverOptions {
let renamePackages = Object.assign({}, ...this.allActiveAddons.map(dep => dep.meta['renamed-packages']));
let renameModules = Object.assign({}, ...this.allActiveAddons.map(dep => dep.meta['renamed-modules']));

let activeAddons: AdjustImportsOptions['activeAddons'] = {};
let activeAddons: ResolverOptions['activeAddons'] = {};
for (let addon of this.allActiveAddons) {
activeAddons[addon.name] = addon.root;
}
Expand Down
13 changes: 9 additions & 4 deletions packages/compat/src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import {
PreprocessedComponentRule,
preprocessComponentRule,
} from './dependency-rules';
import { Package, PackageCache, explicitRelative, extensionsPattern } from '@embroider/core';
import {
Package,
PackageCache,
explicitRelative,
extensionsPattern,
ResolverOptions as CoreResolverOptions,
} from '@embroider/core';
import { dirname, join, relative, sep } from 'path';

import { Options as AdjustImportsOptions } from '@embroider/core/src/babel-plugin-adjust-imports';
import { Memoize } from 'typescript-memoize';
import Options from './options';
import { dasherize, snippetToDasherizedName } from './dasherize-component-name';
Expand Down Expand Up @@ -146,7 +151,7 @@ interface RehydrationParamsWithFile extends RehydrationParamsBase {
}

interface RehydrationParamsWithOptions extends RehydrationParamsBase {
adjustImportsOptions: AdjustImportsOptions;
adjustImportsOptions: CoreResolverOptions;
}

type RehydrationParams = RehydrationParamsWithFile | RehydrationParamsWithOptions;
Expand Down Expand Up @@ -214,7 +219,7 @@ export default class CompatResolver {
}

@Memoize()
get adjustImportsOptions(): AdjustImportsOptions {
get adjustImportsOptions(): CoreResolverOptions {
const { params } = this;
return 'adjustImportsOptionsPath' in params
? // eslint-disable-next-line @typescript-eslint/no-require-imports
Expand Down
2 changes: 2 additions & 0 deletions packages/compat/tests/audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ describe('audit', function () {
null,
2
)}`,
'_adjust_imports.json': JSON.stringify(resolver.adjustImportsOptions),
'_relocated_files.json': JSON.stringify({}),
});
let appMeta: AppMeta = {
type: 'app',
Expand Down
5 changes: 2 additions & 3 deletions packages/compat/tests/resolver.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { removeSync, mkdtempSync, writeFileSync, ensureDirSync, writeJSONSync, realpathSync } from 'fs-extra';
import { join, dirname } from 'path';
import Options, { optionsWithDefaults } from '../src/options';
import { hbsToJS, tmpdir, throwOnWarnings } from '@embroider/core';
import { hbsToJS, tmpdir, throwOnWarnings, ResolverOptions } from '@embroider/core';
import { emberTemplateCompiler } from '@embroider/test-support';
import { Options as AdjustImportsOptions } from '@embroider/core/src/babel-plugin-adjust-imports';
import Resolver from '../src/resolver';
import { PackageRules } from '../src';
import type { AST, ASTPluginEnvironment } from '@glimmer/syntax';
Expand All @@ -18,7 +17,7 @@ describe('compat-resolver', function () {
compatOptions: Options,
otherOptions: {
podModulePrefix?: string;
adjustImportsImports?: Partial<AdjustImportsOptions>;
adjustImportsImports?: Partial<ResolverOptions>;
plugins?: Transform[];
startingFrom?: 'hbs' | 'js';
} = {}
Expand Down
Loading

0 comments on commit d94051e

Please sign in to comment.