Skip to content

fix(bff): make published crossProject client declarations portable - #8797

Merged
zllkjc merged 2 commits into
mainfrom
fix/bff-crossproject-portable-declarations
Aug 13, 2026
Merged

fix(bff): make published crossProject client declarations portable#8797
zllkjc merged 2 commits into
mainfrom
fix/bff-crossproject-portable-declarations

Conversation

@keepview

@keepview keepview commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem

bff.crossProject client .d.ts resolve locally but break once published (fixes #8739, supersedes #8735): consumers hit TS2307 or the type degrades to any. Three causes:

  1. Alias leak — tsc keeps tsconfig paths verbatim in .d.ts (TS#30952), so @shared/* reaches consumers unresolved.
  2. Broken relative specifiers — the client .d.ts is copied one directory shallower than tsc emitted it, mispointing every relative import.
  3. Missing filesdist/shared / dist/api are never added to package.json#files, so they're absent from the tarball.

Changes

  • server-utils: rewrite tsconfig aliases on declaration emit via a new afterDeclarations transformer (Import/Export/ImportType/ImportEquals), keeping specifiers relative and extensionless. No public API change.
  • plugin-bff: the client re-exports the handler declaration in place via a facade instead of copying it; publish every .d.ts (<distPath>/**/*.d.ts); and drive readDirectoryFiles from ApiRouter.getApiFiles() so stray files never reach generateClient.

Testing

Unit (alias rewrite, facade, file filtering) + an out-of-repo tarball consumer that tscs the published client with skipLibCheck: false and asserts no leak and no any degradation.

Co-Authored-By: Riff

crossProject client `.d.ts` were unresolvable once published:

- tsc keeps tsconfig `paths` aliases verbatim in declaration output
  (microsoft/TypeScript#30952), so `@shared/*` leaked to consumers as TS2307.
- the client `.d.ts` was copied one directory shallower than tsc emitted it,
  breaking every relative specifier inside.
- the referenced declarations (`dist/shared`, `dist/api`) were never added to
  `package.json#files`, so they were missing from the packed tarball.

Fixes:

- server-utils: run the tsconfig-paths rewrite on declaration emit via a new
  `afterDeclarations` transformer (Import/Export/ImportType/ImportEquals),
  keeping specifiers relative and extensionless.
- plugin-bff: the client re-exports the handler declaration in place via a
  facade instead of copying it, so relative specifiers stay correct; publish
  every emitted `.d.ts`; and drive readDirectoryFiles from ApiRouter's
  `getApiFiles()` so stray artifacts never reach the client generator.

Verified with an out-of-repo tarball consumer that type-checks the published
client with `skipLibCheck: false` and asserts the type did not degrade to any.

Co-Authored-By: Riff
@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for modernjs-byted ready!

Name Link
🔨 Latest commit 51d7ded
🔍 Latest deploy log https://app.netlify.com/projects/modernjs-byted/deploys/6a759e0bb68d8f0008748b93
😎 Deploy Preview https://deploy-preview-8797--modernjs-byted.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (no change from production)
Accessibility: 100 (no change from production)
Best Practices: 100 (no change from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 51d7ded

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

This PR includes changesets to release 119 packages
Name Type
@modern-js/server-utils Patch
@modern-js/plugin-bff Patch
@modern-js/server Patch
@modern-js/app-tools Patch
bff-api-app Patch
bff-client-app Patch
bff-indep-client-app Patch
bff-hono Patch
deploy-server Patch
pure-esm-project Patch
basic-app-rstest Patch
@modern-js/adapter-rstest Patch
@modern-js/plugin-ssg Patch
@modern-js/plugin-styled-components Patch
@modern-js/plugin-i18n Patch
@modern-js/image Patch
@modern-js/runtime Patch
@modern-js/plugin-polyfill Patch
@integration-test/alias-set Patch
app-document Patch
async-entry-test Patch
tmp Patch
integration-clean-dist-path Patch
integration-compatibility Patch
integration-custom-dist-path Patch
custom-file-system-entry Patch
integration-custom-template Patch
deploy Patch
dev-server Patch
integration-disable-html Patch
entries-app-builder Patch
app-custom-entries Patch
app-custom-routes-runtime Patch
app-custom Patch
app-entry Patch
app-route Patch
app-entry-server Patch
i18n-app-csr-html-lang Patch
i18n-app Patch
i18n-app-ssr-html-lang Patch
i18n-app-ssr Patch
i18n-custom-i18n-wrapper Patch
i18n-mf-app-provider Patch
i18n-mf-component-provider Patch
i18n-mf-consumer Patch
i18n-routes Patch
i18n-routes-ssr Patch
@integration-test/image-component Patch
main-entry-name Patch
nonce Patch
react-compiler-test Patch
routes-match Patch
routes Patch
app-rsbuild-hooks Patch
rsc-csr-app Patch
rsc-csr-routes Patch
rsc-ssr-app Patch
rsc-ssr-routes Patch
basic-app-rstest-browser Patch
runtime-custom-plugin Patch
runtime-custom-config-plugin Patch
select-mul-entry-test Patch
select-one-entry-test Patch
server-config Patch
server-esm-tsx Patch
server-json-script Patch
server-monitors Patch
server-prod Patch
server-routes Patch
@source-code-build/app Patch
ssg-fixtures-mega-list-routes Patch
ssg-fixtures-nested-routes Patch
ssg-fixtures-simple Patch
ssg-fixtures-web-server Patch
ssr-base-async-entry-test Patch
ssr-base-async-pre-entry-test Patch
ssr-base-json-test Patch
ssr-base-test Patch
ssr-base-fallback-test Patch
init Patch
ssr-base-loadable Patch
ssr-partial-test Patch
rsc-closing-tags-test Patch
ssr-script-loading Patch
ssr-useid-test Patch
ssr-streaming-inline-test Patch
ssr-streaming-lazy-test Patch
ssr-streaming-test Patch
styled-components-stream Patch
styled-components-string Patch
integration-tailwindcss-v2 Patch
integration-tailwindcss-v3 Patch
integration-tailwindcss-v4-tools Patch
integration-tailwindcss-v4 Patch
tmp-dir Patch
write-to-dist Patch
@modern-js/bundle-diff-benchmark Patch
@modern-js/main-doc Patch
@modern-js/tsconfig Patch
@modern-js/builder Patch
@modern-js/plugin-data-loader Patch
@modern-js/render Patch
@modern-js/bff-core Patch
@modern-js/bff-runtime Patch
@modern-js/server-core Patch
@modern-js/create-request Patch
@modern-js/prod-server Patch
@modern-js/server-runtime Patch
@modern-js/create Patch
@modern-js/i18n-utils Patch
@modern-js/plugin Patch
@modern-js/runtime-utils Patch
@modern-js/sandpack-react Patch
@modern-js/types Patch
@modern-js/utils Patch
@modern-js/rslib Patch
@scripts/prebundle Patch
@scripts/rstest-config Patch
@scripts/release-node 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 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Rsdoctor Bundle Diff Analysis

📊 Quick Summary
Project Total Size Change
bundle-diff 471.8 KB 0

Generated by Rsdoctor GitHub Action

…tions

The declaration-emit alias rewrite dropped the module extension, so in ESM
output (`package.json#type: "module"`) the `.d.ts` referenced `../shared/types`
while the JS referenced `../shared/types.js`. `node16`/`nodenext` consumers then
hit TS2835 — the same portability failure this rewrite prevents, just on the ESM
branch.

- server-utils: forward `moduleType` to the `afterDeclarations` transformer and
  align its early-return with the `before` hook, so ESM declaration specifiers
  carry `.js` (TS resolves `./x.js` back to `./x.d.ts`).
- plugin-bff: the client facade re-exports with an explicit `.js` extension in
  ESM output too.
- Add an ESM declaration fixture and cover both packages.

Co-Authored-By: Riff
@zllkjc
zllkjc merged commit 8edf91a into main Aug 13, 2026
10 checks passed
@zllkjc
zllkjc deleted the fix/bff-crossproject-portable-declarations branch August 13, 2026 04:31
@caohuilin caohuilin mentioned this pull request Aug 13, 2026
@SoonIter

Copy link
Copy Markdown
Member

<agent: daily-job rstack ecosystem-ci>

Ecosystem CI regression: rspack/modernjs

Attribution: Confirmed downstream source for the four bff-corss-project failures

Impact: Modern.js framework tests now fail the cross-project BFF dev assertions on both Linux and Windows; the client receives fallback HTML or stale placeholder content instead of the BFF responses.

Why this PR

This PR added types-portability.test.ts, which calls modernBuild(apiAppDir) on the same bff-api-app fixture while index.test.ts runs its dev servers from that directory in a parallel test file. The post-merge Linux log shows the portability build removing dist-1/client/index.js and other generated files and triggering a server-runtime reload; the sibling dev assertions then fail with missing .hello, HTML parsed as JSON, and stale bff-client / bff-express text. The same four failures occur on the merge commit's Linux and Windows integration jobs and in ecosystem CI, while the previous ecosystem run used the pre-merge Modern.js main and passed.

Suggested next step

Modern.js tests should build and pack an isolated copy of bff-api-app in types-portability.test.ts, so it cannot mutate the live fixture used by index.test.ts. A fallback is to serialize these two files, though fixture isolation is safer. Verify the focused integration/bff-corss-project tests repeatedly on Linux and Windows, then rerun pnpm test --stack rspack modernjs.

Evidence and attribution checks

BleedingDev pushed a commit to BleedingDev/ultramodern.js that referenced this pull request Aug 17, 2026
…Project declaration portability)

Real merge of upstream mainline release commit eded841
("Release v3.8.2 (web-infra-dev#8810)"), so the fork genuinely tracks upstream 3.8.2 instead of
claiming it. Tag `v3.8.2` (e642cd1) is a parallel
release commit: same parent (8edf91a) and the identical tree
(35d89ac), so merging the mainline commit is
patch-equivalent to merging the tag. Ancestry is asserted against eded841.

The only executable upstream change in v3.8.1..v3.8.2 is 8edf91a
"fix(bff): make published crossProject client declarations portable (web-infra-dev#8797)";
the release commit itself is version/CHANGELOG metadata plus the consumed changeset.

Conflict resolutions (semantic, not side-taking):

- packages/cli/plugin-bff/src/cli.ts, src/utils/clientGenerator.ts: kept the fork's
  extraction (cli/generator.ts + utils/client-generator/*). Upstream's inline
  generator body is superseded by those fork-owned modules; the upstream behavior
  is ported there in the following commit.
- packages/cli/plugin-bff/tests/clientGenerator.test.ts: kept upstream's new
  coverage verbatim (apiFiles filtering, facade shapes, dist glob) on top of the
  fork's import ordering. These arrive RED until the port lands.
- packages/server/utils/src/compilers/typescript/{index,tsconfigPathsPlugin}.ts:
  kept the fork side. The fork replaced the TypeScript Program pipeline with TS-Go
  plus a post-emit textual rewriter, so upstream's `afterDeclarations` AST
  transformer has no host here. Its behavior is reimplemented patch-equivalently
  on the TS-Go post-emit path in the following commit; upstream's declaration unit
  tests and fixtures auto-merged and are kept verbatim as the oracle.
- packages/{runtime/plugin-i18n,solutions/app-tools,toolkit/create}/package.json:
  resolved fieldwise — upstream's 3.8.2 version identity, fork's entry points,
  peer ranges and dependency set.
- tests/integration/bff-cross-project: upstream added its fixture under the
  historical typo directory `bff-corss-project`; the fork renamed that suite to
  `bff-cross-project`. Everything landed under the corrected path and no
  `bff-corss-project` directory exists in the merged tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BleedingDev pushed a commit to BleedingDev/ultramodern.js that referenced this pull request Aug 17, 2026
…to the fork's TS-Go pipeline

Upstream's fix lands as a `program.emit({ afterDeclarations })` AST transform on a
TypeScript Program. The fork replaced that pipeline with TS-Go plus a post-emit
textual rewrite, so the same semantics are reimplemented on the seam that actually
runs, and the upstream unit tests + fixtures merged in with the release are the
oracle for it.

@modern-js/server-utils
- createResolvedTsgoConfig stops forcing `declaration: false`. The app's resolved
  value survives; composite / declarationMap / emitDeclarationOnly / incremental /
  noEmit normalization is unchanged, so this stays a one-shot server emit.
- The post-emit collector now picks up `.d.ts` / `.d.mts` / `.d.cts`, and the
  output-to-source mapper handles their double extension — `path.parse('index.d.ts')`
  reports name `index.d`, so the naive mapping resolved to a source that never
  existed and skipped the alias rewrite for every declaration.
- tests/ts.test.ts keeps upstream's two declaration cases; only the quote-exact
  assertions are loosened to `["']`, because TS-Go preserves the source's quote
  style where tsc normalized to double quotes. Verified emit for all four
  specifier kinds: ImportDeclaration, ExportDeclaration, inline `import("...")`
  and `import x = require("...")`.
- tests/tsgo.test.ts asserted the old defect (`declaration: false`); it now asserts
  the new contract.

@modern-js/plugin-bff
- clientGenerator takes `apiFiles` from ApiRouter (same API_FILE_RULES the runtime
  router uses) instead of recursively reading the lambda directory, so emitted
  `.d.ts`, compiled `.js`, tests and `_`-prefixed modules never reach generateClient.
- The declaration copy is replaced by a facade (`client-generator/type-facade.ts`)
  that re-exports the handler declaration where the compiler emitted it. Copying
  moved it one directory shallower and broke every relative specifier inside.
- `${distPath}/**/*.d.ts` joins the published files closure, so the declarations the
  facade points at actually ship.
- Deliberate deviation from upstream cli.ts: the facade specifier always carries
  `.js`. Upstream derives it from the app-level moduleType, but the fork's
  writeClientModuleBoundary makes `dist/client` its own ESM package unconditionally,
  so an extensionless specifier there is TS2834/TS2835 for node16/nodenext consumers
  (reproduced, and covered by a test).
- A missing origin declaration is now a hard MissingClientDeclarationError rather
  than a logged best-effort skip: setPackage advertises `types` for every generated
  client, so a missing declaration means publishing a type surface that resolves to
  nothing.
- The Effect typed-client declaration is untouched; it describes the generated
  HttpApi client, not a handler module, so it is written directly, not as a facade.

tests/integration/bff-cross-project
- Upstream's portability test is ported to the fork's corrected suite path and
  adapted: it drives TS-Go rather than `ts.createProgram`, since `typescript@7`
  no longer exposes the legacy Program API. It builds, `pnpm pack`s, extracts into
  an isolated consumer with no workspace or source aliases, and strict-type-checks
  with `skipLibCheck: false`; an unused `@ts-expect-error` (TS2578) fails it, so a
  type that degraded to `any` cannot pass. A second case pins the ESM-facade
  deviation under node16/nodenext.

Mutation-checked: removing the `**/*.d.ts` files entry, restoring
`declaration: false`, or emitting an extensionless facade specifier each turns the
packed-consumer tests RED (TS2834 in the last case).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BleedingDev pushed a commit to BleedingDev/ultramodern.js that referenced this pull request Aug 17, 2026
The fork now contains upstream's `Release v3.8.2 (web-infra-dev#8810)`, so the divergence
gate's `dfcd414a` base (`git merge-base HEAD v3.8.1`) is stale: measured against
it, 27 upstream-owned files report budget violations that are nothing but
upstream's own 3.8.2 content arriving through the merge.

Move `DEFAULT_DIVERGENCE_BASE_REF` to the mainline release commit
`eded841256a7cffdaa622e3889fc83407debd3e4`, not the `v3.8.2` tag. Upstream cuts
releases on a parallel commit — tag `v3.8.2` is `e642cd16a8`, sharing this
commit's parent `8edf91adb1` and its tree `35d89ac688` — so the tag is
patch-equivalent but is not an ancestor of HEAD and cannot be used as a base.

Six upstream-owned files grow across the transition. Each was inspected against
`git diff dfcd414 eded841 -- <file>` before being dispositioned, and none is
a merge-resolution defect:

- `plugin-bff/src/cli.ts` (216 -> 218) and `src/utils/clientGenerator.ts`
  (291 -> 338) grow because web-infra-dev#8797 grew upstream's own files while the fork keeps
  a thin entry and a re-export shim over its fork-owned generator. The fork
  already threads both new arguments (`moduleType`, `apiFiles`) from
  `src/cli/generator.ts`.
- `server/utils/src/compilers/typescript/tsconfigPathsPlugin.ts` (203 -> 325,
  and 24 -> 22 hunks) grows because web-infra-dev#8797 added a tsc `afterDeclarations`
  transformer factory. The fork spawns tsgo and has no `Program` to register it
  on, so the same alias stripping happens post-emit in `importRewriter.ts`.
- `server/utils/tests/fixtures/ts-declaration/api/declaration.ts` is a new
  upstream fixture that `biome check --write` reformats by one blank line.
- `server/utils/src/compilers/typescript/index.ts` (454 -> 482) and
  `tests/ts.test.ts` (216 -> 230) are the capped Bucket-B patches that port
  web-infra-dev#8797 onto the tsgo pipeline (20/+4 and 8/+6 changed lines).

Land the ledger rows that AGENTS.md Rule 5 requires for a base re-anchor
(CLI-19, CLI-20, SRV-20 updated, SRV-21, SRV-22, SRV-23; notes N9 and N10),
re-point every doc that named the old base, and record in FORK-DIVERGENCE.md §3
that the allowlist re-record itself has not run yet.

The re-record is the one remaining step and it is not applied here: writing it
requires `--record-growth`, which was denied by this environment's command
policy. Until it runs, `--mode divergence` stops at the base-mismatch assertion
and `divergence.test.js`'s "repo divergence allowlist keeps the current tree
green" fails. That is a louder and more accurate signal than the 27 stale-base
violations the previous state produced, and §3 carries the exact command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BleedingDev pushed a commit to BleedingDev/ultramodern.js that referenced this pull request Aug 17, 2026
…the exact Rule-5 20-line cap

The ws1 port of upstream web-infra-dev#8797 landed at 24 added-plus-removed PR lines on
this audited-base-owned file; the reviewed divergence writer refuses anything
over the exact 20-line cap. Condensing the three explanatory comments keeps
their content and brings the range delta to 19 without touching behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: BFF crossProject client .d.ts unresolvable for consumers (leaked path aliases + depth-shifted relative imports)

3 participants

Sponsor
SponsoredKunjungi sekarang
Promo