feat(v9): ship web packages ESM-first (type:module), drop node export condition#36327
Draft
Hotell wants to merge 20 commits into
Draft
feat(v9): ship web packages ESM-first (type:module), drop node export condition#36327Hotell wants to merge 20 commits into
node export condition#36327Hotell wants to merge 20 commits into
Conversation
…targeted bundlers Adds a `module` condition nested in `node` to every converged v9 package's export map: - node-targeted bundlers (webpack/rollup/vite/esbuild) resolve the ESM build and tree-shake - bare Node ignores `module` and falls back to CommonJS, keeping SSR single-instance / dual-package-hazard free - enables fully-specified .js import emit via swc `baseUrl` so lib/ is valid ESM Updates the migrate-converged-pkg generator (source of truth) + swcrc template, and rolls the change out across all v9 packages with matching beachball change files.
…rt condition Migrates converged v9 web packages to ESM-first packaging: - `type: module` with valid ESM under lib/ (fully-specified .js) and CommonJS under lib-commonjs/*.cjs - drop the `node` export condition; bare-Node `import` resolves ESM, `require` resolves CJS - per-condition types: `require` points at a rolled `dist/*.d.cts` (attw-clean for node16/nodenext CJS) - rename CJS dev configs to .cjs (jest/eslint/tests) for type:module compatibility Build executor auto-emits lib-commonjs .cjs + dist .d.cts (gated on type:module). Generators (migrate-converged-pkg, react-library) emit the new web shape; optional `attw` target added. Excludes CJS-first (platform:node, storybook addons) and just-scripts (charts) packages.
|
Pull request demo site: URL |
@fluentui/tokens is now type:module (ESM, named exports, no default). The generate-tokens script relied on CJS-interop default import; switch to a namespace import.
…e tests - rit.config.js -> .cjs (react-provider) + teach rit loader (args.ts) and workspace-plugin rit-target inference to prefer .cjs - .storybook/main.js -> .cjs (recipes), scripts/server.js -> .cjs (tokens) - getDependencies.spec: react-text main is now lib-commonjs/index.cjs; refresh dep-tree order snapshot - react-library spec: drop stale jest.config.js from scaffold snapshot
Graph traversal order is non-deterministic across machines; sort deps by name for a stable snapshot.
| @@ -153,3 +153,5 @@ gulp-cache | |||
| .cursor/rules/nx-rules.mdc | |||
There was a problem hiding this comment.
🕵🏾♀️ visual changes to review in the Visual Change Report
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png | 413 | Changed |
| vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png | 404 | Changed |
vr-tests-react-components/Positioning 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Positioning.Positioning end.chromium.png | 617 | Changed |
| vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png | 320 | Changed |
vr-tests-react-components/ProgressBar converged 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png | 161 | Changed |
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png | 103 | Changed |
vr-tests-react-components/TagPicker 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/TagPicker.disabled.disabled input hover.chromium.png | 677 | Changed |
vr-tests-web-components/Accordion 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-web-components/Accordion. - Dark Mode.normal.chromium_1.png | 3398 | Changed |
vr-tests-web-components/MenuList 4 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-web-components/MenuList. - RTL.1st selected.chromium_2.png | 39384 | Changed |
| vr-tests-web-components/MenuList. - RTL.2nd selected.chromium.png | 17 | Changed |
| vr-tests-web-components/MenuList. - RTL.normal.chromium_1.png | 39083 | Changed |
| vr-tests-web-components/MenuList. - RTL.2nd selected.chromium_3.png | 38816 | Changed |
vr-tests-web-components/TextInput 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-web-components/TextInput. - Dark Mode.normal.chromium_1.png | 288 | Changed |
vr-tests/react-charting-LineChart 3 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests/react-charting-LineChart.Multiple - RTL.default.chromium.png | 200 | Changed |
| vr-tests/react-charting-LineChart.Multiple.default.chromium.png | 192 | Changed |
| vr-tests/react-charting-LineChart.Multiple - Dark Mode.default.chromium.png | 181 | Changed |
There were 1 duplicate changes discarded. Check the build logs for more information.
Migrate @fluentui/scripts-cypress to type:module so its TS source loads correctly when imported from migrated (type:module) cypress configs: - package.json: type:module; rename node-run configs to .cjs (eslint/jest) - base.config.ts: __dirname -> import.meta.dirname; add webpack resolve.extensionAlias (.js->.ts) - index.ts: explicit .ts specifier for base.config re-export (resolves under native ESM via require(esm) from CommonJS cypress configs); explicit extension on browser type import - browser/index.ts + support/*.js: fully-specified import specifiers; convert support require() to static ESM import - tsconfig.lib.json: noEmit + allowImportingTsExtensions (source-only package) - rit cypress.config template: __dirname -> process.cwd() for ESM/CJS sandbox parity Validated: react-utilities/babel-preset-global-context/react-headless-components-preview e2e, react-menu-grid-preview test-rit (17) e2e + test, scripts-cypress lint/test/type-check, workspace-plugin (239) and react-integration-tester (37) unit tests.
…odule) Complete the ESM-first migration for the one missed web package (it still shipped the pre-migration shape: main=lib-commonjs/index.js, no type field): - package.json: type:module, main->lib-commonjs/index.cjs, per-subpath exports rewritten to ESM-first nested shape (import.types=.d.ts/default=lib.js, require.types=.d.cts/default=lib-commonjs/*.cjs), dist/*.d.cts in files - jest/eslint/test-setup configs renamed to .cjs - generate-api: read subpath types from nested import.types (not just flat types) so exportSubpaths rollups work with the ESM-first export shape Also simplify @fluentui/scripts-cypress: inline base.config into index.ts to remove the only cross-file relative import. This fixes a type-check regression where the prior explicit .ts specifier leaked an allowImportingTsExtensions requirement into every cypress consumer, while still resolving under Node's native ESM loader for CommonJS cypress configs. Validated: react-headless build/attw(green)/lint/test(712)/type-check/e2e(185), react-utilities & babel-preset-global-context e2e, react-menu-grid-preview test-rit(17) e2e, scripts-cypress lint/type-check, workspace-plugin (239) tests.
- react-examples (v8, tsc --module commonjs) compiled FocusTrapZone.e2e.tsx, which imports @fluentui/scripts-cypress; the inlined base.config uses import.meta (required for type:module) and is invalid under module:commonjs. Exclude e2e cypress test files from the v8 library build (they're bundled by cypress at runtime, never part of the shipped lib). - scripts-monorepo: annotate the getDependencies.spec sort comparator so checkJs doesn't flag implicit-any params (TS7006).
…module - rit-tests-v8 cypress.config: __dirname -> import.meta.dirname (cypress loads it as ESM once it imports the type:module @fluentui/scripts-cypress) - react-examples FocusTrapZone e2e: import mount from @cypress/react directly (v8-local) instead of ESM scripts-cypress, so the v8 commonjs build doesn't pull in import.meta; ignore e2e specs in eslint (bundled by cypress, not in tsconfig program)
… instead Restore mount import from @fluentui/scripts-cypress (preserves StrictMode default). Build no longer compiles e2e specs (excluded in tsconfig.json) so the ESM-only scripts-cypress import.meta never reaches the commonjs tsc; eslint ignores e2e.
…resolution react-message-bar: MessagebarActions -> MessageBarActions; react-table: createColumn -> createTableColumn. These named exports were always wrong but only error now under strict ESM.
…aths resolve type:module packages emit bare subpath imports (e.g. use-sync-external-store/shim) without extensions; webpack treats lib/ as ESM and enforces fully-specified. Disable it for the measure bundler.
…ayout scaffolded package lives at <scope>/<name>/library (4 deep); preset must be ../../../../jest.preset.js. Fixes check-tools react-component generator running test.
📊 Bundle size reportUnchanged fixtures
|
…import.meta cy specs import scripts-cypress whose inlined base.config uses import.meta; type-check under commonjs failed (TS1343). esnext module allows it (type-check only, noEmit).
…oad as ESM Their cypress.config.ts imports the type:module @fluentui/scripts-cypress, which forces Node to load the config as ESM; ts-node was emitting CommonJS (exports) into that ESM scope (TS-node CJS/ESM mismatch) -> 'exports is not defined in ES module scope'. Aligning the apps to type:module makes ts-node emit ESM. Renamed jest/eslint/rit/test-setup configs to .cjs accordingly. Validated: rit-tests-v8 e2e(17)+lint+test+type-check, rit-tests-v9 test-rit(17) type-check+test+lint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Migrates converged v9 web packages to ESM-first packaging so bare-Node SSR can
importreal ESM (and tree-shake), whilerequirestill gets CommonJS — no consumer config, no dual-package hazard for single-format graphs.lib/ships valid native ESM (fully-specified.jsvia swcresolveFully).lib-commonjs/ships.cjs(required undertype: module).nodecondition is dropped: bare-Nodeimport→ ESM,require→ CJS; bundlers resolveimport→ ESM and tree-shake.requirepoints at a rolleddist/*.d.ctssonode16/nodenextCJS consumers are@arethetypeswrong-clean.Why (vs the
modulecondition in #36324)The
modulecondition only helps bundled SSR. This unlocks bare-Node externalized ESM (edge/workers, externalized deps) too, by makinglib/genuinely Node-loadable ESM. Latest Griffel (type: module, nonode) already ships this exact shape, so the ecosystem is aligned.What's automated (no per-package manual steps)
lib-commonjs/*.js→*.cjs(+ rewrites relativerequire/maps) and copiesdist/*.d.ts→*.d.cts— gated ontype: module(no-op otherwise).migrate-converged-pkg,react-library): emit the ESM-first shape,.cjsdev configs, nested-types exports, anddist/*.d.ctsinfiles.jest.config.cjs; adds an optionalattwtarget (dependsOn: build, not in CI gates).Scope / exclusions
type: module).platform:nodepackages, storybook addons /preset.js, and just-scripts packages (charts —just.config.*is incompatible withtype: module).Validation
require✅ andimport✅ both load; functionally real.Known limitation (follow-up, not a regression)
Bare-Node
importof icon-dependent packages currently fails inside@fluentui/react-icons— an external package whose ESM (lib/index.js) uses extensionless imports (not valid bare-Node ESM). Bundled SSR, client, andrequire()are unaffected. Needs an upstream fully-specified-ESM fix in react-icons (or temporary exclusion of icon-dependent packages).Reviewer notes
importonly "lights up" once a package's entire transitive@fluentui/*closure is migrated (CJSexport *barrels break Node's named-import interop). Throughout rollout,require+ bundled never regress.type: modulepackages updated to describe the ESM-first state; excluded packages keep the module-condition message.