feat: resolve package import wildcard trailers - #604
Conversation
Replace the `@vercel/nft` pnpm patch with a fallback resolver, so the fix does not have to be re-applied on every nft release (an exact-version `patchedDependencies` key hard-fails installs with ERR_PNPM_UNUSED_PATCH as soon as the version moves). nft exports its own resolver, so `nft.resolve` can wrap it and only fall back to exsolve — which implements the Node resolution algorithm — for specifiers nft throws on. exsolve had the same defect in its `imports` matching and fixes it in 1.1.1 (unjs/exsolve#56). This is purely additive: nft stays the primary resolver, so the fallback only ever turns a "Failed to resolve dependency" warning into a resolved file, and becomes a no-op once vercel/nft#604 lands. It also covers a case the patch did not: wildcard imports whose target is another package (`"#utils/*": "@fixture/nitro-utils/*"`). nft's wildcard branch only handles targets starting with `./`, so those stayed unresolved even with the patch applied. The fixture and test now assert both that and the original `"#*.js": "./runtime/*.js"` trailer case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pull in test from vercel/nft#604 It was already passing
|
I reviewed this specifically for conformance with Node's resolver implementation ( What checks out
Node picks a single End-to-end against real
Three of the fixes aren't claimed in the description:
The repo's own suite agrees on no regressions: One thing that should change
'./dist/*.js'.replace(/\*/g, 'a$&b') // → './dist/a*b.js' ✗ ($& = the matched '*')Node avoids exactly this by using a function replacer — Verified against real Node: with Two-character fix in both places: const resolvedPath =
- pkgPath + target.slice(1).replace(/\*/g, wildcardReplacement);
+ pkgPath + target.slice(1).replace(/\*/g, () => wildcardReplacement); const targetPath = wildcardReplacement
- ? target.slice(1).replace(/\*/g, wildcardReplacement)
+ ? target.slice(1).replace(/\*/g, () => wildcardReplacement)
: target.slice(1);This is pre-existing, but this PR rewrites the line, and Two gaps — your call whether they belong in this PR1. Wildcard { "imports": { "#dep/*": "some-pkg/*" } }Node resolves it: That's the same failure mode the description is about — trace omits a real target, deployment fails at runtime with } else if (isImports && typeof target === 'string') {
// The imports field additionally allows external dependencies as well
const resolved = await resolveDependency(
target.replace(/\*/g, () => wildcardReplacement),
parent,
job,
cjsResolve,
);
return Array.isArray(resolved) ? resolved : [resolved];
}2. I applied the Nit
Also, minor: the new fixture only covers the trailing- |
styfle
left a comment
There was a problem hiding this comment.
Approved since there is no regression here, just additive support for import wildcards.
I changed from fix to feat since changing the import resolution behavior might break someone and is a bit more like a new feature.
Also, the comment above mentions the remaining bugs for this feature so its not fully implemented yet. We can address in a future PR.
|
🎉 This PR is included in version 1.11.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
package.jsonimport/export wildcard patterns that include a trailer, such as#*.jsimports-wildcardtrace fixture covering#internal/marker.jsmapped through#*.jsContext
NFT previously recognized wildcard keys only when they ended in
*. A valid import map such as:{ "imports": { "#*.js": "./dist/*.js" } }therefore failed to resolve
#internal/marker.js. Consumers such as Nitro/nf3 would trace the package entry point but omit the internal target, producing an incomplete deployment artifact that failed at runtime withERR_MODULE_NOT_FOUND.Related reproduction and downstream workaround:
Regression testing
The new
test/unit/imports-wildcardfixture imports#internal/marker.jsand expectsdist/internal/marker.jsin the trace.Before this change, both the cwd-based and root-based fixture variants failed because the marker file was absent. After the resolver change, both variants pass.
Validation performed:
pnpm buildpnpm prettier-checkpnpm exec jest test/unit.test.js --runInBand -t 'imports|exports-wildcard'pnpm exec jest --runInBand --silent— 1,475 tests passed