【问题标题】:How to compile tsconfig.json into a config object using TypeScript API?如何使用 TypeScript API 将 tsconfig.json 编译成配置对象?
【发布时间】:2021-08-29 13:30:58
【问题描述】:

我正在寻找一个公开的官方 TypeScript API(最好是 4.x 版),用于将 tsconfig.json / jsconfig.json 文件解析为完整的 TypeScript 配置对象(通过“完整”我的意思是,它的所有属性都已填充in with values, – 要么显式取自 *.json,要么隐式取自已知默认值),如:

import ts from "typescript";

const tsconfig = await ts.parseConfigFile('./tsconfig.json');

tsconfig.compilerOptions.noEmit;
// false
// (or whatever it is set to in *.json)

typescript npm 包中是否有这种功能? 我自己在docs 和 IntelliSense 建议(由 Ctrl + 空格)。

我看到 another question 提出了同样的请求,但是 a) 没有得到答复,b) link in the comments 引用了 5.5-year-old solution,-从那时起,必须改变 API。


实际上,我只需要配置文件中的几个属性,因此编写几个帮助程序来解析*.json 文件并获取其内容相对容易。然而:

  • 还有更多奇特的用例(例如using Project References 和/或extending parent config object),其中此类解决方案不起作用;
  • 前面提到的 *.json 对象可能有 cmets 和尾随逗号,所以首先解析文件也不是一件容易的事;
  • 重写已知已经写好的东西会很奇怪。

【问题讨论】:

标签: typescript tsconfig


【解决方案1】:

我可能误解了你的问题,但是加载内容tsconfig.json文件并用JSON.parse解析它是否不够?

const fs = require('fs');

const txt = fs.readFileSync('./tsconfig.json');
try {
   const obj = JSON.parse(txt);
   console.log(obj);
} catch (e) {
   console.error(e);
}

【讨论】:

  • 如果配置文件是无效的 JSON,这将不起作用(可能是由于 cmets 和尾随逗号)
  • 嗯,标准 JSON 必须有效,并且不支持 cmets。如果您想支持 cmets,如果您想允许 cmets,可以使用自定义 JSON 解析器,如 npmjs.com/package/comment-json。如果你想检查错误,你可以使用 try/catch。
  • 如果您的问题是特定于 TypeScript 中构建的 tsconfig 解析器,我误解了。我只记得你已经用过的parseConfigFileTextToJson函数了。
【解决方案2】:

简答

在正确处理 tsconfig extends 关键字继承时从 tsconfig 文件读取编译器选项的函数

function getCompilerOptionsJSONFollowExtends(filename: string): {[key: string]: any} {
  let compopts = {};
  const config = ts.readConfigFile(filename, ts.sys.readFile).config;
  if (config.extends) {
    const rqrpath = require.resolve(config.extends);
    compopts = getCompilerOptionsJSONFollowExtends(rqrpath);
  }
  return {
    ...compopts,
    ...config.compilerOptions,
  };
}

其结果可以通过ts.CompilerOptions 类型转换

const jsonCompopts = getCompilerOptionsJSONFollowExtends('tsconfig.json')
const tmp = ts.convertCompilerOptionsFromJson(jsonCompopts,'')
if (tmp.errors.length>0) throw new Error('...')
const tsCompopts:ts.CompilerOptions = tmp.options

TL;DR

这些相关函数存在于typescript@4.3.2:

ts.readConfigFile
ts.parseConfigFileTextToJson
ts.convertCompilerOptionsFromJson
ts.parseJsonConfigFileContent
ts.parseJsonSourceFileConfigFileContent

这篇文章只针对前三个:

ts.readConfigFile

console.log(
  JSON.stringify(
    ts.readConfigFile('./tsconfig.base.json', ts.sys.readFile),
    null,
    2
  )
);

tsconfig.base.json 有内容的地方

{
  "extends": "@tsconfig/node14/tsconfig.json",
//comment
  "compilerOptions": {
    "declaration": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "lib": ["es2020"],// trailing comma
  }
}

结果

{
  "config": {
    "extends": "@tsconfig/node14/tsconfig.json",
    "compilerOptions": {
      "declaration": true,
      "skipLibCheck": true,
      "sourceMap": true,
      "lib": [
        "es2020"
      ]
    }
  }
}

这里要注意的事情:

  1. extends 引用的配置文件未拉入和展开。
  2. 编译器选项未转换为 typescript 编译器 API 函数所需的内部形式。 (不是ts.CompilerOptions 类型)
  3. 评论被删除,尾随逗号被忽略。

ts.parseConfigFileTextToJson

const parsed2 = ts.parseConfigFileTextToJson(
  ''/*'./tsconfig.base.json'*/, `
  {
    "extends": "@tsconfig/node14/tsconfig.json",
    // comment
    "compilerOptions": {
      "declaration": true,
      "skipLibCheck": true,
      "sourceMap": true,
      "lib": ["es2020"], // trailing comma
    }
  }
  `);
  console.log(JSON.stringify(parsed2, null, 2));

结果

{
  "config": {
    "extends": "@tsconfig/node14/tsconfig.json",
    "compilerOptions": {
      "declaration": true,
      "skipLibCheck": true,
      "sourceMap": true,
      "lib": [
        "es2020"
      ]
    }
  }
}

功能与ts.readConfigFile相同,只是文字为
传递而不是文件名。

注意:第一个参数(文件名)被忽略,除非可能有错误。添加真实文件名但将第二个参数留空会导致空输出。此函数无法读取文件。

ts.convertCompilerOptionsFromJson

  const parsed1 = ts.convertCompilerOptionsFromJson(
    {
      lib: ['es2020'],
      module: 'commonjs',
      target: 'es2020',
    },
    ''
  );
  console.log(JSON.stringify(parsed1, null, 2));

