Spaces:
Running
Running
/** | |
* Filesystem Cache | |
* | |
* Given a file and a transform function, cache the result into files | |
* or retrieve the previously cached files if the given file is already known. | |
* | |
* @see https://github.com/babel/babel-loader/issues/34 | |
* @see https://github.com/babel/babel-loader/pull/41 | |
*/ | |
const os = require("os"); | |
const path = require("path"); | |
const zlib = require("zlib"); | |
const crypto = require("crypto"); | |
const { | |
promisify | |
} = require("util"); | |
const { | |
readFile, | |
writeFile, | |
mkdir | |
} = require("fs/promises"); | |
const findCacheDirP = import("find-cache-dir"); | |
const transform = require("./transform"); | |
// Lazily instantiated when needed | |
let defaultCacheDirectory = null; | |
let hashType = "sha256"; | |
// use md5 hashing if sha256 is not available | |
try { | |
crypto.createHash(hashType); | |
} catch (err) { | |
hashType = "md5"; | |
} | |
const gunzip = promisify(zlib.gunzip); | |
const gzip = promisify(zlib.gzip); | |
/** | |
* Read the contents from the compressed file. | |
* | |
* @async | |
* @params {String} filename | |
* @params {Boolean} compress | |
*/ | |
const read = async function (filename, compress) { | |
const data = await readFile(filename + (compress ? ".gz" : "")); | |
const content = compress ? await gunzip(data) : data; | |
return JSON.parse(content.toString()); | |
}; | |
/** | |
* Write contents into a compressed file. | |
* | |
* @async | |
* @params {String} filename | |
* @params {Boolean} compress | |
* @params {String} result | |
*/ | |
const write = async function (filename, compress, result) { | |
const content = JSON.stringify(result); | |
const data = compress ? await gzip(content) : content; | |
return await writeFile(filename + (compress ? ".gz" : ""), data); | |
}; | |
/** | |
* Build the filename for the cached file | |
* | |
* @params {String} source File source code | |
* @params {Object} options Options used | |
* | |
* @return {String} | |
*/ | |
const filename = function (source, identifier, options) { | |
const hash = crypto.createHash(hashType); | |
const contents = JSON.stringify({ | |
source, | |
options, | |
identifier | |
}); | |
hash.update(contents); | |
return hash.digest("hex") + ".json"; | |
}; | |
/** | |
* Handle the cache | |
* | |
* @params {String} directory | |
* @params {Object} params | |
*/ | |
const handleCache = async function (directory, params) { | |
const { | |
source, | |
options = {}, | |
cacheIdentifier, | |
cacheDirectory, | |
cacheCompression | |
} = params; | |
const file = path.join(directory, filename(source, cacheIdentifier, options)); | |
try { | |
// No errors mean that the file was previously cached | |
// we just need to return it | |
return await read(file, cacheCompression); | |
} catch (err) {} | |
const fallback = typeof cacheDirectory !== "string" && directory !== os.tmpdir(); | |
// Make sure the directory exists. | |
try { | |
// overwrite directory if exists | |
await mkdir(directory, { | |
recursive: true | |
}); | |
} catch (err) { | |
if (fallback) { | |
return handleCache(os.tmpdir(), params); | |
} | |
throw err; | |
} | |
// Otherwise just transform the file | |
// return it to the user asap and write it in cache | |
const result = await transform(source, options); | |
// Do not cache if there are external dependencies, | |
// since they might change and we cannot control it. | |
if (!result.externalDependencies.length) { | |
try { | |
await write(file, cacheCompression, result); | |
} catch (err) { | |
if (fallback) { | |
// Fallback to tmpdir if node_modules folder not writable | |
return handleCache(os.tmpdir(), params); | |
} | |
throw err; | |
} | |
} | |
return result; | |
}; | |
/** | |
* Retrieve file from cache, or create a new one for future reads | |
* | |
* @async | |
* @param {Object} params | |
* @param {String} params.cacheDirectory Directory to store cached files | |
* @param {String} params.cacheIdentifier Unique identifier to bust cache | |
* @param {Boolean} params.cacheCompression Whether compressing cached files | |
* @param {String} params.source Original contents of the file to be cached | |
* @param {Object} params.options Options to be given to the transform fn | |
* | |
* @example | |
* | |
* const result = await cache({ | |
* cacheDirectory: '.tmp/cache', | |
* cacheIdentifier: 'babel-loader-cachefile', | |
* cacheCompression: false, | |
* source: *source code from file*, | |
* options: { | |
* experimental: true, | |
* runtime: true | |
* }, | |
* }); | |
*/ | |
module.exports = async function (params) { | |
let directory; | |
if (typeof params.cacheDirectory === "string") { | |
directory = params.cacheDirectory; | |
} else { | |
if (defaultCacheDirectory === null) { | |
const { | |
default: findCacheDir | |
} = await findCacheDirP; | |
defaultCacheDirectory = findCacheDir({ | |
name: "babel-loader" | |
}) || os.tmpdir(); | |
} | |
directory = defaultCacheDirectory; | |
} | |
return await handleCache(directory, params); | |
}; |