Skip to content

Commit 459fa4c

Browse files
committed
refactor(fmt): use shared cached import helper
1 parent 74d6842 commit 459fa4c

3 files changed

Lines changed: 45 additions & 2 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** Share the pending import while allowing a failed import to be retried. */
2+
export const cachedImport = <T>(
3+
importer: () => Promise<T>,
4+
): (() => Promise<T>) => {
5+
let promise: Promise<T> | undefined;
6+
7+
return () =>
8+
(promise ??= importer().catch((error: unknown) => {
9+
promise = undefined;
10+
throw error;
11+
}));
12+
};

‎packages/rstack/src/fmt/worker.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { hash } from 'node:crypto';
44
import { readFileSync, writeFileSync } from 'node:fs';
5+
import { cachedImport } from '../cachedImport.ts';
56
import type { FmtCacheEntry } from './cacheStore.ts';
67
import { hasDottedBasename } from './pathHelpers.ts';
78
import type { FmtFileCache, FmtFileRequest, FmtWorkerResult } from './types.ts';
@@ -12,7 +13,7 @@ interface FormatFileTask {
1213
cache?: FmtFileCache;
1314
}
1415

15-
let formatModule: Promise<typeof import('./format.ts')> | undefined;
16+
const getFormatModule = cachedImport(() => import('./format.ts'));
1617

1718
const hashContent = (content: string | Uint8Array): string =>
1819
hash('sha256', content, 'base64url').slice(0, 16);
@@ -67,7 +68,7 @@ const formatFile = async ({
6768
}
6869
}
6970

70-
const { formatFmtSource } = await (formatModule ??= import('./format.ts'));
71+
const { formatFmtSource } = await getFormatModule();
7172
const result = await formatFmtSource(file, () => (source ??= readSource()));
7273
if (result.status === 'unsupported') {
7374
return cache
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { expect, rs, test } from 'rstack/test';
2+
import { cachedImport } from '../src/cachedImport.ts';
3+
4+
test('loads lazily and shares pending and fulfilled imports', async () => {
5+
const module = { value: 1 };
6+
const importer = rs.fn(() => Promise.resolve(module));
7+
const load = cachedImport(importer);
8+
9+
expect(importer).not.toHaveBeenCalled();
10+
const pending = load();
11+
expect(load()).toBe(pending);
12+
await expect(pending).resolves.toBe(module);
13+
await expect(load()).resolves.toBe(module);
14+
expect(importer).toHaveBeenCalledTimes(1);
15+
});
16+
17+
test('allows retrying a failed import', async () => {
18+
const error = new Error('Failed to load module');
19+
const module = { value: 1 };
20+
const importer = rs
21+
.fn<() => Promise<typeof module>>()
22+
.mockRejectedValueOnce(error)
23+
.mockResolvedValue(module);
24+
const load = cachedImport(importer);
25+
26+
await expect(load()).rejects.toBe(error);
27+
await expect(load()).resolves.toBe(module);
28+
await expect(load()).resolves.toBe(module);
29+
expect(importer).toHaveBeenCalledTimes(2);
30+
});

0 commit comments

Comments
 (0)