From 593dca669a4f67d49de551aa5b0665940918b567 Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Wed, 9 Jun 2021 17:40:45 -0600 Subject: [PATCH] [Bugfix] Ensure stability of filename cache-keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `JSON.stringify(structure)` isn’t inherently stable as it relies on various internal details of how `structure` was created. As written, if a given babel configuration is create in an dynamic manner, it is possible for babel-loader to have spurious cache misses. To address this, we can use one of the many stable stringify alternatives. For this PR I have selected [fast-json-stable-stringify](https://github.com/epoberezkin/fast-json-stable-stringify) for that task, as it appears both popular and it’s benchmarks look promising. This PR does not explicitly include tests, as testing this is both tricky to test in this context, and the important tests are contained within fast-json-stable-stringify itself. --- package.json | 1 + src/cache.js | 5 ++--- src/index.js | 3 ++- test/cache.test.js | 2 +- yarn.lock | 3 ++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2338859e..6c94a8db 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "node": ">= 8.9" }, "dependencies": { + "fast-json-stable-stringify": "^2.1.0", "find-cache-dir": "^3.3.1", "loader-utils": "^1.4.0", "make-dir": "^3.1.0", diff --git a/src/cache.js b/src/cache.js index 7f7721ff..0051149a 100644 --- a/src/cache.js +++ b/src/cache.js @@ -24,6 +24,7 @@ const writeFile = promisify(fs.writeFile); const gunzip = promisify(zlib.gunzip); const gzip = promisify(zlib.gzip); const makeDir = require("make-dir"); +const stringify = require("fast-json-stable-stringify"); /** * Read the contents from the compressed file. @@ -65,9 +66,7 @@ const write = async function (filename, compress, result) { const filename = function (source, identifier, options) { const hash = crypto.createHash("md4"); - const contents = JSON.stringify({ source, options, identifier }); - - hash.update(contents); + hash.update(stringify({ source, options, identifier })); return hash.digest("hex") + ".json"; }; diff --git a/src/index.js b/src/index.js index 07eec2a1..1a1eb87a 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ const schema = require("./schema"); const { isAbsolute } = require("path"); const loaderUtils = require("loader-utils"); const validateOptions = require("schema-utils"); +const stringify = require("fast-json-stable-stringify"); function subscribe(subscriber, metadata, context) { if (context[subscriber]) { @@ -186,7 +187,7 @@ async function loader(source, inputSourceMap, overrides) { const { cacheDirectory = null, - cacheIdentifier = JSON.stringify({ + cacheIdentifier = stringify({ options, "@babel/core": transform.version, "@babel/loader": version, diff --git a/test/cache.test.js b/test/cache.test.js index 1bc5a902..d1110ec6 100644 --- a/test/cache.test.js +++ b/test/cache.test.js @@ -376,7 +376,7 @@ test.cb("should allow to specify the .babelrc file", t => { fs.readdir(t.context.cacheDirectory, (err, files) => { t.is(err, null); - t.true(files.length === 2); + t.true(files.length === 1); t.end(); }); }); diff --git a/yarn.lock b/yarn.lock index b10e011e..e1776bd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2140,6 +2140,7 @@ __metadata: eslint-config-prettier: ^6.3.0 eslint-plugin-flowtype: ^5.2.0 eslint-plugin-prettier: ^3.0.0 + fast-json-stable-stringify: ^2.1.0 find-cache-dir: ^3.3.1 husky: ^4.3.0 lint-staged: ^10.5.1 @@ -3711,7 +3712,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 7df3fabfe445d65953b2d9d9d3958bd895438b215a40fb87dae8b2165c5169a897785eb5d51e6cf0eb03523af756e3d82ea01083f6ac6341fe16db532fee3016