@phryneas/experimental-fork-ts-checker-webpack-plugin
v0.0.3
Published
Runs typescript type checker and linter on separate process.
Downloads
10
Maintainers
Readme
Fork TS Checker Webpack Plugin
Webpack plugin that runs TypeScript type checker on a separate process.
Installation
This plugin requires minimum webpack 2.3, TypeScript 2.1 and optionally tslint 4.0
npm install --save-dev fork-ts-checker-webpack-plugin
Basic webpack config (with ts-loader)
var ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
var 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 is 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 use tslint and we wanted to run this, along with type checker, in a separate process.
This is why we've created this plugin. To provide better performance, 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.
TSLint
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
tsconfig
string
: Path to tsconfig.json file. Default:path.resolve(compiler.options.context, './tsconfig.json')
.compilerOptions
object
: Allows overriding TypeScript options. Should be specified in the same format as you would do for thecompilerOptions
property in tsconfig.json. Default:{}
.tslint
string | true | undefined
:If
string
, path to tslint.json file to check source files against.If
true
, path totslint.json
file will be computed with respect to currently checked file, just like TSLint CLI would do. Suppose you have a project:./ tslint.json src/ file.ts anotherFile.ts lib/ tslint.json someHelperFile.ts
In such a case
src/file.ts
andsrc/anotherFile.ts
would be checked against roottslint.json
, andsrc/lib/someHelperFile.ts
would be checked againstsrc/lib/tslint.json
.
Default:
undefined
.tslintAutoFix
boolean
: Passes on--fix
flag while runningtslint
to auto fix linting errors. Default: false.watch
string | string[]
: Directories or files to watch by service. Not necessary but improves performance (reduces number offs.stat
calls).async
boolean
: True by default -async: false
can block webpack's emit to wait for type checker/linter and to add errors to the webpack's compilation. We recommend to set this tofalse
in projects where type checking is faster than webpack's build - it's better for integration with other plugins. Another scenario where you might want to set this tofalse
is if you use theoverlay
functionality ofwebpack-dev-server
.ignoreDiagnostics
number[]
: List of TypeScript diagnostic codes to ignore.ignoreLints
string[]
: List of tslint rule names to ignore.ignoreLintWarnings
boolean
: If true, will ignore all lint warnings.reportFiles
string[]
: Only report errors on files matching these glob patterns. This can be useful when certain types definitions have errors that are not fatal to your application. Default:[]
. Please note that this may behave unexpectedly if using the incremental API as the incremental API doesn't look for global and semantic errors if it has already found syntactic errors.
// in webpack.config.js
new ForkTsCheckerWebpackPlugin({
reportFiles: ['src/**/*.{ts,tsx}', '!src/skip.ts']
});
colors
boolean
: Iffalse
, disables built-in colors in logger messages. Default:true
.logger
object
: Logger instance. It should be object that implements method:error
,warn
,info
. Default:console
.formatter
'default' | 'codeframe' | ((message: NormalizedMessage, useColors: boolean) => string)
: Formatter for diagnostics and lints. By default usesdefault
formatter. You can also pass your own formatter as a function (seesrc/NormalizedMessage.js
andsrc/formatter/
for api reference).formatterOptions
object
: Options passed to formatters (currently onlycodeframe
- see available options)silent
boolean
: Iftrue
, logger will not be used. Default:false
.checkSyntacticErrors
boolean
: This option is useful if you're using ts-loader inhappyPackMode
with HappyPack or thread-loader to parallelise your builds. Iftrue
it will ensure that the plugin checks for both syntactic errors (egconst array = [{} {}];
) and semantic errors (egconst x: number = '1';
). By default the plugin only checks for semantic errors. This is because when ts-loader is used intranspileOnly
mode, ts-loader will still report syntactic errors. When used inhappyPackMode
it does not. Default:false
.memoryLimit
number
: Memory limit for service process in MB. If service exits with allocation failed error, increase this number. Default:2048
.workers
number
: You can split type checking to a few workers to speed-up increment build. Be careful - if you don't want to increase build time, you should keep free 1 core for build and 1 core for a system (for example system with 4 CPUs should use max 2 workers). Second thing - node doesn't share memory between workers - keep in mind that memory usage will increase. Be aware that in some scenarios increasing workers number can increase checking time. Default:ForkTsCheckerWebpackPlugin.ONE_CPU
.vue
boolean
: Iftrue
, the linter and compiler will process VueJs single-file-component (.vue) files. See the Vue section further down for information on how to correctly setup your project.useTypescriptIncrementalApi
boolean
: If true, the plugin will use incremental compilation API introduced in TypeScript 2.7. In this mode you can only have 1 worker, but if the changes in your code are small (like you normally have when you work in 'watch' mode), the compilation may be much faster, even compared to multi-threaded compilation. Defaults totrue
when working with TypeScript 3+ andfalse
when below 3. The default can be overridden by directly specifying a value.measureCompilationTime
boolean
: If true, the plugin will measure the time spent inside the compilation code. This may be useful to compare modes, especially if there are other loaders/plugins involved in the compilation. requires node 8+typescript
string
: If supplied this is a custom path wheretypescript
can be found. Defaults torequire.resolve('typescript')
.resolveModuleNameModule and resolveTypeReferenceDirectiveModule
string
: Both of those options refer to files on the disk that respectively export aresolveModuleName
or aresolveTypeReferenceDirectiveModule
function. These functions will be used to resolve the import statements and the<reference types="...">
directives instead of the default TypeScript implementation. Check the following code for an example of what those functions should look like: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:
ForkTsCheckerWebpackPlugin.ONE_CPU
- always use one CPUForkTsCheckerWebpackPlugin.ALL_CPUS
- always use all CPUs (will increase build time)ForkTsCheckerWebpackPlugin.ONE_CPU_FREE
- leave only one CPU for build (probably will increase build time)ForkTsCheckerWebpackPlugin.TWO_CPUS_FREE
- recommended - leave two CPUs free (one for build, one for system)
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 interface
s and / or type
s 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.
// 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.
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
- Turn on the vue option in the plugin in your webpack config:
new ForkTsCheckerWebpackPlugin({
tslint: true,
vue: true
});
- To activate TypeScript in your
.vue
files, you need to ensure your script tag's language attribute is set tots
ortsx
(also make sure you include the.vue
extension in all your import statements as shown below):
<script lang="ts">
import Hello from '@/components/hello.vue';
// ...
</script>
- Ideally you are also using
ts-loader
(in transpileOnly mode). Your Webpack config rules may look something like this:
{
test: /\.ts$/,
loader: 'ts-loader',
include: [resolve('src'), resolve('test')],
options: {
appendTsSuffixTo: [/\.vue$/],
transpileOnly: true
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
- 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:
{
"defaultSeverity": "error",
"extends": ["tslint-config-standard"]
}
- Ensure your
tsconfig.json
includes .vue files:
// tsconfig.json
{
"include": [
"src/**/*.ts",
"src/**/*.vue"
],
"exclude": [
"node_modules"
]
}
- It accepts any wildcard in your TypeScript configuration:
// tsconfig.json
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
],
"~/*": [
"src/*"
]
}
}
}
// In a .ts or .vue file...
import Hello from '@/components/hello.vue'
- If you are working in VSCode, you can get extensions Vetur and TSLint Vue to complete the developer workflow.
License
MIT