Skip to content

feat(check-parser): resolve and bundle Node.js subpath #imports [RED-688] [show]#1388

Merged
sorccu merged 3 commits into
mainfrom
simo/red-688-node-subpath-imports
Jul 7, 2026
Merged

feat(check-parser): resolve and bundle Node.js subpath #imports [RED-688] [show]#1388
sorccu merged 3 commits into
mainfrom
simo/red-688-node-subpath-imports

Conversation

@sorccu

@sorccu sorccu commented Jul 7, 2026

Copy link
Copy Markdown
Member

Linear: RED-688

What

Resolve and bundle Node.js subpath imports (the package.json imports field, referenced via #-prefixed specifiers like import x from '#utils/foo').

The parser already read the imports field into its schema, but the dependency resolver had an explicit // TODO that silently skipped every #-specifier. The mapped files were therefore never discovered or bundled, so subpath imports failed at runtime unless the user forced the files into the bundle with a manual include. This makes Playwright Check Suites resolve them automatically.

How

  • PackageJsonFile gains hasImports() / resolveImportPath(), reusing the existing conditional-target + wildcard machinery used for exports (the two fields share the same target shape, so the private resolver is generalized to #resolveConditionalTargets). Unlike exports, no subpath normalization is applied — imports keys and the specifier are both #-prefixed and matched verbatim.
  • The resolver resolves a #-specifier against the nearest package.json's imports map (the importing file's package scope). Relative targets are bundled as local files (a missing target is reported); bare targets are treated as external packages or workspace neighbors via a shared closure extracted from the existing loop-tail logic. Every #-specifier path terminates in continue resolve, so an unmatched #foo is never misrecorded as an external npm dependency.

Tests

  • Unit tests for resolveImportPath (relative, conditional import/require, wildcard, external/bare, unmatched).
  • End-to-end fixture + tests (unrestricted and restricted) asserting the mapped files are bundled and that raw #-specifiers never leak into the dependency set.

Known limitation

In restricted mode (Api/Browser/MultiStep checks) the nearest package.json is deliberately not bundled, so the imports map is unavailable at runtime. Playwright Check Suites — the target use case — are unrestricted, so this fully addresses the ticket; the restricted-mode case is left as a documented limitation.

Other changes

Adds end-to-end coverage for the exports field (previously only unit-tested via resolveExportPath): a fixture whose package exposes its entry point solely through a conditional exports map is now resolved through parser.parse(), asserting the matching node condition wins over a non-matching browser condition and the default fallback.

sorccu and others added 2 commits July 7, 2026 14:06
Add hasImports()/resolveImportPath() to PackageJsonFile, resolving Node.js
subpath import specifiers (#-prefixed) against the package's `imports` map.

The `imports` and `exports` fields share the same conditional-target shape, so
the private exports resolver is generalized to #resolveConditionalTargets and
reused. Unlike exports, no subpath normalization is applied: `imports` keys and
the specifier are both #-prefixed and matched verbatim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3ajE9Vq8Mw8C5YNAzQwn8
Replace the resolver's TODO that silently skipped `#`-prefixed import
specifiers. `#imports` now resolve against the nearest package.json's `imports`
map (the importing file's package scope): relative targets are bundled as local
files, and bare targets are treated as external packages or workspace neighbors.

Previously the mapped files were never discovered, so subpath imports failed at
runtime unless the user forced the files into the bundle with a manual
`include`. This makes Playwright Check Suites resolve them automatically.

The workspace-neighbor/external-fallback logic is extracted into a shared
closure so both ordinary specifiers and bare `#import` targets reuse it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3ajE9Vq8Mw8C5YNAzQwn8
@sorccu sorccu changed the title [RED-688] Implement #imports support for PWCS feat(check-parser): resolve and bundle Node.js subpath #imports [RED-688] Jul 7, 2026
Exports resolution was covered by resolveExportPath unit tests but never
exercised through parser.parse(). Add an e2e fixture whose package exposes its
entry point only via a conditional `exports` map (no `main`), so the target is
discovered solely through exports resolution, and assert the matching `node`
condition is selected over the non-matching `browser` and the `default`
fallback. Covered in both unrestricted and restricted modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3ajE9Vq8Mw8C5YNAzQwn8
@sorccu sorccu changed the title feat(check-parser): resolve and bundle Node.js subpath #imports [RED-688] feat(check-parser): resolve and bundle Node.js subpath #imports [RED-688] [show] Jul 7, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: ship/show PR from a same-repo branch.

@sorccu sorccu merged commit 1b63bbe into main Jul 7, 2026
16 checks passed
@sorccu sorccu deleted the simo/red-688-node-subpath-imports branch July 7, 2026 07:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant