skip to content

In a create-vue TypeScript project, why does tsconfig.json list no files but reference tsconfig.app, tsconfig.node and tsconfig.vitest, and what does npm run build run?

level: seniorimportance: nice to knowfreq 13%

answer

  1. one root, several projects
  2. browser code, tool configs, tests
  3. different globals per environment
  4. type-check and build in parallel
  5. the alias lives in two places

basics

~20 s

The root tsconfig.json only references separate projects: app code for the browser, tool configs for Node, and tests for jsdom, each with its own globals. npm run build runs vue-tsc --build and vite build in parallel.

solid answer

~30 s

create-vue writes a **solution-style** `tsconfig.json`: `files: []` plus `references` to `tsconfig.node.json`, `tsconfig.app.json` and, with Vitest, `tsconfig.vitest.json`. Each covers one environment: `tsconfig.app.json` extends `@vue/tsconfig/tsconfig.dom.json` for `src` and `.vue` files and excludes tests; `tsconfig.node.json` extends `@tsconfig/node24` for `vite.config.*`, `vitest.config.*` and other tool configs with `noEmit`; `tsconfig.vitest.json` extends the app config for `src/**/__tests__/*` with Node and jsdom types. `npm run build` is `run-p type-check "build-only {@}" --`: `vue-tsc --build` checks every referenced project while `vite build` bundles. The `@/*` alias is declared both in `vite.config` and in `tsconfig.app.json` paths.

code

json · 8 lines
json
{
  "files": [],
  "references": [
    { "path": "./tsconfig.node.json" },
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.vitest.json" }
  ]
}

go deeper

for a junior

Recall that npm run build type-checks and bundles, and that the @ alias points at src.

for a middle

Explain the three projects the root references, which files each includes and why tests and tool configs are separate.

for a senior

Show the parallel build script, how arguments reach vite build, and the alias drift and missing-include mistakes you would catch in review.

for a principal

Decide how far teams may diverge from the generated TypeScript layout, and which settings, such as noUncheckedIndexedAccess, become house policy.

## Why one tsconfig is not enough A Vue project contains code that runs in **different environments**: - application code in the browser, with DOM types and `.vue` files; - tool configuration such as `vite.config.ts`, which runs in Node; - unit tests, which run in Node with a simulated DOM from `jsdom`. One `tsconfig.json` would have to include DOM and Node types everywhere, so browser code could call Node APIs without an error. create-vue instead writes a **solution-style** root: `files: []` and a list of `references`, one per environment. Each referenced file is a separate TypeScript project. ## The generated files | File | Extends | Includes | Notable settings | |---|---|---|---| | `tsconfig.json` | nothing | no files | `references` to the others | | `tsconfig.app.json` | `@vue/tsconfig/tsconfig.dom.json` | `env.d.ts`, `src/**/*`, `src/**/*.vue`; excludes `src/**/__tests__/*` | `paths` for `@/*`, `noUncheckedIndexedAccess: true` | | `tsconfig.node.json` | `@tsconfig/node24` | `vite.config.*`, `vitest.config.*`, `cypress.config.*`, `playwright.config.*`, `eslint.config.*` | `module: preserve`, `moduleResolution: bundler`, `types: ['node']`, `noEmit` | | `tsconfig.vitest.json` | `./tsconfig.app.json` | `src/**/__tests__/*`, `env.d.ts` | `lib: []`, `types: ['node', 'jsdom']` | Each project writes its `.tsbuildinfo` under `node_modules/.tmp`, so incremental checks stay out of the repository root. `env.d.ts` references `vite/client`, which types `import.meta.env` and asset imports. ## What npm run build runs The TypeScript layer rewrites the build scripts: ```json { "build": "run-p type-check \"build-only {@}\" --", "build-only": "vite build", "type-check": "vue-tsc --build" } ``` 1. `run-p` from `npm-run-all2` starts both scripts **in parallel**. 2. `type-check` runs `vue-tsc --build`, which follows the root's references and checks every project, including `.vue` templates. 3. `build-only` runs `vite build`, which transpiles without type-checking. 4. `{@}` forwards any arguments given after `--` to `build-only`, so `npm run build -- --mode staging` reaches Vite. If either fails, the build fails. `npm run dev` runs only Vite, so type errors surface in the editor and at build time, not in the dev server. ## The alias in two places The `@` import alias is declared twice: `vite.config` maps `@` to `./src` for bundling, and `tsconfig.app.json` maps `@/*` to `./src/*` for type-checking. Changing one without the other gives an app that builds but does not type-check, or the reverse. ## Where the types and tools come from - **`@vue/tsconfig`** supplies the browser-side base with Vue-friendly compiler options; **`@tsconfig/node24`** supplies the Node base for tool configs. - **`typescript`** is pinned to `~6.0.0` and **`vue-tsc`** to `^3.3`, so type-check behaviour only changes when you bump them. - **`env.d.ts`** holds a single reference to `vite/client`, and it is included by the app and test projects. - **End-to-end folders** get their own configs: Playwright's `e2e/tsconfig.json` and Cypress's `cypress/tsconfig.json`. The root does not reference them, so `vue-tsc --build` leaves them to the test runner; only Cypress Component Testing adds a referenced `tsconfig.cypress-ct.json`. ## Mistakes worth catching - Moving a shared helper outside `src` without adding it to an `include`: the app project no longer checks it, and imports of it are only checked as far as module resolution reaches. - Adding `"include": ["src"]` to the root `tsconfig.json`: the root is meant to hold only references, and the app project already covers `src`. - Putting a new tool config, such as a script in the project root, outside every `include`: it is then checked by no project. - Deleting `tsconfig.vitest.json` and wondering why test files lose `jsdom` types. - Running `vite build` directly in CI and shipping type errors, because the type check only lives in the `build` script.

  • Why does tsconfig.vitest.json exclude nothing while tsconfig.app.json excludes the tests?
    Each file belongs to exactly one project. The app project excludes `__tests__` so test-only globals never leak into application code; the test project includes only the tests and reaches the application code they import through module resolution, with Node and jsdom types added.
  • What does noUncheckedIndexedAccess add in the generated app config?
    Indexing an array or a record returns `T | undefined` instead of `T`, so code must handle a missing element. The generated comment calls it extra safety that may produce false positives; teams keep it or turn it off deliberately.

saying these in an interview costs you the question

  • The empty files array in tsconfig.json means nothing is type-checked.
  • npm run build only runs vite build.
  • vite.config.ts is type-checked by tsconfig.app.json.
  • The @ alias only needs to be declared in tsconfig paths.
  • Test files share the app project's types and need no separate config.