Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

feat: add persistent cache for bundless #51

Merged
merged 5 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"babel-plugin-module-resolver": "4.1.0",
"babel-plugin-react-require": "3.1.3",
"babel-plugin-transform-define": "2.0.1",
"file-system-cache": "2.0.0",
"loader-runner": "4.2.0",
"minimatch": "3.1.2",
"tsconfig-paths": "4.0.0",
Expand Down
16 changes: 13 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 43 additions & 2 deletions src/builder/bundless/dts/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { chalk, logger, winPath } from '@umijs/utils';
import fs from 'fs';
import path from 'path';
// @ts-ignore
import tsPathsTransformer from '../../../../compiled/@zerollup/ts-transform-paths';
import { getCache } from '../../../utils';

/**
* get parsed tsconfig.json for specific path
Expand Down Expand Up @@ -29,6 +31,7 @@ export default async function getDeclarations(
inputFiles: string[],
opts: { cwd: string },
) {
const cache = getCache('dts');
const output: { file: string; content: string; sourceFile: string }[] = [];
// use require() rather than import(), to avoid jest runner to fail
// ref: https://github.com/nodejs/node/issues/35889
Expand Down Expand Up @@ -71,21 +74,49 @@ export default async function getDeclarations(
});

const tsHost = ts.createCompilerHost(tsconfig.options);
const cacheKeys = inputFiles.reduce<Record<string, string>>(
(ret, file) => ({
...ret,
// format: {path:mtime:config}
[file]: [
file,
fs.lstatSync(file).mtimeMs,
JSON.stringify(tsconfig.options),
].join(':'),
}),
{},
);
const cacheRets: Record<string, typeof output> = {};

tsHost.writeFile = (fileName, declaration, _a, _b, sourceFiles) => {
const sourceFile = sourceFiles![0].fileName;

// only collect dts for input files, to avoid output error in watch mode
// ref: https://github.com/umijs/father-next/issues/43
if (inputFiles.includes(sourceFile)) {
output.push({
const ret = {
file: path.basename(fileName),
content: declaration,
sourceFile,
});
};

output.push(ret);

// group cache by file (d.ts & d.ts.map)
cacheRets[cacheKeys[sourceFile]] ??= [];
cacheRets[cacheKeys[sourceFile]].push(ret);
}
};

// use cache first
inputFiles = inputFiles.filter((file) => {
const cacheRet = cache.getSync(cacheKeys[file], '');

if (!cacheRet) return true;
output.push(...cacheRet);
return false;
});

const program = ts.createProgram(
inputFiles,
tsconfig.options as any,
Expand All @@ -111,6 +142,16 @@ export default async function getDeclarations(
)}`,
);
}

// save cache
Object.keys(cacheRets).forEach((key) => cache.setSync(key, cacheRets[key]));

// process no d.ts inputs, fallback to empty array
inputFiles.forEach((file) => {
const cacheKey = cacheKeys[file];

if (!cacheRets[cacheKey]) cache.setSync(cacheKey, []);
});
}

return output;
Expand Down
21 changes: 19 additions & 2 deletions src/builder/bundless/loaders/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs';
import { runLoaders } from 'loader-runner';
import type { IApi } from '../../../types';
import { getCache } from '../../../utils';
import type { IBundlessConfig } from '../../config';
import type { IBundlessLoader, ILoaderOutput } from './types';

Expand Down Expand Up @@ -42,6 +43,18 @@ export default async (
fileAbsPath: string,
opts: { config: IBundlessConfig; pkg: IApi['pkg']; cwd: string },
) => {
const cache = getCache('loader');
// format: {path:mtime:config}
const cacheKey = [
fileAbsPath,
fs.statSync(fileAbsPath).mtimeMs,
JSON.stringify(opts.config),
].join(':');
const cacheRet = await cache.get(cacheKey, '');

// use cache first
if (cacheRet) return Promise.resolve<ILoaderOutput>(cacheRet);

// get matched loader by test
const matched = loaders.find((item) => {
switch (typeof item.test) {
Expand Down Expand Up @@ -81,10 +94,14 @@ export default async (
reject(err);
} else if (result) {
// FIXME: handle buffer type?
resolve({
const ret = {
content: result[0] as unknown as string,
options: outputOpts,
});
};

// save cache then resolve
cache.set(cacheKey, ret);
resolve(ret);
} else {
resolve(void 0);
}
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export const WATCH_DEBOUNCE_STEP = 300;
export const DEV_COMMAND = 'dev';
export const BUILD_COMMANDS = ['build', 'prebundle'];
export const DEBUG_BUNDLESS_NAME = 'father:bundless';
export const CACHE_PATH = 'node_modules/.cache/father';
15 changes: 15 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { pkgUp } from '@umijs/utils';
import Cache from 'file-system-cache';
import path from 'path';
import { CACHE_PATH } from './constants';
import { IApi } from './types';

const caches: Record<string, ReturnType<typeof Cache>> = {};

/**
* get file-system cache for specific namespace
*/
export function getCache(ns: string): typeof caches['0'] {
// return fake cache if cache disabled
if (process.env.FATHER_CACHE === 'none') {
return { set() {}, get() {}, setSync() {}, getSync() {} } as any;
}
return (caches[ns] ??= Cache({ basePath: CACHE_PATH, ns }));
}

/**
* get valid type field value from package.json
*/
Expand Down
7 changes: 6 additions & 1 deletion tests/build.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import path from 'path';
import { distToMap, getDirCases } from './utils';
import * as cli from '../src/cli/cli';
import { distToMap, getDirCases } from './utils';

const CASES_DIR = path.join(__dirname, 'fixtures/build');

beforeAll(() => {
process.env.FATHER_CACHE = 'none';
});

afterAll(() => {
delete process.env.APP_ROOT;
delete process.env.FATHER_CACHE;
});

// generate cases
Expand Down
9 changes: 7 additions & 2 deletions tests/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path';
import { mockProcessExit } from 'jest-mock-process';
import { distToMap } from './utils';
import path from 'path';
import * as cli from '../src/cli/cli';
import { distToMap } from './utils';

jest.mock('@umijs/utils', () => {
const originalModule = jest.requireActual('@umijs/utils');
Expand All @@ -25,8 +25,13 @@ jest.mock('@umijs/utils', () => {
const mockExit = mockProcessExit();
const CASES_DIR = path.join(__dirname, 'fixtures/config');

beforeAll(() => {
process.env.FATHER_CACHE = 'none';
});

afterAll(() => {
delete process.env.APP_ROOT;
delete process.env.FATHER_CACHE;
mockExit.mockRestore();
});
test('config: cyclic extends', async () => {
Expand Down