Skip to content
/ tsc-ls Public

TypeScript compiler using language service plugins

License

Notifications You must be signed in to change notification settings

Chnapy/tsc-ls

Repository files navigation

tsc-ls

TypeScript compiler overlay using language service plugins.

npm license CI - CD

One of TypeScript compiler limitation is its inability to use language services plugins during compilation. By design, language services are meant to be used by editors only.

This limitation makes it impossible to type-check typescript plugins in CLI environments, like with CIs.

A good example would be with typescript-plugin-css-modules plugin, which allows to add CSS classes typing:

// tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "plugins": [
      {
        "name": "typescript-plugin-css-modules"
      }
    ]
  },
  "include": ["."]
}
/* toto.module.css */

.button {
  color: red;
}
import styles from './toto.module.css';

styles.button; // OK
styles.toto; // Editor shows type error

With this example, editors like VSCode shows correctly the error. But if you use tsc to compile your file, it will emit no error.

This is why tsc-ls was made, to make compilation using language service plugins. So it shows errors in the console like it should.

Limitations

This tool is young, tested but only for some cases, nothing is guaranteed.

This inconsistency is due to the TypeScript environment, which does not allow it to easily work with its compiler. As mentioned above, language services were not meant to be used outside of editors. Like with some popular plugins, multiple globals may be mutated during the process. tsc-ls cannot know what plugins will do, so it's not possible to offer a totally generic solution that works with every plugin.

For now it was tested with these plugins:

Using other plugins may not work. In this case, consider opening an issue, I would be happy to help.

Also, only some TypeScript project configurations are handled, it may not work with your projects - again here, consider opening an issue.

Currently only TypeScript build mode is handled (with --build or -b).

You may see some performance impact, depending on plugins you are using.

Benchmark

You can see performance impact using tsc-ls -b instead of tsc -b using no-plugins: https://chnapy.github.io/tsc-ls/dev/bench

Benchmark is done without plugin to keep a consistant comparison basis. Also plugin performance impact depends entirely of plugin itself.

If you have performance issues please first check plugins you are using.

Quick start

yarn add -D tsc-ls

You can use tsc-ls like tsc, using same compiler options.

yarn tsc-ls -b .

Because of configuration limitation, you must use build mode (--build or -b). Check tsc CLI documentation for more.

You can now use this command into your package.json.

{
  "scripts": {
    "type-check": "tsc-ls -b"
  }
}

Use into scripts

You can use this tsc-ls functions into your own scripts.

#!/usr/bin/env node

import ts from 'typescript';
import { compile, workarounds } from 'tsc-ls';

const parsedCommandLine = workarounds.parsePatchedCommandLine(
  ts.sys.args,
  ts.sys.readFile
);

compile({
  parsedCommandLine,
}).then(({ hasErrors, writeDiagnostics }) => {
  writeDiagnostics();

  if (hasErrors) {
    process.exit(1);
  }
});

For more check source files, like bin.ts.

Use by plugins

tsc-ls pass to plugins a language service typed as LanguageServiceWithDiagnostics which has an optional pluginsDiagnostics property. It allows plugins to put diagnostics directly to compiler result. It can be useful when you want to pass errors outside of sourceFile creation/update process and trigger compilation error.

import { LanguageServiceWithDiagnostics, PluginInit } from 'tsc-ls';

const init: PluginInit = (modules) => {
  const ts = modules.typescript;

  const create = (info) => {
    const languageServiceWithDiagnostics: LanguageServiceWithDiagnostics = info.languageService;

    const { pluginsDiagnostics } = languageServiceWithDiagnostics;

    // plugin logic...

    if(pluginsDiagnostics) {
      const fileDiagnostics = pluginsDiagnostics.get(fileName) ?? [];

      fileDiagnostics.push(
        ...// put your errors & other diagnostics
      );

      pluginsDiagnostics.set(fileName, fileDiagnostics);
    }

    return languageServiceWithDiagnostics;
  };

  return { create };
};

export = init;