From d53ba883c23986e7b3f3297859403358997868af Mon Sep 17 00:00:00 2001 From: Edward Faulkner Date: Thu, 10 Nov 2022 11:47:08 -0500 Subject: [PATCH] allow v2 addons to use app tree fallback resolution 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. --- .../core/src/babel-plugin-adjust-imports.ts | 16 ++--- tests/scenarios/v2-addon-test.ts | 62 ++++++++++++++++++- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/packages/core/src/babel-plugin-adjust-imports.ts b/packages/core/src/babel-plugin-adjust-imports.ts index 99c8b87b5a..61a7454c82 100644 --- a/packages/core/src/babel-plugin-adjust-imports.ts +++ b/packages/core/src/babel-plugin-adjust-imports.ts @@ -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` ); @@ -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` ); @@ -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])); } diff --git a/tests/scenarios/v2-addon-test.ts b/tests/scenarios/v2-addon-test.ts index 956931ed86..c6699e0f6b 100644 --- a/tests/scenarios/v2-addon-test.ts +++ b/tests/scenarios/v2-addon-test.ts @@ -1,4 +1,4 @@ -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'; @@ -6,7 +6,7 @@ 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'] = @@ -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': '
', + }, + }, + }); + 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("
it works
") + export default class ExampleComponent extends Component {} + setComponentTemplate(TEMPLATE, ExampleComponent); + `, + }, + }); + intermediate.addDependency(inner); + merge(project.files, { app: { templates: { @@ -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('')); + assert.dom('.intermediate-hello .inner').containsText('it works'); + }); + }); + `, + }, unit: { 'import-test.js': ` import { module, test } from 'qunit';