From d862d9291853c6b7430a0dbdc965b16db0723925 Mon Sep 17 00:00:00 2001 From: Jonathan Grimes Date: Mon, 30 Nov 2020 16:06:09 -0600 Subject: [PATCH] fix: recompile speedups (#55) * fix: don't use threads after intial build * fix: flip threads default * docs: move `threads` option Co-authored-by: Ricardo Gobbo de Souza --- README.md | 7 +++++++ declarations/utils.d.ts | 1 + src/getESLint.js | 40 ++++++++++++++++++++++++++++++++++------ src/options.js | 1 - src/options.json | 2 +- src/utils.js | 21 +++++++++++++++++++++ test/utils/conf.js | 2 ++ 7 files changed, 66 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bc36638..db9d173 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,13 @@ Accepts a function that will have one argument: an array of eslint messages (obj Lint only changed files, skip lint on start. +#### `threads` + +- Type: `Boolean | Number` +- Default: `false` + +Will run lint tasks across a thread pool. The pool size is automatic unless you specify a number. + ### Errors and Warning **By default the plugin will auto adjust error reporting depending on eslint errors/warnings counts.** diff --git a/declarations/utils.d.ts b/declarations/utils.d.ts index 7eb1a4a..af94077 100644 --- a/declarations/utils.d.ts +++ b/declarations/utils.d.ts @@ -18,3 +18,4 @@ export function parseFoldersToGlobs( patterns: string | string[], extensions?: string | string[] ): string[]; +export function jsonStringifyReplacerSortKeys(_: string, value: any): any; diff --git a/src/getESLint.js b/src/getESLint.js index faa1b9c..c8a3816 100644 --- a/src/getESLint.js +++ b/src/getESLint.js @@ -3,6 +3,10 @@ import os from 'os'; import JestWorker from 'jest-worker'; import { getESLintOptions } from './options'; +import { jsonStringifyReplacerSortKeys } from './utils'; + +/** @type {{[key: string]: any}} */ +const cache = {}; /** @typedef {import('eslint').ESLint} ESLint */ /** @typedef {import('eslint').ESLint.LintResult} LintResult */ @@ -47,6 +51,7 @@ export function loadESLint(options) { * @returns {Linter} */ export function loadESLintThreaded(poolSize, options) { + const key = getCacheKey(options); const { eslintPath = 'eslint' } = options; const source = require.resolve('./worker'); const workerOptions = { @@ -57,18 +62,28 @@ export function loadESLintThreaded(poolSize, options) { const local = loadESLint(options); - /** @type {Worker} */ + /** @type {Worker?} */ // prettier-ignore - const worker = (/** @type {Worker} */ new JestWorker(source, workerOptions)); + let worker = (/** @type {Worker} */ new JestWorker(source, workerOptions)); - return { + /** @type {Linter} */ + const context = { ...local, threads: poolSize, - lintFiles: (files) => worker.lintFiles(files), + lintFiles: async (files) => + (worker && (await worker.lintFiles(files))) || + /* istanbul ignore next */ [], cleanup: async () => { - worker.end(); + cache[key] = local; + context.lintFiles = (files) => local.lintFiles(files); + if (worker) { + worker.end(); + worker = null; + } }, }; + + return context; } /** @@ -84,5 +99,18 @@ export default function getESLint({ threads, ...options }) { : /* istanbul ignore next */ threads; - return max > 1 ? loadESLintThreaded(max, options) : loadESLint(options); + const key = getCacheKey({ threads, ...options }); + if (!cache[key]) { + cache[key] = + max > 1 ? loadESLintThreaded(max, options) : loadESLint(options); + } + return cache[key]; +} + +/** + * @param {Options} options + * @returns {string} + */ +function getCacheKey(options) { + return JSON.stringify(options, jsonStringifyReplacerSortKeys); } diff --git a/src/options.js b/src/options.js index 0763850..d6ea242 100644 --- a/src/options.js +++ b/src/options.js @@ -47,7 +47,6 @@ import schema from './options.json'; export function getOptions(pluginOptions) { const options = { extensions: 'js', - threads: true, ...pluginOptions, }; diff --git a/src/options.json b/src/options.json index 7aadd89..c104396 100644 --- a/src/options.json +++ b/src/options.json @@ -77,7 +77,7 @@ ] }, "threads": { - "description": "Set to true to auto-selected based on number of cpus. Set to a number greater than 1 to set an explicit maximum. Set to false, 1 or less to disable and only run in main process.", + "description": "Default is false. Set to true for an auto-selected pool size based on number of cpus. Set to a number greater than 1 to set an explicit pool size. Set to false, 1, or less to disable and only run in main process.", "anyOf": [{ "type": "number" }, { "type": "boolean" }] } } diff --git a/src/utils.js b/src/utils.js index c202312..03a35ae 100644 --- a/src/utils.js +++ b/src/utils.js @@ -60,3 +60,24 @@ export function parseFoldersToGlobs(patterns, extensions = []) { return pattern; }); } + +/** + * + * @param {string} _ key, but unused + * @param {any} value + */ +export const jsonStringifyReplacerSortKeys = (_, value) => { + /** + * @param {{ [x: string]: any; }} sorted + * @param {string | number} key + */ + const insert = (sorted, key) => { + // eslint-disable-next-line no-param-reassign + sorted[key] = value[key]; + return sorted; + }; + + return value instanceof Object && !(value instanceof Array) + ? Object.keys(value).sort().reduce(insert, {}) + : value; +}; diff --git a/test/utils/conf.js b/test/utils/conf.js index ede9d72..5cbde18 100644 --- a/test/utils/conf.js +++ b/test/utils/conf.js @@ -18,6 +18,8 @@ export default (entry, pluginConf = {}, webpackConf = {}) => { // folder to skip it on the global linting, but here we want the opposite // (we only use .eslintignore on the test that checks this) ignore: false, + // TODO: update tests to run both states: test.each([[{threads: false}], [{threads: true}]])('it should...', async ({threads}) => {...}) + threads: true, ...pluginConf, }), ],