Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disk cache prototype #1364

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/bin.ts
Expand Up @@ -4,7 +4,7 @@ import { join, resolve, dirname, parse as parsePath } from 'path';
import { inspect } from 'util';
import Module = require('module');
import arg = require('arg');
import { parse, createRequire } from './util';
import { parse, createRequire, hasOwnProperty } from './util';
import { EVAL_FILENAME, EvalState, createRepl, ReplService } from './repl';
import { VERSION, TSError, register } from './index';
import type { TSInternal } from './ts-compiler-types';
Expand Down Expand Up @@ -373,11 +373,6 @@ function evalAndExit(
}
}

/** Safe `hasOwnProperty` */
function hasOwnProperty(object: any, property: string): boolean {
return Object.prototype.hasOwnProperty.call(object, property);
}

if (require.main === module) {
main();
}
52 changes: 52 additions & 0 deletions src/cache.ts
@@ -0,0 +1,52 @@
import {hasOwnProperty} from './util';
/*
* cache entries are stored by: (via nested JS objects)
* - project hash (ts-node, ts, swc version numbers, config object hash)
* - abs file path
* - file size
* - file hash
*/

export function createCache<T>(cacheString?: string) {
let _cache = {};
if(cacheString) {
try {
_cache = JSON.parse(cacheString);
} catch(e) {}
}
const cache = _cache;
let dirty = false;
function getRoot() {
return cache;
}
function getSubcache(subcache: any, key: string) {
return hasOwnProperty(subcache, key) ? subcache[key] : undefined;
}
function getOrCreateSubcache(subcache: any, key: string) {
if(hasOwnProperty(subcache, key)) {
return subcache[key];
}
const newSubcache = {};
subcache[key] = newSubcache;
return newSubcache;
}
function setEntry(cacheFrom: any, subcacheKey: string, value: T) {
cacheFrom[subcacheKey] = value;
dirty = true;
}
function getEntry(cacheFrom: any, subcacheKey: string): T {
return hasOwnProperty(cacheFrom, subcacheKey) ? cacheFrom[subcacheKey] : undefined;
}
function serializeToString() {
return JSON.stringify(cache);
}
function registerCallbackOnProcessExitAndDirty(cb: Function) {
process.on('exit', () => {
if(dirty) {
cb();
}
});
}

return {getOrCreateSubcache, getEntry, getSubcache, setEntry, serializeToString, registerCallbackOnProcessExitAndDirty, getRoot};
}
30 changes: 28 additions & 2 deletions src/transpilers/swc.ts
@@ -1,7 +1,9 @@
import {existsSync, readFileSync, statSync, writeFileSync} from 'fs';
import type * as ts from 'typescript';
import type * as swcWasm from '@swc/wasm';
import type * as swcTypes from '@swc/core';
import type { CreateTranspilerOptions, Transpiler } from './types';
import type { CreateTranspilerOptions, TranspileOutput, Transpiler } from './types';
import { createCache } from '../cache';

export interface SwcTranspilerOptions extends CreateTranspilerOptions {
/**
Expand Down Expand Up @@ -92,6 +94,13 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {

const transpile: Transpiler['transpile'] = (input, transpileOptions) => {
const { fileName } = transpileOptions;
const cacheEntry = cacheApi.getEntry(rootCacheNode, fileName);
let hash = input;
try {
hash = `${+statSync(fileName).mtime}`;
} catch {}
if(cacheEntry && cacheEntry.hash === hash) return cacheEntry.value;

const swcOptions =
fileName.endsWith('.tsx') || fileName.endsWith('.jsx')
? tsxOptions
Expand All @@ -100,9 +109,26 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
...swcOptions,
filename: fileName,
});
return { outputText: code, sourceMapText: map };
const result = { outputText: code, sourceMapText: map };
cacheApi.setEntry(rootCacheNode, fileName, {hash, value: result});
return result;
};

interface CacheEntry {
hash: string;
value: {
outputText: string;
sourceMapText: string;
}
}

const cachePath = './swc-cache.json';
const cacheApi = createCache<CacheEntry>(existsSync(cachePath) ? readFileSync(cachePath, 'utf16le') : undefined);
const rootCacheNode = cacheApi.getOrCreateSubcache(cacheApi.getRoot(), 'TODO build a hash of versions and config');
cacheApi.registerCallbackOnProcessExitAndDirty(() => {
writeFileSync(cachePath, cacheApi.serializeToString(), 'utf16le');
});

return {
transpile,
};
Expand Down
5 changes: 5 additions & 0 deletions src/util.ts
Expand Up @@ -61,3 +61,8 @@ export function parse(value: string | undefined): object | undefined {
export function normalizeSlashes(value: string): string {
return value.replace(/\\/g, '/');
}

/** Safe `hasOwnProperty` */
export function hasOwnProperty(object: any, property: string): boolean {
return Object.prototype.hasOwnProperty.call(object, property);
}