|
| 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