◆

tsconfig noEmit 与 noEmitOnError 配置详解

2023-02-02 01:370

noEmit

作用:不生成输出文件,但文件报错会有提示信息。

noEmitOnError

作用:报错时不生成输出文件。

在执行 tsc 进行编译时,如果 noEmitOnError 配置为 true 时,如果编译的某个文件中有 ts 报错, 则 ts 将不会产生输出文件。

两者的关键差别

它们的名字很像,作用却是两个维度的:

  • noEmit: true 是永远不产出,不管有没有报错。tsc 退化成一个纯类型检查器。
  • noEmitOnError: true 是有条件产出,没报错就正常写文件,有报错就一个都不写。

noEmitOnError 默认是 false,这一点常常出人意料——TypeScript 默认是「报错也照样产出 JS」的。因为类型错误大多不影响生成的 JS(类型注解本来就要被擦掉),tsc 的设计哲学是尽量让你拿到可运行的产物。所以 CI 里如果只跑 tsc 就发布,类型错误是拦不住的,必须显式开 noEmitOnError,或者把类型检查和构建拆成两步。

noEmitOnError 的行为是全局的、原子的:只要项目里任何一个文件有错,所有输出文件都不写(增量编译时已存在的旧文件也不会被更新)。不是「只跳过出错的那个文件」。

noEmit 的典型用法

现在绝大多数前端项目的 JS 产物是 esbuild / SWC / Babel 生成的,它们只做语法转换、直接丢弃类型,速度快但完全不做类型检查。所以 tsc 的职责就只剩类型检查了:

{
  "compilerOptions": {
    "noEmit": true,
    "strict": true
  }
}
{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "build": "vue-tsc --noEmit && vite build"
  }
}

Vite 官方模板里的 vue-tsc --noEmit && vite build 就是这个思路:先让 tsc(vue-tsc 是它的 Vue SFC 增强版)跑一遍类型检查,通过了再让 Vite 打包。这样类型错误会中断构建,同时打包仍然享受 esbuild 的速度。

命令行 tsc --noEmit 和配置文件里写 "noEmit": true 等价,命令行参数优先级更高。项目里已经配了 noEmit: true 时再加命令行参数是冗余的,但也无害。

只想产出类型声明

noEmit: true 会连 .d.ts 一起不产出,这对发布 npm 包是个问题——包需要类型声明。这时组合是:

{
  "compilerOptions": {
    "declaration": true,
    "emitDeclarationOnly": true,
    "outDir": "dist/types"
  }
}

emitDeclarationOnly 是「只产出 .d.ts,不产出 .js」,正好填上 noEmit 和完整编译之间的空档。注意它要求 declaration 或 composite 至少开一个,否则 tsc 会报 Option 'emitDeclarationOnly' cannot be specified without 'declaration' or 'composite'。

另外 noEmit 和 emitDeclarationOnly 同时为 true 会直接报冲突错误,二者互斥。

noEmit 和 isolatedModules / project references 的坑

noEmit: true 时 --build 模式和 project references 会出问题。因为 project references 依赖被引用项目产出的 .d.ts 和 .tsbuildinfo 来做增量判断,noEmit 把这些都掐掉了,tsc --build 会报 Referenced project may not disable emit。

解决办法是给被引用的子项目单独一份不带 noEmit 的配置,或者用 TypeScript 5.6 引入的 noCheck 反过来做——只产出不检查:

{
  "compilerOptions": {
    "noCheck": true
  }
}

noCheck 和 noEmit 恰好是一对镜像。把两者拆到不同配置文件里,就能实现「检查」和「产出」两条完全独立、可并行的流水线,大仓库里能省不少时间。

相关的几个 emit 控制项

{
  "compilerOptions": {
    // 不产出 sourcemap 之外的注释
    "removeComments": true,
    // 不产出 helper 函数(__extends 等),改为从 tslib 导入
    "importHelpers": true,
    // 不产出被 declare 标记的文件对应的 js(默认行为,一般不用管)
    "noEmitHelpers": false,
    // 输出目录,不设则 js 和 ts 放在一起
    "outDir": "./dist",
    // 保留 ts 文件的目录结构基准
    "rootDir": "./src"
  }
}

noEmitHelpers 和 importHelpers 容易搞混:前者是「不生成 helper,也不导入」,需要你自己保证全局有这些函数;后者是「不生成 helper,改成从 tslib 导入」。发布库时用 importHelpers 配 tslib 依赖,能避免每个文件都内联一份相同的 helper 代码。

编辑器里的报错和 tsc 的报错可能不一致

这是个实际会遇到的困扰:VS Code 里干干净净,跑 tsc --noEmit 却一堆错。原因通常是编辑器只检查当前打开的文件及其依赖,而 tsc 检查 include / files 匹配到的全部文件。没打开过的文件里的错误,编辑器不会主动报。

VS Code 里可以用 TypeScript: Restart TS Server 强制重建,或者装个能跑全项目检查的插件。但最可靠的做法还是在 pre-commit 钩子里跑一次 tsc --noEmit,把这层保证放在流程里而不是靠编辑器。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEtsconfig noEmit 与 noEmitOnError 配置详解加载中