Skip to content

Commit 42888a0

Browse files
committed
feat: implement SourceTransformTracker for deduplication of source transformations
1 parent a64564c commit 42888a0

3 files changed

Lines changed: 56 additions & 15 deletions

File tree

docs/src/Icon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { forwardRef, useId } from "react";
22
import type { CSSProperties, SVGProps } from "react";
33

4-
// export { default as Stack } from "./Stack";
4+
export { default as Stack } from "./Stack.tsx";
55

66
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "path" | "color" | "rotate"> {
77
path: string;

src/module-tsx.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isRelativeSpecifier,
1111
} from "./specifier.ts";
1212
import { addReactImport, needsReactImport } from "./react.ts";
13+
import { SourceTransformTracker } from "./source-tracker.ts";
1314
import { createSourceFile, printSourceFile, transform } from "./ts.ts";
1415

1516
interface ModuleTSXConfig {
@@ -38,6 +39,7 @@ export class ModuleTSX extends EventTarget implements IModuleTSX {
3839
public readonly baseUrl: string;
3940
public readonly importMap: ImportMapData;
4041
public readonly fetch: (url: string) => Promise<Response>;
42+
private readonly sourceTracker = new SourceTransformTracker<ResourceType>();
4143
private readonly fetchText = async (url: string) => {
4244
return this.fetch(url).then((res) => res.text());
4345
};
@@ -89,15 +91,19 @@ export class ModuleTSX extends EventTarget implements IModuleTSX {
8991

9092
/** Transform module source code and return a blob URL with the transformed content */
9193
private async transformSourceModule(sourceType: ResourceType, sourceUrl: string, sourceCode: string) {
92-
if (this._sourceMap.has(sourceUrl)) {
93-
return this._sourceMap.get(sourceUrl)!;
94+
const cachedBlobUrl = this.sourceTracker.get(sourceType, sourceUrl);
95+
if (cachedBlobUrl) {
96+
return cachedBlobUrl;
9497
}
95-
const loader = this.getLoaderByResourceType(sourceType);
96-
const code = `import.meta.url=${JSON.stringify(sourceUrl)};\n` + (await loader(sourceUrl, sourceCode));
97-
const blob = new Blob([code], { type: "text/javascript" });
98-
const blobUrl = URL.createObjectURL(blob);
99-
this._track(sourceUrl, blobUrl);
100-
return blobUrl;
98+
99+
return this.sourceTracker.runWithDedup(sourceType, sourceUrl, async () => {
100+
const loader = this.getLoaderByResourceType(sourceType);
101+
const code = `import.meta.url=${JSON.stringify(sourceUrl)};\n` + (await loader(sourceUrl, sourceCode));
102+
const blob = new Blob([code], { type: "text/javascript" });
103+
const blobUrl = URL.createObjectURL(blob);
104+
this.sourceTracker.set(sourceType, sourceUrl, blobUrl);
105+
return blobUrl;
106+
});
101107
}
102108

103109
private getLoaderByResourceType(type: ResourceType): Loader {
@@ -211,12 +217,6 @@ export class ModuleTSX extends EventTarget implements IModuleTSX {
211217
return resolved;
212218
}
213219

214-
private _sourceMap: Map<string, string> = new Map<string, string>();
215-
private _blobMap: Map<string, string> = new Map<string, string>();
216-
private _track(sourceUrl: string, blobUrl: string) {
217-
this._sourceMap.set(sourceUrl, blobUrl);
218-
this._blobMap.set(blobUrl, sourceUrl);
219-
}
220220
}
221221

222222
type ResourceType = "esm" | "css" | "css-module";

src/source-tracker.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export class SourceTransformTracker<TSourceType extends string> {
2+
private readonly sourceMap = new Map<string, string>();
3+
private readonly inFlightSourceMap = new Map<string, Promise<string>>();
4+
private readonly blobMap = new Map<string, string>();
5+
6+
public get(sourceType: TSourceType, sourceUrl: string): string | undefined {
7+
return this.sourceMap.get(this.getSourceKey(sourceType, sourceUrl));
8+
}
9+
10+
public set(sourceType: TSourceType, sourceUrl: string, blobUrl: string): void {
11+
this.sourceMap.set(this.getSourceKey(sourceType, sourceUrl), blobUrl);
12+
this.blobMap.set(blobUrl, sourceUrl);
13+
}
14+
15+
public getSourceUrlByBlob(blobUrl: string): string | undefined {
16+
return this.blobMap.get(blobUrl);
17+
}
18+
19+
public runWithDedup(
20+
sourceType: TSourceType,
21+
sourceUrl: string,
22+
run: () => Promise<string>,
23+
): Promise<string> {
24+
const sourceKey = this.getSourceKey(sourceType, sourceUrl);
25+
const inFlight = this.inFlightSourceMap.get(sourceKey);
26+
if (inFlight) {
27+
return inFlight;
28+
}
29+
30+
const task = run();
31+
this.inFlightSourceMap.set(sourceKey, task);
32+
33+
return task.finally(() => {
34+
this.inFlightSourceMap.delete(sourceKey);
35+
});
36+
}
37+
38+
private getSourceKey(sourceType: TSourceType, sourceUrl: string): string {
39+
return `${sourceType}:${sourceUrl}`;
40+
}
41+
}

0 commit comments

Comments
 (0)