Skip to content

Commit

Permalink
perf: load and cache matches on server start
Browse files Browse the repository at this point in the history
related to #32
fixes #27
  • Loading branch information
danielwaltz committed Dec 1, 2024
1 parent 43ed87b commit 69c1d97
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 84 deletions.
106 changes: 52 additions & 54 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@ import {
generate,
loadContext,
} from '@graphql-codegen/cli';
import {
type FileMatcher,
isCodegenConfig,
isGraphQLDocument,
isGraphQLSchema,
} from './utils/fileMatchers';
import { isCodegenConfig } from './utils/fileMatchers';
import { isBuildMode, isServeMode, type ViteMode } from './utils/viteModes';
import { debugLog } from './utils/debugLog';
import { createMatchCache } from './utils/matchCache';

export interface Options {
/**
Expand Down Expand Up @@ -181,67 +177,69 @@ export function GraphQLCodegen(options?: Options): Plugin {
configureServer(server) {
if (!enableWatcher) return;

const listener = async (filePath = '') => {
log('File changed:', filePath);
const matchCache = createMatchCache(codegenContext, {
matchOnDocuments,
matchOnSchemas,
});

const isConfig = await isCodegenConfig(filePath, codegenContext);
async function checkFile(filePath: string) {
log(`Checking file: ${filePath}`);

if (matchCache.has(filePath)) {
log('File is in match cache');

try {
await generateWithOverride(configOverrideWatcher);
log('Generation successful in file watcher');
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// GraphQL Codegen handles logging useful errors
log('Generation failed in file watcher');
}

if (isConfig) {
log('Codegen config file changed, restarting vite');
server.restart();
return;
}

const matchers: [
enabled: boolean,
name: string,
matcher: FileMatcher,
][] = [
[matchOnDocuments, 'document', isGraphQLDocument],
[matchOnSchemas, 'schema', isGraphQLSchema],
];

const matcherResults = await Promise.all(
matchers.map(async ([enabled, name, matcher]) => {
if (!enabled) {
log(`Check for ${name} file skipped in file watcher by config`);
return false;
}
if (isCodegenConfig(filePath, codegenContext)) {
log('Codegen config file matched, restarting vite');
server.restart();
return;
}

try {
const isMatch = await matcher(filePath, codegenContext);
log('File did not match');
}

log(`Check for ${name} file successful in file watcher`);
async function initializeWatcher() {
try {
log('Match cache initialing');
await matchCache.init();
log('Match cache initialized');
} catch (error) {
log('Match cache initialization failed', error);
}

if (isMatch) log(`File matched a graphql ${name}`);
else log(`File did not match a graphql ${name}`);
server.watcher.on('add', async (filePath) => {
log(`File added: ${filePath}`);

return isMatch;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// GraphQL Codegen handles logging useful errors
log(`Check for ${name} file failed in file watcher`);
return false;
}
}),
);
try {
log('Match cache refreshing');
await matchCache.refresh();
log('Match cache refreshed');
} catch (error) {
log('Match cache refresh failed', error);
}

const isMatch = matcherResults.some((result) => result);
await checkFile(filePath);
});

if (!isMatch) return;
server.watcher.on('change', async (filePath) => {
log(`File changed: ${filePath}`);

try {
await generateWithOverride(configOverrideWatcher);
log('Generation successful in file watcher');
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// GraphQL Codegen handles logging useful errors
log('Generation failed in file watcher');
}
};
await checkFile(filePath);
});
}

server.watcher.on('add', listener);
server.watcher.on('change', listener);
initializeWatcher();
},
} as const satisfies Plugin;
}
Expand Down
5 changes: 3 additions & 2 deletions src/utils/debugLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ const BRIGHT = '\x1b[1m';
const DIM = '\x1b[2m';
const FG_CYAN = '\x1b[36m';

const LOG_PREFIX =
`${FG_CYAN}${BRIGHT}VITE PLUGIN GRAPHQL CODEGEN${RESET} ` as const;

export function debugLog(...args: unknown[]) {
const LOG_PREFIX =
`${FG_CYAN}${BRIGHT}VITE PLUGIN GRAPHQL CODEGEN${RESET} ` as const;
console.log(LOG_PREFIX, DIM, ...args, RESET);
}
30 changes: 2 additions & 28 deletions src/utils/fileMatchers.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,8 @@
import { normalizePath } from 'vite';
import type { CodegenContext } from '@graphql-codegen/cli';
import { getDocumentPaths, getSchemaPaths } from '../utils/configPaths';

export type FileMatcher = (
filePath: string,
context: CodegenContext,
) => Promise<boolean>;

export const isCodegenConfig: FileMatcher = async (filePath, context) => {
export function isCodegenConfig(filePath: string, context: CodegenContext) {
if (!context.filepath) return false;

return normalizePath(filePath) === normalizePath(context.filepath);
};

export const isGraphQLDocument: FileMatcher = async (filePath, context) => {
const documentPaths = await getDocumentPaths(context);

if (!documentPaths.length) return false;

const normalizedFilePath = normalizePath(filePath);

return documentPaths.some((path) => normalizedFilePath.includes(path));
};

export const isGraphQLSchema: FileMatcher = async (filePath, context) => {
const schemaPaths = await getSchemaPaths(context);

if (!schemaPaths.length) return false;

const normalizedFilePath = normalizePath(filePath);

return schemaPaths.some((path) => normalizedFilePath.includes(path));
};
}
33 changes: 33 additions & 0 deletions src/utils/matchCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { CodegenContext } from '@graphql-codegen/cli';
import { getDocumentPaths, getSchemaPaths } from './configPaths';
import { normalizePath } from 'vite';
import type { Options } from '..';

export function createMatchCache(
context: CodegenContext,
options: Pick<Required<Options>, 'matchOnDocuments' | 'matchOnSchemas'>,
) {
const cache = new Set<string>();

const refresh = async () => {
const matchers = [] as Promise<string[]>[];
if (options.matchOnDocuments) matchers.push(getDocumentPaths(context));
if (options.matchOnSchemas) matchers.push(getSchemaPaths(context));

const results = await Promise.all(matchers);

const entries = results.flat().map(normalizePath);

cache.clear();

for (const entry of entries) {
cache.add(entry);
}
};

return {
init: refresh,
refresh,
has: (filePath: string) => cache.has(normalizePath(filePath)),
};
}
1 change: 1 addition & 0 deletions test/codegen-config-file/codegen-config-file.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('codegen-config-file', () => {
});

it('generates', async () => {
await new Promise((resolve) => setTimeout(resolve, 200));
const file = await fs.readFile(OUTPUT_FILE, 'utf-8');

expect(file).toMatchSnapshot();
Expand Down
1 change: 1 addition & 0 deletions test/graphql-config-file/graphql-config-file.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('graphql-config-file', () => {
});

it('generates', async () => {
await new Promise((resolve) => setTimeout(resolve, 200));
const file = await fs.readFile(OUTPUT_FILE, 'utf-8');

expect(file).toMatchSnapshot();
Expand Down
1 change: 1 addition & 0 deletions test/inline-config/inline-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ describe('inline-config', () => {
});

it('generates', async () => {
await new Promise((resolve) => setTimeout(resolve, 200));
const file = await fs.readFile(OUTPUT_FILE, 'utf-8');

expect(file).toMatchSnapshot();
Expand Down
1 change: 1 addition & 0 deletions test/main/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('main', () => {

const isFileGenerated = async (): Promise<boolean> => {
try {
await new Promise((resolve) => setTimeout(resolve, 200));
await fs.access(OUTPUT_FILE);
return true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
1 change: 1 addition & 0 deletions test/match-on-glob-schema/match-on-glob-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('match-on-glob-schema', () => {

const isFileGenerated = async (): Promise<boolean> => {
try {
await new Promise((resolve) => setTimeout(resolve, 200));
await fs.access(OUTPUT_FILE);
return true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
1 change: 1 addition & 0 deletions test/match-on-schema/match-on-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('match-on-schema', () => {

const isFileGenerated = async (): Promise<boolean> => {
try {
await new Promise((resolve) => setTimeout(resolve, 200));
await fs.access(OUTPUT_FILE);
return true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down

0 comments on commit 69c1d97

Please sign in to comment.