diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f4182b..587aa9a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## v0.4.14 + +* [Add support for `reportFiles` option](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/179) (#179) + +## v0.4.13 + +* [Merge in `compilerOptions` prior to calling `parseJsonConfigFileContent`](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/176) (#176) + +## v0.4.12 + +* [Add `compilerOptions` option](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/173) (#173) + +## v0.4.11 + +* [Fix os.cpus is not a function](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/172) (#172) + ## v0.4.10 * [Allow fork-ts-checker-webpack-plugin to be imported in .ts files using ESM import syntax](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/163) (#163) diff --git a/README.md b/README.md index 202f2ee2..e218c039 100644 --- a/README.md +++ b/README.md @@ -1,240 +1 @@ -# Fork TS Checker Webpack Plugin -[![Npm version](https://img.shields.io/npm/v/fork-ts-checker-webpack-plugin.svg?style=flat-square)](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin) -[![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 -npm install --save-dev fork-ts-checker-webpack-plugin -``` -Basic webpack config (with [ts-loader](https://github.com/TypeStrong/ts-loader)) -```js -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](https://github.com/s-panferov/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](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 -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 -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. -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')`. - -* **tslint** `string | true`: -Path to *tslint.json* file or `true`. If `true`, uses `path.resolve(compiler.options.context, './tslint.json')`. Default: `undefined`. - -* **watch** `string | string[]`: -Directories or files to watch by service. Not necessary but improves performance (reduces number of `fs.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 to `false` 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 to `false` is if you use the `overlay` functionality of `webpack-dev-server`. - -* **ignoreDiagnostics** `number[]`: -List of typescript diagnostic codes to ignore. - -* **ignoreLints** `string[]`: -List of tslint rule names to ignore. - -* **colors** `boolean`: -If `false`, 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 uses `default` formatter. You can also pass your own formatter as a function -(see `src/NormalizedMessage.js` and `src/formatter/` for api reference). - -* **formatterOptions** `object`: -Options passed to formatters (currently only `codeframe` - see [available options](https://www.npmjs.com/package/babel-code-frame#options)) - -* **silent** `boolean`: -If `true`, logger will not be used. Default: `false`. - -* **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. 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`: -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`: -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. - -### 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) - * `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](https://webpack.js.org/configuration/watch/#watch) 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](https://github.com/Turbo87/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](https://github.com/johnnyreilly/fork-ts-checker-notifier-webpack-plugin). 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](https://github.com/Realytics/fork-ts-checker-webpack-plugin/issues/36). - -## Plugin Hooks -This plugin provides some custom webpack hooks (all are sync): - -| Event name | Description | Params | -|------------|-------------|--------| -|`fork-ts-checker-cancel`| Cancellation has been requested | `cancellationToken` | -|`fork-ts-checker-waiting`| Waiting for results | `hasTsLint` | -|`fork-ts-checker-service-before-start`| Async plugin that can be used for delaying `fork-ts-checker-service-start` | - | -|`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-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` | - -## Vue -1. Turn on the vue option in the plugin in your webpack config: - -``` - new ForkTsCheckerWebpackPlugin({ - tslint: true, - vue: true - }) -``` - -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): - -```html - -``` - -3. 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 -}, -``` -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 -{ - "defaultSeverity": "error", - "extends": [ - "tslint-config-standard" - ] -} -``` -5. Ensure your `tsconfig.json` includes .vue files: - -``` -// tsconfig.json -{ - "include": [ - "src/**/*.ts", - "src/**/*.vue" - ], - "exclude": [ - "node_modules" - ] -} -``` - -6. 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' -``` - -7. If you are working in **VSCode**, you can get extensions [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur) and [TSLint Vue](https://marketplace.visualstudio.com/items?itemName=prograhammer.tslint-vue) to complete the developer workflow. - -## License -MIT +This is a fork of [`fork-ts-checker-webpack-plugin`](https://github.com/Realytics/fork-ts-checker-webpack-plugin). You probably want to use that package instead. diff --git a/package.json b/package.json index 0de7a273..4393052d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "fork-ts-checker-webpack-plugin", - "version": "0.4.10", + "name": "fork-ts-checker-webpack-plugin-alt", + "version": "0.4.14", "description": "Runs typescript type checker and linter on separate process.", "main": "lib/index.js", "types": "lib/types/index.d.ts", @@ -9,8 +9,8 @@ ], "scripts": { "build": "tsc --version && tsc --project \"./src\"", - "test:unit": "mocha -R spec ./test/unit", - "test:integration": "mocha -R spec ./test/integration && rimraf tmp", + "test:unit": "mocha -R spec ./test/unit --exit", + "test:integration": "mocha -R spec ./test/integration --exit && rimraf tmp", "test": "npm run build && npm run test:unit && npm run test:integration", "test:watch": "mocha -R spec --watch ./test/unit", "test:coverage": "rimraf coverage && istanbul cover -root lib --include-all-sources mocha -- -R spec ./test/unit ./test/integration", @@ -18,7 +18,7 @@ "lint:fix": "tslint --project src/tsconfig.json --fix && eslint ./test --fix" }, "repository": { - "url": "https://github.com/Realytics/fork-ts-checker-webpack-plugin.git", + "url": "https://github.com/Timer/fork-ts-checker-webpack-plugin.git", "type": "git" }, "keywords": [ @@ -51,10 +51,8 @@ "devDependencies": { "@types/babel-code-frame": "^6.20.1", "@types/chokidar": "^1.7.5", - "@types/lodash.endswith": "^4.2.3", - "@types/lodash.isfunction": "^3.0.3", - "@types/lodash.isstring": "^4.0.3", - "@types/lodash.startswith": "^4.2.3", + "@types/lodash": "^4.14.117", + "@types/micromatch": "^3.1.0", "@types/minimatch": "^3.0.1", "@types/node": "^8.0.26", "@types/resolve": "0.0.4", @@ -65,7 +63,7 @@ "husky": "^1.1.2", "istanbul": "^0.4.5", "lint-staged": "^7.3.0", - "mocha": "^3.4.1", + "mocha": "^5.2.0", "mock-fs": "^4.3.0", "mock-require": "^2.0.2", "prettier": "^1.14.3", @@ -82,18 +80,14 @@ "webpack": "^4.16.5" }, "peerDependencies": { - "tslint": "^4.0.0 || ^5.0.0", - "typescript": "^2.1.0 || ^3.0.0", "webpack": "^2.3.0 || ^3.0.0 || ^4.0.0" }, "dependencies": { "babel-code-frame": "^6.22.0", "chalk": "^2.4.1", "chokidar": "^2.0.4", - "lodash.endswith": "^4.2.1", - "lodash.isfunction": "^3.0.8", - "lodash.isstring": "^4.0.1", - "lodash.startswith": "^4.2.1", + "lodash": "^4.17.11", + "micromatch": "^3.1.10", "minimatch": "^3.0.4", "resolve": "^1.5.0", "tapable": "^1.0.0" diff --git a/src/CancellationToken.ts b/src/CancellationToken.ts index 4f7d20ff..89a09a64 100644 --- a/src/CancellationToken.ts +++ b/src/CancellationToken.ts @@ -2,7 +2,7 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import * as ts from 'typescript'; +import { OperationCanceledException } from './OperationCanceledException'; interface CancellationTokenData { isCancelled: boolean; @@ -54,7 +54,7 @@ export class CancellationToken { throwIfCancellationRequested() { if (this.isCancellationRequested()) { - throw new ts.OperationCanceledException(); + throw new OperationCanceledException(); } } diff --git a/src/FilesRegister.ts b/src/FilesRegister.ts index fc133000..ee4f95d0 100644 --- a/src/FilesRegister.ts +++ b/src/FilesRegister.ts @@ -1,3 +1,4 @@ +// tslint:disable-next-line:no-implicit-dependencies import * as ts from 'typescript'; interface DataShape { diff --git a/src/FilesWatcher.ts b/src/FilesWatcher.ts index 54a1e279..67032ade 100644 --- a/src/FilesWatcher.ts +++ b/src/FilesWatcher.ts @@ -1,6 +1,6 @@ import * as chokidar from 'chokidar'; import * as path from 'path'; -import startsWith = require('lodash.startswith'); +import startsWith = require('lodash/startsWith'); export class FilesWatcher { watchPaths: string[]; diff --git a/src/IncrementalChecker.ts b/src/IncrementalChecker.ts index f6b8d9e6..ea99461d 100644 --- a/src/IncrementalChecker.ts +++ b/src/IncrementalChecker.ts @@ -1,7 +1,9 @@ import * as fs from 'fs'; -import endsWith = require('lodash.endswith'); +import endsWith = require('lodash/endsWith'); import * as path from 'path'; +// tslint:disable-next-line:no-implicit-dependencies import * as ts from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies import { Configuration, Linter } from 'tslint'; // Imported for types alone; actual requires take place in methods below import { FilesRegister } from './FilesRegister'; import { FilesWatcher } from './FilesWatcher'; @@ -10,6 +12,7 @@ import { NormalizedMessage } from './NormalizedMessage'; import { CancellationToken } from './CancellationToken'; import * as minimatch from 'minimatch'; import { VueProgram } from './VueProgram'; +import { TypeScriptInstance } from './TypeScriptInstance'; // Need some augmentation here - linterOptions.exclude is not (yet) part of the official // types for tslint. @@ -22,6 +25,7 @@ interface ConfigurationFile extends Configuration.IConfigurationFile { export class IncrementalChecker { programConfigFile: string; + compilerOptions: object; linterConfigFile: string | false; watchPaths: string[]; workNumber: number; @@ -33,6 +37,7 @@ export class IncrementalChecker { linterConfig: ConfigurationFile; linterExclusions: minimatch.IMinimatch[]; + typescript: TypeScriptInstance; program: ts.Program; programConfig: ts.ParsedCommandLine; watcher: FilesWatcher; @@ -40,7 +45,9 @@ export class IncrementalChecker { vue: boolean; constructor( + typescriptPath: string, programConfigFile: string, + compilerOptions: object, linterConfigFile: string | false, watchPaths: string[], workNumber: number, @@ -48,7 +55,9 @@ export class IncrementalChecker { checkSyntacticErrors: boolean, vue: boolean ) { + this.typescript = require(typescriptPath); this.programConfigFile = programConfigFile; + this.compilerOptions = compilerOptions; this.linterConfigFile = linterConfigFile; this.watchPaths = watchPaths; this.workNumber = workNumber || 0; @@ -68,18 +77,33 @@ export class IncrementalChecker { })); } - static loadProgramConfig(configFile: string) { - return ts.parseJsonConfigFileContent( - // Regardless of the setting in the tsconfig.json we want isolatedModules to be false - Object.assign(ts.readConfigFile(configFile, ts.sys.readFile).config, { - isolatedModules: false - }), - ts.sys, + static loadProgramConfig( + typescript: TypeScriptInstance, + configFile: string, + compilerOptions: object + ) { + const tsconfig = typescript.readConfigFile( + configFile, + typescript.sys.readFile + ).config; + + tsconfig.compilerOptions = tsconfig.compilerOptions || {}; + tsconfig.compilerOptions = { + ...tsconfig.compilerOptions, + ...compilerOptions + }; + + const parsed = typescript.parseJsonConfigFileContent( + tsconfig, + typescript.sys, path.dirname(configFile) ); + + return parsed; } static loadLinterConfig(configFile: string): ConfigurationFile { + // tslint:disable-next-line:no-implicit-dependencies const tslint = require('tslint'); return tslint.Configuration.loadConfigurationFromPath( @@ -88,12 +112,13 @@ export class IncrementalChecker { } static createProgram( + typescript: TypeScriptInstance, programConfig: ts.ParsedCommandLine, files: FilesRegister, watcher: FilesWatcher, oldProgram: ts.Program ) { - const host = ts.createCompilerHost(programConfig.options); + const host = typescript.createCompilerHost(programConfig.options); const realGetSourceFile = host.getSourceFile; host.getSourceFile = (filePath, languageVersion, onError) => { @@ -119,7 +144,7 @@ export class IncrementalChecker { return files.getData(filePath).source; }; - return ts.createProgram( + return typescript.createProgram( programConfig.fileNames, programConfig.options, host, @@ -128,6 +153,7 @@ export class IncrementalChecker { } static createLinter(program: ts.Program) { + // tslint:disable-next-line:no-implicit-dependencies const tslint = require('tslint'); return new tslint.Linter({ fix: false }, program); @@ -189,9 +215,14 @@ export class IncrementalChecker { loadVueProgram() { this.programConfig = this.programConfig || - VueProgram.loadProgramConfig(this.programConfigFile); + VueProgram.loadProgramConfig( + this.typescript, + this.programConfigFile, + this.compilerOptions + ); return VueProgram.createProgram( + this.typescript, this.programConfig, path.dirname(this.programConfigFile), this.files, @@ -203,9 +234,14 @@ export class IncrementalChecker { loadDefaultProgram() { this.programConfig = this.programConfig || - IncrementalChecker.loadProgramConfig(this.programConfigFile); + IncrementalChecker.loadProgramConfig( + this.typescript, + this.programConfigFile, + this.compilerOptions + ); return IncrementalChecker.createProgram( + this.typescript, this.programConfig, this.files, this.watcher, @@ -254,7 +290,12 @@ export class IncrementalChecker { // normalize and deduplicate diagnostics return NormalizedMessage.deduplicate( - diagnostics.map(NormalizedMessage.createFromDiagnostic) + diagnostics.map(d => + NormalizedMessage.createFromDiagnostic( + this.typescript.flattenDiagnosticMessageText, + d + ) + ) ); } diff --git a/src/NormalizedMessage.ts b/src/NormalizedMessage.ts index 2256a1e1..161f9c71 100644 --- a/src/NormalizedMessage.ts +++ b/src/NormalizedMessage.ts @@ -1,8 +1,6 @@ -import { - Diagnostic, - DiagnosticCategory, - flattenDiagnosticMessageText -} from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies +import { Diagnostic, DiagnosticMessageChain } from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies import { RuleFailure } from 'tslint'; type ErrorType = 'diagnostic' | 'lint'; @@ -45,7 +43,13 @@ export class NormalizedMessage { } // message types - static createFromDiagnostic(diagnostic: Diagnostic) { + static createFromDiagnostic( + flattenDiagnosticMessageText: ( + messageText: string | DiagnosticMessageChain | undefined, + newLine: string + ) => string, + diagnostic: Diagnostic + ) { let file: string; let line: number; let character: number; @@ -61,9 +65,10 @@ export class NormalizedMessage { return new NormalizedMessage({ type: NormalizedMessage.TYPE_DIAGNOSTIC, code: diagnostic.code, - severity: DiagnosticCategory[ - diagnostic.category - ].toLowerCase() as Severity, + // TODO: Keep in sync with TypeScript's DiagnosticCategory.Error or pass in + severity: (diagnostic.category.toString() === '1' + ? NormalizedMessage.SEVERITY_ERROR + : NormalizedMessage.SEVERITY_WARNING) as Severity, content: flattenDiagnosticMessageText(diagnostic.messageText, '\n'), file, line, diff --git a/src/OperationCanceledException.ts b/src/OperationCanceledException.ts new file mode 100644 index 00000000..51440b7e --- /dev/null +++ b/src/OperationCanceledException.ts @@ -0,0 +1 @@ +export class OperationCanceledException {} diff --git a/src/TypeScriptInstance.ts b/src/TypeScriptInstance.ts new file mode 100644 index 00000000..8c9c815a --- /dev/null +++ b/src/TypeScriptInstance.ts @@ -0,0 +1,30 @@ +// tslint:disable-next-line:no-implicit-dependencies +import * as ts from 'typescript'; + +// TODO: keep in sync with TypeScript +export enum ScriptKind { + Unknown = 0, + JS = 1, + JSX = 2, + TS = 3, + TSX = 4, + External = 5, + JSON = 6, + /** + * Used on extensions that doesn't define the ScriptKind but the content defines it. + * Deferred extensions are going to be included in all project contexts. + */ + Deferred = 7 +} + +export interface TypeScriptInstance { + parseJsonConfigFileContent: typeof ts.parseJsonConfigFileContent; + readConfigFile: typeof ts.readConfigFile; + createCompilerHost: typeof ts.createCompilerHost; + createProgram: typeof ts.createProgram; + flattenDiagnosticMessageText: typeof ts.flattenDiagnosticMessageText; + resolveModuleName: typeof ts.resolveModuleName; + createSourceFile: typeof ts.createSourceFile; + version: typeof ts.version; + sys: typeof ts.sys; +} diff --git a/src/VueProgram.ts b/src/VueProgram.ts index 5dc42e43..5b668dff 100644 --- a/src/VueProgram.ts +++ b/src/VueProgram.ts @@ -1,8 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; +// tslint:disable-next-line:no-implicit-dependencies import * as ts from 'typescript'; import { FilesRegister } from './FilesRegister'; import { FilesWatcher } from './FilesWatcher'; +import { ScriptKind, TypeScriptInstance } from './TypeScriptInstance'; // tslint:disable-next-line import * as vueCompiler from 'vue-template-compiler'; @@ -12,15 +14,15 @@ interface ResolvedScript { } export class VueProgram { - static loadProgramConfig(configFile: string) { + static loadProgramConfig(typescript: TypeScriptInstance, configFile: string, compilerOptions: object) { const extraExtensions = ['vue']; const parseConfigHost: ts.ParseConfigHost = { - fileExists: ts.sys.fileExists, - readFile: ts.sys.readFile, - useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, + fileExists: typescript.sys.fileExists, + readFile: typescript.sys.readFile, + useCaseSensitiveFileNames: typescript.sys.useCaseSensitiveFileNames, readDirectory: (rootDir, extensions, excludes, includes, depth) => { - return ts.sys.readDirectory( + return typescript.sys.readDirectory( rootDir, extensions.concat(extraExtensions), excludes, @@ -30,11 +32,16 @@ export class VueProgram { } }; - const parsed = ts.parseJsonConfigFileContent( - // Regardless of the setting in the tsconfig.json we want isolatedModules to be false - Object.assign(ts.readConfigFile(configFile, ts.sys.readFile).config, { - isolatedModules: false - }), + const tsconfig = typescript.readConfigFile(configFile, typescript.sys.readFile).config; + + tsconfig.compilerOptions = tsconfig.compilerOptions || {}; + tsconfig.compilerOptions = { + ...tsconfig.compilerOptions, + ...compilerOptions + }; + + const parsed = typescript.parseJsonConfigFileContent( + tsconfig, parseConfigHost, path.dirname(configFile) ); @@ -102,13 +109,14 @@ export class VueProgram { } static createProgram( + typescript: TypeScriptInstance, programConfig: ts.ParsedCommandLine, basedir: string, files: FilesRegister, watcher: FilesWatcher, oldProgram: ts.Program ) { - const host = ts.createCompilerHost(programConfig.options); + const host = typescript.createCompilerHost(programConfig.options); const realGetSourceFile = host.getSourceFile; // We need a host that can parse Vue SFCs (single file components). @@ -137,7 +145,7 @@ export class VueProgram { // get typescript contents from Vue file if (source && VueProgram.isVue(filePath)) { const resolved = VueProgram.resolveScriptBlock(source.text); - source = ts.createSourceFile( + source = typescript.createSourceFile( filePath, resolved.content, languageVersion, @@ -155,7 +163,7 @@ export class VueProgram { for (const moduleName of moduleNames) { // Try to use standard resolution. - const { resolvedModule } = ts.resolveModuleName( + const { resolvedModule } = typescript.resolveModuleName( moduleName, containingFile, programConfig.options, @@ -221,7 +229,7 @@ export class VueProgram { return resolvedModules; }; - return ts.createProgram( + return typescript.createProgram( programConfig.fileNames, programConfig.options, host, @@ -231,14 +239,14 @@ export class VueProgram { private static getScriptKindByLang(lang: string) { if (lang === 'ts') { - return ts.ScriptKind.TS; + return ScriptKind.TS; } else if (lang === 'tsx') { - return ts.ScriptKind.TSX; + return ScriptKind.TSX; } else if (lang === 'jsx') { - return ts.ScriptKind.JSX; + return ScriptKind.JSX; } else { // when lang is "js" or no lang specified - return ts.ScriptKind.JS; + return ScriptKind.JS; } } @@ -264,7 +272,7 @@ export class VueProgram { // No