Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

Nested JSX components under React Server Components: derive flat exports from the signature

Ouverte
#8,901 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

@cknitt y travaille déjà.

Depuis le 18/10/2026.

  • #8904 par @cknitt — ouverte

Évaluation

Difficulté
5/5
Temps estimé
Plus d'une semaine
Accessibilité débutants
25/100
Type d'issue
Fonctionnalité
Clarté
Plutôt claire
Activité
Active
Stack technique
javascript, next.js, react
Domaine
compilers

Piste de recherche

Start by reviewing #8886 and its linked review, then trace how the compiler derives exports from signatures and rewrites nested .make paths. The issue proposes a signature-based naming rule, but leaves JSX-only versus all-read rewriting and dynamic imports open. Done means resolving those design questions and verifying the export behavior across the listed bundlers and optimization modes.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

🤖 Drafted with Claude (Claude Code), based on the review of #8886.

Problem

Nested components such as <Sidebar.Provider /> compile to a read through the module object:

import * as Sidebar from "./Sidebar.mjs";
JsxRuntime.jsx(Sidebar.Provider.make, …)

Under React Server Components, a server component can only use a client module's top-level exports. Sidebar.Provider is already a client reference, so reading .make on it fails ("You cannot dot into a client module from a server component"). Nested components from "use client" files therefore can't be used from server components (e.g. Next.js App Router).

Previous attempts:

  • #8293: PoC rewriting JSX component paths to flat exports.
  • #8402: @res.hoistedFunction plus a .cmj table of hoisted exports, as the first step.
  • #8886: automatic hoisting of nested components on top of #8402.

Why the current approach is fragile

In #8402/#8886, whether a flat export exists depends on the producer's implementation, and it is recorded only in the producer's .cmj. Consumers compile against the .cmi, and both build systems rebuild dependents only when the .cmi changes. With a .resi, an edit that touches only the implementation (e.g. adding let make = React.memo(make)) removes Provider$make while the .cmi stays byte-identical. Already-compiled consumers then read undefined and React throws "Element type is invalid". The same applies to adding or removing @res.hoistedFunction in a .res file.

Most other issues found in the review of #8886 follow from the same design: components that are silently skipped, the JsxExternal rewrite (lost @deprecated/@genType, false reanalyze reports, rename bug, lost imports), property-read aliases that defeat tree-shaking, and linear lookups in the .cmj table. See https://github.com/rescript-lang/rescript/pull/8886#pullrequestreview-5562112336 for details.

Proposal

Use one fixed naming rule that both sides derive from the signature only:

Every make in a nested module that is visible in the file's final signature is also exported as A$B$make.

  • Producer: walk the signature after the .resi has been applied. For each such make, add export { <existing binding> as A$B$make }. This adds no statement, so tree-shaking is unaffected. Only a module with no static binding (a functor application or an unpacked first-class module) gets a const A$B$make = A.B.make fallback. Relative externals are covered because the producer already exposes them as values. Package externals are unaffected because consumers import them directly.
  • Consumer: compile Global.A.B.make as Global.A$B$make, based purely on the shape of the path. No .cmj lookup.
  • Clash: module A$B = {let make} next to A.B.make would both map to A$B$make. Report a compile error in the producer.
  • Remove @res.hoistedFunction, the .cmj table of hoisted exports, and the related lookup code. They have only shipped in v13.0.0-alpha.6.

The export set then changes exactly when the .cmi changes, so rebuilding when the .cmi changes stays correct. No marker on the component definition, no identity checks, and no gating on "use client" are needed.

Open questions

  1. Rewrite every nested .make read, or only JSX element types? Rewriting every read is simpler and also covers components passed as props. Restricting it to JSX element types needs a marker attribute from the JSX transform. Either way, jest.spyOn(Lib.Trigger, "make") and vi.mock factories written against the module object would no longer intercept, which should get a changelog note.
  2. Dynamic imports: module D = await Lib binds a module namespace, so D.Provider.make could become D.Provider$make as well.
  3. To verify: unused extra export names are free in esbuild/Rolldown/webpack/Turbopack, and the consumer-side path rewrite works with cross-module optimization both on and off.
Langage dominant
ReScript
Étoiles
7.5k
Forks
483
Merge moyen
1 j 2 h
PR mergées (30 j)
147

Préparer son environnement

Ouvrir dans Codespaces

Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de rescript-lang/rescript

Toutes les issues de rescript-lang/rescript

Issues similaires

Plus d'issues Compilers

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.