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

Generate per-package implicit-modules imports #1536

Merged
merged 5 commits into from
Jul 16, 2023
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
2 changes: 1 addition & 1 deletion packages/compat/src/babel-plugin-adjust-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function lazyPackageLookup(config: InternalConfig, filename: string) {
return {
get owningPackage() {
if (!owningPackage) {
owningPackage = { result: config.resolver.owningPackage(filename) };
owningPackage = { result: config.resolver.packageCache.ownerOfFile(filename) };
}
return owningPackage.result;
},
Expand Down
76 changes: 12 additions & 64 deletions packages/compat/src/compat-app-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { AppFiles, RouteFiles } from '@embroider/core/src/app-files';
import { PortableHint, maybeNodeModuleVersion } from '@embroider/core/src/portable';
import assertNever from 'assert-never';
import { Memoize } from 'typescript-memoize';
import { join, dirname, sep } from 'path';
import { join, dirname } from 'path';
import resolve from 'resolve';
import { V1Config } from './v1-config';
import { AddonMeta, Package, PackageInfo } from '@embroider/core';
Expand Down Expand Up @@ -140,9 +140,9 @@ export class CompatAppBuilder {
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 definitely appear in one of the other
// sections.
addon => pkg.packageJSON.dependencies?.[addon.name] || pkg.packageJSON.devDependencies?.[addon.name]
// 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[];
Expand Down Expand Up @@ -703,10 +703,10 @@ export class CompatAppBuilder {

private engines: { engine: Engine; appSync: SyncDir; fastbootSync: SyncDir | undefined }[] | undefined;

private updateAppJS(inputPaths: OutputPaths<TreeNames>): AppFiles[] {
private updateAppJS(appJSPath: string): AppFiles[] {
if (!this.engines) {
this.engines = this.partitionEngines(inputPaths.appJS).map(engine => {
if (engine.sourcePath === inputPaths.appJS) {
this.engines = this.partitionEngines(appJSPath).map(engine => {
if (engine.sourcePath === appJSPath) {
// this is the app. We have more to do for the app than for other
// engines.
let fastbootSync: SyncDir | undefined;
Expand All @@ -718,7 +718,7 @@ export class CompatAppBuilder {
}
return {
engine,
appSync: new SyncDir(inputPaths.appJS, this.root),
appSync: new SyncDir(appJSPath, this.root),
fastbootSync,
};
} else {
Expand Down Expand Up @@ -938,7 +938,7 @@ export class CompatAppBuilder {
this.firstBuild = false;
}

let appFiles = this.updateAppJS(inputPaths);
let appFiles = this.updateAppJS(inputPaths.appJS);
let emberENV = this.configTree.readConfig().EmberENV;
let assets = this.gatherAssets(inputPaths);

Expand Down Expand Up @@ -1192,7 +1192,7 @@ export class CompatAppBuilder {
return cached;
}

let eagerModules = [];
let eagerModules: string[] = [];

let requiredAppFiles = [this.requiredOtherFiles(appFiles)];
if (!this.options.staticComponents) {
Expand Down Expand Up @@ -1272,7 +1272,7 @@ export class CompatAppBuilder {

// this is a backward-compatibility feature: addons can force inclusion of
// modules.
this.gatherImplicitModules('implicit-modules', appFiles, amdModules);
eagerModules.push('./#embroider-implicit-modules');

let params = { amdModules, fastbootOnlyAmdModules, lazyRoutes, lazyEngines, eagerModules, styles };
if (entryParams) {
Expand Down Expand Up @@ -1337,7 +1337,7 @@ export class CompatAppBuilder {
let amdModules: { runtime: string; buildtime: string }[] = [];
// this is a backward-compatibility feature: addons can force inclusion of
// test support modules.
this.gatherImplicitModules('implicit-test-modules', engine, amdModules);
eagerModules.push('./#embroider-implicit-test-modules');

for (let relativePath of engine.tests) {
amdModules.push(this.importPaths(engine, relativePath));
Expand All @@ -1357,44 +1357,6 @@ export class CompatAppBuilder {
prepared.set(asset.relativePath, asset);
return asset;
}

private gatherImplicitModules(
section: 'implicit-modules' | 'implicit-test-modules',
{ engine }: AppFiles,
lazyModules: { runtime: string; buildtime: string }[]
) {
for (let addon of engine.addons) {
let implicitModules = addon.meta[section];
if (implicitModules) {
let renamedModules = inverseRenamedModules(addon.meta, this.resolvableExtensionsPattern);
for (let name of implicitModules) {
let packageName = addon.name;

if (addon.isV2Addon()) {
let renamedMeta = addon.meta['renamed-packages'];
if (renamedMeta) {
Object.entries(renamedMeta).forEach(([key, value]) => {
if (value === addon!.name) {
packageName = key;
}
});
}
}

let runtime = join(packageName, name).replace(this.resolvableExtensionsPattern, '');
let runtimeRenameLookup = runtime.split('\\').join('/');
if (renamedModules && renamedModules[runtimeRenameLookup]) {
runtime = renamedModules[runtimeRenameLookup];
}
runtime = runtime.split(sep).join('/');
lazyModules.push({
runtime,
buildtime: posix.join(packageName, name),
});
}
}
}
}
}

function maybeReplace(dom: JSDOM, element: Element | undefined): Node | undefined {
Expand Down Expand Up @@ -1541,20 +1503,6 @@ const { babelFilter } = require(${JSON.stringify(require.resolve('@embroider/cor
module.exports = babelFilter({{json-stringify skipBabel}}, "{{js-string-escape appRoot}}");
`) as (params: { skipBabel: Options['skipBabel']; appRoot: string }) => string;

// meta['renamed-modules'] has mapping from classic filename to real filename.
// This takes that and converts it to the inverst mapping from real import path
// to classic import path.
function inverseRenamedModules(meta: AddonPackage['meta'], extensions: RegExp) {
let renamed = meta['renamed-modules'];
if (renamed) {
let inverted = {} as { [name: string]: string };
for (let [classic, real] of Object.entries(renamed)) {
inverted[real.replace(extensions, '')] = classic.replace(extensions, '');
}
return inverted;
}
}

function combinePackageJSON(...layers: object[]) {
function custom(objValue: any, srcValue: any, key: string, _object: any, _source: any, stack: { size: number }) {
if (key === 'keywords' && stack.size === 0) {
Expand Down
2 changes: 1 addition & 1 deletion packages/compat/src/dependency-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export function activePackageRules(
}

export function appTreeRulesDir(root: string, resolver: Resolver) {
let pkg = resolver.owningPackage(root);
let pkg = resolver.packageCache.ownerOfFile(root);
if (pkg?.isV2Addon()) {
// in general v2 addons can keep their app tree stuff in other places than
// "_app_" and we would need to check their package.json to see. But this code
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export {
ResolverFunction,
SyncResolverFunction,
} from './module-resolver';
export { ResolverLoader } from './resolver-loader';
export { virtualContent } from './virtual-content';
export type { Engine } from './app-files';

Expand Down
56 changes: 41 additions & 15 deletions packages/core/src/module-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
virtualContent,
fastbootSwitch,
decodeFastbootSwitch,
decodeImplicitModules,
} from './virtual-content';
import { Memoize } from 'typescript-memoize';
import { describeExports } from './describe-exports';
Expand Down Expand Up @@ -144,7 +145,7 @@ export type SyncResolverFunction<R extends ModuleRequest = ModuleRequest, Res ex
) => Res;

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

beforeResolve<R extends ModuleRequest>(request: R): R {
if (request.specifier === '@embroider/macros') {
Expand All @@ -157,6 +158,7 @@ export class Resolver {

request = this.handleFastbootSwitch(request);
request = this.handleGlobalsCompat(request);
request = this.handleImplicitModules(request);
request = this.handleRenaming(request);
// we expect the specifier to be app relative at this point - must be after handleRenaming
request = this.generateFastbootSwitch(request);
Expand Down Expand Up @@ -251,7 +253,7 @@ export class Resolver {
type: 'found',
result: {
type: 'virtual' as 'virtual',
content: virtualContent(request.specifier),
content: virtualContent(request.specifier, this),
filename: request.specifier,
},
};
Expand Down Expand Up @@ -279,14 +281,10 @@ export class Resolver {
}
}

private get packageCache() {
get packageCache() {
return RewrittenPackageCache.shared('embroider', this.options.appRoot);
}

owningPackage(fromFile: string): Package | undefined {
return this.packageCache.ownerOfFile(fromFile);
}

private logicalPackage(owningPackage: V2Package, file: string): V2Package {
let logicalLocation = this.reverseSearchAppTree(owningPackage, file);
if (logicalLocation) {
Expand All @@ -300,7 +298,7 @@ export class Resolver {
}

private generateFastbootSwitch<R extends ModuleRequest>(request: R): R {
let pkg = this.owningPackage(request.fromFile);
let pkg = this.packageCache.ownerOfFile(request.fromFile);

if (!pkg) {
return request;
Expand Down Expand Up @@ -358,7 +356,7 @@ export class Resolver {
return logTransition('non-special import in fastboot switch', request);
}

let pkg = this.owningPackage(match.filename);
let pkg = this.packageCache.ownerOfFile(match.filename);
if (pkg) {
let rel = explicitRelative(pkg.root, match.filename);

Expand Down Expand Up @@ -397,13 +395,41 @@ export class Resolver {
return logTransition('failed to match in fastboot switch', request);
}

private handleImplicitModules<R extends ModuleRequest>(request: R): R {
let im = decodeImplicitModules(request.specifier);
if (!im) {
return request;
}

let pkg = this.packageCache.ownerOfFile(request.fromFile);
if (!pkg?.isV2Ember()) {
throw new Error(`bug: found implicit modules import in non-ember package at ${request.fromFile}`);
}

let packageName = getPackageName(im.fromFile);
if (packageName) {
let dep = this.packageCache.resolve(packageName, pkg);
return logTransition(
`dep's implicit modules`,
request,
request.virtualize(resolve(dep.root, `#embroider-${im.type}`))
);
} else {
return logTransition(
`own implicit modules`,
request,
request.virtualize(resolve(pkg.root, `#embroider-${im.type}`))
);
}
}

private handleGlobalsCompat<R extends ModuleRequest>(request: R): R {
let match = compatPattern.exec(request.specifier);
if (!match) {
return request;
}
let { type, rest } = match.groups!;
let fromPkg = this.owningPackage(request.fromFile);
let fromPkg = this.packageCache.ownerOfFile(request.fromFile);
if (!fromPkg?.isV2Ember()) {
return request;
}
Expand Down Expand Up @@ -677,7 +703,7 @@ export class Resolver {
}

private handleRewrittenPackages<R extends ModuleRequest>(request: R): R {
let requestingPkg = this.owningPackage(request.fromFile);
let requestingPkg = this.packageCache.ownerOfFile(request.fromFile);
if (!requestingPkg) {
return request;
}
Expand Down Expand Up @@ -734,7 +760,7 @@ export class Resolver {
return request;
}

let pkg = this.owningPackage(request.fromFile);
let pkg = this.packageCache.ownerOfFile(request.fromFile);
if (!pkg || !pkg.isV2Ember()) {
return request;
}
Expand Down Expand Up @@ -795,7 +821,7 @@ export class Resolver {
return request;
}
let { specifier, fromFile } = request;
let pkg = this.owningPackage(fromFile);
let pkg = this.packageCache.ownerOfFile(fromFile);
if (!pkg || !pkg.isV2Ember()) {
return request;
}
Expand Down Expand Up @@ -916,7 +942,7 @@ export class Resolver {
return request;
}

let pkg = this.owningPackage(fromFile);
let pkg = this.packageCache.ownerOfFile(fromFile);
if (!pkg) {
return logTransition('no identifiable owningPackage', request);
}
Expand Down Expand Up @@ -1116,7 +1142,7 @@ export class Resolver {
// check if this file is resolvable as a global component, and if so return
// its dasherized name
reverseComponentLookup(filename: string): string | undefined {
const owningPackage = this.owningPackage(filename);
const owningPackage = this.packageCache.ownerOfFile(filename);
if (!owningPackage?.isV2Ember()) {
return;
}
Expand Down
32 changes: 32 additions & 0 deletions packages/core/src/resolver-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { readJSONSync } from 'fs-extra';
import { Resolver, Options } from './module-resolver';
import { locateEmbroiderWorkingDir } from '@embroider/shared-internals';
import { join } from 'path';
import { watch as fsWatch, FSWatcher } from 'fs';

export class ResolverLoader {
#resolver: Resolver | undefined;
#configFile: string;
#watcher: FSWatcher | undefined;

constructor(readonly appRoot: string, watch = false) {
this.#configFile = join(locateEmbroiderWorkingDir(this.appRoot), 'resolver.json');
if (watch) {
this.#watcher = fsWatch(this.#configFile, { persistent: false }, () => {
this.#resolver = undefined;
});
}
}

close() {
this.#watcher?.close();
}

get resolver(): Resolver {
if (!this.#resolver) {
let config: Options = readJSONSync(join(locateEmbroiderWorkingDir(this.appRoot), 'resolver.json'));
this.#resolver = new Resolver(config);
}
return this.#resolver;
}
}
Loading