Skip to content

Commit 6a4b12b

Browse files
authored
[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
1 parent 7659949 commit 6a4b12b

11 files changed

+267
-0
lines changed

packages/react-fs/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -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

+12
Original file line numberDiff line numberDiff line change
@@ -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

+12
Original file line numberDiff line numberDiff line change
@@ -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

+12
Original file line numberDiff line numberDiff line change
@@ -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';
+7
Original file line numberDiff line numberDiff line change
@@ -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

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
module.exports = require('./index.node');

packages/react-fs/npm/index.node.js

+7
Original file line numberDiff line numberDiff line change
@@ -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

+26
Original file line numberDiff line numberDiff line change
@@ -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+
}
+148
Original file line numberDiff line numberDiff line change
@@ -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
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ declare module 'EventListener' {
7070
declare function __webpack_chunk_load__(id: string): Promise<mixed>;
7171
declare function __webpack_require__(id: string): any;
7272

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+
}
7383
declare module 'pg' {
7484
declare var Pool: (
7585
options: mixed,

scripts/rollup/bundles.js

+18
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,24 @@ const bundles = [
153153
externals: ['react', 'http', 'https'],
154154
},
155155

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+
156174
/******* React PG Browser (experimental, new) *******/
157175
{
158176
bundleTypes: [NODE_DEV, NODE_PROD],

0 commit comments

Comments
 (0)