diff --git a/packages/addon-shim/addon-main.js b/packages/addon-shim/addon-main.js deleted file mode 100644 index 6a1a29de3..000000000 --- a/packages/addon-shim/addon-main.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -module.exports = { - name: require('./package').name, - - included(...args) { - this._super.included.apply(this, args); - this.addons - .find((a) => a.name === 'ember-auto-import') - .registerV2Addon(this.parent.name, this.parent.pkg.root); - }, -}; diff --git a/packages/addon-shim/package.json b/packages/addon-shim/package.json index 3d7b0da1c..6bead5e23 100644 --- a/packages/addon-shim/package.json +++ b/packages/addon-shim/package.json @@ -2,9 +2,7 @@ "name": "@embroider/addon-shim", "version": "0.49.0", "description": "Make v2 addons work in non-Embroider apps.", - "keywords": [ - "ember-addon" - ], + "keywords": [], "main": "./src/index.js", "repository": { "type": "git", @@ -22,7 +20,6 @@ }, "dependencies": { "@embroider/shared-internals": "^0.49.0", - "ember-auto-import": "^2.2.0", "semver": "^7.3.5" }, "devDependencies": { @@ -33,9 +30,6 @@ "engines": { "node": "12.* || 14.* || >= 16" }, - "ember-addon": { - "main": "addon-main.js" - }, "volta": { "extends": "../../package.json" } diff --git a/packages/addon-shim/src/index.ts b/packages/addon-shim/src/index.ts index db6e336e9..f55440de8 100644 --- a/packages/addon-shim/src/index.ts +++ b/packages/addon-shim/src/index.ts @@ -40,45 +40,49 @@ export function addonV1Shim(directory: string, options: ShimOptions = {}) { return tree; } + let autoImportInstance: EAI2Instance | undefined; + return { name: pkg.name, included(this: AddonInstance, ...args: unknown[]) { - if ((this.parent.pkg['ember-addon']?.version ?? 1) < 2) { - let autoImportVersion = this.parent.addons.find( - (a) => a.name === 'ember-auto-import' - )?.pkg.version; + let parentOptions; + let parentName: string; + if (isDeepAddonInstance(this)) { + parentOptions = this.parent.options; + parentName = this.parent.name; + } else { + parentOptions = this.app.options; + parentName = this.parent.name(); + } - if (!autoImportVersion) { + // if we're being used by a v1 package, that package needs ember-auto-import 2 + if ((this.parent.pkg['ember-addon']?.version ?? 1) < 2) { + let autoImport = locateAutoImport(this.parent.addons); + if (!autoImport.present) { throw new Error( - `${this.parent.name} needs to depend on ember-auto-import in order to use ${this.name}` + `${parentName} needs to depend on ember-auto-import in order to use ${this.name}` ); } - if ( - !satisfies(autoImportVersion, '>=2.0.0-alpha.0', { - includePrerelease: true, - }) - ) { + if (!autoImport.satisfiesV2) { throw new Error( - `${this.parent.name} has ember-auto-import ${autoImportVersion} which is not new enough to use ${this.name}. It needs to upgrade to >=2.0` + `${parentName} has ember-auto-import ${autoImport.version} which is not new enough to use ${this.name}. It needs to upgrade to >=2.0` ); } - } - - let parentOptions; - if (isDeepAddonInstance(this)) { - parentOptions = this.parent.options; + autoImportInstance = autoImport.instance; + autoImportInstance.registerV2Addon(this.name, directory); } else { - parentOptions = this.app.options; + // if we're being used by a v2 addon, it also has this shim and will + // forward our registration onward to ember-auto-import + (this.parent as EAI2Instance).registerV2Addon(this.name, directory); } if (options.disabled) { disabled = options.disabled(parentOptions); } - // this is here so that our possible exceptions above take precedence over - // the one that ember-auto-import will also throw if the app doesn't have - // ember-auto-import + // this is at the end so we can find our own autoImportInstance before any + // deeper v2 addons ask us to forward registrations upward to it this._super.included.apply(this, args); }, @@ -153,6 +157,10 @@ export function addonV1Shim(directory: string, options: ShimOptions = {}) { let appInstance = this._findHost(); return isInside(directory, appInstance.project.root); }, + + registerV2Addon(name: string, root: string): void { + autoImportInstance!.registerV2Addon(name, root); + }, }; } @@ -160,3 +168,44 @@ function isInside(parentDir: string, otherDir: string): boolean { let rel = relative(parentDir, otherDir); return Boolean(rel) && !rel.startsWith('..') && !isAbsolute(rel); } + +type EAI2Instance = AddonInstance & { + registerV2Addon(name: string, root: string): void; +}; + +function locateAutoImport(addons: AddonInstance[]): + | { present: false } + | { + present: true; + version: string; + satisfiesV2: false; + } + | { + present: true; + version: string; + satisfiesV2: true; + instance: EAI2Instance; + } { + let instance = addons.find((a) => a.name === 'ember-auto-import'); + if (!instance) { + return { present: false }; + } + let version = instance.pkg.version; + let satisfiesV2 = satisfies(version, '>=2.0.0-alpha.0', { + includePrerelease: true, + }); + if (satisfiesV2) { + return { + present: true, + version, + satisfiesV2, + instance: instance as EAI2Instance, + }; + } else { + return { + present: true, + version, + satisfiesV2, + }; + } +} diff --git a/packages/core/src/babel-plugin-adjust-imports.ts b/packages/core/src/babel-plugin-adjust-imports.ts index 1aedeb0ca..e2a72b684 100644 --- a/packages/core/src/babel-plugin-adjust-imports.ts +++ b/packages/core/src/babel-plugin-adjust-imports.ts @@ -231,6 +231,27 @@ function handleExternal(specifier: string, sourceFile: AdjustFile, opts: Options return makeExternal(specifier, sourceFile, opts); } + if (!pkg.meta['auto-upgraded'] && emberVirtualPeerDeps.has(packageName)) { + // Native v2 addons are allowed to use the emberVirtualPeerDeps like + // `@glimmer/component`. And like all v2 addons, it's important that they + // see those dependencies after those dependencies have been converted to + // v2. + // + // But unlike auto-upgraded addons, native v2 addons are not necessarily + // copied out of their original place in node_modules. And from that + // original place they might accidentally resolve the emberVirtualPeerDeps + // that are present there in v1 format. + // + // So before we even check isResolvable, we adjust these imports to point at + // the app's copies instead. + if (emberVirtualPeerDeps.has(packageName)) { + if (!opts.activeAddons[packageName]) { + throw new Error(`${pkg.name} is trying to import the app's ${packageName} package, but it seems to be missing`); + } + return explicitRelative(dirname(sourceFile.name), specifier.replace(packageName, opts.activeAddons[packageName])); + } + } + let relocatedPkg = sourceFile.relocatedIntoPackage(); if (relocatedPkg) { // this file has been moved into another package (presumably the app). @@ -278,33 +299,19 @@ function handleExternal(specifier: string, sourceFile: AdjustFile, opts: Options return explicitRelative(dirname(sourceFile.name), specifier.replace(packageName, opts.activeAddons[packageName])); } - // auto-upgraded packages can fall back to attmpeting to find dependencies at - // runtime. Native v2 packages can only get this behavior in the - // isExplicitlyExternal case above because they need to explicitly ask for - // externals. if (pkg.meta['auto-upgraded']) { + // auto-upgraded packages can fall back to attempting to find dependencies at + // runtime. Native v2 packages can only get this behavior in the + // isExplicitlyExternal case above because they need to explicitly ask for + // externals. return makeExternal(specifier, sourceFile, opts); - } - - if (pkg.isV2Ember()) { + } else { // native v2 packages don't automatically externalize *everything* the way // auto-upgraded packages do, but they still externalize known and approved // ember virtual packages (like @ember/component) if (emberVirtualPackages.has(packageName)) { return makeExternal(specifier, sourceFile, opts); } - - // native v2 packages don't automatically get to use every other addon as a - // peerDep, but they do get the known and approved ember virtual peer deps, - // like @glimmer/component - if (emberVirtualPeerDeps.has(packageName)) { - if (!opts.activeAddons[packageName]) { - throw new Error( - `${pkg.name} is trying to import from ${packageName}, which is supposed to be present in all ember apps but seems to be missing` - ); - } - return explicitRelative(dirname(sourceFile.name), specifier.replace(packageName, opts.activeAddons[packageName])); - } } // non-resolvable imports in dynamic positions become runtime errors, not diff --git a/packages/shared-internals/src/ember-standard-modules.ts b/packages/shared-internals/src/ember-standard-modules.ts index 5bddf564b..03c99abbe 100644 --- a/packages/shared-internals/src/ember-standard-modules.ts +++ b/packages/shared-internals/src/ember-standard-modules.ts @@ -17,7 +17,7 @@ import mappings from 'ember-rfc176-data/mappings.json'; // map them into real modules within ember-source. export const emberVirtualPackages = new Set(mappings.map((m: any) => m.module)); -// these are *real* packages that every ember addon is allow to resolve *as if +// these are *real* packages that every ember addon is allowed to resolve *as if // they were peerDepenedencies, because the host application promises to have // these packages. In principle, we could force every addon to declare these as // real peerDeps all the way down the dependency graph, but in practice that