- 开发工具
- IDE
- 语言服务
【免费下载链接】language-tools
⚡ High-performance Vue language tooling based-on Volar.js
本篇技术指南以 language-tools 仓库中的真实测试工作区 test-workspace/tscBuild/vitepress 为核心,讲解如何通过vueCompilerOptions.vitePressExtensions让 vue-tsc 对 VitePress 的.md文件执行完整的 TypeScript 类型检查(包括<script setup lang="ts">脚本与{{ msg }}模板插值)。读完本文,你将掌握vitePressExtensions的配置方法、vue-tsc --build的项目级校验流程,以及 @vue/language-core 内部file-md插件把 Markdown 转换为 Vue SFC 的底层实现原理。
从一个最小工作区认识 VitePress 类型检查
仓库的tscBuild目录是一个专用于验证vue-tsc --build行为的测试工作区,其中 vitepress 子项目只有三个文件:
main.md(待类型检查的 Markdown 文件):
# Title <script setup lang="ts"> const msg = 1; </script> {{ msg }}main.vue(作为对照的等价 Vue 单文件组件):
<template> <div>{{ msg }}</div> </template> <script setup lang="ts"> const msg = 1; </script>tsconfig.json(启用 VitePress 扩展的关键配置):
{ "extends": "../../tsconfig.base.json", "vueCompilerOptions": { "vitePressExtensions": [".md"] }, "include": ["**/*"] }这个最小工作区清晰地展示了一个事实:在 Vue 生态中,{{ msg }}这类插值表达式不只出现在.vue文件的<template>里,也大量出现在 VitePress 文档站点的 Markdown 正文中。VitePress 文档页面常常在 Markdown 顶部编写<script setup lang="ts">定义状态与逻辑,并在正文中通过插值渲染。只要在tsconfig.json的vueCompilerOptions中声明"vitePressExtensions": [".md"],vue-tsc就会把这些.md文件也纳入类型检查范围,让脚本中的类型错误和模板插值中的错误在 CI 阶段即可暴露。
在你的项目中启用.md类型检查
安装依赖
vue-tsc是tsc的包装器(wrapper),使用前需要同时安装 TypeScript(peerDependencies 要求typescript >= 5.0.0,见 packages/tsc/package.json):
npm install vue-tsc typescript --save-dev配置vitePressExtensions
在项目根目录的tsconfig.json(或 VitePress 专用的 tsconfig)中加入:
{ "extends": "./tsconfig.base.json", "vueCompilerOptions": { "vitePressExtensions": [".md"] }, "include": ["**/*"] }参数说明(对照 @vue/language-core README 与默认值实现 packages/language-core/lib/compilerOptions.ts):
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
vitePressExtensions | string[] | [] | 需要被当作 VitePress Markdown 处理的文件扩展名列表 |
extensions | string[] | ['.vue'] | 需要被当作 Vue SFC 处理的扩展名 |
petiteVueExtensions | string[] | [] | 需要被当作 Petite Vue HTML 处理的扩展名(如['.html']) |
vitePressExtensions的默认值为空数组,即默认情况下.md文件不会被类型检查;只有显式声明后才会生效。仓库在 test-workspace/tscBuild/petiteVue/tsconfig.json 中展示了同族配置petiteVueExtensions: [".html"]的用法,可对照理解。
在 package.json 中接入脚本
参照 packages/tsc/README.md 的推荐写法:
{ "scripts": { "type-check": "vue-tsc --noEmit", "build:types": "vue-tsc --declaration --emitDeclarationOnly" } }运行npm run type-check即可对包括.md在内的所有声明扩展名文件执行纯类型检查(--noEmit不产出任何文件);--build模式则配合项目引用(references)做增量构建,仓库的tscBuild工作区正是后者的验证场景。
底层原理:file-md插件如何把 Markdown 变成 SFC
vue-tsc之所以能理解.md文件,靠的是 @vue/language-core 内置的文件解析插件file-md,实现位于 packages/language-core/lib/plugins/file-md.ts。
第一步:语言 ID 识别
在 getLanguageId 中,插件通过文件名后缀匹配vitePressExtensions:
getLanguageId(fileName) { if (vueCompilerOptions.vitePressExtensions.some(ext => fileName.endsWith(ext))) { return 'markdown'; } }随后isValidFile校验语言 ID 是否为markdown。也就是说,file-md只负责vitePressExtensions中声明的那批文件,与处理.vue的 file-vue.ts、处理.html的 file-html.ts 三者互不干扰。
第二步:掩码(mask)无关内容,提取脚本块
parseSFC2 是核心逻辑,它用一组精心设计的正则表达式先把“不应当被当作模板内容解析”的部分替换为等长空格,再抽取script/style块:
frontmatterRE:---包裹的 Frontmatter 区域(#L8);codeblockRE:``` 或$$包裹的代码块(#L9);codeSnippetImportRE:<<<形式的代码片段导入(#L10);sfcBlockRE:<script>/<style>及其闭合标签(#L11);inlineCodeRE/angleBracketRE:行内代码与xxx:xxx形式的尖括号标签,用于标记“歧义区间”,避免把 Markdown 语法误判为 HTML(#L14-L15);htmlTagRE/interpolationRE:真正要保留语义的 HTML 标签与{{ }}插值(#L12-L13)。
流程分三步:先掩掉 frontmatter、代码块与代码导入(#L36-L38);再从matchAll(sfcBlockRE)中取出script/style块并同样掩掉(#L56-L63);最后把剩下的正文用<template>\n…\n</template>包裹起来(#L72-L74),交给 parseSfc.ts 走标准的 SFC 解析。此时最初的:
# Title {{ msg }}就被转换成了一个「模板 + 脚本」结构,等价于前面看到的那种带<script setup lang="ts">的 Vue SFC。
第三步:SourceMap 回映射
由于虚拟代码(转换后的 SFC 文本)与源 Markdown 的字节偏移并不一致,file-md会用buildMappings构建映射表,再通过SourceMap把每个 block 的loc起始/结束偏移转换回源文件位置(#L76-L110)。这正是类型诊断(error)能正确报告到main.md原始行列的关键——你在编辑器或 CI 输出里看到main.md(3,1)这类定位,就来自这一步。
在 vue-tsc 命令行中如何生效
vue-tsc的入口 packages/tsc/index.ts 展示了扩展名配置如何被动态接管:
- 启动时维护一个
runExtensions集合,初始只有['.vue']; - 运行
runTsc前先解析 tsconfig:从configFilePath读取vueCompilerOptions(若没有 tsconfig 则按空配置解析,index.ts#L17-L20); - 调用 getAllExtensions,把
extensions、vitePressExtensions、petiteVueExtensions三组配置去重合并; - 若合并结果中出现新的扩展名,就将其加入
runExtensions并抛出一个标记性的retryToken重新运行(index.ts#L31-L36),外层while (true)循环捕获后重试,直到扩展名集合稳定; - 之后通过
createVueLanguagePlugin创建 Vue 语言插件,把.vue/.md/.html文件统一转换为 TypeScript 虚拟代码再交给tsc。
这也是 packages/tsc/README.md 中“vue-tsc自动从vueCompilerOptions.extensions读取文件类型,默认['.vue'];若配置了vitePressExtensions或petiteVueExtensions,这些扩展名也会被一并处理”这句话的实现出处。
测试验证:tscBuild工作区与--build
tscBuild项目用「项目引用」把三个独立 tsconfig 组合起来(见 test-workspace/tscBuild/tsconfig.json):
{ "references": [ { "path": "./petiteVue/tsconfig.json" }, { "path": "./vitepress/tsconfig.json" }, { "path": "./vue/tsconfig.json" } ], "files": [] }配套的测试用例在 packages/tsc/tests/build.spec.ts:
test(`vue-tsc --build`, () => { expect(() => runTsc('tscBuild')).not.toThrow(); });它通过 packages/tsc/tests/utils.ts 以--build <tscBuild目录> --pretty false的形式真实调用vue-tsc,校验整套 Markdown / HTML / Vue 混合工作区在--build模式下不会抛错。注意tscBuild各子项目都继承自 test-workspace/tsconfig.base.json,后者统一设置了"vueCompilerOptions": { "target": 99 }(即对最新 Vue 版本目标做代码生成)。也就是说,仓库用自动化测试持续保证:配置了vitePressExtensions后,.md文件在--build与--noEmit两种模式下都能被正确解析与类型检查。
在真实 VitePress 项目中的实战建议
基于上述配置与实现,把这项能力迁移到自己的 VitePress 项目时,只需三点:
- 在 tsconfig 中声明
"vitePressExtensions": [".md"](与"include": ["docs/**/*.md"]配合可缩小检查范围); - 把
vue-tsc --noEmit或vue-tsc --build接入package.json的 scripts,并纳入 CI; - 注意
file-md的解析边界:Frontmatter、代码块、<<<代码片段导入会被掩码,不会进入模板解析;行内代码与xxx:xxx形式的内容会被识别为歧义区间而跳过;真正被类型检查的是 Markdown 正文中的 HTML 标签与{{ }}插值,以及<script setup lang="ts">脚本本身。
这样,VitePress 文档里的插值表达式、<script setup>中的类型定义乃至事件绑定,都能与.vue组件一样获得完整、可定位到源文件行列的类型诊断,而不必等到构建运行时才发现问题。
参考文件
- 测试工作区:test-workspace/tscBuild/vitepress/main.md、test-workspace/tscBuild/vitepress/main.vue、test-workspace/tscBuild/vitepress/tsconfig.json
- 底层实现:packages/language-core/lib/plugins/file-md.ts、packages/language-core/lib/languagePlugin.ts、packages/language-core/lib/compilerOptions.ts
- CLI 入口:packages/tsc/index.ts、packages/tsc/README.md
- 测试用例:packages/tsc/tests/build.spec.ts、packages/tsc/tests/utils.ts
- 开发工具
- IDE
- 语言服务
【免费下载链接】language-tools
⚡ High-performance Vue language tooling based-on Volar.js
相关推荐
@vue/cli-plugin-typescript 插件深入解析:ts-loader 构建链、类型检查与并行化配置实战
@vue/cli plugin typescript 插件深入解析:ts loader 构建链、类型检查与并行化配置实战 本文以 vue cli 仓库中 @vu
前端开发工具构建工具TypeScript类型收窄实战:使用Array.isArray进行数组类型检查
TypeScript类型收窄实战:使用Array.isArray进行数组类型检查 类型收窄概述 在TypeScript开发中,类型收窄(Type Narrowi
文档教程知识库wtfpython静态分析:使用pyright进行类型检查
wtfpython静态分析:使用pyright进行类型检查 引言:为什么Python需要静态类型检查? Python作为一门动态类型语言,以其灵活性和易用性著称
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考