Skip to content

[api-extractor] Fix ExtractorConfig failing when bundled into ESM output - #5924

Open
Phạm Mạnh Lực (MLuc24) wants to merge 2 commits into
microsoft:mainfrom
MLuc24:fix/api-extractor-esm-bundling
Open

[api-extractor] Fix ExtractorConfig failing when bundled into ESM output#5924
Phạm Mạnh Lực (MLuc24) wants to merge 2 commits into
microsoft:mainfrom
MLuc24:fix/api-extractor-esm-bundling

Conversation

@MLuc24

Copy link
Copy Markdown

Summary

Fixes #5864

@microsoft/api-extractor throws at import time when it is bundled into ESM output:

Error: File does not exist: /path/to/bundle-output/schemas/api-extractor-defaults.json

ExtractorConfig read its default config from a __dirname-relative path while the module was initializing. __dirname does not exist in ESM, so bundlers substitute a replacement (rolldown, for instance, recommends defining it as import.meta.dirname), and that resolves to the bundle's output folder rather than the api-extractor package folder. The file is never found there.

Details

The defaults are now imported statically, exactly the way the sibling api-extractor.schema.json is already imported a few lines above in the same file. Bundlers inline a static JSON import, so the runtime filesystem read disappears and the package works whether it is consumed directly or bundled.

Two details worth calling out, since neither is obvious from the issue:

The suggested fix in the issue cannot be applied as written. api-extractor-defaults.json is JSONC, not JSON — it carries four // ("x" is required) notes, which is why the code used JsonFile.load rather than an import in the first place. JSON.parse rejects the file as it stands, so resolveJsonModule (or an import ... with { type: 'json' }) would fail to build. This PR removes those four comments so the file is strict JSON. What they documented is now stated at the point where the import is consumed.

The cast has to go through unknown. The defaults deliberately omit fields that become required once a section is used (apiReport.enabled, docModel.enabled, dtsRollup.enabled), so the imported object does not structurally overlap Partial<IConfigFile> and a direct assertion is a TS2352 error. JsonFile.load returned untyped data, so this is the same looseness as before, now spelled out.

Not completely solved: ExtractorConfig._tsdocBaseFilePath still resolves ../../extends/tsdoc-base.json through __dirname. That one names a file that has to exist on disk, so it cannot simply be inlined, and it does not throw at import time. I left it alone rather than widen this PR; happy to follow up if you'd like it addressed.

No public API changes, and no behavior change outside of bundled ESM consumption.

How it was tested

Reproduced against the published @microsoft/api-extractor 7.58.12, bundled with esbuild to ESM the way the issue describes:

$ npx esbuild entry.js --bundle --platform=node --format=esm \
    --define:__dirname=import.meta.dirname --define:__filename=import.meta.filename \
    --banner:js="import{createRequire as __cr}from'node:module';const require=__cr(import.meta.url);" \
    --outfile=out/bundle.mjs

$ node out/bundle.mjs
Error: File does not exist: .../esm-repro/schemas/api-extractor-defaults.json

Applying this change to the package's compiled output — the defaults inlined instead of read — and rebundling identically:

$ node out/fixed.mjs
loaded ExtractorConfig, FILENAME = api-extractor.json

Also confirmed that the edited defaults file now passes a strict JSON.parse, and type-checked the assertion against a reduced model of IConfigFile with strict and resolveJsonModule enabled: the direct cast reports TS2352 on apiReport, while the cast through unknown compiles clean.

I was not able to run the monorepo's own build or unit tests locally, so rush build / rush test for api-extractor is worth a look in CI.

ExtractorConfig loaded its default config at module initialization from a
__dirname-relative path. __dirname does not exist in ESM, and the replacement
bundlers inject resolves to the bundle's own output folder, so the schema file
was never found.

The defaults are now imported statically, the same way the sibling
api-extractor.schema.json already is, which lets bundlers inline them and
removes the runtime filesystem read. The four comments in the defaults file
were dropped so that it parses as strict JSON; the note they carried is now
stated where the import is consumed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@MLuc24

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

[api-extractor] ExtractorConfig fails when bundled into ESM output

2 participants