fix(i18n): use .js extensions on dayjs subpath imports for valid Node ESM#3231
Conversation
β¦ ESM dayjs ships no "exports" map, so Node's native ESM resolver requires a file extension on subpath imports. Because vite externalizes dayjs, the built dist/es/*.mjs preserved the extensionless specifiers verbatim, producing invalid ESM: bundlers (Turbopack/Webpack/Vite) resolve them fine, but Node's native loader throws ERR_MODULE_NOT_FOUND -- e.g. Next.js 16 loading the SDK as a server external during "Collecting page data". Add .js to all dayjs plugin and locale subpath imports (8 plugins + 12 locales) so the emitted ESM is valid under Node's native loader. Also fixes a stale JSDoc example and the registerTranslation log message.
The existing validate-cjs smoke test loads the bundle with require(), whose CJS resolver tolerates extensionless subpath imports -- so it never caught the invalid ESM output that broke Node's native loader. Add an ESM counterpart that imports dist/es/index.mjs under the native loader and wire it into CI right after validate-cjs. Verified: the check passes on a healthy build and fails with ERR_MODULE_NOT_FOUND when a .js extension is stripped from a dayjs subpath import in the built output.
π WalkthroughWalkthroughDayjs import paths are updated to use explicit ChangesESM compatibility fixes
Estimated code review effort: 2 (Simple) | ~10 minutes π₯ Pre-merge checks | β 5β Passed checks (5 passed)
β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Size Change: +48 B (+0.01%) Total Size: 798 kB π¦ View Changed
βΉοΈ View Unchanged
|
Codecov Reportβ
All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3231 +/- ##
==========================================
+ Coverage 84.21% 84.25% +0.04%
==========================================
Files 485 485
Lines 14872 14873 +1
Branches 4708 4709 +1
==========================================
+ Hits 12524 12531 +7
+ Misses 2348 2342 -6 β View full report in Codecov by Harness. π New features to boost your workflow:
|
- root: vite 8.0.14 -> 8.1.3, vitest + @vitest/coverage-v8 4.1.7 -> 4.1.9, @vitest/eslint-plugin 1.6.18 -> 1.6.20 (pulls rolldown 1.0.2 -> 1.1.3) - examples/tutorial, examples/vite: vite 8.1.3 + @vitejs/plugin-react 6.0.3 - .yarnrc.yml: lower npmMinimalAgeGate to 1d and preapprove vite so the fresh release installs; drop enableHardenedMode Build output is unchanged apart from additional `/* @__PURE__ */` annotations and one internal import alias, both from the rolldown bump -- no behavioral or API-surface change (verified by diffing dist/es and dist/cjs across versions).
There was a problem hiding this comment.
π§Ή Nitpick comments (1)
.yarnrc.yml (1)
15-17: π Security & Privacy | π΅ Trivial | π€ Low valueConsider a one-time bypass instead of permanently preapproving
vite.Adding
vitetonpmPreapprovedPackagespermanently exempts all future vite releases from the 1-day age-gate quarantine, not just the version needed for this bump. Yarn supports a--no-time-gateflag onyarn add/yarn upfor one-off bypasses, which would avoid permanently weakening the quarantine window for a package that will be installed frequently going forward.π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.yarnrc.yml around lines 15 - 17, The change in npmPreapprovedPackages permanently whitelists vite and bypasses the age gate for all future releases, so revert that persistent exemption and use a one-time --no-time-gate bypass when running yarn add or yarn up for the specific vite bump. Keep the existing stream-chat entry if still needed, and avoid leaving vite in .yarnrc.yml so the quarantine window remains intact going forward.
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.yarnrc.yml:
- Around line 15-17: The change in npmPreapprovedPackages permanently whitelists
vite and bypasses the age gate for all future releases, so revert that
persistent exemption and use a one-time --no-time-gate bypass when running yarn
add or yarn up for the specific vite bump. Keep the existing stream-chat entry
if still needed, and avoid leaving vite in .yarnrc.yml so the quarantine window
remains intact going forward.
βΉοΈ Review info
βοΈ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 342d4d2d-7556-4e31-bb85-39143f123fd9
β Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
π Files selected for processing (4)
.yarnrc.ymlexamples/tutorial/package.jsonexamples/vite/package.jsonpackage.json
β Files skipped from review due to trivial changes (1)
- examples/vite/package.json
## [14.6.1](v14.6.0...v14.6.1) (2026-07-03) ### Bug Fixes * bug bashing ChannelList + Channel ([#2474](#2474), [#2441](#2441), [#2393](#2393)) ([#3227](#3227)) ([f790520](f790520)), closes [GetStream/stream-chat-js#1788](GetStream/stream-chat-js#1788) [#2599](#2599) [#2599](#2599) * **i18n:** use .js extensions on dayjs subpath imports for valid Node ESM ([#3231](#3231)) ([7663b18](7663b18)), closes [#3188](#3188) [#3188](#3188) * **MessageList:** don't count thread replies toward channel unread UI state ([#3229](#3229)) ([ca5ed16](ca5ed16)) * **renderText:** recognize uppercase URL schemes ([#3226](#3226)) ([21d57e3](21d57e3)) * **renderText:** recognize uppercase URL schemes in message links ([6178513](6178513)) * return background colors to LinkPreviewCard and TypingIndicator ([a768e30](a768e30))
|
π This PR is included in version 14.6.1 π The release is available on: Your semantic-release bot π¦π |
π― Goal
The published ESM bundle imported dayjs plugins and locales via extensionless subpaths (e.g.
import 'dayjs/plugin/calendar',import 'dayjs/locale/de'). dayjs ships noexportsmap, so Node's native ESM resolver requires the file extension and rejects these specifiers withERR_MODULE_NOT_FOUND.Bundlers (Turbopack, Webpack, Vite) resolve extensionless subpaths fine, so the bug is invisible in most setups. But Node's native loader does not β and Next.js 16 loads
stream-chat-reactas a server external during "Collecting page data", so the consumer build fails:Regressed in #3188 β first shipped in
14.4.0. Verified by loading each publisheddist/es/index.mjsunder Node's native ESM loader:14.3.0loads cleanly,14.4.0(and every release since) throwsERR_MODULE_NOT_FOUND β dayjs/plugin/calendar. #3188 broadened the Viteexternalsubpath regex from(\/[\w-]+)?(one segment) to(\/.+)?(any depth).dayjs/plugin/calendaris a two-segment subpath, so the old regex didn't match it and Vite bundled the plugin inline (no bare import in the output); the new regex externalizes it, emitting the extensionless specifier verbatim. The extensionless imports had always been in source β the build used to bundle them away.Note that broadening the regex was itself a deliberate ESM-correctness fix (keeping CJS
require()glue out of the ESM output), so externalizing subpaths is intended. This PR keeps the externalization and makes the specifiers valid, rather than reverting it and reintroducing the CJS-glue problem.π Implementation details
The fix β add
.jsto every dayjs subpath import in shipped source, so the specifiers Vite externalizes (emits verbatim) are valid ESM:src/i18n/Streami18n.tsβ 8 plugin imports + 12 locale side-effect importssrc/context/TranslationContext.tsxβ 2 plugin importssrc/i18n/utils.tsβ 1 type-only import (keeps the emitted.d.tsconsistent)registerTranslationlog message.jsis safe across every consumer: Node ESM (the fix), bundlers, and TS types (moduleResolution: "bundler"maps.jsβ.d.ts). The CJS output is unaffected.Regression guard β the existing
validate-cjssmoke test loads the bundle withrequire(), whose CJS resolver tolerates extensionless imports, so it never caught this. Added the missing ESM counterpart:scripts/validate-esm-node-bundle.mjsβ importsdist/es/index.mjsunder Node's native loaderyarn validate-esmscript + a CI step inci.ymlright aftervalidate-cjsVerified:
validate-esmpasses on a healthy build and fails withERR_MODULE_NOT_FOUNDwhen a.jsis stripped from a dayjs import in the built output.yarn build,yarn types, ESLint, and the i18n tests all pass.Also included β toolchain bump (independent of the fix, bundled here):
vite8.0.14 β 8.1.3,vitest+@vitest/coverage-v84.1.7 β 4.1.9,@vitest/eslint-plugin1.6.18 β 1.6.20 (pulls rolldown 1.0.2 β 1.1.3); theexamples/*workspaces bumped to match;.yarnrc.ymllowersnpmMinimalAgeGateto1dand preapprovesvite, and dropsenableHardenedMode. Diffingdist/es+dist/cjsbefore/after the bump shows no behavioral or API-surface change β only additional/* @__PURE__ */annotations and one internal import alias, both from the rolldown bump.π¨ UI Changes
None β packaging/build fix, no runtime or visual behavior change.
Summary by CodeRabbit
Bug Fixes
.jsextensions.Chores