Typescript ESM tsconfig – which property(ies)/values read internally from tsconfig definitely tell if output is ESM?

Issue

I am writing a typescript API transform that processes source files during the emit phase. I have access in the transform to the internal value of the compileOptions, via program.getCompilerOptions().

I need to know if the output will be ‘CommonJS’, esm script, or something else.
I think I can tell from the module field, but I’m not certain.
Typescript defines –

  //   export enum ModuleKind {
  //     None = 0,
  //     CommonJS = 1,
  //     AMD = 2,
  //     UMD = 3,
  //     System = 4,
  //     ES2015 = 5,
  //     ES2020 = 6,
  //     ...
  //     ESNext = 99
  // }

and I think I can use this logic –

  const moduleKind: ts.ModuleKind | undefined =
    program.getCompilerOptions().module;
  if (!moduleKind) {
    throw new Error(
      `compilerOptions.module is undefined (should have default value)`
    );
  }
  if (
    moduleKind < ts.ModuleKind.ES2015 &&
    moduleKind !== ts.ModuleKind.CommonJS
  ) {
    // not esm and not commonjs
  }
  else if (moduleKind === ts.ModuleKind.CommonJS) {
    // commonjs
  } else {
    // esm
  }

However, on the typescript tsconfig man page for the module property
it lists in the sidebar these allowed values

none
commonjs
amd
umd
system
es6/es2015
es2020
es2022
esnext
node12
nodenext 

from which node12 and nodenext are almost certainly "commonjs".
Which makes me think maybe some values >=es6/es2015 might be (and if not today, then someday) non-esm values. I’m looking for a definitive answer to this question,
because the manual doesn’t answer that question explicitly.

Solution

I opened an issue Clarity on how to determine progmatically whether a sourceFile is to be output as "commonjs" or "esm". #48794 on typescript issues and obtained a solution.

From Typescript code documentation

(property) SourceFile.impliedNodeFormat?: ts.ModuleKind.CommonJS | ts.ModuleKind.ESNext | undefined

When module is Node12 or NodeNext, this field controls whether the source file in question is an ESNext-output-format file, or a CommonJS-output-format module. This is derived by the module resolver as it looks up the file, since it is derived from either the file extension of the module, or the containing package.json context, and affects both checking and emit.

It is public so that (pre)transformers can set this field, since it switches the builtin node module
transform. Generally speaking, if unset, the field is treated as though it is ModuleKind.CommonJS.

An API client trying to determine per-Program, per-SourceFile whether it will be transformed to definitely-CommonJs, definitely-Esm, or something else, can use this coding logic:

if SourceFile.impliedNodeFormat===ModuleKind.CommonJS, then "commonjs"
else if SourceFile.impliedNodeFormat===ModuleKind.ESNext, then "module"
else if compilerOptions.module===ts.ModuleKind.CommonJS, then "commonjs"
else if compilerOptions.module>=ts.ModuleKind.ES2015, then "module"
else not definitely "commonjs" nor definitely "module"

Answered By – Craig Hicks

Answer Checked By – David Goodson (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.