Skip to content

Commit

Permalink
allow v2 addons to use app tree fallback resolution
Browse files Browse the repository at this point in the history
We are careful to not allow most of the bad backward-compatible kinds of package resolution inside v2 addons.

But there's an exception that we need to allow and don't: due to app tree merging, files from a v2 addon can end up needing to resolve back to that v2 addon from the app, even when the v2 addon is not properly resolvable from the app.

This is already handled for v1 addons through a process of attempting to resolve from the app, from the original addon, and then from the global list of all known addons. This change allows v2 addons to also use that process, but only when resolving their own name.
  • Loading branch information
ef4 committed Nov 10, 2022
1 parent d31bee3 commit d53ba88
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 10 deletions.
16 changes: 8 additions & 8 deletions packages/core/src/babel-plugin-adjust-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,10 @@ function handleExternal(specifier: string, sourceFile: AdjustFile, opts: Options
if (relocatedPkg) {
// this file has been moved into another package (presumably the app).

// self-imports are legal in the app tree, even for v2 packages
if (packageName === pkg.name) {
return specifier;
}

// first try to resolve from the destination package
if (isResolvable(packageName, relocatedPkg, opts.appRoot)) {
if (!pkg.meta['auto-upgraded']) {
// self-imports are legal in the app tree, even for v2 packages.
if (!pkg.meta['auto-upgraded'] && packageName !== pkg.name) {
throw new Error(
`${pkg.name} is trying to import ${packageName} from within its app tree. This is unsafe, because ${pkg.name} can't control which dependencies are resolvable from the app`
);
Expand All @@ -275,7 +271,8 @@ function handleExternal(specifier: string, sourceFile: AdjustFile, opts: Options
// second try to resolve from the source package
let targetPkg = isResolvable(packageName, pkg, opts.appRoot);
if (targetPkg) {
if (!pkg.meta['auto-upgraded']) {
// self-imports are legal in the app tree, even for v2 packages.
if (!pkg.meta['auto-upgraded'] && packageName !== pkg.name) {
throw new Error(
`${pkg.name} is trying to import ${packageName} from within its app tree. This is unsafe, because ${pkg.name} can't control which dependencies are resolvable from the app`
);
Expand All @@ -297,7 +294,10 @@ function handleExternal(specifier: string, sourceFile: AdjustFile, opts: Options
}

// auto-upgraded packages can fall back to the set of known active addons
if (pkg.meta['auto-upgraded'] && opts.activeAddons[packageName]) {
//
// v2 packages can fall back to the set of known active addons only to find
// themselves (which is needed due to app tree merging)
if ((pkg.meta['auto-upgraded'] || packageName === pkg.name) && opts.activeAddons[packageName]) {
return explicitRelative(dirname(sourceFile.name), specifier.replace(packageName, opts.activeAddons[packageName]));
}

Expand Down
62 changes: 60 additions & 2 deletions tests/scenarios/v2-addon-test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { appScenarios, baseV2Addon } from './scenarios';
import { appScenarios, baseAddon, baseV2Addon } from './scenarios';
import { PreparedApp } from 'scenario-tester';
import QUnit from 'qunit';
import merge from 'lodash/merge';

const { module: Qmodule, test } = QUnit;

appScenarios
.map('v2-addon', project => {
.map('v2-addon-basics', project => {
let addon = baseV2Addon();
addon.pkg.name = 'v2-addon';
(addon.pkg as any)['ember-addon']['app-js']['./components/example-component.js'] =
Expand Down Expand Up @@ -49,6 +49,47 @@ appScenarios

project.addDevDependency(addon);

// a v1 addon, which will have a v2 addon as a dep
let intermediate = baseAddon();
intermediate.pkg.name = 'intermediate';
intermediate.linkDependency('ember-auto-import', { baseDir: __dirname });
merge(intermediate.files, {
app: {
components: {
'hello.js': 'export { default } from "intermediate/components/hello"',
},
},
addon: {
components: {
'hello.hbs': '<div class="intermediate-hello"><Inner /></div>',
},
},
});
project.addDevDependency(intermediate);

// the inner v2 addon, which gets consumed by `intermediate`
let inner = baseV2Addon();
inner.pkg.name = 'inner';
(inner.pkg as any)['ember-addon']['app-js']['./components/inner.js'] = 'app/components/inner.js';
merge(inner.files, {
app: {
components: {
'inner.js': `export { default } from 'inner/components/inner';`,
},
},
components: {
'inner.js': `
import Component from '@glimmer/component';
import { hbs } from 'ember-cli-htmlbars';
import { setComponentTemplate } from '@ember/component';
const TEMPLATE = hbs("<div class='inner'>it works</div>")
export default class ExampleComponent extends Component {}
setComponentTemplate(TEMPLATE, ExampleComponent);
`,
},
});
intermediate.addDependency(inner);

merge(project.files, {
app: {
templates: {
Expand All @@ -75,6 +116,23 @@ appScenarios
});
`,
},
intergration: {
'intermediate-test.js': `
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | intermediate', function(hooks) {
setupRenderingTest(hooks);
test('v1 addon can invoke v2 addon through the app tree', async function(assert) {
await render(hbs('<Hello />'));
assert.dom('.intermediate-hello .inner').containsText('it works');
});
});
`,
},
unit: {
'import-test.js': `
import { module, test } from 'qunit';
Expand Down

0 comments on commit d53ba88

Please sign in to comment.