TypeScript 踩坑之旅

13,515

前提

因为最近在做一个 TypeScript NPM 第三方库,在网上看到一篇简单的搭建流程,于是打算翻译一下 - 译文。在跟着文中步骤搭建的时候踩了一些坑,在此记录一下。本人 TypeScript 新手,如若有理解不对的地方,欢迎评论👏。

问题描述

照着文章步骤一步步搭建的时候,当第一次执行 npm run build 时,能正常运行;下载完 jest 之后再次运行 npm run build 报如下错

tsc node_modules/@types/babel__template/index.d.ts:16:28 — error TS2583: Cannot find name ‘Set’. Do you need to change your target library? Try changing the lib compiler option to es2015 or later.

16 placeholderWhitelist?: Set;

Found 1 error.

我的 node, npm, tsc 的版本分别是:

node -v // v8.12.0
npm -v // v6.4.1
tsc -v // v3.4.5

项目代码:github.com/irenetang19…

Q1:为什么我在 tsconfig.json 中已经 exclude node_modules了,tsc 还是执行到 node_modules/@types 中去了?

Q2:为什么在 src 目录下新建一个 index.d.ts 声明文件不会被编译?

解答

  • 先回顾下 tsconfig.json

    {
     "compilerOptions": {
       "target": "es5",
       "module": "commonjs",
       "declaration": true,
       "outDir": "./lib",
       "strict": true
    },
     "include": ["./src"],
     "exclude": ["node_modules", "**/__tests__/*"]
    }
    
  • Answer 1

    基于上面的配置,即使设置了 exclude: ["node_modules"] 也不能阻止 tsc 编译 ./node_modules/@types 里的内容。原因摘自官网 TypeScript - types and typeRoots,如下:

    默认所有可见的 @types 包会在编译过程中被包含进来。 ./node_modules/@types 文件夹下以及它们子文件夹下的所有包都是可见的。也就是说, ./node_modules/@types/,../node_modules/@types/和 ../../node_modules/@types/ 等等。

    如果指定了 typeRoots,只有 typeRoots下面的包才会被包含进来。 比如:

    {
      "compilerOptions": {
          "typeRoots" : ["./typings"]
      }
    }
    

    这个配置文件会包含所有 ./typings 下面的包,而不包含 ./node_modules/@types 里面的包。

    如果指定了 types,只有被列出来的包才会被包含进来。 比如:

    {
      "compilerOptions": {
           "types" : ["node", "lodash", "express"]
      }
    }
    

    这个 tsconfig.json 文件将仅会包含 ./node_modules/@types/node,./node_modules/@types/lodash 和 ./node_modules/@types/express。 ./node_modules/@types/* 里面的其它包不会被引入进来。

    指定 "types": [] 来禁用自动引入 @types 包。

    注意,自动引入只在你使用了全局的声明(相反于模块)时是重要的。 如果你使用 import "foo" 语句,TypeScript 仍然会查找 node_modules 和 node_modules/@types 文件夹来获取 foo 包。

再来看看报的错 Cannot find name 'Set'。从上一个问题的回答中我们已经知道在当前 tsconfig.json 的配置下, ./node_modules/@types 下的声明文件在运行 tsc 的时候是会被编译的,之所以报 Can not find name 'Set' 是因为 Set 是 ES6 的语法特性,而 tsconfig.json 中指定的 "target": "es5",并且没有指定 lib 的话,默认的 lib 是 DOM,ES5,ScriptHost,所以 tsc 不能正确的编译 Set。

解决这个报错的其中一个简单的方法就是指定 "target": "es6" 或者 lib 加上 es6。但我觉得不应该为了第三方库缺少某个声明的问题而去修改项目中的 tsconfig.json,所以就有了第二种方式,在项目中添加一个声明文件,结果又出现了上面提到的 Q2。

  • Answer 2

    先看一下 src 文件夹:

    index.ts 是一个普通的 .ts 文件,tests 下是一些测试文件,index.d.ts 是我添加的声明文件,内容如下:

    declare interface Set<T> {}
    

    一开始我以为在 tsconfig.json 中已经 include src 了,所以 index.d.ts 应该会被编译,但是并没有;如果把它的文件名改成 test.d.ts 就被编译了,神奇!!!

    后来我在官网找到了这句话:

    需要注意编译器不会去引入那些可能做为输出的文件;比如,假设我们包含了index.ts,那么index.d.ts和index.js会被排除在外。 通常来讲,不推荐只有扩展名的不同来区分同目录下的文件。

    也就是说,因为我在 src 下已经有 index.ts 了,所以 index.d.ts 被排除了;当我把文件名改成 test.d.ts 之后又能被包含了。

其他

  • tsc 是否使用 tsconfig.json
    • 不带任何输入文件的情况下调用 tsc,编译器会从当前目录开始去查找 tsconfig.json文件,逐级向上搜索父目录。

    • 不带任何输入文件的情况下调用 tsc,且使用命令行参数 --project(或-p)指定一个包含 tsconfig.json 文件的目录。

    • 当命令行上指定了输入文件时,tsconfig.json 文件会被忽略。

  • tsconfig.json - target & lib
    • 如果 .ts 文件中使用了某些高于 target 指定的语法特性,那么就需要在 lib 中指定这些语法特性所在的 ES 版本。例如:tsconfig.json 中指定的 target 是 "es5",在 index.ts 文件中使用了 ES2015 的 Set 会提示如下错误:error TS2583: Cannot find name 'Set'。解决这个错误有三种办法:

    • 在 lib 中添加 "es6"

      {
        target: "es5",
        compilerOptions: {
          "lib": ["es6"]
       }
      }
      
    • 将 target 改成 es6

      {
        target: "es6"
      }
      
    • 添加一个声明文件,如上缺少 Set 声明,就在声明文件中写 Set 声明。注意文件名命名,不要踩了上面提到的坑。

      declare interface Set<T> {}
      

其他文章链接

TypeScript 踩坑之旅

[译文]一步步构建发布一个 TypeScript NPM 包

npm install package-lock.json 的更新策略

[译]浏览器语言首选项

Date.prototype.toLocaleString()