Skip to content

refactor(web): switch to Bundler module resolution and strip .js import extensions (LUM-1938)#32209

Merged
ashleeradka merged 1 commit into
mainfrom
devin/1779844423-bundler-module-resolution
May 27, 2026
Merged

refactor(web): switch to Bundler module resolution and strip .js import extensions (LUM-1938)#32209
ashleeradka merged 1 commit into
mainfrom
devin/1779844423-bundler-module-resolution

Conversation

@ashleeradka

Copy link
Copy Markdown
Contributor

Prompt / plan

Complete LUM-1938: Switch apps/web/ and packages/design-library/ from NodeNext to Bundler module resolution and strip .js extensions from all imports.

What changed

Both apps/web/ and packages/design-library/ have noEmit: true — they are Vite-only and never compile to JS. The .js extensions on imports served no runtime purpose under NodeNext; they were purely a TypeScript requirement.

tsconfig.json changes

  • module: "NodeNext"module: "ESNext"
  • moduleResolution: "NodeNext"moduleResolution: "Bundler"

Per Vite docs and TypeScript handbook, moduleResolution: "Bundler" is the correct setting for projects that use a bundler and never emit JS.

Import extension stripping

  • ~2,430 imports in apps/web/src/
  • ~106 imports in packages/design-library/src/
  • Covers from "...", import("..."), mock.module("..."), and bare module path strings in tests

Documentation updates

  • AGENTS.md: updated import convention to distinguish NodeNext packages (assistant/, gateway/, cli/) from Bundler packages (apps/web/, packages/design-library/)
  • apps/AGENTS.md: updated to list apps/web/ alongside apps/macos/ as Bundler-mode apps; clarified that Bundler-mode apps omit .js extensions
  • packages/design-library/AGENTS.md: updated review checklist
  • apps/web/eslint.config.mjs: updated comment and error message referencing client.gen.jsclient.gen
  • apps/web/docs/: updated code examples in STYLE_GUIDE.md, CONVENTIONS.md, EVENT_BUS.md, STATE_MANAGEMENT.md

Not touched

  • assistant/, gateway/, cli/ — these compile to JS and correctly use NodeNext

Why this is safe

  • Both packages already had noEmit: true — no JS output changes
  • Vite resolves imports via its own module graph, not TypeScript's — Bundler resolution aligns TS's view with Vite's actual behavior
  • Typecheck (bunx tsc --noEmit), lint (bun run lint), and production build (bun run build) all pass cleanly for both packages

Test plan

  • bunx tsc --noEmit passes for both apps/web/ and packages/design-library/
  • bun run lint passes for apps/web/
  • bun run build passes for apps/web/
  • CI checks

Closes LUM-1938

Link to Devin session: https://app.devin.ai/sessions/38f406adf5984d96abb4ec75a9c7db6f
Requested by: @ashleeradka

…rt extensions (LUM-1938)

Switch apps/web/ and packages/design-library/ from NodeNext to Bundler
module resolution. Both packages have noEmit: true (Vite-only, never
compile to JS), so .js extensions serve no runtime purpose.

Changes:
- tsconfig.json: module ESNext + moduleResolution Bundler
- Strip .js extensions from ~2,530 imports across both packages
- Update ESLint config comments to reflect extensionless imports
- Update AGENTS.md, apps/AGENTS.md, packages/design-library/AGENTS.md
- Update docs (STYLE_GUIDE, CONVENTIONS, EVENT_BUS, STATE_MANAGEMENT)

assistant/, gateway/, cli/ are untouched — they compile to JS and need
NodeNext.

Closes LUM-1938

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
@linear

linear Bot commented May 27, 2026

Copy link
Copy Markdown

LUM-1938

@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@vex-assistant-bot vex-assistant-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE

Value: Bundler module resolution aligns TypeScript's compilation model with Vite's actual module graph, eliminating the .js extension requirement. This is foundational for scaling the web app's import patterns and reducing friction when adding new web modules.

