@samitouri / QOS-React-2 / commits / 03126dd087

[Flight] Add read-only fs methods (#20412)

* Don't allocate the inner cache unnecessarily We only need it when we're asking for text. I anticipate I'll want to avoid allocating it in other methods too when it's not strictly necessary. * Add fs.access * Add fs.lstat * Add fs.stat * Add fs.readdir * Add fs.readlink * Add fs.realpath * Rename functions to disambiguate two caches

Dan Abramov committed Dec 9, 2020 at 21:46 UTC 03126dd087b285789447ab814ee7f3385e1c179d
3 files changed +278 -10
packages/react-fs/src/ReactFilesystem.js
+243 -10
@@ -20,19 +20,19 @@ const Rejected = 2;
20 type PendingRecord = {|
21 status: 0,
22 value: Wakeable,
23 - cache: Array<mixed>,
23 + cache: null,
24 |};
25
26 type ResolvedRecord<T> = {|
27 status: 1,
28 value: T,
29 - cache: Array<mixed>,
29 + cache: null | Array<mixed>,
30 |};
31
32 type RejectedRecord = {|
33 status: 2,
34 value: mixed,
35 - cache: Array<mixed>,
35 + cache: null,
36 |};
37
38 type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
@@ -41,7 +41,7 @@ function createRecordFromThenable<T>(thenable: Thenable<T>): Record<T> {
41 const record: Record<T> = {
42 status: Pending,
43 value: thenable,
44 - cache: [],
44 + cache: null,
45 };
46 thenable.then(
47 value => {
@@ -62,9 +62,10 @@ function createRecordFromThenable<T>(thenable: Thenable<T>): Record<T> {
62 return record;
63 }
64
65 -function readRecordValue<T>(record: Record<T>): T {
65 +function readRecord<T>(record: Record<T>): ResolvedRecord<T> {
66 if (record.status === Resolved) {
67 - return record.value;
67 + // This is just a type refinement.
68 + return record;
69 } else {
70 throw record.value;
71 }
@@ -91,7 +92,122 @@ function checkPathInDev(path: string) {
92 }
93 }
94
94 -function createReadFileCache(): Map<string, Record<Buffer>> {
95 +function createAccessMap(): Map<string, Array<number | Record<void>>> {
96 + return new Map();
97 +}
98 +
99 +export function access(path: string, mode?: number): void {
100 + checkPathInDev(path);
101 + if (mode == null) {
102 + mode = 0; // fs.constants.F_OK
103 + }
104 + const map = unstable_getCacheForType(createAccessMap);
105 + let accessCache = map.get(path);
106 + if (!accessCache) {
107 + accessCache = [];
108 + map.set(path, accessCache);
109 + }
110 + let record;
111 + for (let i = 0; i < accessCache.length; i += 2) {
112 + const cachedMode: number = (accessCache[i]: any);
113 + if (mode === cachedMode) {
114 + const cachedRecord: Record<void> = (accessCache[i + 1]: any);
115 + record = cachedRecord;
116 + break;
117 + }
118 + }
119 + if (!record) {
120 + const thenable = fs.access(path, mode);
121 + record = createRecordFromThenable(thenable);
122 + accessCache.push(mode, record);
123 + }
124 + readRecord(record); // No return value.
125 +}
126 +
127 +function createLstatMap(): Map<string, Array<boolean | Record<mixed>>> {
128 + return new Map();
129 +}
130 +
131 +export function lstat(path: string, options?: {bigint?: boolean}): mixed {
132 + checkPathInDev(path);
133 + let bigint = false;
134 + if (options && options.bigint) {
135 + bigint = true;
136 + }
137 + const map = unstable_getCacheForType(createLstatMap);
138 + let lstatCache = map.get(path);
139 + if (!lstatCache) {
140 + lstatCache = [];
141 + map.set(path, lstatCache);
142 + }
143 + let record;
144 + for (let i = 0; i < lstatCache.length; i += 2) {
145 + const cachedBigint: boolean = (lstatCache[i]: any);
146 + if (bigint === cachedBigint) {
147 + const cachedRecord: Record<void> = (lstatCache[i + 1]: any);
148 + record = cachedRecord;
149 + break;
150 + }
151 + }
152 + if (!record) {
153 + const thenable = fs.lstat(path, {bigint});
154 + record = createRecordFromThenable(thenable);
155 + lstatCache.push(bigint, record);
156 + }
157 + const stats = readRecord(record).value;
158 + return stats;
159 +}
160 +
161 +function createReaddirMap(): Map<
162 + string,
163 + Array<string | boolean | Record<mixed>>,
164 +> {
165 + return new Map();
166 +}
167 +
168 +export function readdir(
169 + path: string,
170 + options?: string | {encoding?: string, withFileTypes?: boolean},
171 +): mixed {
172 + checkPathInDev(path);
173 + let encoding = 'utf8';
174 + let withFileTypes = false;
175 + if (typeof options === 'string') {
176 + encoding = options;
177 + } else if (options != null) {
178 + if (options.encoding) {
179 + encoding = options.encoding;
180 + }
181 + if (options.withFileTypes) {
182 + withFileTypes = true;
183 + }
184 + }
185 + const map = unstable_getCacheForType(createReaddirMap);
186 + let readdirCache = map.get(path);
187 + if (!readdirCache) {
188 + readdirCache = [];
189 + map.set(path, readdirCache);
190 + }
191 + let record;
192 + for (let i = 0; i < readdirCache.length; i += 3) {
193 + const cachedEncoding: string = (readdirCache[i]: any);
194 + const cachedWithFileTypes: boolean = (readdirCache[i + 1]: any);
195 + if (encoding === cachedEncoding && withFileTypes === cachedWithFileTypes) {
196 + const cachedRecord: Record<void> = (readdirCache[i + 2]: any);
197 + record = cachedRecord;
198 + break;
199 + }
200 + }
201 + if (!record) {
202 + const thenable = fs.readdir(path, {encoding, withFileTypes});
203 + record = createRecordFromThenable(thenable);
204 + readdirCache.push(encoding, withFileTypes, record);
205 + }
206 + const files = readRecord(record).value;
207 + return files;
208 +}
209 +
210 +function createReadFileMap(): Map<string, Record<Buffer>> {
211 return new Map();
212 }
213
@@ -106,15 +222,16 @@ export function readFile(
222 signal?: mixed, // We'll have our own signal
223 },
224 ): string | Buffer {
109 - const map = unstable_getCacheForType(createReadFileCache);
225 checkPathInDev(path);
226 + const map = unstable_getCacheForType(createReadFileMap);
227 let record = map.get(path);
228 if (!record) {
229 const thenable = fs.readFile(path);
230 record = createRecordFromThenable(thenable);
231 map.set(path, record);
232 }
117 - const buffer: Buffer = readRecordValue(record);
233 + const resolvedRecord = readRecord(record);
234 + const buffer: Buffer = resolvedRecord.value;
235 if (!options) {
236 return buffer;
237 }
@@ -136,7 +253,7 @@ export function readFile(
253 if (typeof encoding !== 'string') {
254 return buffer;
255 }
139 - const textCache = record.cache;
256 + const textCache = resolvedRecord.cache || (resolvedRecord.cache = []);
257 for (let i = 0; i < textCache.length; i += 2) {
258 if (textCache[i] === encoding) {
259 return (textCache[i + 1]: any);
@@ -146,3 +263,119 @@ export function readFile(
263 textCache.push(encoding, text);
264 return text;
265 }
266 +
267 +function createReadlinkMap(): Map<string, Array<string | Record<mixed>>> {
268 + return new Map();
269 +}
270 +
271 +export function readlink(
272 + path: string,
273 + options?: string | {encoding?: string},
274 +): mixed {
275 + checkPathInDev(path);
276 + let encoding = 'utf8';
277 + if (typeof options === 'string') {
278 + encoding = options;
279 + } else if (options != null) {
280 + if (options.encoding) {
281 + encoding = options.encoding;
282 + }
283 + }
284 + const map = unstable_getCacheForType(createReadlinkMap);
285 + let readlinkCache = map.get(path);
286 + if (!readlinkCache) {
287 + readlinkCache = [];
288 + map.set(path, readlinkCache);
289 + }
290 + let record;
291 + for (let i = 0; i < readlinkCache.length; i += 2) {
292 + const cachedEncoding: string = (readlinkCache[i]: any);
293 + if (encoding === cachedEncoding) {
294 + const cachedRecord: Record<void> = (readlinkCache[i + 1]: any);
295 + record = cachedRecord;
296 + break;
297 + }
298 + }
299 + if (!record) {
300 + const thenable = fs.readlink(path, {encoding});
301 + record = createRecordFromThenable(thenable);
302 + readlinkCache.push(encoding, record);
303 + }
304 + const linkString = readRecord(record).value;
305 + return linkString;
306 +}
307 +
308 +function createRealpathMap(): Map<string, Array<string | Record<mixed>>> {
309 + return new Map();
310 +}
311 +
312 +export function realpath(
313 + path: string,
314 + options?: string | {encoding?: string},
315 +): mixed {
316 + checkPathInDev(path);
317 + let encoding = 'utf8';
318 + if (typeof options === 'string') {
319 + encoding = options;
320 + } else if (options != null) {
321 + if (options.encoding) {
322 + encoding = options.encoding;
323 + }
324 + }
325 + const map = unstable_getCacheForType(createRealpathMap);
326 + let realpathCache = map.get(path);
327 + if (!realpathCache) {
328 + realpathCache = [];
329 + map.set(path, realpathCache);
330 + }
331 + let record;
332 + for (let i = 0; i < realpathCache.length; i += 2) {
333 + const cachedEncoding: string = (realpathCache[i]: any);
334 + if (encoding === cachedEncoding) {
335 + const cachedRecord: Record<void> = (realpathCache[i + 1]: any);
336 + record = cachedRecord;
337 + break;
338 + }
339 + }
340 + if (!record) {
341 + const thenable = fs.realpath(path, {encoding});
342 + record = createRecordFromThenable(thenable);
343 + realpathCache.push(encoding, record);
344 + }
345 + const resolvedPath = readRecord(record).value;
346 + return resolvedPath;
347 +}
348 +
349 +function createStatMap(): Map<string, Array<boolean | Record<mixed>>> {
350 + return new Map();
351 +}
352 +
353 +export function stat(path: string, options?: {bigint?: boolean}): mixed {
354 + checkPathInDev(path);
355 + let bigint = false;
356 + if (options && options.bigint) {
357 + bigint = true;
358 + }
359 + const map = unstable_getCacheForType(createStatMap);
360 + let statCache = map.get(path);
361 + if (!statCache) {
362 + statCache = [];
363 + map.set(path, statCache);
364 + }
365 + let record;
366 + for (let i = 0; i < statCache.length; i += 2) {
367 + const cachedBigint: boolean = (statCache[i]: any);
368 + if (bigint === cachedBigint) {
369 + const cachedRecord: Record<void> = (statCache[i + 1]: any);
370 + record = cachedRecord;
371 + break;
372 + }
373 + }
374 + if (!record) {
375 + const thenable = fs.stat(path, {bigint});
376 + record = createRecordFromThenable(thenable);
377 + statCache.push(bigint, record);
378 + }
379 + const stats = readRecord(record).value;
380 + return stats;
381 +}
scripts/flow/environment.js
+34
@@ -71,6 +71,20 @@ declare function __webpack_chunk_load__(id: string): Promise<mixed>;
71 declare function __webpack_require__(id: string): any;
72
73 declare module 'fs/promises' {
74 + declare var access: (path: string, mode?: number) => Promise<void>;
75 + declare var lstat: (
76 + path: string,
77 + options?: ?{bigint?: boolean},
78 + ) => Promise<mixed>;
79 + declare var readdir: (
80 + path: string,
81 + options?:
82 + | ?string
83 + | {
84 + encoding?: ?string,
85 + withFileTypes?: ?boolean,
86 + },
87 + ) => Promise<Buffer>;
88 declare var readFile: (
89 path: string,
90 options?:
@@ -79,6 +93,26 @@ declare module 'fs/promises' {
93 encoding?: ?string,
94 },
95 ) => Promise<Buffer>;
96 + declare var readlink: (
97 + path: string,
98 + options?:
99 + | ?string
100 + | {
101 + encoding?: ?string,
102 + },
103 + ) => Promise<mixed>;
104 + declare var realpath: (
105 + path: string,
106 + options?:
107 + | ?string
108 + | {
109 + encoding?: ?string,
110 + },
111 + ) => Promise<mixed>;
112 + declare var stat: (
113 + path: string,
114 + options?: ?{bigint?: boolean},
115 + ) => Promise<mixed>;
116 }
117 declare module 'pg' {
118 declare var Pool: (
scripts/rollup/modules.js
+1
@@ -10,6 +10,7 @@ const HAS_NO_SIDE_EFFECTS_ON_IMPORT = false;
10 // const HAS_SIDE_EFFECTS_ON_IMPORT = true;
11 const importSideEffects = Object.freeze({
12 fs: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
13 + 'fs/promises': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
14 path: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
15 'prop-types/checkPropTypes': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
16 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface': HAS_NO_SIDE_EFFECTS_ON_IMPORT,