A single deep-dive on Content Mappers, which landed in TypeScript 7.1 Beta (2026-10-20). This isn't a release recap: it reads the design itself, meaning how TypeScript now shows non-TypeScript files like
.vue,.svelteand.mdxto its type checker.
vue-tsc worked by patching the JavaScript compiler from outside so it could read .vue files, and once that JS compiler was gone in 7.0 there was nothing left to patch..vue and 882 .ts files) dropped from 14.19s with vue-tsc 3 to 3.10s (4.6x), while the error count rose from 445 to 1,127. Speed comes bundled with a change in what counts as a type error, and that's why teams are poking at it right now.--runExternalCode flag should be mandatory. Every design decision is argued in public, which makes it unusually good teaching material.TypeScript can only type-check TS/JS syntax. Real components, though, are single-file components (SFCs) that mix template, script and style in one file. Three problems have to be solved.
tsc), the editor (LSP), and --build, --watch and incremental builds.Until now the answer was Volar. Volar converted each .vue file into virtual TS with a map back to the original, and either embedded TypeScript's language service or hooked into TypeScript in roundabout ways. The result was a split world where the editor's language server and the CLI type-check (vue-tsc) were separate systems, and because it leaned on TypeScript internals it broke with TS 7. The easiest way to understand Content Mappers is as the Volar model brought back as a first-class part of TypeScript itself.
Users add contentMappers to tsconfig.json as a sibling of compilerOptions, not inside it.
{
"compilerOptions": { "strict": true, "noEmit": true },
"contentMappers": [
{
"package": "@vue/content-mapper",
"extensions": [".vue"],
"options": { "target": 3.5 }
}
],
"include": ["src/**/*.ts", "src/**/*.vue"]
}
package is resolved as a Node.js module name. The mapper's own package.json declares how to launch it and which compiler options affect its output.
{
"name": "vue-content-mapper",
"version": "1.0.0",
"typescript": {
"contentMapper": {
"exec": ["node", "dist/server.js"],
"compilerOptions": ["module", "jsx", "jsxImportSource"],
"dynamicConfig": true
}
}
}
The key point is that exec can be any command. The npm package is just a way to carry a versioned identity; the mapper itself doesn't have to be Node.
When module resolution finds a file with a registered extension, tsc starts the mapper as a child process and talks JSON-RPC over STDIO. TypeScript always sends the requests; the mapper only answers. There are just four methods.