Skip to content

Commit 295067f

Browse files
Document genType tsconfig mapping rules
1 parent ea6af06 commit 295067f

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

docs/gentype-tsconfig-mapping.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# genType tsconfig Mapping Rules
2+
3+
This note defines the rules for issue #44: when adding ReScript to an
4+
existing TypeScript project, infer the ReScript and genType configuration from
5+
the project's effective `tsconfig.json` instead of asking the user to choose a
6+
module system manually.
7+
8+
## Research Summary
9+
10+
- ReScript v12 genType is configured through top-level `gentypeconfig` in
11+
`rescript.json`.
12+
- genType supports `gentypeconfig.module` values `esmodule` and `commonjs`.
13+
- genType supports `gentypeconfig.moduleResolution` values `node`, `node16`,
14+
and `bundler`.
15+
- ReScript's TypeScript integration currently requires `"in-source": true` and
16+
generated JS suffixes ending in `.js`, for example `.res.js`.
17+
- ReScript's TypeScript integration requires TypeScript `allowJs: true`.
18+
- ReScript's `bundler` genType mode requires TypeScript 5.0+ and
19+
`allowImportingTsExtensions: true`.
20+
- TypeScript `allowImportingTsExtensions` is only valid when `noEmit` or
21+
`emitDeclarationOnly` is enabled.
22+
- TypeScript `node16`, `node18`, `node20`, and `nodenext` module modes can emit
23+
CommonJS or ESM per file. For `.ts` and `.tsx` files, package.json `"type"`
24+
decides the format: `"module"` means ESM, otherwise CommonJS.
25+
- TypeScript `moduleResolution: "bundler"` models bundlers and does not require
26+
file extensions on relative imports.
27+
28+
## Effective Input
29+
30+
Read the effective TypeScript configuration, not only the raw root
31+
`tsconfig.json`.
32+
33+
1. Find `tsconfig.json` in the current project root.
34+
2. Resolve `extends` using TypeScript semantics before mapping values.
35+
3. Preserve the root project's `package.json` context.
36+
4. Read at least these fields:
37+
- `compilerOptions.module`
38+
- `compilerOptions.moduleResolution`
39+
- `compilerOptions.allowJs`
40+
- `compilerOptions.allowImportingTsExtensions`
41+
- `compilerOptions.noEmit`
42+
- `compilerOptions.emitDeclarationOnly`
43+
- `compilerOptions.jsx`
44+
- package.json `type`
45+
46+
Use a JSONC-aware parser or TypeScript's own config parser when implementing
47+
this. `tsconfig.json` can contain comments, trailing commas, and inherited
48+
settings.
49+
50+
If there is no `tsconfig.json`, or the effective config cannot be read, keep the
51+
current manual module prompt.
52+
53+
## ReScript Output Baseline
54+
55+
When genType is enabled for an existing TypeScript project, set:
56+
57+
```json
58+
{
59+
"package-specs": {
60+
"module": "<mapped module>",
61+
"in-source": true
62+
},
63+
"suffix": ".res.js",
64+
"gentypeconfig": {
65+
"module": "<mapped module>",
66+
"moduleResolution": "<mapped module resolution>",
67+
"generatedFileExtension": "<mapped generated file extension>"
68+
}
69+
}
70+
```
71+
72+
Use `.res.js` for both ESM and CommonJS genType setups. The current CLI behavior
73+
of using `.res.mjs` for ESM is fine for plain ReScript output, but it conflicts
74+
with the documented genType limitation that TypeScript integration currently
75+
supports suffixes ending in `.js`.
76+
77+
The mapped `gentypeconfig.module` should match `package-specs.module`.
78+
79+
## Module Format Mapping
80+
81+
Normalize `compilerOptions.module` and `package.json.type` to lowercase before
82+
mapping.
83+
84+
| TypeScript input | package.json `type` | ReScript `package-specs.module` | genType `module` | Notes |
85+
| --- | --- | --- | --- | --- |
86+
| `commonjs` | any | `commonjs` | `commonjs` | Straight CommonJS mapping. |
87+
| `es2015`, `es6`, `es2020`, `es2022`, `esnext` | any | `esmodule` | `esmodule` | Runtime-agnostic ESM output. |
88+
| `preserve` | any | `esmodule` | `esmodule` | Best fit for bundlers. Warn if the project relies on statement-level CommonJS preservation. |
89+
| `node16`, `node18`, `node20`, `nodenext` | `module` | `esmodule` | `esmodule` | `.ts` and `.tsx` files emit as ESM in this package scope. |
90+
| `node16`, `node18`, `node20`, `nodenext` | absent or `commonjs` | `commonjs` | `commonjs` | `.ts` and `.tsx` files emit as CommonJS in this package scope. |
91+
| `amd`, `umd`, `system`, `none` | any | none | none | Unsupported by ReScript's two genType module formats. Fall back to manual prompt or abort genType setup. |
92+
| missing or unknown | any | none | none | Do not guess. Fall back to the current manual prompt. |
93+
94+
For Node dual-format projects, warn when the project contains `.mts` or `.cts`
95+
files, or package subdirectories with their own package.json `type`. ReScript
96+
has one project-level module output setting, so it cannot exactly mirror a mixed
97+
per-file TypeScript module graph.
98+
99+
## Module Resolution Mapping
100+
101+
Normalize `compilerOptions.moduleResolution` to lowercase. If it is missing,
102+
infer the effective TypeScript default only when the `module` setting makes the
103+
default unambiguous.
104+
105+
| Effective TypeScript module resolution | genType `moduleResolution` | Notes |
106+
| --- | --- | --- |
107+
| `bundler` | `bundler` | Also requires `allowImportingTsExtensions: true`. |
108+
| `node16` | `node16` | Exact documented genType mapping. |
109+
| `nodenext` | `node16` | ReScript v12 documents NodeNext as a use case but only exposes `node16`; warn that this is an approximation. |
110+
| `node`, `node10` | `node` | Legacy Node/CommonJS resolver. |
111+
| `classic` | none | Unsupported for genType setup. Fall back to manual prompt or abort genType setup. |
112+
| missing with `module: "preserve"` | `bundler` | TypeScript uses bundler-style resolution for preserve-mode bundled projects. |
113+
| missing with `module: "node16"`, `node18`, `node20`, or `nodenext` | `node16` | Use the Node ESM-compatible genType mode. Warn for `nodenext` as above. |
114+
| missing with `commonjs` | `node` | Legacy CommonJS default. |
115+
| missing with ESM-family module values | none | Do not invent `node`; use TypeScript's effective value if the parser provides one, otherwise prompt. |
116+
| unknown | none | Do not guess. Fall back to manual prompt. |
117+
118+
## TypeScript Config Edits And Warnings
119+
120+
### `allowJs`
121+
122+
If `compilerOptions.allowJs` is not `true`, warn and offer to set it to `true`.
123+
ReScript's TypeScript integration requires this so TypeScript can accept the JS
124+
files emitted by ReScript and imported by generated genType files.
125+
126+
### `allowImportingTsExtensions`
127+
128+
If the mapped genType module resolution is `bundler`:
129+
130+
1. If `allowImportingTsExtensions` is already `true`, no action is needed.
131+
2. If `noEmit: true` or `emitDeclarationOnly: true`, offer to set
132+
`allowImportingTsExtensions: true`.
133+
3. Otherwise, warn that TypeScript does not allow
134+
`allowImportingTsExtensions` unless `noEmit` or `emitDeclarationOnly` is
135+
enabled. Continue only after confirmation, or fall back to manual setup.
136+
137+
### Generated File Extension
138+
139+
Use:
140+
141+
| Signal | `gentypeconfig.generatedFileExtension` |
142+
| --- | --- |
143+
| `compilerOptions.jsx` is present | `.gen.tsx` |
144+
| React, Next.js, or another JSX framework is detected in package.json dependencies | `.gen.tsx` |
145+
| No JSX signal | `.gen.ts` |
146+
147+
If we want to minimize behavior differences from ReScript's documented default,
148+
omit `generatedFileExtension` when no JSX signal is available and accept the
149+
compiler default of `.gen.tsx`. If we want the least surprising setup for
150+
non-React TypeScript projects, set `.gen.ts` explicitly.
151+
152+
## Fallback Rules
153+
154+
Fall back to the current manual module prompt when:
155+
156+
- There is no readable `tsconfig.json`.
157+
- The effective TypeScript config cannot be resolved.
158+
- `module` is missing or unsupported.
159+
- `moduleResolution` is `classic`, unknown, or conflicts with the module mode.
160+
- The project is a mixed Node dual-format project that cannot be represented by
161+
one ReScript project-level module setting.
162+
- The project uses `module: "preserve"` and package contents suggest meaningful
163+
CommonJS-style exports that ReScript cannot preserve statement-by-statement.
164+
165+
When falling back, still surface the detected values in the prompt so the user
166+
can make an informed choice.
167+
168+
## Implementation Notes
169+
170+
- Prefer loading the target project's `typescript` package and using its config
171+
parser when available. That handles JSONC and `extends` correctly.
172+
- If TypeScript is not available yet, use a JSONC parser and implement only the
173+
documented `extends` behavior needed for `compilerOptions`, or ask the user to
174+
install dependencies first.
175+
- Do not copy the existing Next.js template's legacy `gentypeconfig` shape
176+
(`language`, `shims`) into the add-to-existing flow. The v12 manual documents
177+
the current compiler-integrated fields used above.
178+
- Keep generated JS ignore behavior tied to the selected suffix. For genType,
179+
the suffix is always `.res.js`, so the gitignore prompt should refer to
180+
generated `.res.js` files.
181+
182+
## Sources
183+
184+
- ReScript build configuration, especially `package-specs`, `suffix`, and
185+
`gentypeconfig`: https://rescript-lang.org/docs/manual/build-configuration/
186+
- ReScript TypeScript integration setup, `allowJs`, module resolution, and
187+
genType limitations: https://rescript-lang.org/docs/manual/typescript-integration/
188+
- TypeScript `module` option and Node dual-format behavior:
189+
https://www.typescriptlang.org/tsconfig/module.html
190+
- TypeScript module resolution reference:
191+
https://www.typescriptlang.org/tsconfig/moduleResolution.html
192+
- TypeScript module reference for Node and bundler resolution:
193+
https://www.typescriptlang.org/docs/handbook/modules/reference.html
194+
- TypeScript `allowImportingTsExtensions` constraints:
195+
https://www.typescriptlang.org/tsconfig/allowImportingTsExtensions.html
196+
- TypeScript `extends` behavior:
197+
https://www.typescriptlang.org/tsconfig/extends.html

0 commit comments

Comments
 (0)