Skip to content

Commit

Permalink
Merge pull request #1376 from embroider-build/moved-addon-resolving
Browse files Browse the repository at this point in the history
legacy addon resolving
  • Loading branch information
ef4 authored Mar 17, 2023
2 parents 936fd63 + a3d30d0 commit 00ec2e7
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 18 deletions.
70 changes: 66 additions & 4 deletions packages/core/src/module-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
} from './virtual-content';
import { Memoize } from 'typescript-memoize';
import { describeExports } from './describe-exports';
import { readFileSync } from 'fs';
import { existsSync, readFileSync } from 'fs';
import { readJSONSync } from 'fs-extra';

const debug = makeDebug('embroider:resolver');
function logTransition<R extends ModuleRequest>(reason: string, before: R, after: R = before): R {
Expand Down Expand Up @@ -151,6 +152,7 @@ export class Resolver {

request = this.handleFastbootCompat(request);
request = this.handleGlobalsCompat(request);
request = this.handleLegacyAddons(request);
request = this.handleRenaming(request);
return this.preHandleExternal(request);
}
Expand All @@ -177,9 +179,7 @@ export class Resolver {

// synchronous alternative to resolve() above. Because our own internals are
// all synchronous, you can use this if your defaultResolve function is
// synchronous. At present, we need this for the case where we are compiling
// non-strict templates and doing component resolutions inside the template
// compiler inside babel, which is a synchronous context.
// synchronous.
resolveSync<Req extends ModuleRequest, Res extends Resolution>(
request: Req,
defaultResolve: SyncResolverFunction<Req, Res>
Expand Down Expand Up @@ -596,6 +596,68 @@ export class Resolver {
return owningEngine;
}

private handleLegacyAddons<R extends ModuleRequest>(request: R): R {
let packageCache = PackageCache.shared('embroider-stage3', this.options.appRoot);

// first we handle output requests from moved packages
let pkg = this.owningPackage(request.fromFile);
if (!pkg) {
return request;
}
let originalRoot = this.legacyAddonsIndex.v2toV1.get(pkg.root);
if (originalRoot) {
request = logTransition(
'outbound from moved v1 addon',
request,
request.rehome(resolve(originalRoot, 'package.json'))
);
pkg = packageCache.get(originalRoot)!;
}

// then we handle inbound requests to moved packages
let packageName = getPackageName(request.specifier);
if (packageName && packageName !== pkg.name) {
// non-relative, non-self request, so check if it aims at a rewritten addon
try {
let target = PackageCache.shared('embroider-stage3', this.options.appRoot).resolve(packageName, pkg);
if (target) {
let movedRoot = this.legacyAddonsIndex.v1ToV2.get(target.root);
if (movedRoot) {
request = logTransition(
'inbound to moved v1 addon',
request,
this.resolveWithinPackage(request, packageCache.get(movedRoot))
);
}
}
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
}
}

return request;
}

@Memoize()
private get legacyAddonsIndex(): { v1ToV2: Map<string, string>; v2toV1: Map<string, string> } {
let addonsDir = resolve(this.options.appRoot, 'node_modules', '.embroider', 'addons');
let indexFile = resolve(addonsDir, 'v1-addon-index.json');
if (existsSync(indexFile)) {
let { v1Addons } = readJSONSync(indexFile) as { v1Addons: Record<string, string> };
return {
v1ToV2: new Map(
Object.entries(v1Addons).map(([oldRoot, relativeNewRoot]) => [oldRoot, resolve(addonsDir, relativeNewRoot)])
),
v2toV1: new Map(
Object.entries(v1Addons).map(([oldRoot, relativeNewRoot]) => [resolve(addonsDir, relativeNewRoot), oldRoot])
),
};
}
return { v1ToV2: new Map(), v2toV1: new Map() };
}

private handleRenaming<R extends ModuleRequest>(request: R): R {
if (request.isVirtual) {
return request;
Expand Down
76 changes: 62 additions & 14 deletions tests/scenarios/core-resolver-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AddonMeta, AppMeta } from '@embroider/shared-internals';
import { outputFileSync } from 'fs-extra';
import { resolve } from 'path';
import QUnit from 'qunit';
import { Project, Scenarios } from 'scenario-tester';
import { PreparedApp, Project, Scenarios } from 'scenario-tester';
import { CompatResolverOptions } from '@embroider/compat/src/resolver-transform';
import { ExpectAuditResults } from '@embroider/test-support/audit-assertions';
import { installAuditAssertions } from '@embroider/test-support/audit-assertions';
Expand Down Expand Up @@ -55,10 +55,26 @@ Scenarios.fromProject(() => new Project())
}

let configure: (opts?: ConfigureOpts) => Promise<void>;
let app: PreparedApp;

function addonPackageJSON(addonMeta?: Partial<AddonMeta>) {
return JSON.stringify(
(() => {
let meta: AddonMeta = { type: 'addon', version: 2, 'auto-upgraded': true, ...(addonMeta ?? {}) };
return {
name: 'my-addon',
keywords: ['ember-addon'],
'ember-addon': meta,
};
})(),
null,
2
);
}

hooks.beforeEach(async assert => {
installAuditAssertions(assert);
let app = await scenario.prepare();
app = await scenario.prepare();

givenFiles = function (files: Record<string, string>) {
for (let [filename, contents] of Object.entries(files)) {
Expand Down Expand Up @@ -110,18 +126,7 @@ Scenarios.fromProject(() => new Project())
module.exports = function(filename) { return true }
`,
'.embroider/resolver.json': JSON.stringify(resolverOptions),
'node_modules/my-addon/package.json': JSON.stringify(
(() => {
let meta: AddonMeta = { type: 'addon', version: 2, 'auto-upgraded': true, ...(opts?.addonMeta ?? {}) };
return {
name: 'my-addon',
keywords: ['ember-addon'],
'ember-addon': meta,
};
})(),
null,
2
),
'node_modules/my-addon/package.json': addonPackageJSON(opts?.addonMeta),
});

expectAudit = await assert.audit({ outputDir: app.dir });
Expand Down Expand Up @@ -594,5 +599,48 @@ Scenarios.fromProject(() => new Project())
switcherModule.resolves('./browser').to('./node_modules/my-addon/_app_/hello-world.js');
});
});

Qmodule('legacy-addons', function () {
test('app can resolve file in rewritten addon', async function () {
givenFiles({
'node_modules/.embroider/addons/v1-addon-index.json': JSON.stringify({
v1Addons: {
[resolve(app.dir, 'node_modules/my-addon')]: 'my-addon.1234',
},
}),
'node_modules/.embroider/addons/my-addon.1234/hello-world.js': ``,
'node_modules/.embroider/addons/my-addon.1234/package.json': addonPackageJSON(),
'app.js': `import "my-addon/hello-world"`,
});

await configure({});

expectAudit
.module('./app.js')
.resolves('my-addon/hello-world')
.to('./node_modules/.embroider/addons/my-addon.1234/hello-world.js');
});

test('moved addon resolves dependencies from its original location', async function () {
givenFiles({
'node_modules/my-addon/node_modules/inner-dep/index.js': '',
'node_modules/.embroider/addons/v1-addon-index.json': JSON.stringify({
v1Addons: {
[resolve(app.dir, 'node_modules/my-addon')]: 'my-addon.1234',
},
}),
'node_modules/.embroider/addons/my-addon.1234/hello-world.js': `import "inner-dep"`,
'node_modules/.embroider/addons/my-addon.1234/package.json': addonPackageJSON(),
'app.js': `import "my-addon/hello-world"`,
});

await configure({});

expectAudit
.module('./node_modules/.embroider/addons/my-addon.1234/hello-world.js')
.resolves('inner-dep')
.to('./node_modules/my-addon/node_modules/inner-dep/index.js');
});
});
});
});

0 comments on commit 00ec2e7

Please sign in to comment.