Skip to content

[code-infra] Generate package.json imports field on build#1533

Merged
Janpot merged 6 commits into
masterfrom
worktree-package-imports-field
Jun 12, 2026
Merged

[code-infra] Generate package.json imports field on build#1533
Janpot merged 6 commits into
masterfrom
worktree-package-imports-field

Conversation

@Janpot

@Janpot Janpot commented Jun 10, 2026

Copy link
Copy Markdown
Member

The build tool generates the package.json exports field but was deleting the imports field. This regenerates imports the same way, so packages can use internal #-subpath imports pointing at source files.

Changes

  • build.mjs: Extracted the condition-rebuilding logic from createPackageExports into a shared finalizeConditions() helper. Added createPackageImports(), which mirrors createPackageExports (reusing expandExportGlobs/createExportsFor/finalizeConditions) minus the ./package.json/main/types index handling that only applies to public exports. Keys are validated to start with #; bare specifiers (e.g. external packages) are passed through unchanged.
  • cmdBuild.mjs: writePackageJson now regenerates imports (in parallel with exports) instead of deleting it.
  • build.test.mjs: Tests for subpath rewrite, condition ordering, bare-specifier passthrough, glob expansion, the #-prefix validation, and the no-imports case.

@code-infra-dashboard

code-infra-dashboard Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-1533--mui-internal.netlify.app/

Bundle size

Total Size Change: 0B(0.00%) - Total Gzip Change: 0B(0.00%)
Files: 64 total (0 added, 0 removed, 0 changed)

Show details for 64 more bundles

@mui/internal-docs-infra/abstractCreateDemoparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/abstractCreateDemoClientparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/abstractCreateStreamparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/abstractCreateTypesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/ChunkProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/cliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CodeControllerContextparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CodeExternalsContextparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CodeHighlighterparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CodeHighlighter/errorsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CodeHighlighter/typesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CodeProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CoordinatedLazyparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/CoordinatedLazy/typesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/createDemoDataparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/createDemoData/typesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/createSitemapparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/createSitemap/typesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useCodeparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useCodeWindowparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useCoordinatedparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useCopierparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useDemoparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useErrorsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useLocalStorageStateparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/usePreferenceparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useScrollAnchorparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useSearchparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useSearch/typesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useStreamparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useStream/typesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useTypeparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useTypesparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/useUrlHashStateparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/internal-docs-infra/withDocsInfraparsed: 0B(0.00%) gzip: 0B(0.00%)
addLineGuttersparsed: 0B(0.00%) gzip: 0B(0.00%)
chunkparsed: 0B(0.00%) gzip: 0B(0.00%)
CodeHighlighterChunkparsed: 0B(0.00%) gzip: 0B(0.00%)
CodeHighlighterClientparsed: 0B(0.00%) gzip: 0B(0.00%)
CodeInitialSourceLoaderparsed: 0B(0.00%) gzip: 0B(0.00%)
CodeSourceLoaderparsed: 0B(0.00%) gzip: 0B(0.00%)
createFrameparsed: 0B(0.00%) gzip: 0B(0.00%)
createParseSourceWorkerClientparsed: 0B(0.00%) gzip: 0B(0.00%)
EditingEngineparsed: 0B(0.00%) gzip: 0B(0.00%)
embedTransformsparsed: 0B(0.00%) gzip: 0B(0.00%)
enhanceCodeEmphasisparsed: 0B(0.00%) gzip: 0B(0.00%)
findExpandingRangesparsed: 0B(0.00%) gzip: 0B(0.00%)
getHastTextContentparsed: 0B(0.00%) gzip: 0B(0.00%)
grammarLoadersparsed: 0B(0.00%) gzip: 0B(0.00%)
grammarsparsed: 0B(0.00%) gzip: 0B(0.00%)
isFrameSpanparsed: 0B(0.00%) gzip: 0B(0.00%)
loadIsomorphicCodeVariantparsed: 0B(0.00%) gzip: 0B(0.00%)
parseSourceparsed: 0B(0.00%) gzip: 0B(0.00%)
source.cssparsed: 0B(0.00%) gzip: 0B(0.00%)
source.jsparsed: 0B(0.00%) gzip: 0B(0.00%)
source.jsonparsed: 0B(0.00%) gzip: 0B(0.00%)
source.mdxparsed: 0B(0.00%) gzip: 0B(0.00%)
source.shellparsed: 0B(0.00%) gzip: 0B(0.00%)
source.tsparsed: 0B(0.00%) gzip: 0B(0.00%)
source.tsxparsed: 0B(0.00%) gzip: 0B(0.00%)
source.yamlparsed: 0B(0.00%) gzip: 0B(0.00%)
text.html.basicparsed: 0B(0.00%) gzip: 0B(0.00%)
text.mdparsed: 0B(0.00%) gzip: 0B(0.00%)
TransformEngineparsed: 0B(0.00%) gzip: 0B(0.00%)

Details of bundle changes

Performance

Total duration: 17.34 ms -0.62 ms(-3.4%) | Renders: 4 (+0) | Paint: 76.62 ms -2.89 ms(-3.6%)

No significant changes — details


Check out the code infra dashboard for more information about this PR.

@Janpot Janpot added the scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd). label Jun 10, 2026
@Janpot
Janpot requested a review from brijeshb42 June 10, 2026 15:20
@Janpot
Janpot marked this pull request as ready for review June 10, 2026 15:20
@michaldudak

