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

Derive default resolver config #2104

Merged
merged 4 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 18 additions & 21 deletions packages/compat/src/babel-plugin-adjust-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import type { NodePath } from '@babel/traverse';
import type * as Babel from '@babel/core';
import type { types as t } from '@babel/core';
import { ImportUtil } from 'babel-import-util';
import { readJSONSync } from 'fs-extra';
import type { CompatResolverOptions } from './resolver-transform';
import type { Package } from '@embroider/core';
import { cleanUrl, locateEmbroiderWorkingDir, packageName, Resolver, unrelativize } from '@embroider/core';
import { cleanUrl, packageName, type Resolver, ResolverLoader, unrelativize } from '@embroider/core';
import { snippetToDasherizedName } from './dasherize-component-name';
import type { ActivePackageRules, ComponentRules, ModuleRules, TemplateRules } from './dependency-rules';
import { appTreeRulesDir } from './dependency-rules';
Expand All @@ -27,8 +26,7 @@ interface ExtraImports {
}

type InternalConfig = {
resolverOptions: CompatResolverOptions;
resolver: Resolver;
loader: ResolverLoader;

// rule-based extra dependencies, indexed by filename
extraImports: ExtraImports;
Expand All @@ -44,15 +42,12 @@ export default function main(babel: typeof Babel) {
if (cached) {
return cached;
}
let resolverOptions: CompatResolverOptions = readJSONSync(
join(locateEmbroiderWorkingDir(appRoot), 'resolver.json')
);
let resolver = new Resolver(resolverOptions);
let loader = new ResolverLoader(appRoot);

cached = {
resolverOptions,
resolver,
extraImports: preprocessExtraImports(resolverOptions, resolver),
componentExtraImports: preprocessComponentExtraImports(resolverOptions),
loader,
extraImports: preprocessExtraImports(loader),
componentExtraImports: preprocessComponentExtraImports(loader),
};
return cached;
}
Expand Down Expand Up @@ -80,7 +75,7 @@ function addExtraImports(t: BabelTypes, path: NodePath<t.Program>, config: Inter
applyRules(t, path, entry, adder, config, filename);
}

let componentName = config.resolver.reverseComponentLookup(filename);
let componentName = config.loader.resolver.reverseComponentLookup(filename);
if (componentName) {
let rules = config.componentExtraImports[componentName];
if (rules) {
Expand Down Expand Up @@ -138,9 +133,10 @@ function amdDefine(t: BabelTypes, adder: ImportUtil, path: NodePath<t.Program>,
);
}

function preprocessExtraImports(config: CompatResolverOptions, resolver: Resolver): ExtraImports {
function preprocessExtraImports(loader: ResolverLoader): ExtraImports {
let extraImports: ExtraImports = {};
for (let rule of config.activePackageRules) {
let config = loader.resolver.options as CompatResolverOptions;
for (let rule of config.activePackageRules ?? []) {
if (rule.addonModules) {
for (let [filename, moduleRules] of Object.entries(rule.addonModules)) {
for (let root of rule.roots) {
Expand All @@ -156,7 +152,7 @@ function preprocessExtraImports(config: CompatResolverOptions, resolver: Resolve
// But this code is only for applying packageRules to auto-upgraded v1
// addons, and those we always organize with their treeForApp output
// in _app_.
expandDependsOnRules(appTreeRulesDir(root, resolver), filename, moduleRules, extraImports);
expandDependsOnRules(appTreeRulesDir(root, loader.resolver), filename, moduleRules, extraImports);
}
}
}
Expand All @@ -170,7 +166,7 @@ function preprocessExtraImports(config: CompatResolverOptions, resolver: Resolve
if (rule.appTemplates) {
for (let [filename, moduleRules] of Object.entries(rule.appTemplates)) {
for (let root of rule.roots) {
expandInvokesRules(appTreeRulesDir(root, resolver), filename, moduleRules, extraImports);
expandInvokesRules(appTreeRulesDir(root, loader.resolver), filename, moduleRules, extraImports);
}
}
}
Expand All @@ -184,7 +180,7 @@ function lazyPackageLookup(config: InternalConfig, filename: string) {
return {
get owningPackage() {
if (!owningPackage) {
owningPackage = { result: config.resolver.packageCache.ownerOfFile(filename) };
owningPackage = { result: config.loader.resolver.packageCache.ownerOfFile(filename) };
}
return owningPackage.result;
},
Expand All @@ -193,17 +189,18 @@ function lazyPackageLookup(config: InternalConfig, filename: string) {
owningEngine = { result: undefined };
let p = this.owningPackage;
if (p) {
owningEngine.result = config.resolver.owningEngine(p);
owningEngine.result = config.loader.resolver.owningEngine(p);
}
}
return owningEngine.result;
},
};
}

function preprocessComponentExtraImports(config: CompatResolverOptions): ExtraImports {
function preprocessComponentExtraImports(loader: ResolverLoader): ExtraImports {
let extraImports: ExtraImports = {};
for (let rule of config.activePackageRules) {
let config = loader.resolver.options as CompatResolverOptions;
for (let rule of config.activePackageRules ?? []) {
if (rule.components) {
for (let [componentName, rules] of Object.entries(rule.components)) {
if (rules.invokes) {
Expand Down
212 changes: 23 additions & 189 deletions packages/compat/src/compat-app-builder.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import type { AddonPackage, Engine } from '@embroider/core';
import { explicitRelative, locateEmbroiderWorkingDir } from '@embroider/core';
import { resolve as resolvePath } from 'path';
import type { AddonPackage } from '@embroider/core';
import { locateEmbroiderWorkingDir } from '@embroider/core';
import type Options from './options';
import type { CompatResolverOptions } from './resolver-transform';
import type { PackageRules } from './dependency-rules';
import { activePackageRules } from './dependency-rules';
import flatMap from 'lodash/flatMap';
import bind from 'bind-decorator';
import { outputJSONSync, writeFileSync, realpathSync } from 'fs-extra';
import { outputJSONSync, writeFileSync } from 'fs-extra';
import type { PortableHint } from '@embroider/core/src/portable';
import { maybeNodeModuleVersion, Portable } from '@embroider/core/src/portable';
import { Memoize } from 'typescript-memoize';
Expand All @@ -21,6 +18,7 @@ import { readdirSync } from 'fs-extra';
import type CompatApp from './compat-app';
import type { CompatBabelState } from './babel';
import { MacrosConfig } from '@embroider/macros/src/node';
import { buildResolverOptions } from '@embroider/core/src/module-resolver-options';

// This exists during the actual broccoli build step. As opposed to CompatApp,
// which also exists during pipeline-construction time.
Expand All @@ -37,65 +35,6 @@ export class CompatAppBuilder {
private synthStyles: Package
) {}

private activeAddonChildren(pkg: Package): AddonPackage[] {
let result = (pkg.dependencies.filter(this.isActiveAddon) as AddonPackage[]).filter(
// When looking for child addons, we want to ignore 'peerDependencies' of
// a given package, to align with how ember-cli resolves addons. So here
// we only include dependencies that are definitely active due to one of
// the other sections.
addon => pkg.categorizeDependency(addon.name) !== 'peerDependencies'
);
if (pkg === this.appPackageWithMovedDeps) {
let extras = [this.synthVendor, this.synthStyles].filter(this.isActiveAddon) as AddonPackage[];
result = [...result, ...extras];
}
return result.sort(this.orderAddons);
}

@Memoize()
private get allActiveAddons(): AddonPackage[] {
let result = this.appPackageWithMovedDeps.findDescendants(this.isActiveAddon) as AddonPackage[];
let extras = [this.synthVendor, this.synthStyles].filter(this.isActiveAddon) as AddonPackage[];
let extraDescendants = flatMap(extras, dep => dep.findDescendants(this.isActiveAddon)) as AddonPackage[];
result = [...result, ...extras, ...extraDescendants];
return result.sort(this.orderAddons);
}

@bind
private isActiveAddon(pkg: Package): boolean {
// stage1 already took care of converting everything that's actually active
// into v2 addons. If it's not a v2 addon, we don't want it.
//
// We can encounter v1 addons here when there is inactive stuff floating
// around in the node_modules that accidentally satisfy something like an
// optional peer dep.
return pkg.isV2Addon();
}

@bind
private orderAddons(depA: Package, depB: Package): number {
let depAIdx = 0;
let depBIdx = 0;

if (depA && depA.meta && depA.isV2Addon()) {
depAIdx = depA.meta['order-index'] || 0;
}
if (depB && depB.meta && depB.isV2Addon()) {
depBIdx = depB.meta['order-index'] || 0;
}

return depAIdx - depBIdx;
}

private resolvableExtensions(): string[] {
let fromEnv = process.env.EMBROIDER_RESOLVABLE_EXTENSIONS;
if (fromEnv) {
return fromEnv.split(',');
} else {
return ['.mjs', '.gjs', '.js', '.mts', '.gts', '.ts', '.hbs', '.hbs.js', '.json'];
}
}

private modulePrefix(): string {
return this.configTree.readConfig().modulePrefix;
}
Expand All @@ -105,141 +44,36 @@ export class CompatAppBuilder {
}

@Memoize()
private activeRules() {
return activePackageRules(this.options.packageRules.concat(defaultAddonPackageRules()), [
{ name: this.origAppPackage.name, version: this.origAppPackage.version, root: this.origAppPackage.root },
...this.allActiveAddons.filter(p => p.meta['auto-upgraded']),
]);
}

private resolverConfig(engines: Engine[]): CompatResolverOptions {
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 options: CompatResolverOptions['options'] = {
staticHelpers: this.options.staticHelpers,
staticModifiers: this.options.staticModifiers,
staticComponents: this.options.staticComponents,
allowUnsafeDynamicComponents: this.options.allowUnsafeDynamicComponents,
};

let config: CompatResolverOptions = {
// this part is the base ModuleResolverOptions as required by @embroider/core
renameModules,
renamePackages,
resolvableExtensions: this.resolvableExtensions(),
appRoot: this.origAppPackage.root,
engines: engines.map(engine => ({
packageName: engine.package.name,
// we need to use the real path here because webpack requests always use the real path i.e. follow symlinks
root: realpathSync(engine.package.root),
fastbootFiles: {},
activeAddons: [...engine.addons]
.map(([addon, canResolveFromFile]) => ({
name: addon.name,
root: addon.root,
canResolveFromFile,
}))
// the traditional order is the order in which addons will run, such
// that the last one wins. Our resolver's order is the order to
// search, so first one wins.
.reverse(),
isLazy: engine.package.isLazyEngine(),
})),
amdCompatibility: this.options.amdCompatibility,

// this is the additional stufff that @embroider/compat adds on top to do
// global template resolving
private get resolverConfig(): CompatResolverOptions {
return buildResolverOptions({
appPackage: this.appPackageWithMovedDeps,
modulePrefix: this.modulePrefix(),
splitAtRoutes: this.options.splitAtRoutes,
podModulePrefix: this.podModulePrefix(),
activePackageRules: this.activeRules(),
options,
autoRun: this.compatApp.autoRun,
splitAtRoutes: this.options.splitAtRoutes,
staticAppPaths: this.options.staticAppPaths,
emberVersion: this.emberVersion(),
};

return config;
}

// recurse to find all active addons that don't cross an engine boundary.
// Inner engines themselves will be returned, but not those engines' children.
// The output set's insertion order is the proper ember-cli compatible
// ordering of the addons.
private findActiveAddons(pkg: Package, engine: Engine, isChild = false): void {
for (let child of this.activeAddonChildren(pkg)) {
if (!child.isEngine()) {
this.findActiveAddons(child, engine, true);
}
let canResolveFrom = resolvePath(pkg.root, 'package.json');
engine.addons.set(child, canResolveFrom);
}
// ensure addons are applied in the correct order, if set (via @embroider/compat/v1-addon)
if (!isChild) {
engine.addons = new Map(
[...engine.addons].sort(([a], [b]) => {
return (a.meta['order-index'] || 0) - (b.meta['order-index'] || 0);
})
);
}
}

private partitionEngines(): Engine[] {
let queue: Engine[] = [
{
package: this.appPackageWithMovedDeps,
addons: new Map(),
isApp: true,
modulePrefix: this.modulePrefix(),
appRelativePath: '.',
extraDeps: new Map([[this.appPackageWithMovedDeps.root, [this.synthVendor, this.synthStyles] as AddonPackage[]]]),
extend: (options: CompatResolverOptions, allActiveAddons) => {
options.activePackageRules = activePackageRules(this.options.packageRules.concat(defaultAddonPackageRules()), [
{ name: this.origAppPackage.name, version: this.origAppPackage.version, root: this.origAppPackage.root },
...allActiveAddons.filter(p => p.meta['auto-upgraded']),
]);
options.options = {
staticHelpers: this.options.staticHelpers,
staticModifiers: this.options.staticModifiers,
staticComponents: this.options.staticComponents,
allowUnsafeDynamicComponents: this.options.allowUnsafeDynamicComponents,
};
return options;
},
];
let done: Engine[] = [];
let seenEngines: Set<Package> = new Set();
while (true) {
let current = queue.shift();
if (!current) {
break;
}
this.findActiveAddons(current.package, current);
for (let addon of current.addons.keys()) {
if (addon.isEngine() && !seenEngines.has(addon)) {
seenEngines.add(addon);
queue.push({
package: addon,
addons: new Map(),
isApp: !current,
modulePrefix: addon.name,
appRelativePath: explicitRelative(this.origAppPackage.root, addon.root),
});
}
}
done.push(current);
}
return done;
}

private emberVersion() {
let pkg = this.activeAddonChildren(this.appPackageWithMovedDeps).find(a => a.name === 'ember-source');
if (!pkg) {
throw new Error('no ember version!');
}
return pkg.version;
});
}

private engines: Engine[] | undefined;

async build() {
// on the first build, we lock down the macros config. on subsequent builds,
// this doesn't do anything anyway because it's idempotent.
this.compatApp.macrosConfig.finalize();

if (!this.engines) {
this.engines = this.partitionEngines();
}

let resolverConfig = this.resolverConfig(this.engines);
let resolverConfig = this.resolverConfig;
let config = this.configTree.readConfig();
let contentForConfig = this.contentForTree.readContents();

Expand Down
6 changes: 3 additions & 3 deletions packages/compat/src/resolver-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import { Memoize } from 'typescript-memoize';
import type { WithJSUtils } from 'babel-plugin-ember-template-compilation';
import assertNever from 'assert-never';
import { join, sep } from 'path';
import { readJSONSync } from 'fs-extra';
import { dasherize, snippetToDasherizedName } from './dasherize-component-name';
import type { ResolverOptions as CoreResolverOptions } from '@embroider/core';
import { Resolver, cleanUrl, locateEmbroiderWorkingDir } from '@embroider/core';
import { Resolver, ResolverLoader, cleanUrl } from '@embroider/core';
import type CompatOptions from './options';
import type { AuditMessage, Loc } from './audit';
import { camelCase, mergeWith } from 'lodash';
Expand Down Expand Up @@ -976,7 +975,8 @@ class TemplateResolver implements ASTPlugin {

// This is the AST transform that resolves components, helpers and modifiers at build time
export default function makeResolverTransform({ appRoot, emberVersion }: Options) {
let config: CompatResolverOptions = readJSONSync(join(locateEmbroiderWorkingDir(appRoot), 'resolver.json'));
let loader = new ResolverLoader(appRoot);
let config = loader.resolver.options as CompatResolverOptions;
const resolverTransform: ASTPluginBuilder<Env> = env => {
if (env.strictMode) {
return {
Expand Down
1 change: 0 additions & 1 deletion packages/compat/tests/audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ describe('audit', function () {
},
],
resolvableExtensions,
autoRun: true,
staticAppPaths: [],
emberVersion: '4.0.0',
};
Expand Down
Loading