Skip to content

Commit

Permalink
feat(build-cache): add build-cache to support incremental builds
Browse files Browse the repository at this point in the history
  • Loading branch information
mister-what committed Jan 28, 2022
1 parent 0e9fa97 commit 63e64f4
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 8 deletions.
14 changes: 14 additions & 0 deletions src/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const createBuildCache = (
buildCallback: (key: string, input: string) => Promise<string> | string
) => {
const cache = new Map<string, { input: string; output: string }>();
return async (key: string, input: string) => {
let cacheEntry = cache.get(key);
if (cacheEntry == null || cacheEntry?.input !== input) {
cacheEntry = { input, output: await buildCallback(key, input) };
cache.set(key, cacheEntry);
return cacheEntry.output;
}
return cacheEntry.output;
};
};
56 changes: 56 additions & 0 deletions src/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import path from "path";
import { runInThisContext } from "vm";
import * as esbuild from "esbuild";
import getTransformKeywordDef from "ajv-keywords/dist/definitions/transform";
import * as CodegenModule from "ajv/dist/standalone";
import type { Options } from "./plugin";
import { AjvPlugin } from "./plugin";
import * as BuildCacheModule from "./cache";

const wrapInCjs = (source: string) =>
[
Expand Down Expand Up @@ -73,6 +75,34 @@ const evaluateCjs = <M>(code: string): M => {
return moduleObj.exports;
};

const makeIncrementalBuild = () => {
const ajvPlugin = AjvPlugin({
ajvOptions: { coerceTypes: true },
});
return esbuild.build({
stdin: {
contents: `
import validator from "./testSchema.json?ajv";
export const validate = (data) => {
if (!validator(data)) return false;
return data;
};`,
loader: "js",
sourcefile: "test-entrypoint.js",
resolveDir: path.resolve(__dirname, "__fixtures__"),
},
outfile: path.resolve(__dirname, "build", "main.js"),
target: "node16",
bundle: true,
format: "esm",
minify: false,
write: false,
plugins: [ajvPlugin],
incremental: true,
});
};

describe("AjvPlugin", () => {
it("builds a a validation with external keywords", async () => {
const { validate } = evaluateCjs<{ validate: (x: unknown) => unknown }>(
Expand Down Expand Up @@ -124,4 +154,30 @@ describe("AjvPlugin", () => {
printWidth: 120,
});
});

it("caches build results", async () => {
const compileSpy = jest.spyOn(CodegenModule, "default");
const createBuildCacheSpy = jest
.spyOn(BuildCacheModule, "createBuildCache")
.mockImplementation(
(cb) =>
(...args) =>
Promise.resolve(cb(...args))
);

const result = await makeIncrementalBuild();
expect(compileSpy).toHaveBeenCalledTimes(1);
await result.rebuild?.();
expect(compileSpy).toHaveBeenCalledTimes(2);
result.rebuild?.dispose();

createBuildCacheSpy.mockRestore();
compileSpy.mockClear();

const resultCached = await makeIncrementalBuild();
expect(compileSpy).toHaveBeenCalledTimes(1);
await resultCached.rebuild?.();
expect(compileSpy).toHaveBeenCalledTimes(1);
resultCached.rebuild?.dispose();
});
});
23 changes: 15 additions & 8 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
import Ajv from "ajv";
import standaloneCode from "ajv/dist/standalone";
import { mergeDeepLeft } from "ramda";
import { createBuildCache } from "./cache";

export interface Options {
extraKeywords?: CodeKeywordDefinition[];
Expand Down Expand Up @@ -47,6 +48,16 @@ export const AjvPlugin = ({
ajv.addKeyword(keywordDef);
}

const compileWithCache = createBuildCache(async (filePath, fileContent) => {
ajv.removeSchema();
const schema = JSON.parse(fileContent) as AnySchemaObject;
schema.$id = `http://example.com/${path.relative(
process.cwd(),
filePath
)}`;
return standaloneCode(ajv, await ajv.compileAsync(schema));
});

build.onResolve(
{ filter: /\.json\?ajv$/i },
async ({ path: rawPath, resolveDir }) => {
Expand All @@ -61,15 +72,11 @@ export const AjvPlugin = ({
build.onLoad(
{ namespace: "ajv-validator", filter: /.*/ },
async ({ path: filePath }) => {
const schema = JSON.parse(
await fs.readFile(filePath, "utf8")
) as AnySchemaObject;
schema.$id = `http://example.com/${path.relative(
process.cwd(),
filePath
)}`;
return {
contents: standaloneCode(ajv, await ajv.compileAsync(schema)),
contents: await compileWithCache(
filePath,
await fs.readFile(filePath, "utf8")
),
loader: "js" as const,
resolveDir: path.dirname(filePath),
};
Expand Down

0 comments on commit 63e64f4

Please sign in to comment.