Fork TS Checker Webpack Plugin

Webpack plugin that runs TypeScript type checker on a separate process.

[![npm version](https://img.shields.io/npm/v/fork-ts-checker-webpack-plugin.svg)](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin) [![npm beta version](https://img.shields.io/npm/v/fork-ts-checker-webpack-plugin/beta.svg)](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin) [![build status](https://travis-ci.org/TypeStrong/fork-ts-checker-webpack-plugin.svg?branch=master)](https://travis-ci.org/TypeStrong/fork-ts-checker-webpack-plugin) [![downloads](http://img.shields.io/npm/dm/fork-ts-checker-webpack-plugin.svg)](https://npmjs.org/package/fork-ts-checker-webpack-plugin) [![commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)

Installation

This plugin requires minimum webpack 2.3, TypeScript 2.1 and optionally ESLint 6.0.0 or TSLint 4.0

```sh

with npm

npm install --save-dev fork-ts-checker-webpack-plugin

with yarn

yarn add --dev fork-ts-checker-webpack-plugin ```

Basic webpack config (with ts-loader)

```js const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

const webpackConfig = { context: __dirname, // to automatically find tsconfig.json entry: './src/index.ts', module: { rules: [ { test: /.tsx?$/, loader: 'ts-loader', options: { // disable type checker - we will use it in fork plugin transpileOnly: true } } ] }, plugins: [new ForkTsCheckerWebpackPlugin()] }; ```

Motivation

There was already similar solution - awesome-typescript-loader. You can add CheckerPlugin and delegate checker to the separate process. The problem with awesome-typescript-loader was that, in our case, it was a lot slower than ts-loader on an incremental build (~20s vs ~3s). Secondly, we used tslint and we wanted to run this, along with type checker, in a separate process. This is why this plugin was created. To provide better performance, the plugin reuses Abstract Syntax Trees between compilations and shares these trees with TSLint. It can be scaled with a multi-process mode to utilize maximum CPU power.

Modules resolution

It's very important to be aware that this plugin uses TypeScript's, not webpack's modules resolution. It means that you have to setup tsconfig.json correctly. For example if you set files: ['./src/someFile.ts'] in tsconfig.json, this plugin will check only someFile.ts for semantic errors. It's because of performance. The goal of this plugin is to be as fast as possible. With TypeScript's module resolution we don't have to wait for webpack to compile files (which traverses dependency graph during compilation) - we have a full list of files from the begin.

To debug TypeScript's modules resolution, you can use tsc --traceResolution command.

ESLint

ESLint is the future of linting in the TypeScript world. If you'd like to use eslint with the plugin, supply this option: eslint: true and ensure you have the relevant dependencies installed:

yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev

You should have an ESLint configuration file in your root project directory. Here is a sample .eslintrc.js configuration for a TypeScript project:

js const path = require('path'); module.exports = { parser: '@typescript-eslint/parser', // Specifies the ESLint parser extends: [ 'plugin:@typescript-eslint/recommended' // Uses the recommended rules from the @typescript-eslint/eslint-plugin ], parserOptions: { project: path.resolve(__dirname, './tsconfig.json'), tsconfigRootDir: __dirname, ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features sourceType: 'module', // Allows for the use of imports }, rules: { // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs // e.g. "@typescript-eslint/explicit-function-return-type": "off", } };

There's a good explanation on setting up TypeScript ESLint support by Robert Cooper here.

TSLint

TSLint is being replaced by ESLint. https://eslint.org/blog/2019/01/future-typescript-eslint. As a consequence, support for TSLint in fork-ts-checker-webpack-plugin will be deprecated and removed in future versions of the plugin.

If you have installed tslint, you can enable it by setting tslint: true or tslint: './path/to/tslint.json'. We recommend changing defaultSeverity to a "warning" in tslint.json file. It helps to distinguish lints from TypeScript's diagnostics.

Options

Default: undefined.

js // in webpack.config.js new ForkTsCheckerWebpackPlugin({ reportFiles: ['src/**/*.{ts,tsx}', '!src/skip.ts'] });

Code sample ```js const { resolveModuleName } = require(`ts-pnp`); exports.resolveModuleName = ( typescript, moduleName, containingFile, compilerOptions, resolutionHost ) => { return resolveModuleName( moduleName, containingFile, compilerOptions, resolutionHost, typescript.resolveModuleName ); }; exports.resolveTypeReferenceDirective = ( typescript, moduleName, containingFile, compilerOptions, resolutionHost ) => { return resolveModuleName( moduleName, containingFile, compilerOptions, resolutionHost, typescript.resolveTypeReferenceDirective ); }; ```

Pre-computed consts:

Different behaviour in watch mode

If you turn on webpacks watch mode the fork-ts-checker-notifier-webpack-plugin will take care of logging type errors, not webpack itself. That means if you set silent: true you won't see type errors in your console in watch mode.

You can either set silent: false to show the logging from fork-ts-checker-notifier-webpack-plugin or set async: false. Now webpack itself will log type errors again, but note that this can slow down your builds depending on the size of your project.

Notifier

You may already be using the excellent webpack-notifier plugin to make build failures more obvious in the form of system notifications. There's an equivalent notifier plugin designed to work with the fork-ts-checker-webpack-plugin. It is the fork-ts-checker-notifier-webpack-plugin and can be found here. This notifier deliberately has a similar API as the webpack-notifier plugin to make migration easier.

Known Issue Watching Non-Emitting Files

At present there is an issue with the plugin regarding the triggering of type-checking when a change is made in a source file that will not emit js. If you have a file which contains only interfaces and / or types then changes to it will not trigger the type checker whilst in watch mode. Sorry about that.

We hope this will be resolved in future; the issue can be tracked here.

Plugin Hooks

This plugin provides some custom webpack hooks (all are sync):

| Event name | Hook Access Key | Description | Params | | --------------------------------------- | -------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | fork-ts-checker-cancel | cancel | Cancellation has been requested | cancellationToken | | fork-ts-checker-waiting | waiting | Waiting for results | hasTsLint | | fork-ts-checker-service-before-start | serviceBeforeStart | Async plugin that can be used for delaying fork-ts-checker-service-start | - | | fork-ts-checker-service-start | serviceStart | Service will be started | tsconfigPath, tslintPath, watchPaths, workersNumber, memoryLimit | | fork-ts-checker-service-start-error | serviceStartError | Cannot start service | error | | fork-ts-checker-service-out-of-memory | serviceOutOfMemory | Service is out of memory | - | | fork-ts-checker-receive | receive | Plugin receives diagnostics and lints from service | diagnostics, lints | | fork-ts-checker-emit | emit | Service will add errors and warnings to webpack compilation ('build' mode) | diagnostics, lints, elapsed | | fork-ts-checker-done | done | Service finished type checking and webpack finished compilation ('watch' mode) | diagnostics, lints, elapsed |

The Event name is there for backward compatibility with webpack 2/3. Regardless of the version of webpack (2, 3 or 4) you are using, we will always access plugin hooks with Hook Access Keys as described below.

Accessing plugin hooks

All plugin hooks are compatible with both webpack version 4 and version 2. To access plugin hooks and tap into the event, we need to use the getCompilerHooks static method. When we call this method with a webpack compiler instance, it returns the series of tapable hooks where you can pass in your callbacks.

js // require the plugin const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); // setup compiler with the plugin const compiler = webpack({ // .. webpack config }); // Optionally add the plugin to the compiler // **Don't do this if already added through configuration** new ForkTsCheckerWebpackPlugin({ silent: true, async: true }).apply(compiler); // Now get the plugin hooks from compiler const tsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(compiler); // These hooks provide access to different events // =================================================== // // The properties of tsCheckerHooks corresponds to the // // Hook Access Key of the table above. // // =================================================== // // Example, if we want to run some code when plugin has received diagnostics // and lint tsCheckerHooks.receive.tap('yourListenerName', (diagnostics, lint) => { // do something with diagnostics, perhaps show custom message console.log(diagnostics); }); // Say we want to show some message when plugin is waiting for typecheck results tsCheckerHooks.waiting.tap('yourListenerName', () => { console.log('waiting for typecheck results'); });

Calling .tap() on any hooks, requires two arguments.

name (string)

The first argument passed to .tap is the name of your listener callback (yourListenerName). It doesn't need to correspond to anything special. It is intended to be used internally as the name of the hook.

callback (function)

The second argument is the callback function. Depending on the hook you are tapping into, several arguments are passed to the function. Do check the table above to find out which arguments are passed to which hooks.

Accessing hooks on Webpack Multi-Compiler instance

The above method will not work on webpack multi compiler instance. The reason is getCompilerHooks expects (at lease as of now) the same compiler instance to be passed where the plugin was attached. So in case of multi compiler, we need to access individual compiler instances.

```js const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); // setup multi compiler with the plugin const compiler = webpack([ { // .. webpack config }, { // .. webpack config } ]);

// safely determine if instance is multi-compiler if ('compilers' in compiler) { compiler.compilers.forEach(singleCompiler => { // get plugin hooks from the single compiler instance const tsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks( singleCompiler ); // now access hooks just like before tsCheckerHooks.waiting.tap('yourListenerName', () => { console.log('waiting for typecheck results'); }); }); } ```

Vue

  1. Turn on the vue option in the plugin in your webpack config:

js new ForkTsCheckerWebpackPlugin({ tslint: true, vue: true }); Optionally change default vue-template-compiler to nativescript-vue-template-compiler if you use nativescript-vue new ForkTsCheckerWebpackPlugin({ tslint: true, vue: { enabled: true, compiler: 'nativescript-vue-template-compiler' } });

  1. To activate TypeScript in your .vue files, you need to ensure your script tag's language attribute is set to ts or tsx (also make sure you include the .vue extension in all your import statements as shown below):

```html

```

  1. Ideally you are also using ts-loader (in transpileOnly mode). Your Webpack config rules may look something like this:

js { test: /\.ts$/, loader: 'ts-loader', include: [resolve('src'), resolve('test')], options: { appendTsSuffixTo: [/\.vue$/], transpileOnly: true } }, { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig },

  1. Add rules to your tslint.json and they will be applied to Vue files. For example, you could apply the Standard JS rules tslint-config-standard like this:

json { "defaultSeverity": "error", "extends": ["tslint-config-standard"] }

  1. Ensure your tsconfig.json includes .vue files:

js // tsconfig.json { "include": [ "src/**/*.ts", "src/**/*.vue" ], "exclude": [ "node_modules" ] }

  1. It accepts any wildcard in your TypeScript configuration:

```js // tsconfig.json { "compilerOptions": {

// ...

"baseUrl": ".",
"paths": {
  "@/*": [
    "src/*"
  ],
  "~/*": [
    "src/*"
  ]
}

} }

// In a .ts or .vue file... import Hello from '@/components/hello.vue' ```

  1. If you are working in VSCode, you can get extensions Vetur and TSLint Vue to complete the developer workflow.

Credits

This plugin was created in Realytics in 2017. Thank you for supporting Open Source.

License

MIT License