Skip to content

Commit

Permalink
Merge pull request #2104 from embroider-build/default-resolver-config
Browse files Browse the repository at this point in the history
Derive default resolver config
  • Loading branch information
ef4 authored Sep 11, 2024
2 parents 2ff89c0 + 4b13840 commit 94a010f
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 251 deletions.
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

0 comments on commit 94a010f

Please sign in to comment.