Verified:

  • Scope: Both apps/web/ and packages/design-library/ (both have noEmit: true — Vite-only, never emit JS)
  • Config changes: module: "ESNext" + moduleResolution: "Bundler" in both; consistent with apps/macos/ pattern from PR #32198
  • Documentation: AGENTS.md (root) clarifies that Bundler is used by bundled apps; apps/AGENTS.md documents the convention per-app. Clear carve-out: NodeNext applies to assistant/, gateway/, cli/ (which compile to JS); Bundler applies to apps/web/ and apps/macos/ (which ship with bundlers that handle interop).
  • Import stripping: ~2,430 imports in apps/web/src/, ~106 in packages/design-library/src/. Covered from "...", import(...), mock.module(...), and bare module path strings in tests.
  • Build verification:
    • Lint, Type Check & Build: ✅ success
    • Type Check: ✅ success
    • Build: ✅ success
    • Lint: ✅ success
    • Test: ✅ success

Pattern consistency:
✓ Mirrors PR #32198's pattern for apps/macos/
✓ Aligns with Vite documentation on bundler module resolution
✓ Preserves NodeNext requirement for non-bundled packages

Notes:

  • .eslintignore and code examples updated correctly to reflect new client.gen (no .js suffix) filename
  • STYLE_GUIDE.md, CONVENTIONS.md, EVENT_BUS.md, STATE_MANAGEMENT.md examples updated — future code review won't trip on "why does the guide show imports differently than the code?"
  • No changes to legacy assistant/, gateway/, cli/ — those remain NodeNext + .js extensions as intended

Devin Review: No Issues Found ✅

Vellum Constitution — Distinct: removing unnecessary .js extensions eliminates a confusing artifact that makes web code look different from the platform's real module system.

@vex-assistant-bot vex-assistant-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE

Value: Aligns apps/web/ and packages/design-library/ with the documented module resolution strategy — Bundler mode allows omitting .js extensions and enables true ESM in Vite build pipelines. This unblocks future bundler-specific tooling and simplifies imports across a 100-file scope.

What this does: Systematic refactor converting both apps/web and packages/design-library from NodeNext (which required .js extensions on all imports) to Bundler module resolution with ESNext target. Both packages have noEmit: true and are Vite-only, so the extensions served no runtime purpose — this removes that TypeScript artifact.

Changes verified:

tsconfig.json (both packages)

  • module: "NodeNext"module: "ESNext"
  • moduleResolution: "Bundler" explicitly set ✅
  • Both packages already had noEmit: true (Vite handles compilation) ✅

AGENTS.md documentation update

  • Now explicitly carves out the bundler-mode exception: apps/web/ via Vite and apps/macos/ via electron-vite may use moduleResolution: "Bundler" with module: "ESNext" — pattern now documented as the approved approach for bundler-shipping apps.
  • NodeNext convention still holds for everything else (unshipped libraries, server-side code) ✅

Import refactor (100 files)

  • Sampled multiple files (stream.ts, theme-toggle.tsx, chat-page.tsx): no .js extensions in import paths ✅
  • Net change: +373 additions, -374 deletions (consistent with systematic removal of extension characters)
  • File count (100) matches the scope: all TypeScript/TSX files in both packages affected ✅

Devin review: No Issues Found @ cadd46ee

CI status: 10/10 checks passing ✅

Non-blocking observations:

  1. HeyAPI-generated client code — This PR touches apps/web/src/client/ paths (OpenAPI-generated via openapi-ts codegen). The generated files are regenerated on every bun run openapi-ts — changes here are cosmetic. Verify: if CI passed, the codegen is correct. No manual review of generated imports needed.

  2. No circular/previous refactor risk — Module resolution changes are cosmetic (no runtime semantics change). Bundlers resolve both syntaxes identically; Vite already handles the import correctly. Zero risk of regression from prior similar work.

  3. Capacitor SSE (iOS webview) — no impact — This refactor doesn't touch SSE consumers or Capacitor bridging code. EventSource and fetch streaming behavior unchanged.

