UNPKG

22.9 kBJavaScriptView Raw
1"use strict";
2var __assign = (this && this.__assign) || function () {
3 __assign = Object.assign || function(t) {
4 for (var s, i = 1, n = arguments.length; i < n; i++) {
5 s = arguments[i];
6 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7 t[p] = s[p];
8 }
9 return t;
10 };
11 return __assign.apply(this, arguments);
12};
13var __importStar = (this && this.__importStar) || function (mod) {
14 if (mod && mod.__esModule) return mod;
15 var result = {};
16 if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
17 result["default"] = mod;
18 return result;
19};
20var __importDefault = (this && this.__importDefault) || function (mod) {
21 return (mod && mod.__esModule) ? mod : { "default": mod };
22};
23var path = __importStar(require("path"));
24var childProcess = __importStar(require("child_process"));
25var semver = __importStar(require("semver"));
26var micromatch_1 = __importDefault(require("micromatch"));
27var chalk_1 = __importDefault(require("chalk"));
28var worker_rpc_1 = require("worker-rpc");
29var CancellationToken_1 = require("./CancellationToken");
30var formatter_1 = require("./formatter");
31var FsHelper_1 = require("./FsHelper");
32var hooks_1 = require("./hooks");
33var RpcTypes_1 = require("./RpcTypes");
34var issue_1 = require("./issue");
35var checkerPluginName = 'fork-ts-checker-webpack-plugin';
36/**
37 * ForkTsCheckerWebpackPlugin
38 * Runs typescript type checker and linter on separate process.
39 * This speed-ups build a lot.
40 *
41 * Options description in README.md
42 */
43var ForkTsCheckerWebpackPlugin = /** @class */ (function () {
44 function ForkTsCheckerWebpackPlugin(options) {
45 this.eslint = false;
46 this.eslintOptions = {};
47 this.tsconfigPath = undefined;
48 // eslint-disable-next-line @typescript-eslint/no-explicit-any
49 this.compiler = undefined;
50 this.started = undefined;
51 this.elapsed = undefined;
52 this.cancellationToken = undefined;
53 this.isWatching = false;
54 this.checkDone = false;
55 this.compilationDone = false;
56 this.diagnostics = [];
57 this.lints = [];
58 this.eslintVersion = undefined;
59 this.startAt = 0;
60 this.nodeArgs = [];
61 options = options || {};
62 this.options = __assign({}, options);
63 this.ignoreDiagnostics = options.ignoreDiagnostics || [];
64 this.ignoreLints = options.ignoreLints || [];
65 this.ignoreLintWarnings = options.ignoreLintWarnings === true;
66 this.reportFiles = options.reportFiles || [];
67 this.logger = options.logger || console;
68 this.silent = options.silent === true; // default false
69 this.async = options.async !== false; // default true
70 this.checkSyntacticErrors = options.checkSyntacticErrors === true; // default false
71 this.resolveModuleNameModule = options.resolveModuleNameModule;
72 this.resolveTypeReferenceDirectiveModule =
73 options.resolveTypeReferenceDirectiveModule;
74 this.memoryLimit =
75 options.memoryLimit || ForkTsCheckerWebpackPlugin.DEFAULT_MEMORY_LIMIT;
76 this.formatter = formatter_1.createFormatter(options.formatter, options.formatterOptions);
77 this.rawFormatter = formatter_1.createRawFormatter();
78 this.emitCallback = this.createNoopEmitCallback();
79 this.doneCallback = this.createDoneCallback();
80 var _a = this.validateTypeScript(options), typescript = _a.typescript, typescriptPath = _a.typescriptPath, typescriptVersion = _a.typescriptVersion, tsconfig = _a.tsconfig, compilerOptions = _a.compilerOptions;
81 this.typescript = typescript;
82 this.typescriptPath = typescriptPath;
83 this.typescriptVersion = typescriptVersion;
84 this.tsconfig = tsconfig;
85 this.compilerOptions = compilerOptions;
86 if (options.eslint === true) {
87 var _b = this.validateEslint(options), eslintVersion = _b.eslintVersion, eslintOptions = _b.eslintOptions;
88 this.eslint = true;
89 this.eslintVersion = eslintVersion;
90 this.eslintOptions = eslintOptions;
91 }
92 this.vue = ForkTsCheckerWebpackPlugin.prepareVueOptions(options.vue);
93 this.useTypescriptIncrementalApi =
94 options.useTypescriptIncrementalApi === undefined
95 ? semver.gte(this.typescriptVersion, '3.0.0') && !this.vue.enabled
96 : options.useTypescriptIncrementalApi;
97 this.measureTime = options.measureCompilationTime === true;
98 if (this.measureTime) {
99 if (semver.lt(process.version, '8.5.0')) {
100 throw new Error("To use 'measureCompilationTime' option, please update to Node.js >= v8.5.0 " +
101 ("(current version is " + process.version + ")"));
102 }
103 // Node 8+ only
104 // eslint-disable-next-line node/no-unsupported-features/node-builtins
105 this.performance = require('perf_hooks').performance;
106 }
107 }
108 // eslint-disable-next-line @typescript-eslint/no-explicit-any
109 ForkTsCheckerWebpackPlugin.getCompilerHooks = function (compiler) {
110 return hooks_1.getForkTsCheckerWebpackPluginHooks(compiler);
111 };
112 ForkTsCheckerWebpackPlugin.prototype.validateTypeScript = function (options) {
113 var typescriptPath = options.typescript || require.resolve('typescript');
114 var tsconfig = options.tsconfig || './tsconfig.json';
115 var compilerOptions = typeof options.compilerOptions === 'object'
116 ? options.compilerOptions
117 : {};
118 var typescript, typescriptVersion;
119 try {
120 typescript = require(typescriptPath);
121 typescriptVersion = typescript.version;
122 }
123 catch (_ignored) {
124 throw new Error('When you use this plugin you must install `typescript`.');
125 }
126 if (semver.lt(typescriptVersion, '2.1.0')) {
127 throw new Error("Cannot use current typescript version of " + typescriptVersion + ", the minimum required version is 2.1.0");
128 }
129 return {
130 typescriptPath: typescriptPath,
131 typescript: typescript,
132 typescriptVersion: typescriptVersion,
133 tsconfig: tsconfig,
134 compilerOptions: compilerOptions
135 };
136 };
137 ForkTsCheckerWebpackPlugin.prototype.validateEslint = function (options) {
138 var eslintVersion;
139 var eslintOptions = typeof options.eslintOptions === 'object' ? options.eslintOptions : {};
140 if (semver.lt(process.version, '8.10.0')) {
141 throw new Error("To use 'eslint' option, please update to Node.js >= v8.10.0 " +
142 ("(current version is " + process.version + ")"));
143 }
144 try {
145 eslintVersion = require('eslint').Linter.version;
146 }
147 catch (error) {
148 throw new Error("When you use 'eslint' option, make sure to install 'eslint'.");
149 }
150 return { eslintVersion: eslintVersion, eslintOptions: eslintOptions };
151 };
152 ForkTsCheckerWebpackPlugin.prepareVueOptions = function (vueOptions) {
153 var defaultVueOptions = {
154 compiler: 'vue-template-compiler',
155 enabled: false
156 };
157 if (typeof vueOptions === 'boolean') {
158 return Object.assign(defaultVueOptions, { enabled: vueOptions });
159 }
160 else if (typeof vueOptions === 'object' && vueOptions !== null) {
161 return Object.assign(defaultVueOptions, vueOptions);
162 }
163 else {
164 return defaultVueOptions;
165 }
166 };
167 // eslint-disable-next-line @typescript-eslint/no-explicit-any
168 ForkTsCheckerWebpackPlugin.prototype.apply = function (compiler) {
169 this.compiler = compiler;
170 this.tsconfigPath = this.computeContextPath(this.tsconfig);
171 // validate config
172 var tsconfigOk = FsHelper_1.fileExistsSync(this.tsconfigPath);
173 // validate logger
174 if (this.logger) {
175 if (!this.logger.error || !this.logger.warn || !this.logger.info) {
176 throw new Error("Invalid logger object - doesn't provide `error`, `warn` or `info` method.");
177 }
178 }
179 if (!tsconfigOk) {
180 throw new Error('Cannot find "' +
181 this.tsconfigPath +
182 '" file. Please check webpack and ForkTsCheckerWebpackPlugin configuration. \n' +
183 'Possible errors: \n' +
184 ' - wrong `context` directory in webpack configuration' +
185 ' (if `tsconfig` is not set or is a relative path in fork plugin configuration)\n' +
186 ' - wrong `tsconfig` path in fork plugin configuration' +
187 ' (should be a relative or absolute path)');
188 }
189 this.pluginStart();
190 this.pluginStop();
191 this.pluginCompile();
192 this.pluginEmit();
193 this.pluginDone();
194 };
195 ForkTsCheckerWebpackPlugin.prototype.computeContextPath = function (filePath) {
196 return path.isAbsolute(filePath)
197 ? filePath
198 : path.resolve(this.compiler.options.context, filePath);
199 };
200 ForkTsCheckerWebpackPlugin.prototype.pluginStart = function () {
201 var _this = this;
202 var run = function (compilation, callback) {
203 _this.isWatching = false;
204 callback();
205 };
206 var watchRun = function (compiler, callback) {
207 _this.isWatching = true;
208 callback();
209 };
210 this.compiler.hooks.run.tapAsync(checkerPluginName, run);
211 this.compiler.hooks.watchRun.tapAsync(checkerPluginName, watchRun);
212 };
213 ForkTsCheckerWebpackPlugin.prototype.pluginStop = function () {
214 var _this = this;
215 var watchClose = function () {
216 _this.killService();
217 };
218 var done = function () {
219 if (!_this.isWatching) {
220 _this.killService();
221 }
222 };
223 this.compiler.hooks.watchClose.tap(checkerPluginName, watchClose);
224 this.compiler.hooks.done.tap(checkerPluginName, done);
225 process.on('exit', function () {
226 _this.killService();
227 });
228 };
229 ForkTsCheckerWebpackPlugin.prototype.pluginCompile = function () {
230 var _this = this;
231 var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
232 this.compiler.hooks.compile.tap(checkerPluginName, function () {
233 _this.compilationDone = false;
234 forkTsCheckerHooks.serviceBeforeStart.callAsync(function () {
235 if (_this.cancellationToken) {
236 // request cancellation if there is not finished job
237 _this.cancellationToken.requestCancellation();
238 forkTsCheckerHooks.cancel.call(_this.cancellationToken);
239 }
240 _this.checkDone = false;
241 _this.started = process.hrtime();
242 // create new token for current job
243 _this.cancellationToken = new CancellationToken_1.CancellationToken(_this.typescript);
244 if (!_this.service || !_this.service.connected) {
245 _this.spawnService();
246 }
247 try {
248 if (_this.measureTime) {
249 _this.startAt = _this.performance.now();
250 }
251 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
252 _this.serviceRpc.rpc(RpcTypes_1.RUN, _this.cancellationToken.toJSON()).then(function (result) {
253 if (result) {
254 _this.handleServiceMessage(result);
255 }
256 });
257 }
258 catch (error) {
259 if (!_this.silent && _this.logger) {
260 _this.logger.error(chalk_1.default.red('Cannot start checker service: ' +
261 (error ? error.toString() : 'Unknown error')));
262 }
263 forkTsCheckerHooks.serviceStartError.call(error);
264 }
265 });
266 });
267 };
268 ForkTsCheckerWebpackPlugin.prototype.pluginEmit = function () {
269 var _this = this;
270 // eslint-disable-next-line @typescript-eslint/no-explicit-any
271 var emit = function (compilation, callback) {
272 if (_this.isWatching && _this.async) {
273 callback();
274 return;
275 }
276 _this.emitCallback = _this.createEmitCallback(compilation, callback);
277 if (_this.checkDone) {
278 _this.emitCallback();
279 }
280 _this.compilationDone = true;
281 };
282 this.compiler.hooks.emit.tapAsync(checkerPluginName, emit);
283 };
284 ForkTsCheckerWebpackPlugin.prototype.pluginDone = function () {
285 var _this = this;
286 var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
287 this.compiler.hooks.done.tap(checkerPluginName, function () {
288 if (!_this.isWatching || !_this.async) {
289 return;
290 }
291 if (_this.checkDone) {
292 _this.doneCallback();
293 }
294 else {
295 if (_this.compiler) {
296 forkTsCheckerHooks.waiting.call();
297 }
298 if (!_this.silent && _this.logger) {
299 _this.logger.info('Type checking in progress...');
300 }
301 }
302 _this.compilationDone = true;
303 });
304 };
305 ForkTsCheckerWebpackPlugin.prototype.spawnService = function () {
306 var _this = this;
307 var env = __assign({}, process.env, { TYPESCRIPT_PATH: this.typescriptPath, TSCONFIG: this.tsconfigPath, COMPILER_OPTIONS: JSON.stringify(this.compilerOptions), CONTEXT: this.compiler.options.context, ESLINT: String(this.eslint), ESLINT_OPTIONS: JSON.stringify(this.eslintOptions), MEMORY_LIMIT: String(this.memoryLimit), CHECK_SYNTACTIC_ERRORS: String(this.checkSyntacticErrors), USE_INCREMENTAL_API: String(this.useTypescriptIncrementalApi === true), VUE: JSON.stringify(this.vue) });
308 if (typeof this.resolveModuleNameModule !== 'undefined') {
309 env.RESOLVE_MODULE_NAME = this.resolveModuleNameModule;
310 }
311 else {
312 delete env.RESOLVE_MODULE_NAME;
313 }
314 if (typeof this.resolveTypeReferenceDirectiveModule !== 'undefined') {
315 env.RESOLVE_TYPE_REFERENCE_DIRECTIVE = this.resolveTypeReferenceDirectiveModule;
316 }
317 else {
318 delete env.RESOLVE_TYPE_REFERENCE_DIRECTIVE;
319 }
320 this.service = childProcess.fork(path.resolve(__dirname, './service.js'), [], {
321 env: env,
322 execArgv: ['--max-old-space-size=' + this.memoryLimit].concat(this.nodeArgs),
323 stdio: ['inherit', 'inherit', 'inherit', 'ipc']
324 });
325 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
326 this.serviceRpc = new worker_rpc_1.RpcProvider(function (message) { return _this.service.send(message); });
327 this.service.on('message', function (message) {
328 if (_this.serviceRpc) {
329 // ensure that serviceRpc is defined to avoid race-conditions
330 _this.serviceRpc.dispatch(message);
331 }
332 });
333 var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
334 forkTsCheckerHooks.serviceStart.call(this.tsconfigPath, this.memoryLimit);
335 if (!this.silent && this.logger) {
336 this.logger.info('Starting type checking service...');
337 }
338 this.service.on('exit', function (code, signal) {
339 return _this.handleServiceExit(code, signal);
340 });
341 };
342 ForkTsCheckerWebpackPlugin.prototype.killService = function () {
343 if (!this.service) {
344 return;
345 }
346 try {
347 if (this.cancellationToken) {
348 this.cancellationToken.cleanupCancellation();
349 }
350 // clean-up listeners
351 this.service.removeAllListeners();
352 this.service.kill();
353 this.service = undefined;
354 this.serviceRpc = undefined;
355 }
356 catch (e) {
357 if (this.logger && !this.silent) {
358 this.logger.error(e);
359 }
360 }
361 };
362 ForkTsCheckerWebpackPlugin.prototype.handleServiceMessage = function (message) {
363 var _this = this;
364 if (this.measureTime) {
365 var delta = this.performance.now() - this.startAt;
366 var deltaRounded = Math.round(delta * 100) / 100;
367 this.logger.info("Compilation took: " + deltaRounded + " ms.");
368 }
369 if (this.cancellationToken) {
370 this.cancellationToken.cleanupCancellation();
371 // job is done - nothing to cancel
372 this.cancellationToken = undefined;
373 }
374 this.checkDone = true;
375 this.elapsed = process.hrtime(this.started);
376 this.diagnostics = message.diagnostics;
377 this.lints = message.lints;
378 if (this.ignoreDiagnostics.length) {
379 this.diagnostics = this.diagnostics.filter(function (diagnostic) {
380 return !_this.ignoreDiagnostics.includes(parseInt(diagnostic.code, 10));
381 });
382 }
383 if (this.ignoreLints.length) {
384 this.lints = this.lints.filter(function (lint) { return !_this.ignoreLints.includes(lint.code); });
385 }
386 if (this.reportFiles.length) {
387 var reportFilesPredicate = function (issue) {
388 if (issue.file) {
389 var relativeFileName = path.relative(_this.compiler.options.context, issue.file);
390 var matchResult = micromatch_1.default([relativeFileName], _this.reportFiles);
391 if (matchResult.length === 0) {
392 return false;
393 }
394 }
395 return true;
396 };
397 this.diagnostics = this.diagnostics.filter(reportFilesPredicate);
398 this.lints = this.lints.filter(reportFilesPredicate);
399 }
400 var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
401 forkTsCheckerHooks.receive.call(this.diagnostics, this.lints);
402 if (this.compilationDone) {
403 this.isWatching && this.async ? this.doneCallback() : this.emitCallback();
404 }
405 };
406 ForkTsCheckerWebpackPlugin.prototype.handleServiceExit = function (_code, signal) {
407 if (signal !== 'SIGABRT') {
408 return;
409 }
410 // probably out of memory :/
411 if (this.compiler) {
412 var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
413 forkTsCheckerHooks.serviceOutOfMemory.call();
414 }
415 if (!this.silent && this.logger) {
416 this.logger.error(chalk_1.default.red('Type checking and linting aborted - probably out of memory. ' +
417 'Check `memoryLimit` option in ForkTsCheckerWebpackPlugin configuration.'));
418 }
419 };
420 ForkTsCheckerWebpackPlugin.prototype.createEmitCallback = function (compilation, callback) {
421 return function emitCallback() {
422 var _this = this;
423 if (!this.elapsed) {
424 throw new Error('Execution order error');
425 }
426 var elapsed = Math.round(this.elapsed[0] * 1e9 + this.elapsed[1]);
427 var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
428 forkTsCheckerHooks.emit.call(this.diagnostics, this.lints, elapsed);
429 this.diagnostics.concat(this.lints).forEach(function (issue) {
430 // webpack message format
431 var formatted = {
432 rawMessage: _this.rawFormatter(issue),
433 message: _this.formatter(issue),
434 location: {
435 line: issue.line,
436 character: issue.character
437 },
438 file: issue.file
439 };
440 if (issue.severity === issue_1.IssueSeverity.WARNING) {
441 if (!_this.ignoreLintWarnings) {
442 compilation.warnings.push(formatted);
443 }
444 }
445 else {
446 compilation.errors.push(formatted);
447 }
448 });
449 callback();
450 };
451 };
452 ForkTsCheckerWebpackPlugin.prototype.createNoopEmitCallback = function () {
453 // this function is empty intentionally
454 // eslint-disable-next-line @typescript-eslint/no-empty-function
455 return function noopEmitCallback() { };
456 };
457 ForkTsCheckerWebpackPlugin.prototype.printLoggerMessage = function (issue, formattedIssue) {
458 if (issue.severity === issue_1.IssueSeverity.WARNING) {
459 if (this.ignoreLintWarnings) {
460 return;
461 }
462 this.logger.warn(formattedIssue);
463 }
464 else {
465 this.logger.error(formattedIssue);
466 }
467 };
468 ForkTsCheckerWebpackPlugin.prototype.createDoneCallback = function () {
469 return function doneCallback() {
470 var _this = this;
471 if (!this.elapsed) {
472 throw new Error('Execution order error');
473 }
474 var elapsed = Math.round(this.elapsed[0] * 1e9 + this.elapsed[1]);
475 if (this.compiler) {
476 var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
477 forkTsCheckerHooks.done.call(this.diagnostics, this.lints, elapsed);
478 }
479 if (!this.silent && this.logger) {
480 if (this.diagnostics.length || this.lints.length) {
481 (this.lints || []).concat(this.diagnostics).forEach(function (diagnostic) {
482 var formattedDiagnostic = _this.formatter(diagnostic);
483 _this.printLoggerMessage(diagnostic, formattedDiagnostic);
484 });
485 }
486 if (!this.diagnostics.length) {
487 this.logger.info(chalk_1.default.green('No type errors found'));
488 }
489 this.logger.info('Version: typescript ' +
490 chalk_1.default.bold(this.typescriptVersion) +
491 (this.eslint
492 ? ', eslint ' + chalk_1.default.bold(this.eslintVersion)
493 : ''));
494 this.logger.info('Time: ' + chalk_1.default.bold(Math.round(elapsed / 1e6).toString()) + 'ms');
495 }
496 };
497 };
498 ForkTsCheckerWebpackPlugin.DEFAULT_MEMORY_LIMIT = 2048;
499 return ForkTsCheckerWebpackPlugin;
500}());
501module.exports = ForkTsCheckerWebpackPlugin;
502//# sourceMappingURL=index.js.map
\No newline at end of file