@samitouri / QOS-React-2 / commits / 6a4b12b81c

[Flight] Add rudimentary FS binding (#20409)

* [Flight] Add rudimentary FS binding * Throw for unsupported * Don't mess with hidden class * Use absolute path as the key * Warn on relative and non-normalized paths

Dan Abramov committed Dec 9, 2020 at 02:37 UTC 6a4b12b81c4c855934985225f9f448f1b49a8a14
11 files changed +267
packages/react-fs/README.md new
+12
@@ -0,0 +1,12 @@
1 +# react-fs
2 +
3 +This package is meant to be used alongside yet-to-be-released, experimental React features. It's unlikely to be useful in any other context.
4 +
5 +**Do not use in a real application.** We're publishing this early for
6 +demonstration purposes.
7 +
8 +**Use it at your own risk.**
9 +
10 +# No, Really, It Is Unstable
11 +
12 +The API ~~may~~ will change wildly between versions.
packages/react-fs/index.browser.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +throw new Error(
11 + 'This entry point is not yet supported in the browser environment',
12 +);
packages/react-fs/index.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +export * from './index.node';
packages/react-fs/index.node.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +export * from './src/ReactFilesystem';
packages/react-fs/npm/index.browser.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-fs.browser.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-fs.browser.development.js');
7 +}
packages/react-fs/npm/index.js new
+3
@@ -0,0 +1,3 @@
1 +'use strict';
2 +
3 +module.exports = require('./index.node');
packages/react-fs/npm/index.node.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-fs.node.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-fs.node.development.js');
7 +}
packages/react-fs/package.json new
+26
@@ -0,0 +1,26 @@
1 +{
2 + "private": true,
3 + "name": "react-fs",
4 + "description": "React bindings for the filesystem",
5 + "version": "0.0.0",
6 + "repository": {
7 + "type" : "git",
8 + "url" : "https://github.com/facebook/react.git",
9 + "directory": "packages/react-fs"
10 + },
11 + "files": [
12 + "LICENSE",
13 + "README.md",
14 + "build-info.json",
15 + "index.js",
16 + "index.node.js",
17 + "index.browser.js",
18 + "cjs/"
19 + ],
20 + "peerDependencies": {
21 + "react": "^17.0.0"
22 + },
23 + "browser": {
24 + "./index.js": "./index.browser.js"
25 + }
26 +}
packages/react-fs/src/ReactFilesystem.js new
+148
@@ -0,0 +1,148 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Wakeable, Thenable} from 'shared/ReactTypes';
11 +
12 +import {unstable_getCacheForType} from 'react';
13 +import * as fs from 'fs/promises';
14 +import {isAbsolute, normalize} from 'path';
15 +
16 +const Pending = 0;
17 +const Resolved = 1;
18 +const Rejected = 2;
19 +
20 +type PendingResult = {|
21 + status: 0,
22 + value: Wakeable,
23 + cache: Array<mixed>,
24 +|};
25 +
26 +type ResolvedResult<T> = {|
27 + status: 1,
28 + value: T,
29 + cache: Array<mixed>,
30 +|};
31 +
32 +type RejectedResult = {|
33 + status: 2,
34 + value: mixed,
35 + cache: Array<mixed>,
36 +|};
37 +
38 +type Result<T> = PendingResult | ResolvedResult<T> | RejectedResult;
39 +
40 +function toResult<T>(thenable: Thenable<T>): Result<T> {
41 + const result: Result<T> = {
42 + status: Pending,
43 + value: thenable,
44 + cache: [],
45 + };
46 + thenable.then(
47 + value => {
48 + if (result.status === Pending) {
49 + const resolvedResult = ((result: any): ResolvedResult<T>);
50 + resolvedResult.status = Resolved;
51 + resolvedResult.value = value;
52 + }
53 + },
54 + err => {
55 + if (result.status === Pending) {
56 + const rejectedResult = ((result: any): RejectedResult);
57 + rejectedResult.status = Rejected;
58 + rejectedResult.value = err;
59 + }
60 + },
61 + );
62 + return result;
63 +}
64 +
65 +function readResult<T>(result: Result<T>): T {
66 + if (result.status === Resolved) {
67 + return result.value;
68 + } else {
69 + throw result.value;
70 + }
71 +}
72 +
73 +// We don't want to normalize every path ourselves in production.
74 +// However, relative or non-normalized paths will lead to cache misses.
75 +// So we encourage the developer to fix it in DEV and normalize on their end.
76 +function checkPathInDev(path: string) {
77 + if (__DEV__) {
78 + if (!isAbsolute(path)) {
79 + console.error(
80 + 'The provided path was not absolute: "%s". ' +
81 + 'Convert it to an absolute path first.',
82 + path,
83 + );
84 + } else if (path !== normalize(path)) {
85 + console.error(
86 + 'The provided path was not normalized: "%s". ' +
87 + 'Convert it to a normalized path first.',
88 + path,
89 + );
90 + }
91 + }
92 +}
93 +
94 +function createReadFileCache(): Map<string, Result<Buffer>> {
95 + return new Map();
96 +}
97 +
98 +export function readFile(
99 + path: string,
100 + options:
101 + | string
102 + | {
103 + encoding?: string | null,
104 + // Unsupported:
105 + flag?: string, // Doesn't make sense except "r"
106 + signal?: mixed, // We'll have our own signal
107 + },
108 +): string | Buffer {
109 + const map = unstable_getCacheForType(createReadFileCache);
110 + checkPathInDev(path);
111 + let entry = map.get(path);
112 + if (!entry) {
113 + const thenable = fs.readFile(path);
114 + entry = toResult(thenable);
115 + map.set(path, entry);
116 + }
117 + const result: Buffer = readResult(entry);
118 + if (!options) {
119 + return result;
120 + }
121 + let encoding;
122 + if (typeof options === 'string') {
123 + encoding = options;
124 + } else {
125 + const flag = options.flag;
126 + if (flag != null && flag !== 'r') {
127 + throw Error(
128 + 'The flag option is not supported, and always defaults to "r".',
129 + );
130 + }
131 + if (options.signal) {
132 + throw Error('The signal option is not supported.');
133 + }
134 + encoding = options.encoding;
135 + }
136 + if (typeof encoding !== 'string') {
137 + return result;
138 + }
139 + const textCache = entry.cache;
140 + for (let i = 0; i < textCache.length; i += 2) {
141 + if (textCache[i] === encoding) {
142 + return (textCache[i + 1]: any);
143 + }
144 + }
145 + const text = result.toString((encoding: any));
146 + textCache.push(encoding, text);
147 + return text;
148 +}
scripts/flow/environment.js
+10
@@ -70,6 +70,16 @@ declare module 'EventListener' {
70 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 readFile: (
75 + path: string,
76 + options?:
77 + | ?string
78 + | {
79 + encoding?: ?string,
80 + },
81 + ) => Promise<Buffer>;
82 +}
83 declare module 'pg' {
84 declare var Pool: (
85 options: mixed,
scripts/rollup/bundles.js
+18
@@ -153,6 +153,24 @@ const bundles = [
153 externals: ['react', 'http', 'https'],
154 },
155
156 + /******* React FS Browser (experimental, new) *******/
157 + {
158 + bundleTypes: [NODE_DEV, NODE_PROD],
159 + moduleType: ISOMORPHIC,
160 + entry: 'react-fs/index.browser',
161 + global: 'ReactFilesystem',
162 + externals: [],
163 + },
164 +
165 + /******* React FS Node (experimental, new) *******/
166 + {
167 + bundleTypes: [NODE_DEV, NODE_PROD],
168 + moduleType: ISOMORPHIC,
169 + entry: 'react-fs/index.node',
170 + global: 'ReactFilesystem',
171 + externals: ['react', 'fs/promises', 'path'],
172 + },
173 +
174 /******* React PG Browser (experimental, new) *******/
175 {
176 bundleTypes: [NODE_DEV, NODE_PROD],