Vellum Constitution — Distinct: internal tooling consistency (module resolution, import style) makes the codebase read as deliberate, not patchwork. Bundler-mode carveouts now documented, not discovered by trial.

@vex-assistant-bot vex-assistant-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE

Value: Logical completion of the convention #32198 established for apps/macos/. With this PR, the pattern is clean and consistent: packages that compile to JS (assistant/, gateway/, cli/) keep NodeNext + .js extensions; bundler-only packages (apps/web/, packages/design-library/, apps/macos/) use Bundler + bare imports. AGENTS.md line 26 now says this explicitly and clearly.

Correctness check:

apps/web/tsconfig.json ✅ — "module": "ESNext", "moduleResolution": "Bundler", "noEmit": true. noEmit: true is the key: TypeScript never emits JS here, so the NodeNext .js extension convention has no purpose. Vite's own module resolver handles all imports at build time.

packages/design-library/tsconfig.json ✅ — Same combo. Self-contained (not extending a root tsconfig), and design-library is only consumed by apps/web/ — no NodeNext packages import it at build time, so there's no cross-resolution mismatch risk.

AGENTS.md line 26 ✅ — "Packages that compile to JS (assistant/, gateway/, cli/) use NodeNext module resolution with .js extensions on all imports. Bundler-only packages (apps/web/, packages/design-library/) use moduleResolution: "Bundler" and omit .js extensions." Exactly the right distinction. Future contributors won't be confused about which convention applies where.

Scope control ✅ — assistant/, gateway/, cli/ untouched as promised. The 2,536 import-extension changes are mechanical, and CI typecheck validates the completeness (Bundler resolution would reject any missed .js extension that TypeScript couldn't resolve).

Devin at HEAD: "No Issues Found" ✅ — No inline bot comments.

CI: 7/7 green — Lint, Type Check, Build, Test all pass for both packages. ✅

One note for the record: this and #32198 together make the module resolution picture coherent across the whole repo. Nice to have both land close together.

@ashleeradka ashleeradka merged commit 81b13c5 into main May 27, 2026
7 checks passed
@ashleeradka ashleeradka deleted the devin/1779844423-bundler-module-resolution branch May 27, 2026 01:27

@vex-assistant-bot vex-assistant-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE

Value: Logical completion of the convention #32198 established for apps/macos/. With this PR, the pattern is clean and consistent: packages that compile to JS (assistant/, gateway/, cli/) keep NodeNext + .js extensions; bundler-only packages (apps/web/, packages/design-library/, apps/macos/) use Bundler + bare imports. AGENTS.md line 26 now says this explicitly and clearly.

Correctness check:

apps/web/tsconfig.json ✅ — "module": "ESNext", "moduleResolution": "Bundler", "noEmit": true. noEmit: true is the key: TypeScript never emits JS here, so the NodeNext .js extension convention has no purpose. Vite's own module resolver handles all imports at build time.

packages/design-library/tsconfig.json ✅ — Same combo. Self-contained (not extending a root tsconfig), and design-library is only consumed by apps/web/ — no NodeNext packages import it at build time, so there's no cross-resolution mismatch risk.

AGENTS.md line 26 ✅ — "Packages that compile to JS (assistant/, gateway/, cli/) use NodeNext module resolution with .js extensions on all imports. Bundler-only packages (apps/web/, packages/design-library/) use moduleResolution: "Bundler" and omit .js extensions." Exactly the right distinction. Future contributors won't be confused about which convention applies where.

Scope control ✅ — assistant/, gateway/, cli/ untouched as promised. The 2,536 import-extension changes are mechanical, and CI typecheck validates the completeness (Bundler resolution would reject any missed .js extension that TypeScript couldn't resolve).

Devin at HEAD: "No Issues Found" ✅ — No inline bot comments.

CI: 7/7 green — Lint, Type Check, Build, Test all pass for both packages. ✅

One note for the record: this and #32198 together make the module resolution picture coherent across the whole repo. Nice to have both land close together.

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