Skip to content

Commit

Permalink
improve generation performance (#8642)
Browse files Browse the repository at this point in the history
* improve generation performance

* add changeset

* rethrow unexpected errors

* consider cwd for mkdir
  • Loading branch information
jantimon committed Nov 24, 2022
1 parent 506a3bd commit 5afa923
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/odd-files-train.md
@@ -0,0 +1,5 @@
---
'@graphql-codegen/cli': patch
---

faster type generation
1 change: 0 additions & 1 deletion packages/graphql-codegen-cli/package.json
Expand Up @@ -69,7 +69,6 @@
"json-to-pretty-yaml": "^1.2.2",
"listr2": "^4.0.5",
"log-symbols": "^4.0.0",
"mkdirp": "^1.0.4",
"shell-quote": "^1.7.3",
"string-env-interpolation": "^1.0.1",
"ts-log": "^2.2.3",
Expand Down
48 changes: 34 additions & 14 deletions packages/graphql-codegen-cli/src/generate-and-save.ts
Expand Up @@ -2,8 +2,7 @@ import { lifecycleHooks } from './hooks.js';
import { Types } from '@graphql-codegen/plugin-helpers';
import { executeCodegen } from './codegen.js';
import { createWatcher } from './utils/watcher.js';
import { fileExists, readFile, writeFile, unlinkFile } from './utils/file-system.js';
import mkdirp from 'mkdirp';
import { readFile, writeFile, unlinkFile, mkdirp } from './utils/file-system.js';
import { dirname, join, isAbsolute } from 'path';
import { debugLog } from './utils/debugging.js';
import { CodegenContext, ensureContext } from './config.js';
Expand Down Expand Up @@ -57,41 +56,49 @@ export async function generate(
() =>
Promise.all(
generationResult.map(async (result: Types.FileOutput) => {
const exists = await fileExists(result.filename);
const previousHash = recentOutputHash.get(result.filename) || (await hashFile(result.filename));
const exists = previousHash !== null;

// Store previous hash to avoid reading from disk again
if (previousHash) {
recentOutputHash.set(result.filename, previousHash);
}

if (!shouldOverwrite(config, result.filename) && exists) {
return;
}

const content = result.content || '';
const currentHash = hash(content);
let previousHash = recentOutputHash.get(result.filename);

if (!previousHash && exists) {
previousHash = hash(await readFile(result.filename));
}

if (previousHash && currentHash === previousHash) {
debugLog(`Skipping file (${result.filename}) writing due to indentical hash...`);
return;
} else if (context.checkMode) {
}

// skip updating file in dry mode
if (context.checkMode) {
context.checkModeStaleFiles.push(result.filename);
return; // skip updating file in dry mode
return;
}

if (content.length === 0) {
return;
}

recentOutputHash.set(result.filename, currentHash);
const basedir = dirname(result.filename);
await lifecycleHooks(result.hooks).beforeOneFileWrite(result.filename);
await lifecycleHooks(config.hooks).beforeOneFileWrite(result.filename);
await mkdirp(basedir);

const absolutePath = isAbsolute(result.filename)
? result.filename
: join(input.cwd || process.cwd(), result.filename);
await writeFile(absolutePath, result.content);

const basedir = dirname(absolutePath);
await mkdirp(basedir);

await writeFile(absolutePath, content);
recentOutputHash.set(result.filename, currentHash);

await lifecycleHooks(result.hooks).afterOneFileWrite(result.filename);
await lifecycleHooks(config.hooks).afterOneFileWrite(result.filename);
})
Expand Down Expand Up @@ -143,3 +150,16 @@ function shouldOverwrite(config: Types.Config, outputPath: string): boolean {
function isConfiguredOutput(output: any): output is Types.ConfiguredOutput {
return typeof output.plugins !== 'undefined';
}

async function hashFile(filePath: string): Promise<string | null> {
try {
return hash(await readFile(filePath));
} catch (err) {
if (err && err.code === 'ENOENT') {
// return null if file does not exist
return null;
}
// rethrow unexpected errors
throw err;
}
}
14 changes: 5 additions & 9 deletions packages/graphql-codegen-cli/src/utils/file-system.ts
@@ -1,5 +1,5 @@
import { unlink as fsUnlink, promises } from 'fs';
const { writeFile: fsWriteFile, readFile: fsReadFile, stat: fsStat } = promises;
const { writeFile: fsWriteFile, readFile: fsReadFile, mkdir } = promises;

export function writeFile(filepath: string, content: string) {
return fsWriteFile(filepath, content);
Expand All @@ -9,14 +9,10 @@ export function readFile(filepath: string) {
return fsReadFile(filepath, 'utf-8');
}

export async function fileExists(filePath: string): Promise<boolean> {
try {
return (await fsStat(filePath)).isFile();
} catch (err) {
return false;
}
}

export function unlinkFile(filePath: string, cb?: (err?: Error) => any): void {
fsUnlink(filePath, cb);
}

export function mkdirp(filePath: string) {
return mkdir(filePath, { recursive: true });
}
15 changes: 6 additions & 9 deletions packages/graphql-codegen-cli/tests/generate-and-save.spec.ts
Expand Up @@ -49,8 +49,8 @@ describe('generate-and-save', () => {
const filename = 'overwrite.ts';
const writeSpy = jest.spyOn(fs, 'writeFile').mockImplementation();
// forces file to exist
const fileExistsSpy = jest.spyOn(fs, 'fileExists');
fileExistsSpy.mockImplementation(async file => file === filename);
const fileReadSpy = jest.spyOn(fs, 'readFile');
fileReadSpy.mockImplementation(async () => '');

const output = await generate(
{
Expand All @@ -71,7 +71,7 @@ describe('generate-and-save', () => {

expect(output.length).toBe(1);
// makes sure it checks if file is there
expect(fileExistsSpy).toHaveBeenCalledWith(filename);
expect(fileReadSpy).toHaveBeenCalledWith(filename);
// makes sure it doesn't write a new file
expect(writeSpy).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -105,8 +105,8 @@ describe('generate-and-save', () => {
const filename = 'overwrite.ts';
const writeSpy = jest.spyOn(fs, 'writeFile').mockImplementation();
// forces file to exist
const fileExistsSpy = jest.spyOn(fs, 'fileExists');
fileExistsSpy.mockImplementation(async file => file === filename);
const fileReadSpy = jest.spyOn(fs, 'readFile');
fileReadSpy.mockImplementation(async () => '');

const output = await generate(
{
Expand All @@ -126,7 +126,7 @@ describe('generate-and-save', () => {

expect(output.length).toBe(1);
// makes sure it checks if file is there
expect(fileExistsSpy).toHaveBeenCalledWith(filename);
expect(fileReadSpy).toHaveBeenCalledWith(filename);
// makes sure it doesn't write a new file
expect(writeSpy).not.toHaveBeenCalled();
});
Expand All @@ -136,9 +136,6 @@ describe('generate-and-save', () => {
const writeSpy = jest.spyOn(fs, 'writeFile').mockImplementation();
const readSpy = jest.spyOn(fs, 'readFile').mockImplementation();
readSpy.mockImplementation(async _f => '');
// forces file to exist
const fileExistsSpy = jest.spyOn(fs, 'fileExists');
fileExistsSpy.mockImplementation(async file => file === filename);

const output = await generate(
{
Expand Down

0 comments on commit 5afa923

Please sign in to comment.