Skip to content

fix(observable): add explicit extensions to relative ESM imports - #5307

Merged
odinr merged 1 commit into
nextfrom
fix/observable-esm-import-extensions
Aug 16, 2026
Merged

fix(observable): add explicit extensions to relative ESM imports#5307
odinr merged 1 commit into
nextfrom
fix/observable-esm-import-extensions

Conversation

@odinr

@odinr odinr commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Why is this change needed?
@equinor/fusion-observable's relative import/export specifiers (e.g. ./operators, ../types) had no file extensions. The package's tsconfig inherits moduleResolution: "bundler" from the repo root, so tsc never rewrote these to .js//index.js in the compiled ESM output. That's fine for bundler-based consumers (Vite/webpack), but breaks strict Node.js ESM resolution for any consumer that resolves the published dist/esm output directly (e.g. Vitest running without a bundler), producing module-not-found errors. A downstream consumer had already worked around this with a yarn patch.

What is the current behavior?
Compiled dist/esm/*.js files import sibling modules without extensions (from './operators'), which fails to resolve under strict Node ESM.

What is the new behavior?
Every relative import/export in src/**/*.ts now has an explicit .js (file) or /index.js (directory) extension, so the compiled output resolves correctly under both bundler and strict Node ESM resolution. The package's own tsconfig.json now overrides module/moduleResolution to nodenext so tsc enforces explicit extensions going forward instead of silently allowing them to be omitted again.

What is the intended behavior or invariant?
@equinor/fusion-observable's published ESM output must be resolvable by both bundler-based and non-bundler (native Node ESM) consumers. The package-local nodenext override only affects how this package type-checks its own imports; it does not change how other packages (still on moduleResolution: "bundler") consume this package's emitted .d.ts/.js output.

Does this PR introduce a breaking change?
No. Output paths and public API are unchanged; only the internal module specifiers gained explicit extensions.

