diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1b7df7..bcf00fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v1.0.0-alpha.5 + +* [can now provide path where typescript can be found](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/204) + ## v1.0.0-alpha.4 * [make node 6 compatible](https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/202) diff --git a/README.md b/README.md index fd6112ae..2d5e43f2 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,9 @@ may be much faster, even compared to multi-threaded compilation. 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 where `typescript` can be found. Defaults to `require.resolve('typescript')`. + ### Pre-computed consts: * `ForkTsCheckerWebpackPlugin.ONE_CPU` - always use one CPU * `ForkTsCheckerWebpackPlugin.ALL_CPUS` - always use all CPUs (will increase build time) diff --git a/package.json b/package.json index 045eb8f4..8982c419 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fork-ts-checker-webpack-plugin", - "version": "1.0.0-alpha.4", + "version": "1.0.0-alpha.5", "description": "Runs typescript type checker and linter on separate process.", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/ApiIncrementalChecker.ts b/src/ApiIncrementalChecker.ts index 4a3c11ce..da08b0b4 100644 --- a/src/ApiIncrementalChecker.ts +++ b/src/ApiIncrementalChecker.ts @@ -1,10 +1,12 @@ -import * as ts from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies +import * as ts from 'typescript'; // Imported for types alone +// tslint:disable-next-line:no-implicit-dependencies +import { Configuration, Linter, LintResult } from 'tslint'; import * as minimatch from 'minimatch'; import * as path from 'path'; import { IncrementalCheckerInterface } from './IncrementalCheckerInterface'; import { CancellationToken } from './CancellationToken'; import { NormalizedMessage } from './NormalizedMessage'; -import { Configuration, Linter, LintResult } from 'tslint'; import { CompilerHost } from './CompilerHost'; import { FsHelper } from './FsHelper'; @@ -28,6 +30,7 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface { private lastRemovedFiles: string[] = []; constructor( + typescript: typeof ts, programConfigFile: string, compilerOptions: ts.CompilerOptions, private linterConfigFile: string | false, @@ -37,6 +40,7 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface { this.initLinterConfig(); this.tsIncrementalCompiler = new CompilerHost( + typescript, programConfigFile, compilerOptions, checkSyntacticErrors @@ -64,6 +68,7 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface { } private static loadLinterConfig(configFile: string): ConfigurationFile { + // tslint:disable-next-line:no-implicit-dependencies const tslint = require('tslint'); return tslint.Configuration.loadConfigurationFromPath( @@ -72,6 +77,7 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface { } private createLinter(program: ts.Program): Linter { + // tslint:disable-next-line:no-implicit-dependencies const tslint = require('tslint'); return new tslint.Linter({ fix: this.linterAutoFix }, program); diff --git a/src/CancellationToken.ts b/src/CancellationToken.ts index 1f948d19..9e84d264 100644 --- a/src/CancellationToken.ts +++ b/src/CancellationToken.ts @@ -2,7 +2,8 @@ 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'; +// tslint:disable-next-line:no-implicit-dependencies +import * as ts from 'typescript'; // Imported for types alone import { FsHelper } from './FsHelper'; @@ -15,15 +16,26 @@ export class CancellationToken { private isCancelled: boolean; private cancellationFileName: string; private lastCancellationCheckTime: number; - constructor(cancellationFileName?: string, isCancelled?: boolean) { + constructor( + private typescript: typeof ts, + cancellationFileName?: string, + isCancelled?: boolean + ) { this.isCancelled = !!isCancelled; this.cancellationFileName = cancellationFileName || crypto.randomBytes(64).toString('hex'); this.lastCancellationCheckTime = 0; } - public static createFromJSON(json: CancellationTokenData) { - return new CancellationToken(json.cancellationFileName, json.isCancelled); + public static createFromJSON( + typescript: typeof ts, + json: CancellationTokenData + ) { + return new CancellationToken( + typescript, + json.cancellationFileName, + json.isCancelled + ); } public toJSON() { @@ -56,7 +68,7 @@ export class CancellationToken { public throwIfCancellationRequested() { if (this.isCancellationRequested()) { - throw new ts.OperationCanceledException(); + throw new this.typescript.OperationCanceledException(); } } diff --git a/src/CompilerHost.ts b/src/CompilerHost.ts index 7736906a..fc770bc9 100644 --- a/src/CompilerHost.ts +++ b/src/CompilerHost.ts @@ -1,4 +1,5 @@ -import * as ts from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies +import * as ts from 'typescript'; // Imported for types alone import { LinkedList } from './LinkedList'; import { VueProgram } from './VueProgram'; @@ -51,15 +52,16 @@ export class CompilerHost private compilationStarted = false; constructor( + private typescript: typeof ts, programConfigFile: string, compilerOptions: ts.CompilerOptions, checkSyntacticErrors: boolean ) { - this.tsHost = ts.createWatchCompilerHost( + this.tsHost = typescript.createWatchCompilerHost( programConfigFile, compilerOptions, - ts.sys, - ts.createEmitAndSemanticDiagnosticsBuilderProgram, + typescript.sys, + typescript.createEmitAndSemanticDiagnosticsBuilderProgram, (diag: ts.Diagnostic) => { if (!checkSyntacticErrors && diag.code >= 1000 && diag.code < 2000) { return; @@ -133,11 +135,13 @@ export class CompilerHost item.callback(e.fileName, e.eventKind); files.push(e.fileName); if ( - e.eventKind === ts.FileWatcherEventKind.Created || - e.eventKind === ts.FileWatcherEventKind.Changed + e.eventKind === this.typescript.FileWatcherEventKind.Created || + e.eventKind === this.typescript.FileWatcherEventKind.Changed ) { updatedFiles.push(e.fileName); - } else if (e.eventKind === ts.FileWatcherEventKind.Deleted) { + } else if ( + e.eventKind === this.typescript.FileWatcherEventKind.Deleted + ) { removedFiles.push(e.fileName); } } @@ -186,11 +190,11 @@ export class CompilerHost // dramatic compilation time increase), so we have to stick with these // hacks for now. this.compilationStarted = true; - return ts.sys.setTimeout!(callback, 1, args); + return this.typescript.sys.setTimeout!(callback, 1, args); } public clearTimeout(timeoutId: any): void { - ts.sys.clearTimeout!(timeoutId); + this.typescript.sys.clearTimeout!(timeoutId); } public onWatchStatusChange( @@ -258,7 +262,7 @@ export class CompilerHost // get typescript contents from Vue file if (content && VueProgram.isVue(path)) { - const resolved = VueProgram.resolveScriptBlock(content); + const resolved = VueProgram.resolveScriptBlock(this.typescript, content); return resolved.content; } @@ -285,10 +289,17 @@ export class CompilerHost include?: ReadonlyArray, depth?: number ): string[] { - return ts.sys.readDirectory(path, extensions, exclude, include, depth); + return this.typescript.sys.readDirectory( + path, + extensions, + exclude, + include, + depth + ); } - public createProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram; + public createProgram = this.typescript + .createEmitAndSemanticDiagnosticsBuilderProgram; public getCurrentDirectory(): string { return this.tsHost.getCurrentDirectory(); diff --git a/src/FilesRegister.ts b/src/FilesRegister.ts index f1edf3f8..c9b31a6d 100644 --- a/src/FilesRegister.ts +++ b/src/FilesRegister.ts @@ -1,5 +1,7 @@ -import * as ts from 'typescript'; -import { RuleFailure } from 'tslint'; +// tslint:disable-next-line:no-implicit-dependencies +import * as ts from 'typescript'; // import for types alone +// tslint:disable-next-line:no-implicit-dependencies +import { RuleFailure } from 'tslint'; // import for types alone export interface DataShape { source?: ts.SourceFile; diff --git a/src/IncrementalChecker.ts b/src/IncrementalChecker.ts index 42323a21..5530ba37 100644 --- a/src/IncrementalChecker.ts +++ b/src/IncrementalChecker.ts @@ -1,6 +1,8 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as ts from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies +import * as ts from 'typescript'; // Imported for types alone; actual requires take place in methods below +// tslint:disable-next-line:no-implicit-dependencies import { Configuration, Linter, RuleFailure } from 'tslint'; // Imported for types alone; actual requires take place in methods below import { FilesRegister } from './FilesRegister'; import { FilesWatcher } from './FilesWatcher'; @@ -42,6 +44,7 @@ export class IncrementalChecker implements IncrementalCheckerInterface { private watcher?: FilesWatcher; constructor( + private typescript: typeof ts, private programConfigFile: string, private compilerOptions: object, private linterConfigFile: string | false, @@ -53,8 +56,15 @@ export class IncrementalChecker implements IncrementalCheckerInterface { private vue: boolean = false ) {} - public static loadProgramConfig(configFile: string, compilerOptions: object) { - const tsconfig = ts.readConfigFile(configFile, ts.sys.readFile).config; + public static loadProgramConfig( + typescript: typeof ts, + configFile: string, + compilerOptions: object + ) { + const tsconfig = typescript.readConfigFile( + configFile, + typescript.sys.readFile + ).config; tsconfig.compilerOptions = tsconfig.compilerOptions || {}; tsconfig.compilerOptions = { @@ -62,9 +72,9 @@ export class IncrementalChecker implements IncrementalCheckerInterface { ...compilerOptions }; - const parsed = ts.parseJsonConfigFileContent( + const parsed = typescript.parseJsonConfigFileContent( tsconfig, - ts.sys, + typescript.sys, path.dirname(configFile) ); @@ -72,6 +82,7 @@ export class IncrementalChecker implements IncrementalCheckerInterface { } private static loadLinterConfig(configFile: string): ConfigurationFile { + // tslint:disable-next-line:no-implicit-dependencies const tslint = require('tslint'); return tslint.Configuration.loadConfigurationFromPath( @@ -80,12 +91,13 @@ export class IncrementalChecker implements IncrementalCheckerInterface { } private static createProgram( + typescript: typeof ts, 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) => { @@ -111,7 +123,7 @@ export class IncrementalChecker implements IncrementalCheckerInterface { return files.getData(filePath).source; }; - return ts.createProgram( + return typescript.createProgram( programConfig.fileNames, programConfig.options, host, @@ -120,6 +132,7 @@ export class IncrementalChecker implements IncrementalCheckerInterface { } private createLinter(program: ts.Program) { + // tslint:disable-next-line:no-implicit-dependencies const tslint = require('tslint'); return new tslint.Linter({ fix: this.linterAutoFix }, program); @@ -186,11 +199,13 @@ export class IncrementalChecker implements IncrementalCheckerInterface { this.programConfig = this.programConfig || VueProgram.loadProgramConfig( + this.typescript, this.programConfigFile, this.compilerOptions ); return VueProgram.createProgram( + this.typescript, this.programConfig, path.dirname(this.programConfigFile), this.files, @@ -203,11 +218,13 @@ export class IncrementalChecker implements IncrementalCheckerInterface { this.programConfig = this.programConfig || IncrementalChecker.loadProgramConfig( + this.typescript, this.programConfigFile, this.compilerOptions ); return IncrementalChecker.createProgram( + this.typescript, this.programConfig, this.files, this.watcher!, diff --git a/src/NormalizedMessage.ts b/src/NormalizedMessage.ts index 46328ef1..5d7840a1 100644 --- a/src/NormalizedMessage.ts +++ b/src/NormalizedMessage.ts @@ -2,7 +2,9 @@ import { Diagnostic, DiagnosticCategory, flattenDiagnosticMessageText + // tslint:disable-next-line:no-implicit-dependencies } from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies import { RuleFailure } from 'tslint'; type ErrorType = 'diagnostic' | 'lint'; diff --git a/src/VueProgram.ts b/src/VueProgram.ts index 95c7887d..f5bd7b8b 100644 --- a/src/VueProgram.ts +++ b/src/VueProgram.ts @@ -1,9 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as ts from 'typescript'; +// tslint:disable-next-line:no-implicit-dependencies +import * as ts from 'typescript'; // import for types alone import { FilesRegister } from './FilesRegister'; import { FilesWatcher } from './FilesWatcher'; -// tslint:disable-next-line +// tslint:disable-next-line:no-implicit-dependencies import * as vueCompiler from 'vue-template-compiler'; interface ResolvedScript { @@ -12,15 +13,19 @@ interface ResolvedScript { } export class VueProgram { - public static loadProgramConfig(configFile: string, compilerOptions: object) { + public static loadProgramConfig( + typescript: typeof ts, + 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,7 +35,10 @@ export class VueProgram { } }; - const tsconfig = ts.readConfigFile(configFile, ts.sys.readFile).config; + const tsconfig = typescript.readConfigFile( + configFile, + typescript.sys.readFile + ).config; tsconfig.compilerOptions = tsconfig.compilerOptions || {}; tsconfig.compilerOptions = { @@ -38,7 +46,7 @@ export class VueProgram { ...compilerOptions }; - const parsed = ts.parseJsonConfigFileContent( + const parsed = typescript.parseJsonConfigFileContent( tsconfig, parseConfigHost, path.dirname(configFile) @@ -107,13 +115,14 @@ export class VueProgram { } public static createProgram( + typescript: typeof ts, 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). @@ -141,8 +150,8 @@ export class VueProgram { // get typescript contents from Vue file if (source && VueProgram.isVue(filePath)) { - const resolved = VueProgram.resolveScriptBlock(source.text); - source = ts.createSourceFile( + const resolved = VueProgram.resolveScriptBlock(typescript, source.text); + source = typescript.createSourceFile( filePath, resolved.content, languageVersion, @@ -160,7 +169,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, @@ -226,7 +235,7 @@ export class VueProgram { return resolvedModules; }; - return ts.createProgram( + return typescript.createProgram( programConfig.fileNames, programConfig.options, host, @@ -234,20 +243,23 @@ export class VueProgram { ); } - private static getScriptKindByLang(lang?: string) { + private static getScriptKindByLang(typescript: typeof ts, lang?: string) { if (lang === 'ts') { - return ts.ScriptKind.TS; + return typescript.ScriptKind.TS; } else if (lang === 'tsx') { - return ts.ScriptKind.TSX; + return typescript.ScriptKind.TSX; } else if (lang === 'jsx') { - return ts.ScriptKind.JSX; + return typescript.ScriptKind.JSX; } else { // when lang is "js" or no lang specified - return ts.ScriptKind.JS; + return typescript.ScriptKind.JS; } } - public static resolveScriptBlock(content: string): ResolvedScript { + public static resolveScriptBlock( + typescript: typeof ts, + content: string + ): ResolvedScript { // We need to import vue-template-compiler lazily because it cannot be included it // as direct dependency because it is an optional dependency of fork-ts-checker-webpack-plugin. // Since its version must not mismatch with user-installed Vue.js, @@ -269,12 +281,12 @@ export class VueProgram { // No ' ].join('\n'); - var result = VueProgram.resolveScriptBlock(content); + var result = VueProgram.resolveScriptBlock(ts, content); expect(result.scriptKind).to.be.equal(ts.ScriptKind.TS); expect(result.content).to.be.equal( @@ -150,7 +150,7 @@ describe('[UNIT] VueProgram', function() { '' ].join('\n'); - var result = VueProgram.resolveScriptBlock(content); + var result = VueProgram.resolveScriptBlock(ts, content); expect(result.content).to.be.equal( [ @@ -168,13 +168,17 @@ describe('[UNIT] VueProgram', function() { describe('loadProgramConfig', function() { it('sets allowNonTsExtensions to true on returned options', function() { - var result = VueProgram.loadProgramConfig('tsconfig.foo.json', {}); + var result = VueProgram.loadProgramConfig( + require('typescript'), + 'tsconfig.foo.json', + {} + ); expect(result.options.allowNonTsExtensions).to.equal(true); }); it('merges compilerOptions into config file options', function() { - VueProgram.loadProgramConfig('tsconfig.foo.json', { + VueProgram.loadProgramConfig(require('typescript'), 'tsconfig.foo.json', { bar: false });