---
title: "TypeScript monorepo configuration: types and runtime boundaries"
description: Check ChatCrystal’s shared types, server, Vite client, and Electron settings, then reproduce an alias failure that passes compilation but fails at runtime.
image: "https://zengliangyi.com/og/blog/en/typescript-monorepo-configuration.png"
---

TypeScript files within one repository can run in different environments: a Node server, a Vite browser build, and an Electron main process. Shared types do not imply shared compiler options. This article checks ChatCrystal’s actual inheritance, output paths, and module resolution, then reproduces an alias boundary in a temporary project.

## Map the actual configuration relationships

The current [base configuration](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/tsconfig.base.json) sets ES2022, ESNext modules, bundler resolution, strict, isolatedModules, and other options. Only configurations extending it directly or indirectly inherit them. A root base file does not establish that every child project uses it.

| Scope | Actual relationship | Main execution boundary |

| --- | --- | --- |

| shared | Extends base; composite is true | Package entry points at types/index.ts, sharing type contracts |

| server | Extends base; rootDir is its parent directory | tsc emits JS executed by Node in production |

| client | Root references app and node; neither extends base | app targets the browser and node checks Vite configuration; both use noEmit |

| electron | Independent configuration; module and resolution are Node16 | Main-process output enters the packaging workflow |

The [root package.json](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/package.json) lists shared, server, and client workspaces; electron is absent from that list. Client project references point to app and node rather than the entire shared project. Setting shared’s composite does not automatically form a project-reference build graph for every consumer.

## Separate development execution from production output

[server/tsconfig.json](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/tsconfig.json) includes src and the parent’s shared/types. Consequently rootDir is the parent directory and outDir is server/dist, producing paths such as dist/server/src rather than simply moving src into dist.

The [server scripts](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/package.json) use tsx watch in development. Production builds clean dist, run tsc, then copy seed data; the start entry is dist/server/src/index.js. Running tsc alone does not replace the complete build command. Do not assume the type compiler always places runtime JSON and other resources in their required locations.

The server package declares type:module. Base module:ESNext controls emitted syntax, while package type and Node resolution also determine execution. Validate production output even when tsx can load a path during development; a working development server is insufficient acceptance evidence.

## Client aliases exist in two layers

The [client app configuration](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/client/tsconfig.app.json) uses ES2023, DOM types, react-jsx, bundler resolution, and noEmit. The [node project](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/client/tsconfig.node.json) checks Vite configuration using Node types, rather than copying the DOM setup. Both also have options such as verbatimModuleSyntax; reading an old base configuration misses these actual settings.