Impact assessment:

  • Breaking changes: No
  • Version bump: Patch (changeset included)
  • Consumer impact: Fixes resolution for consumers that load the package's ESM output directly (without a bundler), e.g. Vitest/Node-based tooling.
  • Downstream impact: Verified other in-repo consumers (e.g. packages/react/app, packages/modules/*, packages/dev-portal) still build cleanly via tsc -b since they still use moduleResolution: "bundler" and consume the emitted declaration/JS files, not the source.

Review guidance:
Diff is mechanical (adding extensions) across ~30 files in packages/utils/observable/src; the interesting parts are tsconfig.json (the nodenext override) and FlowSubject.ts as a representative example. Verified: tsc -b --force, vitest run (42/42 tests), biome check, and fusion-lint all pass; a downstream project-reference build (packages/react/app) also builds clean.

Additional context
The two dead test files under src/__tests__/ (Query.test.ts, ReactiveObservable.test.ts) reference modules that no longer exist and are excluded from both this package's tsconfig.json and its vitest.config.ts (test.include: ['tests/**']); they were intentionally left untouched as out of scope.

Related issues
None.

Checklist

  • Confirm completion of the self-review checklist
  • Confirm TSDoc captures intent for functions, hooks, components, classes, and named arrow functions (no TSDoc content changed)
  • Confirm iterator blocks, decision gates, RxJS chains, and complex decisions explain why they exist (unchanged)
  • Confirm React logic and derived values are resolved before markup when applicable (n/a)
  • Confirm README/docs are updated for user-facing changes (n/a, internal fix)
  • Confirm changes to target branch validation
    • Included files validated
    • No new linting warnings
    • Not a duplicate PR
  • Confirm adherence to code of conduct

Relative import/export specifiers in @equinor/fusion-observable's source
lacked file extensions (e.g. './operators' instead of './operators/index.js').
Under the package's inherited moduleResolution: bundler tsconfig, tsc never
adds them, so the compiled ESM output only resolves for bundler-based
consumers. Consumers resolving the published dist directly under strict
Node.js ESM (e.g. vitest without a bundler) fail with module-not-found errors.

Add explicit .js/.js/index.js extensions to every relative import/export in
src, and override the package's own tsconfig to module/moduleResolution:
nodenext so tsc enforces this going forward instead of silently regressing.
@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bd1fbb7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@equinor/fusion-observable Patch
@equinor/fusion-framework-dev-portal Patch
@equinor/fusion-framework-module-analytics Patch
@equinor/fusion-framework-module-telemetry Patch
@equinor/fusion-framework-module-widget Patch
@equinor/fusion-framework-react-app Patch
portal-analytics Patch
@equinor/fusion-framework-cli Patch
@equinor/fusion-framework-app Patch
@equinor/fusion-framework Patch
@equinor/fusion-framework-vite-plugin-spa Patch
@equinor/fusion-framework-vitest-plugin-react-app Patch
poc-portal Patch
portal Patch
@equinor/fusion-framework-dev-server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added 🐞 bug Something isn't working 🛠️ utils packages related to utils labels Aug 16, 2026
@odinr
odinr requested a balanced review from Copilot August 16, 2026 20:09

Copilot AI 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.

Pull request overview

Fixes strict Node.js ESM resolution for @equinor/fusion-observable.

Changes:

  • Adds explicit .js or /index.js extensions to relative module specifiers.
  • Enables NodeNext resolution to enforce valid ESM imports.
  • Adds a patch changeset.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated no comments.

Show a summary per file
File Description
.changeset/observable_fix-esm-import-extensions.md Documents the ESM resolution fix.
packages/utils/observable/tsconfig.json Enables NodeNext module resolution.
packages/utils/observable/src/types/reducers.ts Adds an explicit import extension.
packages/utils/observable/src/types/index.ts Adds explicit export extensions.
packages/utils/observable/src/to-observable.ts Adds an explicit import extension.
packages/utils/observable/src/react/useObservableState.ts Uses the explicit types entry point.
packages/utils/observable/src/react/useObservableSelectorState.ts Adds explicit relative extensions.
packages/utils/observable/src/react/useObservableSelector.ts Adds explicit relative extensions.
packages/utils/observable/src/react/useObservableRef.ts Adds explicit relative extensions.
packages/utils/observable/src/react/useObservableInputState.ts Adds explicit hook import extensions.
packages/utils/observable/src/react/useObservableFlow.ts Adds explicit type import extensions.
packages/utils/observable/src/react/useObservableEffect.ts Adds explicit type import extensions.
packages/utils/observable/src/react/useObservable.ts Adds explicit relative extensions.
packages/utils/observable/src/react/useDebounce.ts Uses the explicit types entry point.
packages/utils/observable/src/react/use-observable-epic.ts Adds an explicit hook import extension.
packages/utils/observable/src/react/index.ts Adds explicit React barrel extensions.
packages/utils/observable/src/operators/switch-map-action.ts Adds explicit operator import extensions.
packages/utils/observable/src/operators/map-prop.ts Uses the explicit types entry point.
packages/utils/observable/src/operators/map-action.ts Adds explicit operator import extensions.
packages/utils/observable/src/operators/index.ts Adds explicit operator barrel extensions.
packages/utils/observable/src/operators/filter-action.ts Uses the explicit types entry point.
packages/utils/observable/src/index.ts Adds explicit root barrel extensions.
packages/utils/observable/src/FlowSubject.ts Adds explicit internal module extensions.
packages/utils/observable/src/create-state.ts Adds explicit internal module extensions.
packages/utils/observable/src/create-reducer.ts Adds explicit type import extensions.
packages/utils/observable/src/actions/utils.ts Adds an explicit action import extension.
packages/utils/observable/src/actions/predicates.ts Adds explicit action import extensions.
packages/utils/observable/src/actions/index.ts Adds explicit action barrel extensions.
packages/utils/observable/src/actions/create-async-action.ts Adds explicit action import extensions.
packages/utils/observable/src/actions/create-action.ts Adds explicit import and export extensions.
packages/utils/observable/src/actions/ActionError.ts Adds an explicit type import extension.
packages/utils/observable/src/actions/action-mapper.ts Adds an explicit type import extension.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@odinr
odinr marked this pull request as ready for review August 16, 2026 20:15
@odinr
odinr requested a review from a team as a code owner August 16, 2026 20:15
@odinr
odinr merged commit 6639e6e into next Aug 16, 2026
9 checks passed
@odinr
odinr deleted the fix/observable-esm-import-extensions branch August 16, 2026 20:15
odinr added a commit that referenced this pull request Aug 19, 2026
Relative import/export specifiers in @equinor/fusion-observable's source
lacked file extensions (e.g. './operators' instead of './operators/index.js').
Under the package's inherited moduleResolution: bundler tsconfig, tsc never
adds them, so the compiled ESM output only resolves for bundler-based
consumers. Consumers resolving the published dist directly under strict
Node.js ESM (e.g. vitest without a bundler) fail with module-not-found errors.

Add explicit .js/.js/index.js extensions to every relative import/export in
src, and override the package's own tsconfig to module/moduleResolution:
nodenext so tsc enforces this going forward instead of silently regressing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 bug Something isn't working 🛠️ utils packages related to utils

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants