index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. "use strict";
  2. var __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. };
  13. var __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. };
  20. var __importDefault = (this && this.__importDefault) || function (mod) {
  21. return (mod && mod.__esModule) ? mod : { "default": mod };
  22. };
  23. var path = __importStar(require("path"));
  24. var childProcess = __importStar(require("child_process"));
  25. var semver = __importStar(require("semver"));
  26. var micromatch_1 = __importDefault(require("micromatch"));
  27. var chalk_1 = __importDefault(require("chalk"));
  28. var worker_rpc_1 = require("worker-rpc");
  29. var CancellationToken_1 = require("./CancellationToken");
  30. var formatter_1 = require("./formatter");
  31. var FsHelper_1 = require("./FsHelper");
  32. var hooks_1 = require("./hooks");
  33. var RpcTypes_1 = require("./RpcTypes");
  34. var issue_1 = require("./issue");
  35. var 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. */
  43. var 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 doneOrFailed = 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, doneOrFailed);
  225. this.compiler.hooks.failed.tap(checkerPluginName, doneOrFailed);
  226. process.on('exit', function () {
  227. _this.killService();
  228. });
  229. };
  230. ForkTsCheckerWebpackPlugin.prototype.pluginCompile = function () {
  231. var _this = this;
  232. var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
  233. this.compiler.hooks.compile.tap(checkerPluginName, function () {
  234. _this.compilationDone = false;
  235. forkTsCheckerHooks.serviceBeforeStart.callAsync(function () {
  236. if (_this.cancellationToken) {
  237. // request cancellation if there is not finished job
  238. _this.cancellationToken.requestCancellation();
  239. forkTsCheckerHooks.cancel.call(_this.cancellationToken);
  240. }
  241. _this.checkDone = false;
  242. _this.started = process.hrtime();
  243. // create new token for current job
  244. _this.cancellationToken = new CancellationToken_1.CancellationToken(_this.typescript);
  245. if (!_this.service || !_this.service.connected) {
  246. _this.spawnService();
  247. }
  248. try {
  249. if (_this.measureTime) {
  250. _this.startAt = _this.performance.now();
  251. }
  252. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  253. _this.serviceRpc.rpc(RpcTypes_1.RUN, _this.cancellationToken.toJSON()).then(function (result) {
  254. if (result) {
  255. _this.handleServiceMessage(result);
  256. }
  257. });
  258. }
  259. catch (error) {
  260. if (!_this.silent && _this.logger) {
  261. _this.logger.error(chalk_1.default.red('Cannot start checker service: ' +
  262. (error ? error.toString() : 'Unknown error')));
  263. }
  264. forkTsCheckerHooks.serviceStartError.call(error);
  265. }
  266. });
  267. });
  268. };
  269. ForkTsCheckerWebpackPlugin.prototype.pluginEmit = function () {
  270. var _this = this;
  271. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  272. var emit = function (compilation, callback) {
  273. if (_this.isWatching && _this.async) {
  274. callback();
  275. return;
  276. }
  277. _this.emitCallback = _this.createEmitCallback(compilation, callback);
  278. if (_this.checkDone) {
  279. _this.emitCallback();
  280. }
  281. _this.compilationDone = true;
  282. };
  283. this.compiler.hooks.emit.tapAsync(checkerPluginName, emit);
  284. };
  285. ForkTsCheckerWebpackPlugin.prototype.pluginDone = function () {
  286. var _this = this;
  287. var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
  288. this.compiler.hooks.done.tap(checkerPluginName, function () {
  289. if (!_this.isWatching || !_this.async) {
  290. return;
  291. }
  292. if (_this.checkDone) {
  293. _this.doneCallback();
  294. }
  295. else {
  296. if (_this.compiler) {
  297. forkTsCheckerHooks.waiting.call();
  298. }
  299. if (!_this.silent && _this.logger) {
  300. _this.logger.info('Type checking in progress...');
  301. }
  302. }
  303. _this.compilationDone = true;
  304. });
  305. };
  306. ForkTsCheckerWebpackPlugin.prototype.spawnService = function () {
  307. var _this = this;
  308. 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) });
  309. if (typeof this.resolveModuleNameModule !== 'undefined') {
  310. env.RESOLVE_MODULE_NAME = this.resolveModuleNameModule;
  311. }
  312. else {
  313. delete env.RESOLVE_MODULE_NAME;
  314. }
  315. if (typeof this.resolveTypeReferenceDirectiveModule !== 'undefined') {
  316. env.RESOLVE_TYPE_REFERENCE_DIRECTIVE = this.resolveTypeReferenceDirectiveModule;
  317. }
  318. else {
  319. delete env.RESOLVE_TYPE_REFERENCE_DIRECTIVE;
  320. }
  321. this.service = childProcess.fork(path.resolve(__dirname, './service.js'), [], {
  322. env: env,
  323. execArgv: ['--max-old-space-size=' + this.memoryLimit].concat(this.nodeArgs),
  324. stdio: ['inherit', 'inherit', 'inherit', 'ipc']
  325. });
  326. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  327. this.serviceRpc = new worker_rpc_1.RpcProvider(function (message) { return _this.service.send(message); });
  328. this.service.on('message', function (message) {
  329. if (_this.serviceRpc) {
  330. // ensure that serviceRpc is defined to avoid race-conditions
  331. _this.serviceRpc.dispatch(message);
  332. }
  333. });
  334. var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
  335. forkTsCheckerHooks.serviceStart.call(this.tsconfigPath, this.memoryLimit);
  336. if (!this.silent && this.logger) {
  337. this.logger.info('Starting type checking service...');
  338. }
  339. this.service.on('exit', function (code, signal) {
  340. return _this.handleServiceExit(code, signal);
  341. });
  342. };
  343. ForkTsCheckerWebpackPlugin.prototype.killService = function () {
  344. if (!this.service) {
  345. return;
  346. }
  347. try {
  348. if (this.cancellationToken) {
  349. this.cancellationToken.cleanupCancellation();
  350. }
  351. // clean-up listeners
  352. this.service.removeAllListeners();
  353. this.service.kill();
  354. this.service = undefined;
  355. this.serviceRpc = undefined;
  356. }
  357. catch (e) {
  358. if (this.logger && !this.silent) {
  359. this.logger.error(e);
  360. }
  361. }
  362. };
  363. ForkTsCheckerWebpackPlugin.prototype.handleServiceMessage = function (message) {
  364. var _this = this;
  365. if (this.measureTime) {
  366. var delta = this.performance.now() - this.startAt;
  367. var deltaRounded = Math.round(delta * 100) / 100;
  368. this.logger.info("Compilation took: " + deltaRounded + " ms.");
  369. }
  370. if (this.cancellationToken) {
  371. this.cancellationToken.cleanupCancellation();
  372. // job is done - nothing to cancel
  373. this.cancellationToken = undefined;
  374. }
  375. this.checkDone = true;
  376. this.elapsed = process.hrtime(this.started);
  377. this.diagnostics = message.diagnostics;
  378. this.lints = message.lints;
  379. if (this.ignoreDiagnostics.length) {
  380. this.diagnostics = this.diagnostics.filter(function (diagnostic) {
  381. return !_this.ignoreDiagnostics.includes(parseInt(diagnostic.code, 10));
  382. });
  383. }
  384. if (this.ignoreLints.length) {
  385. this.lints = this.lints.filter(function (lint) { return !_this.ignoreLints.includes(lint.code); });
  386. }
  387. if (this.reportFiles.length) {
  388. var reportFilesPredicate = function (issue) {
  389. if (issue.file) {
  390. var relativeFileName = path.relative(_this.compiler.options.context, issue.file);
  391. var matchResult = micromatch_1.default([relativeFileName], _this.reportFiles);
  392. if (matchResult.length === 0) {
  393. return false;
  394. }
  395. }
  396. return true;
  397. };
  398. this.diagnostics = this.diagnostics.filter(reportFilesPredicate);
  399. this.lints = this.lints.filter(reportFilesPredicate);
  400. }
  401. var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
  402. forkTsCheckerHooks.receive.call(this.diagnostics, this.lints);
  403. if (this.compilationDone) {
  404. this.isWatching && this.async ? this.doneCallback() : this.emitCallback();
  405. }
  406. };
  407. ForkTsCheckerWebpackPlugin.prototype.handleServiceExit = function (_code, signal) {
  408. if (signal !== 'SIGABRT' && signal !== 'SIGINT') {
  409. return;
  410. }
  411. // probably out of memory :/
  412. if (this.compiler) {
  413. var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
  414. forkTsCheckerHooks.serviceOutOfMemory.call();
  415. }
  416. if (!this.silent && this.logger) {
  417. if (signal === 'SIGINT') {
  418. this.logger.error(chalk_1.default.red('Type checking and linting interrupted - If running in a docker container, this may be caused ' +
  419. "by the container running out of memory. If so, try increasing the container's memory limit " +
  420. 'or lowering the memoryLimit value in the ForkTsCheckerWebpackPlugin configuration.'));
  421. }
  422. else {
  423. this.logger.error(chalk_1.default.red('Type checking and linting aborted - probably out of memory. ' +
  424. 'Check `memoryLimit` option in ForkTsCheckerWebpackPlugin configuration.'));
  425. }
  426. }
  427. };
  428. ForkTsCheckerWebpackPlugin.prototype.createEmitCallback = function (compilation, callback) {
  429. return function emitCallback() {
  430. var _this = this;
  431. if (!this.elapsed) {
  432. throw new Error('Execution order error');
  433. }
  434. var elapsed = Math.round(this.elapsed[0] * 1e9 + this.elapsed[1]);
  435. var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
  436. forkTsCheckerHooks.emit.call(this.diagnostics, this.lints, elapsed);
  437. this.diagnostics.concat(this.lints).forEach(function (issue) {
  438. // webpack message format
  439. var formatted = {
  440. rawMessage: _this.rawFormatter(issue),
  441. message: _this.formatter(issue),
  442. location: {
  443. line: issue.line,
  444. character: issue.character
  445. },
  446. file: issue.file
  447. };
  448. if (issue.severity === issue_1.IssueSeverity.WARNING) {
  449. if (!_this.ignoreLintWarnings) {
  450. compilation.warnings.push(formatted);
  451. }
  452. }
  453. else {
  454. compilation.errors.push(formatted);
  455. }
  456. });
  457. callback();
  458. };
  459. };
  460. ForkTsCheckerWebpackPlugin.prototype.createNoopEmitCallback = function () {
  461. // this function is empty intentionally
  462. // eslint-disable-next-line @typescript-eslint/no-empty-function
  463. return function noopEmitCallback() { };
  464. };
  465. ForkTsCheckerWebpackPlugin.prototype.printLoggerMessage = function (issue, formattedIssue) {
  466. if (issue.severity === issue_1.IssueSeverity.WARNING) {
  467. if (this.ignoreLintWarnings) {
  468. return;
  469. }
  470. this.logger.warn(formattedIssue);
  471. }
  472. else {
  473. this.logger.error(formattedIssue);
  474. }
  475. };
  476. ForkTsCheckerWebpackPlugin.prototype.createDoneCallback = function () {
  477. return function doneCallback() {
  478. var _this = this;
  479. if (!this.elapsed) {
  480. throw new Error('Execution order error');
  481. }
  482. var elapsed = Math.round(this.elapsed[0] * 1e9 + this.elapsed[1]);
  483. if (this.compiler) {
  484. var forkTsCheckerHooks = ForkTsCheckerWebpackPlugin.getCompilerHooks(this.compiler);
  485. forkTsCheckerHooks.done.call(this.diagnostics, this.lints, elapsed);
  486. }
  487. if (!this.silent && this.logger) {
  488. if (this.diagnostics.length || this.lints.length) {
  489. (this.lints || []).concat(this.diagnostics).forEach(function (diagnostic) {
  490. var formattedDiagnostic = _this.formatter(diagnostic);
  491. _this.printLoggerMessage(diagnostic, formattedDiagnostic);
  492. });
  493. }
  494. if (!this.diagnostics.length) {
  495. this.logger.info(chalk_1.default.green('No type errors found'));
  496. }
  497. this.logger.info('Version: typescript ' +
  498. chalk_1.default.bold(this.typescriptVersion) +
  499. (this.eslint
  500. ? ', eslint ' + chalk_1.default.bold(this.eslintVersion)
  501. : ''));
  502. this.logger.info("Time: " + chalk_1.default.bold(Math.round(elapsed / 1e6).toString()) + " ms");
  503. }
  504. };
  505. };
  506. ForkTsCheckerWebpackPlugin.DEFAULT_MEMORY_LIMIT = 2048;
  507. return ForkTsCheckerWebpackPlugin;
  508. }());
  509. module.exports = ForkTsCheckerWebpackPlugin;
  510. //# sourceMappingURL=index.js.map