diff --git a/packages/core/src/module-resolver.ts b/packages/core/src/module-resolver.ts index b0ec4aa08..8ccab84c9 100644 --- a/packages/core/src/module-resolver.ts +++ b/packages/core/src/module-resolver.ts @@ -11,6 +11,7 @@ import { explicitRelative, RewrittenPackageCache } from '@embroider/shared-inter import makeDebug from 'debug'; import assertNever from 'assert-never'; import reversePackageExports from '@embroider/reverse-exports'; +import { exports as resolveExports } from 'resolve.exports'; import { virtualExternalESModule, @@ -946,13 +947,10 @@ export class Resolver { .withMeta({ originalFromFile: request.fromFile }) .rehome(resolve(originalRequestingPkg.root, 'package.json')) ); - } - // TODO is this the right check for this? - else if (packageName !== requestingPkg.name) { + } else { // requesting package was moved and we failed to find its target. We // can't let that accidentally succeed in the defaultResolve because we // could escape the moved package system. - // and it's not a self reference return logTransition('missing outbound request from moved package', request, request.notFound()); } } @@ -1018,6 +1016,26 @@ export class Resolver { request, request.alias(selfImportPath).rehome(resolve(pkg.root, 'package.json')) ); + } else { + // v2 packages are supposed to use package.json `exports` to enable + // self-imports, but not all build tools actually follow the spec. This + // is a workaround for badly behaved packagers. + // + // Known upstream bugs this works around: + // - https://github.com/vitejs/vite/issues/9731 + if (pkg.packageJSON.exports) { + let found = resolveExports(pkg.packageJSON, request.specifier, { + browser: true, + conditions: ['default', 'imports'], + }); + if (found?.[0]) { + return logTransition( + `v2 self-import with package.json exports`, + request, + request.alias(found?.[0]).rehome(resolve(pkg.root, 'package.json')) + ); + } + } } } diff --git a/packages/core/src/node-resolve.ts b/packages/core/src/node-resolve.ts index 3def3c9e3..8d6590cf0 100644 --- a/packages/core/src/node-resolve.ts +++ b/packages/core/src/node-resolve.ts @@ -1,12 +1,10 @@ import { virtualContent } from './virtual-content'; -import { dirname, join } from 'path'; -import { packageName as getPackageName } from '@embroider/shared-internals'; +import { dirname, resolve, isAbsolute } from 'path'; +import { explicitRelative } from '@embroider/shared-internals'; import assertNever from 'assert-never'; -import { exports as resolveExports } from 'resolve.exports'; // these would be circular, but they're type-only so it's fine import type { ModuleRequest, Resolution, Resolver } from './module-resolver'; -import { existsSync } from 'fs-extra'; export class NodeModuleRequest implements ModuleRequest { constructor( @@ -104,24 +102,12 @@ export class NodeModuleRequest implements ModuleRequest { // We can do the path adjustments before doing require.resolve. let { specifier } = request; let fromDir = dirname(request.fromFile); - - let packageName = getPackageName(request.specifier); - - let pkg = this.resolver.packageCache.ownerOfFile(request.fromFile); - - // check for self reference - if (packageName && pkg?.name === packageName && pkg.packageJSON.exports) { - let found = resolveExports(pkg.packageJSON, request.specifier, { - browser: true, - conditions: ['default', 'imports'], - }); - if (found?.[0]) { - let filename = join(pkg.root, found?.[0]); - - // only resolve to this file if it exists, otherwise fallback to other behaviour - if (existsSync(filename)) { - return { type: 'found', filename, result: { type: 'real' as 'real', filename }, isVirtual: false }; - } + if (!isAbsolute(specifier) && specifier.startsWith('.')) { + let targetPath = resolve(fromDir, specifier); + let newFromDir = dirname(targetPath); + if (fromDir !== newFromDir) { + specifier = explicitRelative(newFromDir, targetPath); + fromDir = newFromDir; } }