Copy link
Copy Markdown
Member

I ran into a few issues:

1. Extra conditions aren't path-rewritten. The mui-src object form preserves sibling conditions, but emits their values verbatim — no ./src/… → build-path rewrite, no glob expansion, no extension/.d.ts handling. Your own test documents this:

{ './*': { 'mui-src': './src/*.ts', node: './src/node/*.ts' } }
// →
{ import: { node: './src/node/*.ts', default: './Alert.js' }, ... }

That node value still points at ./src/node/*.ts — a source path (unrewritten, glob intact) that won't exist in the published package. So the moment a condition points at a different source file (the whole reason I'd want conditions here), the output is broken.

2. Standard condition objects are rejected outright. { browser, default } — a perfectly valid conditions map and the first thing you'd reach for — throws Unsupported import for "…". Only a string or an object with "mui-src" field is supported for now.

3. Multi-segment globs silently vanish. "#prehydration/*": "./src/*/prehydrationScript.min.ts" produced no entry at all. The build-time glob expansion matches * only within a single path segment, but consumers import #prehydration/tabs/indicator (two segments), and Node's runtime subpath-* matches across /. So build-time and runtime * semantics diverge, and the entry is dropped without warning. A zero-match warning would at least surface it.

What does work well: with explicit string keys, the path rewriting + ESM/CJS extensions + import/require/types generation is great and removed most of our custom build step. The gap is specifically conditions that resolve to an alternate source file.

Suggestions, roughly in priority order:

  • Rewrite local-file paths (and expand globs) inside all conditions of an entry, not just mui-src/default.
  • Prefer accepting a standard conditions object (source path under default/import/require) over the mui-src key, so the same map is both TS-resolvable and buildable — or document the tsconfig paths/customConditions shim needed alongside mui-src.
  • Align glob * with Node's subpath semantics (match across /), or warn on zero-expansion.

Janpot added 3 commits June 11, 2026 12:19
…pand

Replaces the `mui-src` condition key (which had no consumers) with leaf-based
rewriting that accepts any standard conditions object and rewrites every source
path it contains, including those nested inside sibling conditions. This fixes
the review feedback:

- Sibling conditions are now path-rewritten (conditions stay outer, the
  import/require split is injected at each leaf).
- Standard `{ node, default, ... }` conditions objects are accepted (no special
  key required).
- Glob enumeration is now controlled by a `--expand` CLI flag (default on),
  decoupled from `--flat`; `--no-expand` keeps `*` as a Node runtime subpath
  pattern. A zero-match pattern now warns instead of silently dropping.
- Negation (`null`) keys follow Node's most-specific-wins resolution rather than
  cascade-subtraction, so a deeper positive pattern still resolves under a
  shallower `null`.

Paths that aren't under `src/` (copied assets, build outputs, bare specifiers)
pass through verbatim.
- finalizeConditionValue no longer clobbers a user-authored `default` that sits
  alongside `import`/`require` in the same conditions object; it only synthesizes
  `default` when one isn't already present.
- Glob expansion of a conditions object now drops a sibling condition whose source
  file is missing for a given stem (a sibling glob need not match every stem of
  the primary pattern) instead of failing the build on a non-existent path.

Adds regression tests for both.
@michaldudak

Copy link
Copy Markdown
Member

One (non-blocking) issue came out in Base UI: code-infra now validates import targets exist in the build and warns "./test/index.ts" for "#test-utils" was not found. The published imports then include a broken (dev-only) #test-utils entry. Probably needs a code-infra story for dev-only imports, or base-ui dropping #test-utils from published imports.

@brijeshb42

Copy link
Copy Markdown
Contributor

How do you mark something as dev-only ?

@michaldudak

michaldudak commented Jun 12, 2026

Copy link
Copy Markdown
Member

How about we remove the entry from imports if the target doesn't exist?

michaldudak added a commit to michaldudak/base-ui that referenced this pull request Jun 12, 2026
@Janpot

Janpot commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Personally I'd rather just error when an import/export doesn't resolve, such that CI yells at us when we move something around without updating the import/export, instead of just silently dropping the export.

Can't we just keep test-only things out of there and reserve imports/exports for the published shape only? Why is that test import really there? To prevent long backtracking file paths? Maybe an alias in just the test env is better suited for this purpose?

@michaldudak

Copy link
Copy Markdown
Member

It's just for convenience, to avoid typing long paths with many ".."

@Janpot

Janpot commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Ok, anyway, it's non-blocking for this PR. Another option that I just think about is to

{
  "#test-utils": { "test": "./test/index.ts"  }
}

And drop all test conditions when we generate imports. Then in vitest add test to resolve.conditions. I believe that would be very idiomatic use of import conditions.

…tories

createPackageExports/createPackageImports/createPackageBin now take their subject
(exports/imports/bin) as the first positional argument and the rest as an options
object, surfacing the primary input. createPackageImports is only called when an
imports field is present. No behavior change.
@Janpot Janpot added scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd). and removed scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd). labels Jun 12, 2026
@Janpot
Janpot merged commit bb17bc0 into master Jun 12, 2026
16 of 17 checks passed
@Janpot
Janpot deleted the worktree-package-imports-field branch June 12, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants