Skip to content

Commit e82e8f7

Browse files
sunilsuranaSunil Suranaconnor4312
authored
refactor: speed up sourcemap lookups by checking existence of .map files (#1780)
* If .map file exists no need to parse it * add line space * code comment * update to use previously read children * fix root not getting touched when retrieved * avoid extra stat in createMetadataForFile * fixup tests --------- Co-authored-by: Sunil Surana <[email protected]> Co-authored-by: Connor Peet <[email protected]>
1 parent 3240909 commit e82e8f7

File tree

5 files changed

+83
-35
lines changed

5 files changed

+83
-35
lines changed

src/common/sourceMaps/cacheTree.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export namespace CacheTree {
2020
* separated with forward slashes.
2121
*/
2222
export function getPath<T>(node: CacheTree<T>, directory: string) {
23+
node[touched] = 1;
2324
return _getDir(node, splitDir(directory), 0);
2425
}
2526

src/common/sourceMaps/sourceMapRepository.ts

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------*/
44

5-
import { xxHash32 } from 'js-xxhash';
5+
import { basename } from 'path';
66
import { FileGlobList } from '../fileGlobList';
7-
import { readfile, stat } from '../fsUtils';
7+
import { readfile } from '../fsUtils';
88
import { parseSourceMappingUrl } from '../sourceUtils';
9-
import { absolutePathToFileUrl, completeUrl, fileUrlToAbsolutePath, isDataUri } from '../urlUtils';
9+
import { absolutePathToFileUrl, completeUrl, isDataUri } from '../urlUtils';
1010
import { ISourceMapMetadata } from './sourceMap';
1111

1212
/**
@@ -67,13 +67,21 @@ export interface ISearchStrategy {
6767
*/
6868
export const createMetadataForFile = async (
6969
compiledPath: string,
70+
metadata: { siblings: readonly string[]; mtime: number },
7071
fileContents?: string,
7172
): Promise<Required<ISourceMapMetadata> | undefined> => {
72-
if (typeof fileContents === 'undefined') {
73-
fileContents = await readfile(compiledPath);
73+
let sourceMapUrl;
74+
const compiledFileName = basename(compiledPath);
75+
const maybeSibling = `${compiledFileName}.map`;
76+
if (metadata.siblings.includes(maybeSibling)) {
77+
sourceMapUrl = maybeSibling;
78+
}
79+
if (!sourceMapUrl) {
80+
if (typeof fileContents === 'undefined') {
81+
fileContents = await readfile(compiledPath);
82+
}
83+
sourceMapUrl = parseSourceMappingUrl(fileContents);
7484
}
75-
76-
let sourceMapUrl = parseSourceMappingUrl(fileContents);
7785
if (!sourceMapUrl) {
7886
return;
7987
}
@@ -91,20 +99,9 @@ export const createMetadataForFile = async (
9199
return;
92100
}
93101

94-
let cacheKey: number;
95-
if (smIsDataUri) {
96-
cacheKey = xxHash32(sourceMapUrl);
97-
} else {
98-
const stats = await stat(fileUrlToAbsolutePath(sourceMapUrl) || compiledPath);
99-
if (!stats) {
100-
return; // ENOENT, usually
101-
}
102-
cacheKey = stats.mtimeMs;
103-
}
104-
105102
return {
106103
compiledPath,
107104
sourceMapUrl,
108-
cacheKey,
105+
cacheKey: metadata.mtime,
109106
};
110107
};

src/common/sourceMaps/turboGlobStream.test.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,15 @@ describe('TurboGlobStream', () => {
3131
for (let i = 0; i < 2; i++) {
3232
await new Promise((resolve, reject) => {
3333
const matches: T[] = [];
34-
const tgs = new TurboGlobStream({ cwd: dir, ...opts, cache });
34+
const tgs = new TurboGlobStream({
35+
cwd: dir,
36+
...opts,
37+
cache,
38+
fileProcessor: (fname, meta) => {
39+
delete (meta as Record<string, unknown>).mtime; // delete this since it'll change for every test
40+
return opts.fileProcessor(fname, meta);
41+
},
42+
});
3543
tgs.onError(reject);
3644
tgs.onFile(result => matches.push(result));
3745
tgs.done
@@ -80,7 +88,9 @@ describe('TurboGlobStream', () => {
8088
},
8189
});
8290

83-
expect(fileProcessor.callCount).to.equal(1);
91+
expect(fileProcessor.args).to.deep.equal([
92+
[join(dir, 'a', 'a1.js'), { siblings: ['a1.js', 'a2.js'] }],
93+
]);
8494
});
8595

8696
it('uses platform preferred path', async () => {
@@ -109,7 +119,10 @@ describe('TurboGlobStream', () => {
109119
},
110120
});
111121

112-
expect(fileProcessor.callCount).to.equal(2);
122+
expect(fileProcessor.args.slice().sort((a, b) => a[0].localeCompare(b[0]))).to.deep.equal([
123+
[join(dir, 'a', 'a1.js'), { siblings: ['a1.js', 'a2.js'] }],
124+
[join(dir, 'a', 'a2.js'), { siblings: ['a1.js', 'a2.js'] }],
125+
]);
113126
});
114127

115128
it('globs for files recursively', async () => {
@@ -123,7 +136,13 @@ describe('TurboGlobStream', () => {
123136
},
124137
});
125138

126-
expect(fileProcessor.callCount).to.equal(5);
139+
expect(fileProcessor.args.slice().sort((a, b) => a[0].localeCompare(b[0]))).to.deep.equal([
140+
[join(dir, 'a', 'a1.js'), { siblings: ['a1.js', 'a2.js'] }],
141+
[join(dir, 'a', 'a2.js'), { siblings: ['a1.js', 'a2.js'] }],
142+
[join(dir, 'b', 'b1.js'), { siblings: ['b1.js', 'b2.js'] }],
143+
[join(dir, 'b', 'b2.js'), { siblings: ['b1.js', 'b2.js'] }],
144+
[join(dir, 'c', 'nested', 'a', 'c1.js'), { siblings: ['c1.js'] }],
145+
]);
127146
});
128147

129148
it('globs star dirname', async () => {

src/common/sourceMaps/turboGlobStream.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ interface ITokensContext {
2929
seen: Set<string>;
3030
}
3131

32+
export type FileProcessorFn<T> = (
33+
path: string,
34+
metadata: { siblings: readonly string[]; mtime: number },
35+
) => Promise<T>;
36+
3237
export interface ITurboGlobStreamOptions<E> {
3338
/** Glob patterns */
3439
pattern: string;
@@ -41,7 +46,7 @@ export interface ITurboGlobStreamOptions<E> {
4146
/** Cache state, will be updated. */
4247
cache: CacheTree<IGlobCached<E>>;
4348
/** File to transform a path into extracted data emitted on onFile */
44-
fileProcessor: (path: string) => Promise<E>;
49+
fileProcessor: FileProcessorFn<E>;
4550
}
4651

4752
const forwardSlashRe = /\//g;
@@ -60,7 +65,7 @@ export class TurboGlobStream<E> {
6065

6166
private readonly filter?: (path: string, previousData?: E) => boolean;
6267
private readonly ignore: ((path: string) => boolean)[];
63-
private readonly processor: (path: string) => Promise<E>;
68+
private readonly processor: FileProcessorFn<E>;
6469
private readonly fileEmitter = new EventEmitter<E>();
6570
public readonly onFile = this.fileEmitter.event;
6671
private readonly errorEmitter = new EventEmitter<{ path: string; error: Error }>();
@@ -121,7 +126,7 @@ export class TurboGlobStream<E> {
121126
const depths = tokens[0] === '**' ? [0, 1] : [0];
122127
const cacheEntry = CacheTree.getPath(opts.cache, opts.cwd);
123128
const ctx = { elements: tokens, seen: new Set<string>() };
124-
return Promise.all(depths.map(d => this.readSomething(ctx, d, opts.cwd, cacheEntry)));
129+
return Promise.all(depths.map(d => this.readSomething(ctx, d, opts.cwd, [], cacheEntry)));
125130
}),
126131
).then(() => undefined);
127132
}
@@ -134,6 +139,7 @@ export class TurboGlobStream<E> {
134139
ctx: ITokensContext,
135140
ti: number,
136141
path: string,
142+
siblings: readonly string[],
137143
cache: CacheTree<IGlobCached<E>>,
138144
) {
139145
// Skip already processed files, since we might see them twice during glob stars.
@@ -168,19 +174,32 @@ export class TurboGlobStream<E> {
168174
// children added or removed.
169175
if (cd.type === CachedType.Directory) {
170176
const todo: unknown[] = [];
177+
const entries = Object.entries(cache.children);
178+
const siblings = entries
179+
.filter(([, e]) => e.data?.type !== CachedType.Directory)
180+
.map(([n]) => n);
181+
171182
for (const [name, child] of Object.entries(cache.children)) {
172183
// for cached objects with a type, recurse normally. For ones without,
173184
// try to stat them first (may have been interrupted before they were finished)
174185
todo.push(
175186
child.data !== undefined
176-
? this.handleDirectoryEntry(ctx, ti, path, { name, type: child.data.type }, cache)
187+
? this.handleDirectoryEntry(
188+
ctx,
189+
ti,
190+
path,
191+
{ name, type: child.data.type },
192+
siblings,
193+
cache,
194+
)
177195
: this.stat(path).then(
178196
stat =>
179197
this.handleDirectoryEntry(
180198
ctx,
181199
ti,
182200
path,
183201
{ name, type: stat.isFile() ? CachedType.File : CachedType.Directory },
202+
siblings,
184203
cache,
185204
),
186205
() => undefined,
@@ -200,7 +219,7 @@ export class TurboGlobStream<E> {
200219
await this.handleDir(ctx, ti, stat.mtimeMs, path, cache);
201220
} else {
202221
this.alreadyProcessedFiles.add(cache);
203-
await this.handleFile(stat.mtimeMs, path, cache);
222+
await this.handleFile(stat.mtimeMs, path, siblings, cache);
204223
}
205224
}
206225

@@ -230,6 +249,7 @@ export class TurboGlobStream<E> {
230249
ti: number,
231250
path: string,
232251
dirent: { name: string; type: CachedType },
252+
siblings: readonly string[],
233253
cache: CacheTree<IGlobCached<E>>,
234254
): unknown {
235255
const nextPath = path + '/' + dirent.name;
@@ -246,9 +266,11 @@ export class TurboGlobStream<E> {
246266
}
247267

248268
if (typeof descends === 'number') {
249-
return this.readSomething(ctx, ti + descends, nextPath, nextChild);
269+
return this.readSomething(ctx, ti + descends, nextPath, siblings, nextChild);
250270
} else {
251-
return Promise.all(descends.map(d => this.readSomething(ctx, ti + d, nextPath, nextChild)));
271+
return Promise.all(
272+
descends.map(d => this.readSomething(ctx, ti + d, nextPath, siblings, nextChild)),
273+
);
252274
}
253275
}
254276

@@ -295,11 +317,16 @@ export class TurboGlobStream<E> {
295317
}
296318
}
297319

298-
private async handleFile(mtime: number, path: string, cache: CacheTree<IGlobCached<E>>) {
320+
private async handleFile(
321+
mtime: number,
322+
path: string,
323+
siblings: readonly string[],
324+
cache: CacheTree<IGlobCached<E>>,
325+
) {
299326
const platformPath = sep === '/' ? path : path.replace(forwardSlashRe, sep);
300327
let extracted: E;
301328
try {
302-
extracted = await this.processor(platformPath);
329+
extracted = await this.processor(platformPath, { siblings, mtime });
303330
} catch (error) {
304331
this.errorEmitter.fire({ path: platformPath, error });
305332
return;
@@ -325,6 +352,7 @@ export class TurboGlobStream<E> {
325352
}
326353

327354
const todo: unknown[] = [];
355+
const siblings = files.filter(f => f.isFile()).map(f => f.name);
328356
for (const file of files) {
329357
if (file.name.startsWith('.')) {
330358
continue;
@@ -339,7 +367,9 @@ export class TurboGlobStream<E> {
339367
continue;
340368
}
341369

342-
todo.push(this.handleDirectoryEntry(ctx, ti, path, { name: file.name, type }, cache));
370+
todo.push(
371+
this.handleDirectoryEntry(ctx, ti, path, { name: file.name, type }, siblings, cache),
372+
);
343373
}
344374

345375
await Promise.all(todo);

src/common/sourceMaps/turboSearchStrategy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { truthy } from '../objUtils';
1010
import { fixDriveLetterAndSlashes } from '../pathUtils';
1111
import { CacheTree } from './cacheTree';
1212
import {
13-
createMetadataForFile,
1413
ISearchStrategy,
1514
ISourcemapStreamOptions,
15+
createMetadataForFile,
1616
} from './sourceMapRepository';
1717
import { IGlobCached, TurboGlobStream } from './turboGlobStream';
1818

@@ -82,7 +82,8 @@ export class TurboSearchStrategy implements ISearchStrategy {
8282
cwd: glob.cwd,
8383
cache,
8484
filter: opts.filter,
85-
fileProcessor: file => createMetadataForFile(file).then(m => m && opts.processMap(m)),
85+
fileProcessor: (file, metadata) =>
86+
createMetadataForFile(file, metadata).then(m => m && opts.processMap(m)),
8687
});
8788

8889
tgs.onError(({ path, error }) => {

0 commit comments

Comments
 (0)