Skip to content

Commit

Permalink
[Flight] Add rudimentary FS binding (facebook#20409)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
gaearon authored and koto committed Jun 15, 2021
1 parent 13dfcc0 commit 5a5d503
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 0 deletions.
12 changes: 12 additions & 0 deletions packages/react-fs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# react-fs

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.

**Do not use in a real application.** We're publishing this early for
demonstration purposes.

**Use it at your own risk.**

# No, Really, It Is Unstable

The API ~~may~~ will change wildly between versions.
12 changes: 12 additions & 0 deletions packages/react-fs/index.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

throw new Error(
'This entry point is not yet supported in the browser environment',
);
12 changes: 12 additions & 0 deletions packages/react-fs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

'use strict';

export * from './index.node';
12 changes: 12 additions & 0 deletions packages/react-fs/index.node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

'use strict';

export * from './src/ReactFilesystem';
7 changes: 7 additions & 0 deletions packages/react-fs/npm/index.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-fs.browser.production.min.js');
} else {
module.exports = require('./cjs/react-fs.browser.development.js');
}
3 changes: 3 additions & 0 deletions packages/react-fs/npm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('./index.node');
7 changes: 7 additions & 0 deletions packages/react-fs/npm/index.node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-fs.node.production.min.js');
} else {
module.exports = require('./cjs/react-fs.node.development.js');
}
26 changes: 26 additions & 0 deletions packages/react-fs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"private": true,
"name": "react-fs",
"description": "React bindings for the filesystem",
"version": "0.0.0",
"repository": {
"type" : "git",
"url" : "https://github.com/facebook/react.git",
"directory": "packages/react-fs"
},
"files": [
"LICENSE",
"README.md",
"build-info.json",
"index.js",
"index.node.js",
"index.browser.js",
"cjs/"
],
"peerDependencies": {
"react": "^17.0.0"
},
"browser": {
"./index.js": "./index.browser.js"
}
}
148 changes: 148 additions & 0 deletions packages/react-fs/src/ReactFilesystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {Wakeable, Thenable} from 'shared/ReactTypes';

import {unstable_getCacheForType} from 'react';
import * as fs from 'fs/promises';
import {isAbsolute, normalize} from 'path';

const Pending = 0;
const Resolved = 1;
const Rejected = 2;

type PendingResult = {|
status: 0,
value: Wakeable,
cache: Array<mixed>,
|};

type ResolvedResult<T> = {|
status: 1,
value: T,
cache: Array<mixed>,
|};

type RejectedResult = {|
status: 2,
value: mixed,
cache: Array<mixed>,
|};

type Result<T> = PendingResult | ResolvedResult<T> | RejectedResult;

function toResult<T>(thenable: Thenable<T>): Result<T> {
const result: Result<T> = {
status: Pending,
value: thenable,
cache: [],
};
thenable.then(
value => {
if (result.status === Pending) {
const resolvedResult = ((result: any): ResolvedResult<T>);
resolvedResult.status = Resolved;
resolvedResult.value = value;
}
},
err => {
if (result.status === Pending) {
const rejectedResult = ((result: any): RejectedResult);
rejectedResult.status = Rejected;
rejectedResult.value = err;
}
},
);
return result;
}

function readResult<T>(result: Result<T>): T {
if (result.status === Resolved) {
return result.value;
} else {
throw result.value;
}
}

// We don't want to normalize every path ourselves in production.
// However, relative or non-normalized paths will lead to cache misses.
// So we encourage the developer to fix it in DEV and normalize on their end.
function checkPathInDev(path: string) {
if (__DEV__) {
if (!isAbsolute(path)) {
console.error(
'The provided path was not absolute: "%s". ' +
'Convert it to an absolute path first.',
path,
);
} else if (path !== normalize(path)) {
console.error(
'The provided path was not normalized: "%s". ' +
'Convert it to a normalized path first.',
path,
);
}
}
}

function createReadFileCache(): Map<string, Result<Buffer>> {
return new Map();
}

export function readFile(
path: string,
options:
| string
| {
encoding?: string | null,
// Unsupported:
flag?: string, // Doesn't make sense except "r"
signal?: mixed, // We'll have our own signal
},
): string | Buffer {
const map = unstable_getCacheForType(createReadFileCache);
checkPathInDev(path);
let entry = map.get(path);
if (!entry) {
const thenable = fs.readFile(path);
entry = toResult(thenable);
map.set(path, entry);
}
const result: Buffer = readResult(entry);
if (!options) {
return result;
}
let encoding;
if (typeof options === 'string') {
encoding = options;
} else {
const flag = options.flag;
if (flag != null && flag !== 'r') {
throw Error(
'The flag option is not supported, and always defaults to "r".',
);
}
if (options.signal) {
throw Error('The signal option is not supported.');
}
encoding = options.encoding;
}
if (typeof encoding !== 'string') {
return result;
}
const textCache = entry.cache;
for (let i = 0; i < textCache.length; i += 2) {
if (textCache[i] === encoding) {
return (textCache[i + 1]: any);
}
}
const text = result.toString((encoding: any));
textCache.push(encoding, text);
return text;
}
10 changes: 10 additions & 0 deletions scripts/flow/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ declare module 'EventListener' {
declare function __webpack_chunk_load__(id: string): Promise<mixed>;
declare function __webpack_require__(id: string): any;

declare module 'fs/promises' {
declare var readFile: (
path: string,
options?:
| ?string
| {
encoding?: ?string,
},
) => Promise<Buffer>;
}
declare module 'pg' {
declare var Pool: (
options: mixed,
Expand Down
18 changes: 18 additions & 0 deletions scripts/rollup/bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,24 @@ const bundles = [
externals: ['react', 'http', 'https'],
},

/******* React FS Browser (experimental, new) *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: ISOMORPHIC,
entry: 'react-fs/index.browser',
global: 'ReactFilesystem',
externals: [],
},

/******* React FS Node (experimental, new) *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: ISOMORPHIC,
entry: 'react-fs/index.node',
global: 'ReactFilesystem',
externals: ['react', 'fs/promises', 'path'],
},

/******* React PG Browser (experimental, new) *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
Expand Down

0 comments on commit 5a5d503

Please sign in to comment.