Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for custom resolvers to CompilerHost #250

Merged
merged 10 commits into from
Apr 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v1.1.0

* [Add new custom resolution options](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/250)

## v1.0.4

* [gracefully handle error thrown from the service](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/249)
Expand All @@ -8,7 +12,7 @@

## v1.0.2

* [Fix ignoreLintWarning mark warnings as errors](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/243)
* [Fix ignoreLintWarning mark warnings as errors](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/243)

## v1.0.1

Expand All @@ -20,7 +24,7 @@

This is the first major version of `fork-ts-checker-webpack-plugin`. A long time coming :-)

There are actually no breaking changes that we're aware of; users of 0.x `fork-ts-checker-webpack-plugin` should be be able to upgrade without any drama. Users of TypeScript 3+ may notice a performance improvement as by default the plugin now uses the [incremental watch API](https://github.com/Microsoft/TypeScript/pull/20234) in TypeScript. Should this prove problematic you can opt out of using it by supplying `useTypescriptIncrementalApi: false`.
There are actually no breaking changes that we're aware of; users of 0.x `fork-ts-checker-webpack-plugin` should be be able to upgrade without any drama. Users of TypeScript 3+ may notice a performance improvement as by default the plugin now uses the [incremental watch API](https://github.com/Microsoft/TypeScript/pull/20234) in TypeScript. Should this prove problematic you can opt out of using it by supplying `useTypescriptIncrementalApi: false`.