结果

{
  "options": {
    "lib": [
      "lib.es2020.d.ts"
    ],
    "module": 1,
    "target": 7
  },
  "errors": []
}

结果的 options 属性的值是 typescript 编译器 API 所需的内部格式。 (即它的类型为ts.CompilerOptions

module的值(1)实际上是ts.ModuleKind.CommonJS的编译值,target的值(7)实际上是ts.ScriptTarget.ES2020的编译值。

讨论/扩展

extends 关键字没有发挥作用时 通过使用以下函数:

  • ts.readConfigFile
  • ts.convertCompilerOptionsFromJson

如上图,你应该可以得到你想要的。

但是,当extends 关键字确实发挥作用时,情况就更加复杂了。我找不到现有的 API 函数可以自动扩展。

不过,有一个 CLI 函数可以做到这一点

npx tsc -p tsconfig.base.json --showConfig

结果

{
    "compilerOptions": {
        "lib": [
            "es2020"
        ],
        "module": "commonjs",
        "target": "es2020",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "declaration": true,
        "sourceMap": true
    },
    "files": [
        "./archive/doc-generator.ts",
        "./archive/func-params-exp.ts",
        "./archive/reprinting.ts",
        "./archive/sw.ts",
        ....
        ....
    ]
}    

所有隐含的文件也被输出。

bash 中的以下一行将只产生编译选项 -

echo 'console.log(JSON.stringify(JSON.parse('\'`npx tsc -p tsconfig.base.json --showConfig`\'').compilerOptions,null,2))' | node

只产生编译选项

{
  "lib": [
    "es2020"
  ],
  "module": "commonjs",
  "target": "es2020",
  "strict": true,
  "esModuleInterop": true,
  "skipLibCheck": true,
  "forceConsistentCasingInFileNames": true,
  "declaration": true,
  "sourceMap": true
}

显然,从程序中调用 CLI 远非理想。

如何使用 API 进行扩展

显示原理:

const config1 = ts.readConfigFile('./tsconfig.base.json', ts.sys.readFile).config
console.log(JSON.stringify(config1,null,2))
const tsrpath = ts.sys.resolvePath(config1.extends)
console.log(tsrpath)
const rqrpath = require.resolve(config1.extends)
console.log(rqrpath)
const config2 = ts.readConfigFile(rqrpath, ts.sys.readFile).config
console.log(JSON.stringify(config2,null,2))

结果

{
  "extends": "@tsconfig/node14/tsconfig.json",
  "compilerOptions": {
    "declaration": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "lib": [
      "es2020"
    ]
  }
}
/mnt/common/github/tscapi/@tsconfig/node14/tsconfig.json
/mnt/common/github/tscapi/node_modules/@tsconfig/node14/tsconfig.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Node 14",
  "compilerOptions": {
    "lib": [
      "es2020"
    ],
    "module": "commonjs",
    "target": "es2020",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

请注意,require.resolve 解析为我们想要的,但 ts.sys.resolve 没有。

这是一个返回编译器选项的函数,它正确地继承自extends:

function getCompileOptionsJSONFollowExtends(filename: string): {[key: string]: any} {
  let compopts: ts.CompilerOptions = {};
  const config = ts.readConfigFile(filename, ts.sys.readFile).config;
  if (config.extends) {
    const rqrpath = require.resolve(config.extends);
    compopts = getCompileOptionsJSONFollowExtends(rqrpath);
  }
  compopts = {
    ...compopts,
    ...config.compilerOptions,
  };
  return compopts;
}

试运行-

const jsonCompopts = getCompileOptionsJSONFollowExtends('./tsconfig.base.json')
console.log(JSON.stringify(jsonCompopts,null,2))
const tsCompopts = ts.convertCompilerOptionsFromJson(jsonCompopts,'')
console.log(JSON.stringify(tsCompopts,null,2))
console.log('');

结果

{
  "lib": [
    "es2020"
  ],
  "module": "commonjs",
  "target": "es2020",
  "strict": true,
  "esModuleInterop": true,
  "skipLibCheck": true,
  "forceConsistentCasingInFileNames": true,
  "declaration": true,
  "sourceMap": true
}
{
  "options": {
    "lib": [
      "lib.es2020.d.ts"
    ],
    "module": 1,
    "target": 7,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "sourceMap": true
  },
  "errors": []
}

【讨论】:

【解决方案3】:

有点混乱,你应该使用ts.getParsedCommandLineOfConfigFile()

> ts.getParsedCommandLineOfConfigFile('tsconfig.json', {}, ts.sys)
{
  options: {
    moduleResolution: 2,
    module: 99,
    target: 6,
    lib: [ 'lib.es2019.d.ts' ],
    types: [ 'node' ],
    strict: true,
    sourceMap: true,
    esModuleInterop: true,
    importsNotUsedAsValues: 2,
    importHelpers: true,
    incremental: true,
    composite: true,
    skipLibCheck: true,
    noEmit: true,
    configFilePath: 'C:/code/.../tsconfig.json'
  },
  watchOptions: undefined,
  fileNames: [
    'C:/code/.../src/index.tsx',
...

第三个参数实际上是ts.ParseConfigFileHost,所以你应该手动实现它(使用ts.sys的实现)

如果您已经解析了配置,也可以使用ts.parseJsonFileContent(tsconfigContent, ts.sys, baseDir, {}, errorMessageFileName),例如,当它内联在一些较大的配置文件中时。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-15
    • 2019-09-18
    • 1970-01-01
    • 2022-12-13
    • 1970-01-01
    • 2017-01-16
    • 2017-03-24
    • 2017-06-30
    相关资源
    最近更新 更多