Hur man kompilera tsconfig.json till ett konfigurationsobjekt med TypeScript API?


Jag letar efter ett offentligt TypeScript-API (helst version 4.x) för att parsas tsconfig.json/jsconfig.json-filer till kompletta TypeScript-konfigurationsobjekt (med "kompletta" menar jag att alla dess egenskaper är ifyllda med värden - antingen tagna uttryckligen från *.json eller implicit från kända standardvärden), som i:

import ts from "typescript";

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

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

Finns det en sådan funktion i typescript npm-paketet? Jag kunde inte hitta den själv, varken i dokumentationen eller i IntelliSense-förslag (de som visas med Ctrl + Space).

Jag har sett en annan fråga med samma typ av begäran, men a) den är obesvarad, b) länken i kommentarerna hänvisar till en 5,5 år gammal lösning, - det måste ha skett en förändring i API sedan dess.


I verkligheten behöver jag bara ett fåtal egenskaper från konfigurationsfilen, så det skulle vara relativt enkelt att skriva ett par hjälpmetoder för att parsa *.json-filen och hämta dess innehåll. Men:

  • det finns mer exotiska användningsfall (såsom att använda projektreferenser och/eller utöka föräldrakonfigurationsobjektet), där en sådan lösning inte skulle fungera;
  • de ovan nämnda *.json-objekten kan ha kommentarer och avslutande kommatecken, så det är inte trivialt att ens analysera filen från början;
  • det skulle vara konstigt att skriva om något som redan är känt för att vara skrivet.

Svar #1

Redigera

I en kommentar nedan förklarar @Hiroki Osame att detta svar genom att använda ts.parseJsonConfigFileContent gjorde det möjligt för honom att få extends följd automatiskt utan någon "handtillverkning".

Även på den här sidan verkar @Simon Buchans svar vara liknande korrekt.

Kort svar

En funktion för att läsa kompilatoralternativ från en tsconfig-fil samtidigt som tsconfig extends-nyckelns arv hanteras korrekt

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,
  };
}

Resultatet av det kan konverteras till typen ts.CompilerOptions via

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

Sammanfattning

Dessa relaterade funktioner finns i [email :

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

Detta inlägg tar bara upp de första tre:

ts.readConfigFile

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

där tsconfig.base.json har innehåll

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

resulterar i

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

Sakerna att lägga märke till här:

  1. Konfigurationsfilen som refereras av extends hämtas inte in och expanderas.
  2. Kompileringsalternativen omvandlas inte till det interna format som krävs av TypeScript-kompilatorns API-funktioner. (Inte av typen ts.CompilerOptions)
  3. Kommentarer tas bort och avslutande kommatecken ignoreras.

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));

resulterar i

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

Funktionen är densamma som ts.readConfigFile förutom att text skickas istället för ett filnamn.

Observera: Det första argumentet (filnamn) ignoreras om det inte kanske finns ett fel. Att lägga till ett riktigt filnamn men lämna det andra argumentet tomt resulterar i tomt output. Denna funktion kan inte läsa in filer.

ts.convertCompilerOptionsFromJson

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

resulterar i

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

Värdet av options-egenskapen i resultatet är i det interna format som krävs av TypeScript-kompilator-API:et. (Dvs. det är av typen ts.CompilerOptions)

Värdet (1) av module är faktiskt det kompilerade värdet av ts.ModuleKind.CommonJS, och värdet (7) av target är faktiskt det kompilerade värdet av ts.ScriptTarget.ES2020.

diskussion/utvidgar

När extends-nyckelordet INTE spelar någon roll, då genom att använda följande funktioner:

  • ts.readConfigFile
  • ts.convertCompilerOptionsFromJson

Som visas ovan bör du kunna få vad du vill.

Men när extends-nyckelordet faktiskt används är det mer komplicerat. Jag kan inte hitta någon befintlig API-funktion för att automatiskt följa extends.

Det finns dock en CLI-funktion för att göra det

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

resulterar i

{
    "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",
        ....
        ....
    ]
}    

där alla filer som implicit inkluderas också skrivs ut.

Följande enradare i bash kommer att ge enbart kompilationsalternativen -

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

resulterar endast i kompilationsalternativen

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

Uppenbarligen är det långt ifrån idealiskt att anropa CLI från ett program.

hur man följer extends med hjälp av API

Visa principen:

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))

resulterar i

{
  "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
  }
}

Observera att require.resolve löser till det vi vill, men ts.sys.resolve gör det inte.

Här är en funktion som returnerar kompilatoralternativ korrekt ärvt från 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;
}

Testkörning -

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('');

resulterar i

{
  "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": []
}

Svar #2

Lite förvirrande bör du använda 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',
...

Den tredje parametern är faktiskt en ts.ParseConfigFileHost, så du bör förmodligen implementera den manuellt (med hjälp av implementationer från ts.sys)

Du kan också använda ts.parseJsonFileContent(tsconfigContent, ts.sys, baseDir, {}, errorMessageFileName) om du redan har parsat konfigurationen, till exempel när den är inline i någon större konfigurationsfil.

Relaterade artiklar