From 510b17a20bb124adaee929c183258873353051f7 Mon Sep 17 00:00:00 2001 From: Owen Stowe Date: Wed, 13 Jun 2018 12:08:16 -0400 Subject: [PATCH] Switch up how we insantiate Bottleneck so rate limit can be configured --- README.md | 1 + lib/index.js | 40 +++++++++++++++++++++++++++------------- src/index.js | 35 ++++++++++++++++++++++++++--------- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index e3e14be..3ba7d6a 100644 --- a/README.md +++ b/README.md @@ -194,3 +194,4 @@ The plugin takes a single object as its only parameter. The following properties | `preserve` | `boolean` | Whether or not to remove selectors from primary CSS document once they've been marked as critical. This should prevent duplication of selectors across critical and non-critical CSS. | `true` | | `minify` | `boolean` | Minify output CSS? | `true` | | `ignoreSelectors` | `array` | Array of selectors to globally exclude from critical CSS output | `[]` | +| `fsWriteRate` | `number` | Minimum amount of time between file writes, in millisenconds. This is intended to prevent overlapping file writes for codebases with numerous calls to `@critical` | `250` | diff --git a/lib/index.js b/lib/index.js index 967fc4c..5787464 100755 --- a/lib/index.js +++ b/lib/index.js @@ -14,20 +14,22 @@ var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); * @param {bool} dryRun Do a dry run? * @param {string} filePath Path to write file to. * @param {Object} result PostCSS root object. + * @param {number} fsWriteRate minimum time between file writes * @return {Promise} Resolves with writeCriticalFile or doDryRun function call. */ let dryRunOrWriteFile = (() => { - var _ref = (0, _asyncToGenerator3.default)(function* (dryRun, filePath, result) { + var _ref = (0, _asyncToGenerator3.default)(function* (dryRun, filePath, result, fsWriteRate) { const css = result.css; if (dryRun) { doDryRun(css); } else { - yield limiter.schedule(writeCriticalFile, filePath, css); + // Write file at a maximum of once ever 250ms + yield createOrGetLimiter(fsWriteRate).schedule(writeCriticalFile, filePath, css); } }); - return function dryRunOrWriteFile(_x, _x2, _x3) { + return function dryRunOrWriteFile(_x, _x2, _x3, _x4) { return _ref.apply(this, arguments); }; })(); @@ -49,7 +51,6 @@ let dryRunOrWriteFile = (() => { */ let writeCriticalFile = (() => { var _ref2 = (0, _asyncToGenerator3.default)(function* (filePath, css) { - console.log(`writeCriticalFile: ${filePath}`); try { yield _fsExtra2.default.outputFile(filePath, css, { flag: append ? 'a' : 'w' }); append = true; @@ -61,7 +62,7 @@ let writeCriticalFile = (() => { } }); - return function writeCriticalFile(_x4, _x5) { + return function writeCriticalFile(_x5, _x6) { return _ref2.apply(this, arguments); }; })(); @@ -106,14 +107,24 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de let append = false; /** - * Rate limiter for file writes + * Instance of Bottleneck for rate-limiting file writes + * + * @param {number} fsWriteRate minimum time between file writes */ -const limiter = new _bottleneck2.default({ - maxConcurrent: 1, - minTime: 250 -}); +let limiter; + +function createOrGetLimiter(fsWriteRate) { + if (!limiter) { + limiter = new _bottleneck2.default({ + maxConcurrent: 1, + minTime: fsWriteRate + }); + } + + return limiter; +} /** * Clean the original root node passed to the plugin, removing custom atrules, @@ -187,9 +198,11 @@ function doDryRun(css) { preserve: true, minify: true, dryRun: false, - ignoreSelectors: [':export', ':import'] + ignoreSelectors: [], + fsWriteRate: 250 }, filteredOptions); append = false; + return (() => { var _ref3 = (0, _asyncToGenerator3.default)(function* (css) { const dryRun = args.dryRun, @@ -212,6 +225,7 @@ function doDryRun(css) { const criticalCSS = _postcss2.default.root(); const filePath = _path2.default.join(outputPath, outputFile); criticalOutput[outputFile].each(function (rule) { + // Check if we should remove this selector from output const shouldIgnore = rule.selector && args.ignoreSelectors.some(function (ignore) { return rule.selector.includes(ignore); }); @@ -222,7 +236,7 @@ function doDryRun(css) { return criticalCSS.append(rule.clone()); }); result = yield (0, _postcss2.default)(minify ? [_cssnano2.default] : []).process(criticalCSS, { from: undefined }); - yield dryRunOrWriteFile(dryRun, filePath, result); + yield dryRunOrWriteFile(dryRun, filePath, result, args.fsWriteRate); clean(css, preserve); } } catch (err) { @@ -243,7 +257,7 @@ function doDryRun(css) { return result; }); - return function (_x6) { + return function (_x7) { return _ref3.apply(this, arguments); }; })(); diff --git a/src/index.js b/src/index.js index 2fc187d..ceb2df8 100755 --- a/src/index.js +++ b/src/index.js @@ -14,12 +14,26 @@ import { getCriticalRules } from './getCriticalRules' let append = false /** - * Rate limiter for file writes + * Instance of Bottleneck for rate-limiting file writes */ -const limiter = new Bottleneck({ - maxConcurrent: 1, - minTime: 250 -}) +let limiter + +/** + * Create or get a "singleton" instance of Bottleneck + * + * @param {number} fsWriteRate minimum time between file writes + * @return {Bottleneck} instnance of Bottleneck + */ +function createOrGetLimiter (fsWriteRate: number): Bottleneck { + if (!limiter) { + limiter = new Bottleneck({ + maxConcurrent: 1, + minTime: fsWriteRate + }) + } + + return limiter +} /** * Clean the original root node passed to the plugin, removing custom atrules, @@ -95,19 +109,21 @@ function doDryRun (css: string) { * @param {bool} dryRun Do a dry run? * @param {string} filePath Path to write file to. * @param {Object} result PostCSS root object. + * @param {number} fsWriteRate minimum time between file writes * @return {Promise} Resolves with writeCriticalFile or doDryRun function call. */ async function dryRunOrWriteFile ( dryRun: boolean, filePath: string, - result: Object + result: Object, + fsWriteRate: number ): Promise { const { css } = result if (dryRun) { doDryRun(css) } else { // Write file at a maximum of once ever 250ms - await limiter.schedule( + await createOrGetLimiter(fsWriteRate).schedule( writeCriticalFile, filePath, css @@ -136,7 +152,6 @@ function hasNoOtherChildNodes ( * @param {string} css CSS to write to file. */ async function writeCriticalFile (filePath: string, css: string): Promise { - console.log(`writeCriticalFile: ${filePath}`) try { await fs.outputFile( filePath, @@ -173,9 +188,11 @@ function buildCritical (options: Object = {}): Function { minify: true, dryRun: false, ignoreSelectors: [], + fsWriteRate: 250, ...filteredOptions } append = false + return async (css: Object): Object => { const { dryRun, preserve, minify, outputPath, outputDest } = args const criticalOutput = getCriticalRules(css, outputDest) @@ -196,7 +213,7 @@ function buildCritical (options: Object = {}): Function { }) result = await postcss(minify ? [cssnano] : []) .process(criticalCSS, { from: undefined }) - await dryRunOrWriteFile(dryRun, filePath, result) + await dryRunOrWriteFile(dryRun, filePath, result, args.fsWriteRate) clean(css, preserve) }