2021-05-15 20:09:52 +08:00
|
|
|
import { strict as assert } from "node:assert";
|
|
|
|
|
import crypto from "node:crypto";
|
|
|
|
|
import fs from "node:fs/promises";
|
|
|
|
|
import path from "node:path";
|
|
|
|
|
import { rollup } from "rollup";
|
2021-09-11 09:55:46 +08:00
|
|
|
import {
|
|
|
|
|
PROJECT_ROOT,
|
|
|
|
|
readJson,
|
|
|
|
|
writeJson,
|
|
|
|
|
copyFile,
|
|
|
|
|
} from "../utils/index.mjs";
|
2018-06-18 15:16:40 -03:00
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
class Cache {
|
2021-09-10 10:20:43 +08:00
|
|
|
constructor({ cacheDir, distDir, version }) {
|
|
|
|
|
this.cacheDir = cacheDir || required("cacheDir");
|
|
|
|
|
this.distDir = distDir || required("distDir");
|
|
|
|
|
this.manifest = path.join(cacheDir, "manifest.json");
|
2020-12-02 15:03:32 +09:00
|
|
|
this.version = version || required("version");
|
2018-06-18 15:16:40 -03:00
|
|
|
this.checksums = {};
|
|
|
|
|
this.files = {};
|
2020-12-02 15:03:32 +09:00
|
|
|
this.updated = {
|
|
|
|
|
version: this.version,
|
|
|
|
|
checksums: {},
|
|
|
|
|
files: {},
|
|
|
|
|
};
|
2018-06-18 15:16:40 -03:00
|
|
|
}
|
|
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
// Loads the manifest.json file with the information from the last build
|
|
|
|
|
async load() {
|
|
|
|
|
// This should never throw, if it does, let it fail the build
|
2021-09-10 10:20:43 +08:00
|
|
|
const lockfile = await fs.readFile(
|
|
|
|
|
path.join(PROJECT_ROOT, "yarn.lock"),
|
|
|
|
|
"utf8"
|
|
|
|
|
);
|
2020-12-02 15:03:32 +09:00
|
|
|
const lockfileHash = hashString(lockfile);
|
|
|
|
|
this.updated.checksums["yarn.lock"] = lockfileHash;
|
2018-06-18 15:16:40 -03:00
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
try {
|
2021-09-10 10:20:43 +08:00
|
|
|
const { version, checksums, files } = await readJson(this.manifest);
|
2018-06-18 15:16:40 -03:00
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
// Ignore the cache if the version changed
|
|
|
|
|
assert.strictEqual(this.version, version);
|
2019-06-08 03:58:05 +08:00
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
assert.ok(typeof checksums === "object");
|
|
|
|
|
// If yarn.lock changed, rebuild everything
|
|
|
|
|
assert.strictEqual(lockfileHash, checksums["yarn.lock"]);
|
2019-06-08 03:58:05 +08:00
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
assert.ok(typeof files === "object");
|
2018-06-18 15:16:40 -03:00
|
|
|
|
2021-09-10 10:20:43 +08:00
|
|
|
for (const modules of Object.values(files)) {
|
|
|
|
|
assert.ok(Array.isArray(modules));
|
2020-12-02 15:03:32 +09:00
|
|
|
}
|
2021-09-10 10:20:43 +08:00
|
|
|
|
|
|
|
|
this.checksums = checksums;
|
|
|
|
|
this.files = files;
|
2021-02-25 18:28:55 +08:00
|
|
|
} catch {
|
2021-09-10 10:20:43 +08:00
|
|
|
// noop
|
2018-06-18 15:16:40 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
// Run rollup to get the list of files included in the bundle and check if
|
|
|
|
|
// any (or the list itself) have changed.
|
|
|
|
|
// This takes the same rollup config used for bundling to include files that are
|
|
|
|
|
// resolved by specific plugins.
|
|
|
|
|
async checkBundle(id, inputOptions, outputOptions) {
|
|
|
|
|
const files = new Set(this.files[id]);
|
|
|
|
|
const newFiles = (this.updated.files[id] = []);
|
2018-06-18 15:16:40 -03:00
|
|
|
|
2020-12-02 15:03:32 +09:00
|
|
|
let dirty = false;
|
|
|
|
|
|
|
|
|
|
const bundle = await rollup(getRollupConfig(inputOptions));
|
|
|
|
|
const { output } = await bundle.generate(outputOptions);
|
|
|
|
|
|
|
|
|
|
const modules = output
|
|
|
|
|
.filter((mod) => !/\0/.test(mod.facadeModuleId))
|
2021-09-10 10:20:43 +08:00
|
|
|
.map((mod) => [
|
|
|
|
|
path.relative(PROJECT_ROOT, mod.facadeModuleId),
|
|
|
|
|
mod.code,
|
|
|
|
|
]);
|
2020-12-02 15:03:32 +09:00
|
|
|
|
|
|
|
|
for (const [id, code] of modules) {
|
|
|
|
|
newFiles.push(id);
|
|
|
|
|
// If we already checked this file for another bundle, reuse the hash
|
|
|
|
|
if (!this.updated.checksums[id]) {
|
|
|
|
|
this.updated.checksums[id] = hashString(code);
|
|
|
|
|
}
|
|
|
|
|
const hash = this.updated.checksums[id];
|
|
|
|
|
|
|
|
|
|
// Check if this file changed
|
|
|
|
|
if (!this.checksums[id] || this.checksums[id] !== hash) {
|
|
|
|
|
dirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if this file is new
|
|
|
|
|
if (!files.delete(id)) {
|
|
|
|
|
dirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Final check: if any file was removed, `files` is not empty
|
|
|
|
|
return !dirty && files.size === 0;
|
2018-06-18 15:16:40 -03:00
|
|
|
}
|
2020-12-02 15:03:32 +09:00
|
|
|
|
|
|
|
|
async save() {
|
2021-09-10 10:20:43 +08:00
|
|
|
const { manifest, updated } = this;
|
|
|
|
|
|
|
|
|
|
await writeJson(manifest, updated);
|
|
|
|
|
|
|
|
|
|
const files = Object.keys(updated.files);
|
|
|
|
|
for (const file of files) {
|
|
|
|
|
await copyFile(
|
|
|
|
|
path.join(this.distDir, file),
|
|
|
|
|
path.join(this.cacheDir, "files", file)
|
|
|
|
|
);
|
2020-12-02 15:03:32 +09:00
|
|
|
}
|
|
|
|
|
}
|
2021-01-11 13:25:34 +09:00
|
|
|
|
|
|
|
|
async isCached(inputOptions, outputOption) {
|
2021-09-10 10:20:43 +08:00
|
|
|
const file = path.relative(this.distDir, outputOption.file);
|
|
|
|
|
const useCache = await this.checkBundle(file, inputOptions, outputOption);
|
|
|
|
|
|
2021-01-11 13:25:34 +09:00
|
|
|
if (useCache) {
|
|
|
|
|
try {
|
2021-09-10 10:20:43 +08:00
|
|
|
await copyFile(
|
|
|
|
|
path.join(this.cacheDir, "files", file),
|
|
|
|
|
path.join(this.distDir, file)
|
|
|
|
|
);
|
|
|
|
|
|
2021-01-11 13:25:34 +09:00
|
|
|
return true;
|
2021-09-10 10:20:43 +08:00
|
|
|
} catch {
|
2021-01-11 13:25:34 +09:00
|
|
|
// Proceed to build
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-12-02 15:03:32 +09:00
|
|
|
}
|
2018-06-18 15:16:40 -03:00
|
|
|
|
|
|
|
|
function required(name) {
|
|
|
|
|
throw new Error(name + " is required");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hashString(string) {
|
|
|
|
|
return crypto.createHash("md5").update(string).digest("hex");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getRollupConfig(rollupConfig) {
|
2020-01-20 18:49:52 +08:00
|
|
|
return {
|
|
|
|
|
...rollupConfig,
|
2018-06-18 15:16:40 -03:00
|
|
|
onwarn() {},
|
|
|
|
|
plugins: rollupConfig.plugins.filter(
|
|
|
|
|
(plugin) =>
|
|
|
|
|
// We're not interested in dependencies, we already check `yarn.lock`
|
|
|
|
|
plugin.name !== "node-resolve" &&
|
|
|
|
|
// This is really slow, we need this "preflight" to be fast
|
|
|
|
|
plugin.name !== "babel"
|
|
|
|
|
),
|
2020-01-20 18:49:52 +08:00
|
|
|
};
|
2018-06-18 15:16:40 -03:00
|
|
|
}
|
|
|
|
|
|
2021-05-15 20:09:52 +08:00
|
|
|
export default Cache;
|