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

Make addon-shim a non-ember-addon #1069

Merged
merged 2 commits into from Jan 8, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 0 additions & 12 deletions packages/addon-shim/addon-main.js

This file was deleted.

8 changes: 1 addition & 7 deletions packages/addon-shim/package.json
Expand Up @@ -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",
Expand All @@ -22,7 +20,6 @@
},
"dependencies": {
"@embroider/shared-internals": "^0.49.0",
"ember-auto-import": "^2.2.0",
"semver": "^7.3.5"
},
"devDependencies": {
Expand All @@ -33,9 +30,6 @@
"engines": {
"node": "12.* || 14.* || >= 16"
},
"ember-addon": {
"main": "addon-main.js"
},
"volta": {
"extends": "../../package.json"
}
Expand Down
91 changes: 70 additions & 21 deletions packages/addon-shim/src/index.ts
Expand Up @@ -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);
},

Expand Down Expand Up @@ -153,10 +157,55 @@ 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);
},
};
}

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,
};
}
}
45 changes: 26 additions & 19 deletions packages/core/src/babel-plugin-adjust-imports.ts
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/shared-internals/src/ember-standard-modules.ts
Expand Up @@ -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<string>(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
Expand Down