TypeScript paths maps `@/*` to src. [vite.config.ts](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/client/vite.config.ts) separately defines the build alias using import.meta.dirname. Type and bundler resolution each have configuration, so changing one does not establish that the other follows. The [official paths reference](https://www.typescriptlang.org/tsconfig/paths.html) explains that paths does not rewrite emitted import paths.

## Reproduce an alias counterexample in isolation

The client aliases typescript to the @typescript/typescript6 6.0.2 wrapper. Its underlying dependency resolves to the actual TypeScript 6.0.3 compiler in the [lockfile](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/pnpm-lock.yaml). The exercise directly installs that compiler version, avoiding confusion between wrapper and compiler versions. **It runs no ChatCrystal build or release script.** Create the following files in a new directory and use Node 24+.

Declare the teaching project’s ESM scope in `package.json`:



```json
{   "private"  :   true  ,   "type"  :   "module"   }
```



Install the pinned compiler:



```sh
npm   install   --ignore-scripts   --no-audit   --no-fund   typescript@6.0.3
```



Save `tsconfig.json`. Explicit rootDir determines output layout. This small project emits JS, unlike the client’s noEmit configuration.



```json
{  
    "compilerOptions"  : {  
      "target"  :   "ES2022"  ,  
      "module"  :   "ESNext"  ,  
      "moduleResolution"  :   "bundler"  ,  
      "strict"  :   true  ,  
      "rootDir"  :   "src"  ,  
      "outDir"  :   "dist"  ,  
      "paths"  : {   "@lesson/*"  : [  "./src/*"  ] }  
    },  
    "include"  : [  "src"  ]  
  }
```



Create src and save `src/values.ts`:



```ts
export   const   value   =   42  ;
```



Save `src/main.ts`, initially retaining a deliberate alias:



```ts
import   { value }   from   '@lesson/values.js'  ;  
  console.  log  (value);
```



Compile, then run:



```sh
node   node_modules/typescript/bin/tsc   --project   tsconfig.json  
  node   dist/main.js
```



Compilation should pass, leaving @lesson/values.js in emitted main.js. Ordinary Node cannot resolve this custom alias, so the second command is expected to fail with ERR_MODULE_NOT_FOUND. No bundler, loader, or import mapping is configured here.

Replace src/main.ts’s import with the relative path below. Running the same two commands again should compile and print 42:



```ts
import   { value }   from   './values.js'  ;  
  console.  log  (value);
```



Failure followed by recovery demonstrates a resolution boundary. It does not establish relative imports as the only solution for every project. A builder or runtime-supported mapping is another option, requiring its own configuration and verification.

## Electron’s Node16 mode is not a universal CommonJS switch

[electron/tsconfig.json](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/electron/tsconfig.json) uses Node16 modules and resolution, Node and Electron types, strict, and sourceMap. Node16 considers extensions and package scope when determining module form. Currently electron has no package.json of its own, and the root package lacks type:module. Existing ordinary .ts main-process files therefore occupy a CommonJS package scope. This observation does not mean Node16 always emits only CommonJS.

Before adding options, identify who checks types, emits files, resolves paths, and executes production output. Then inspect shared types, resource copying, and the real start entry. Strict checking catches some type errors; it does not validate external JSON, file existence, or the entire packaging lifecycle. This article runs only the temporary counterexample, without packaging the application or claiming that configuration changes must succeed.

Share:

##  Related Posts

[##  Five AI conversation parsers: retained fields and loss boundaries

Build an acceptance matrix from Claude Code, Codex, Cursor, Trae, and Copilot parsers, checking tool flags, thinking, order, and snapshot coverage.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#TypeScript

#Node.js](/en/blog/ai-conversation-parser-boundaries)    [##  AI SDK Provider Abstraction: Capabilities and Protocols

Test 7 ChatCrystal providers and AI SDK factories with offline mocks for Responses, Chat Completions, embeddings, and structured output rejection.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#Node.js

#TypeScript](/en/blog/ai-sdk-provider-abstraction)    [##  SourceAdapter internals: identity, content hashes, and reimport

Trace ChatCrystal’s registry, importer, and remote payload to verify identity, canonical hashes, metadata shortcuts, and parser-version boundaries.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#TypeScript

#Node.js](/en/blog/source-adapter-internals)

##  From writing to code

Explore ChatCrystal, developer tools and daily learning notes on my GitHub. Questions about the implementation are welcome.

[Visit my GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "TypeScript monorepo configuration: types and runtime boundaries",
  "description": "Check ChatCrystal’s shared types, server, Vite client, and Electron settings, then reproduce an alias failure that passes compilation but fails at runtime.",
  "url": "https://zengliangyi.com/en/blog/typescript-monorepo-configuration/",
  "image": "https://zengliangyi.com/images/blog/typescript-monorepo-configuration.svg",
  "datePublished": "2026-10-06T00:00:00.000Z",
  "dateModified": "2026-10-06T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/en/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/en/blog/typescript-monorepo-configuration/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://zengliangyi.com/en"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog",
      "item": "https://zengliangyi.com/en/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "TypeScript monorepo configuration: types and runtime boundaries",
      "item": "https://zengliangyi.com/en/blog/typescript-monorepo-configuration/"
    }
  ]
}
```