We are aware of an [issue with Vue and the incremental API](https://github.com/Realytics/fork-ts-checker-webpack-plugin/issues/219). We hope it will be fixed soon - a generous member of the community is taking a look. In the meantime, we will *not* default to using the incremental watch API when in Vue mode.

Expand Down Expand Up @@ -79,7 +83,7 @@ Version `1.x` additionally supports webpack 5 alongside webpack 4, whose hooks a
```diff
- compiler.hooks.forkTsCheckerDone.tap(...args)
+ const forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(compiler)
+ forkTsCheckerHooks.done.tap(...args)
+ forkTsCheckerHooks.done.tap(...args)
```

v1.0.0-alpha.0 drops support for node 6.
Expand Down
75 changes: 47 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Build Status](https://travis-ci.org/Realytics/fork-ts-checker-webpack-plugin.svg?branch=master)](https://travis-ci.org/Realytics/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**
```sh
Expand All @@ -23,7 +23,7 @@ var webpackConfig = {
loader: 'ts-loader',
options: {
// disable type checker - we will use it in fork plugin
transpileOnly: true
transpileOnly: true
}
}
]
Expand All @@ -39,21 +39,21 @@ There is already similar solution - [awesome-typescript-loader](https://github.c
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](https://github.com/TypeStrong/ts-loader) on an incremental build (~20s vs ~3s).
Secondly, we use [tslint](https://palantir.github.io/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
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](https://github.com/Microsoft/TypeScript)'s, not
[webpack](https://github.com/webpack/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
It's very important to be aware that **this plugin uses [typescript](https://github.com/Microsoft/TypeScript)'s, not
[webpack](https://github.com/webpack/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](https://palantir.github.io/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.
If you have installed [tslint](https://palantir.github.io/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
Expand All @@ -63,7 +63,7 @@ Path to *tsconfig.json* file. Default: `path.resolve(compiler.options.context, '
* **compilerOptions** `object`:
Allows overriding TypeScript options. Should be specified in the same format as you would do for the `compilerOptions` property in tsconfig.json. Default: `{}`.

* **tslint** `string | true | undefined`:
* **tslint** `string | true | undefined`:
- If `string`, path to *tslint.json* file to check source files against.
- If `true`, path to `tslint.json` file will be computed with respect to currently checked file, just like TSLint
CLI would do. Suppose you have a project:
Expand All @@ -81,13 +81,13 @@ Allows overriding TypeScript options. Should be specified in the same format as
```
In such a case `src/file.ts` and `src/anotherFile.ts` would be checked against root `tslint.json`, and
`src/lib/someHelperFile.ts` would be checked against `src/lib/tslint.json`.

Default: `undefined`.

* **tslintAutoFix** `boolean `:
Passes on `--fix` flag while running `tslint` to auto fix linting errors. Default: false.

* **watch** `string | string[]`:
* **watch** `string | string[]`:
Directories or files to watch by service. Not necessary but improves performance (reduces number of `fs.stat` calls).

* **async** `boolean`:
Expand All @@ -97,13 +97,13 @@ We recommend to set this to `false` in projects where type checking is faster th
* **ignoreDiagnostics** `number[]`:
List of typescript diagnostic codes to ignore.

* **ignoreLints** `string[]`:
* **ignoreLints** `string[]`:
List of tslint rule names to ignore.

* **ignoreLintWarnings** `boolean`:
If true, will ignore all lint warnings.

* **reportFiles** `string[]`:
* **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](https://github.com/Microsoft/TypeScript/blob/89386ddda7dafc63cb35560e05412487f47cc267/src/compiler/watch.ts#L141).

```js
Expand All @@ -127,25 +127,25 @@ Options passed to formatters (currently only `codeframe` - see [available option
* **silent** `boolean`:
If `true`, logger will not be used. Default: `false`.

* **checkSyntacticErrors** `boolean`:
* **checkSyntacticErrors** `boolean`:
This option is useful if you're using ts-loader in `happyPackMode` with [HappyPack](https://github.com/amireh/happypack) or [thread-loader](https://github.com/webpack-contrib/thread-loader) to parallelise your builds. If `true` it will ensure that the plugin checks for *both* syntactic errors (eg `const array = [{} {}];`) and semantic errors (eg `const x: number = '1';`). By default the plugin only checks for semantic errors. This is because when ts-loader is used in `transpileOnly` mode, ts-loader will still report syntactic errors. When used in `happyPackMode` it does not. Default: `false`.

* **memoryLimit** `number`:
* **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
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`:
If `true`, the linter and compiler will process VueJs single-file-component (.vue) files. See the
If `true`, the linter and compiler will process VueJs single-file-component (.vue) files. See the
[Vue section](https://github.com/Realytics/fork-ts-checker-webpack-plugin#vue) 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
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 to `true` when working with typescript 3+ and `false` when below 3. The default can be overridden by directly specifying a value.

* **measureCompilationTime** `boolean`:
Expand All @@ -155,7 +155,26 @@ especially if there are other loaders/plugins involved in the compilation. **req
* **typescript** `string`:
If supplied this is a custom path where `typescript` can be found. Defaults to `require.resolve('typescript')`.

### Pre-computed consts:
* **resolveModuleNameModule** and **resolveTypeReferenceDirectiveModule** `string`:
Both of those options refer to files on the disk that respectively export a `resolveModuleName` or a `resolveTypeReferenceDirectiveModule` 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:
<details>
<summary>Code sample</summary>

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

</details>

### Pre-computed consts:
* `ForkTsCheckerWebpackPlugin.ONE_CPU` - always use one CPU
* `ForkTsCheckerWebpackPlugin.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)
Expand Down Expand Up @@ -188,7 +207,7 @@ This plugin provides some custom webpack hooks (all are sync):
|`fork-ts-checker-service-start`| Service will be started | `tsconfigPath`, `tslintPath`, `watchPaths`, `workersNumber`, `memoryLimit` |
|`fork-ts-checker-service-start-error` | Cannot start service | `error` |
|`fork-ts-checker-service-out-of-memory`| Service is out of memory | - |
|`fork-ts-checker-receive`| Plugin receives diagnostics and lints from service | `diagnostics`, `lints` |
|`fork-ts-checker-receive`| Plugin receives diagnostics and lints from service | `diagnostics`, `lints` |
|`fork-ts-checker-emit`| Service will add errors and warnings to webpack compilation ('build' mode) | `diagnostics`, `lints`, `elapsed` |
|`fork-ts-checker-done`| Service finished type checking and webpack finished compilation ('watch' mode) | `diagnostics`, `lints`, `elapsed` |

Expand All @@ -203,7 +222,7 @@ new ForkTsCheckerWebpackPlugin({
```

2. 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):
to `ts` or `tsx` (also make sure you include the `.vue` extension in all your import statements as shown below):

```html
<script lang="ts">
Expand All @@ -213,7 +232,7 @@ import Hello from '@/components/hello.vue'
</script>
```

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

```js
{
Expand All @@ -231,7 +250,7 @@ import Hello from '@/components/hello.vue'
options: vueLoaderConfig
},
```
4. 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](https://github.com/blakeembrey/tslint-config-standard) like this:
4. 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](https://github.com/blakeembrey/tslint-config-standard) like this:

```json
{
Expand All @@ -241,7 +260,7 @@ import Hello from '@/components/hello.vue'
]
}
```
5. Ensure your `tsconfig.json` includes .vue files:
5. Ensure your `tsconfig.json` includes .vue files:

```js
// tsconfig.json
Expand All @@ -256,12 +275,12 @@ import Hello from '@/components/hello.vue'
}
```

6. It accepts any wildcard in your TypeScript configuration:
6. It accepts any wildcard in your TypeScript configuration:
```js
// tsconfig.json
{
"compilerOptions": {

// ...

"baseUrl": ".",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fork-ts-checker-webpack-plugin",
"version": "1.0.4",
"version": "1.1.0",
"description": "Runs typescript type checker and linter on separate process.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down
9 changes: 7 additions & 2 deletions src/ApiIncrementalChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from './linterConfigHelpers';
import { NormalizedMessage } from './NormalizedMessage';
import { CompilerHost } from './CompilerHost';
import { ResolveModuleName, ResolveTypeReferenceDirective } from './resolution';
import { FsHelper } from './FsHelper';

export class ApiIncrementalChecker implements IncrementalCheckerInterface {
Expand Down Expand Up @@ -41,7 +42,9 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
private context: string,
private linterConfigFile: string | boolean,
private linterAutoFix: boolean,
checkSyntacticErrors: boolean
checkSyntacticErrors: boolean,
resolveModuleName: ResolveModuleName | undefined,
resolveTypeReferenceDirective: ResolveTypeReferenceDirective | undefined
) {
this.hasFixedConfig = typeof this.linterConfigFile === 'string';

Expand All @@ -51,7 +54,9 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
typescript,
programConfigFile,
compilerOptions,
checkSyntacticErrors
checkSyntacticErrors,
resolveModuleName,
resolveTypeReferenceDirective
);
}

Expand Down
50 changes: 49 additions & 1 deletion src/CompilerHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import * as ts from 'typescript'; // Imported for types alone
import { LinkedList } from './LinkedList';
import { VueProgram } from './VueProgram';
import {
ResolveModuleName,
ResolveTypeReferenceDirective,
makeResolutionFunctions
} from './resolution';

interface DirectoryWatchDelaySlot {
events: { fileName: string }[];
Expand Down Expand Up @@ -51,11 +56,16 @@ export class CompilerHost

private compilationStarted = false;

private readonly resolveModuleName: ResolveModuleName;
private readonly resolveTypeReferenceDirective: ResolveTypeReferenceDirective;

constructor(
private typescript: typeof ts,
programConfigFile: string,
compilerOptions: ts.CompilerOptions,
checkSyntacticErrors: boolean
checkSyntacticErrors: boolean,
userResolveModuleName?: ResolveModuleName,
userResolveTypeReferenceDirective?: ResolveTypeReferenceDirective
) {
this.tsHost = typescript.createWatchCompilerHost(
programConfigFile,
Expand All @@ -75,6 +85,17 @@ export class CompilerHost

this.configFileName = this.tsHost.configFileName;
this.optionsToExtend = this.tsHost.optionsToExtend || {};

const {
resolveModuleName,
resolveTypeReferenceDirective
} = makeResolutionFunctions(
userResolveModuleName,
userResolveTypeReferenceDirective
);

this.resolveModuleName = resolveModuleName;
this.resolveTypeReferenceDirective = resolveTypeReferenceDirective;
}

public async processChanges(): Promise<{
Expand Down Expand Up @@ -346,6 +367,33 @@ export class CompilerHost
this.afterCompile();
}

public resolveModuleNames(moduleNames: string[], containingFile: string) {
return moduleNames.map(moduleName => {
return this.resolveModuleName(
this.typescript,
moduleName,
containingFile,
this.optionsToExtend,
this
).resolvedModule;
});
}

public resolveTypeReferenceDirectives(
typeDirectiveNames: string[],
containingFile: string
) {
return typeDirectiveNames.map(typeDirectiveName => {
return this.resolveTypeReferenceDirective(
this.typescript,
typeDirectiveName,
containingFile,
this.optionsToExtend,
this
).resolvedTypeReferenceDirective;
});
}

// the functions below are use internally by typescript. we cannot use non-emitting version of incremental watching API
// because it is
// - much slower for some reason,
Expand Down
Loading