@rm30/tsconfig
v0.1.0
Published
Shared RM30 TypeScript configurations — base, expo, next, node-lib.
Readme
@rm30/tsconfig
Shared TypeScript configurations. Four, matching the four shapes of code in the workspace.
pnpm add -D @rm30/tsconfigDeclare it in every package that extends it, not only at the monorepo root. Root-only
works today because both Expo monorepos set nodeLinker: hoisted, which flattens
node_modules so a nested package resolves upward into it. A project on pnpm's default
strict linker would not, and a package should declare what it uses regardless.
| Config | For |
|---|---|
| @rm30/tsconfig/base.json | Everything else builds on this. Strictness and interop only — no emit decisions, no JSX. |
| @rm30/tsconfig/next.json | A Next.js app. |
| @rm30/tsconfig/expo.json | An Expo app — see the note below, it is used differently. |
| @rm30/tsconfig/node-lib.json | A Node package that emits declarations (Firebase functions, a published lib). |
include, exclude and paths stay in the project
None of these configs set include or exclude, and that is not an oversight.
TypeScript resolves relative paths against the config file they are written in, so an
include: ["src"] shipped from here would resolve inside
node_modules/@rm30/tsconfig/ and match nothing. The same goes for paths, which points
at the project's own directories anyway.
So every project declares its own file set:
{
"extends": "@rm30/tsconfig/next.json",
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}Expo app
Expo ships its own base, and it is not optional — it carries the React Native type resolution. So an Expo app extends both, ours last:
{
"extends": ["expo/tsconfig.base", "@rm30/tsconfig/expo.json"],
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
}
}Array extends needs TypeScript 5.0+; later entries win.
expo.json deliberately does not extend base.json. It would have to reach
expo/tsconfig.base from inside node_modules/@rm30/tsconfig/, where pnpm's symlinked
store gives it no view of the consuming project's expo — so the extend would resolve in
development and break for anyone else. Composing the two in the project keeps resolution
where the packages actually are.
Node library
{
"extends": "@rm30/tsconfig/node-lib.json",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
}
}outDir and rootDir stay local — they are layout, not policy.
Note that most internal packages should not use this. A package consumed only inside
its own monorepo should be source-direct: main and types pointing at src/index.ts,
no build step at all. Metro, Next and tsup all read TypeScript from source, so compiling
buys nothing and costs a build that CI must run before anything type-checks. node-lib is
for the cases that genuinely emit — Firebase functions, or a package published to npm.
What base sets
target/lib ES2022, module ESNext, moduleResolution bundler, strict,
isolatedModules, esModuleInterop, allowSyntheticDefaultImports, resolveJsonModule,
forceConsistentCasingInFileNames, skipLibCheck.
moduleResolution: bundler is deliberate. The old node (node10) mode is deprecated as
of TypeScript 6 and errors without an ignoreDeprecations escape hatch, and every RM30
package is either bundled or consumed by a bundler, so bundler is simply the accurate
description.
