Skip to content

Commit bc380ba

Browse files
committed
Add BinaryChunk type
This lets us have different environments operate on different types, such as passing the raw typed array when possible.
1 parent ba0e6f3 commit bc380ba

File tree

9 files changed

+134
-30
lines changed

9 files changed

+134
-30
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ module.exports = {
454454
$PropertyType: 'readonly',
455455
$ReadOnly: 'readonly',
456456
$ReadOnlyArray: 'readonly',
457+
$ArrayBufferView: 'readonly',
457458
$Shape: 'readonly',
458459
AnimationFrameID: 'readonly',
459460
// For Flow type annotation. Only `BigInt` is valid at runtime.

packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface Destination {
1414

1515
export opaque type PrecomputedChunk = string;
1616
export opaque type Chunk = string;
17+
export opaque type BinaryChunk = string;
1718

1819
export function scheduleWork(callback: () => void) {
1920
callback();
@@ -25,14 +26,14 @@ export function beginWriting(destination: Destination) {}
2526

2627
export function writeChunk(
2728
destination: Destination,
28-
chunk: Chunk | PrecomputedChunk,
29+
chunk: Chunk | PrecomputedChunk | BinaryChunk,
2930
): void {
3031
writeChunkAndReturn(destination, chunk);
3132
}
3233

3334
export function writeChunkAndReturn(
3435
destination: Destination,
35-
chunk: Chunk | PrecomputedChunk,
36+
chunk: Chunk | PrecomputedChunk | BinaryChunk,
3637
): boolean {
3738
return destination.push(chunk);
3839
}
@@ -51,6 +52,12 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
5152
return content;
5253
}
5354

55+
export function typedArrayToBinaryChunk(
56+
content: $ArrayBufferView,
57+
): BinaryChunk {
58+
throw new Error('Not implemented.');
59+
}
60+
5461
export function clonePrecomputedChunk(
5562
chunk: PrecomputedChunk,
5663
): PrecomputedChunk {
@@ -61,6 +68,10 @@ export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
6168
throw new Error('Not implemented.');
6269
}
6370

71+
export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
72+
throw new Error('Not implemented.');
73+
}
74+
6475
export function closeWithError(destination: Destination, error: mixed): void {
6576
// $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
6677
destination.destroy(error);

packages/react-server-dom-fb/src/ReactServerStreamConfigFB.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type Destination = {
1616

1717
export opaque type PrecomputedChunk = string;
1818
export opaque type Chunk = string;
19+
export opaque type BinaryChunk = string;
1920

2021
export function scheduleWork(callback: () => void) {
2122
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
@@ -30,14 +31,14 @@ export function beginWriting(destination: Destination) {}
3031

3132
export function writeChunk(
3233
destination: Destination,
33-
chunk: Chunk | PrecomputedChunk,
34+
chunk: Chunk | PrecomputedChunk | BinaryChunk,
3435
): void {
3536
destination.buffer += chunk;
3637
}
3738

3839
export function writeChunkAndReturn(
3940
destination: Destination,
40-
chunk: Chunk | PrecomputedChunk,
41+
chunk: Chunk | PrecomputedChunk | BinaryChunk,
4142
): boolean {
4243
destination.buffer += chunk;
4344
return true;
@@ -57,6 +58,12 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
5758
return content;
5859
}
5960

61+
export function typedArrayToBinaryChunk(
62+
content: $ArrayBufferView,
63+
): BinaryChunk {
64+
throw new Error('Not implemented.');
65+
}
66+
6067
export function clonePrecomputedChunk(
6168
chunk: PrecomputedChunk,
6269
): PrecomputedChunk {
@@ -67,6 +74,10 @@ export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
6774
throw new Error('Not implemented.');
6875
}
6976

77+
export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
78+
throw new Error('Not implemented.');
79+
}
80+
7081
export function closeWithError(destination: Destination, error: mixed): void {
7182
destination.done = true;
7283
destination.fatal = true;

packages/react-server/src/ReactFlightServer.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow
88
*/
99

10-
import type {Chunk, Destination} from './ReactServerStreamConfig';
10+
import type {Chunk, BinaryChunk, Destination} from './ReactServerStreamConfig';
1111

1212
import {
1313
scheduleWork,
@@ -176,7 +176,7 @@ export type Request = {
176176
pingedTasks: Array<Task>,
177177
completedImportChunks: Array<Chunk>,
178178
completedHintChunks: Array<Chunk>,
179-
completedRegularChunks: Array<Chunk>,
179+
completedRegularChunks: Array<Chunk | BinaryChunk>,
180180
completedErrorChunks: Array<Chunk>,
181181
writtenSymbols: Map<symbol, number>,
182182
writtenClientReferences: Map<ClientReferenceKey, number>,
@@ -235,7 +235,7 @@ export function createRequest(
235235
pingedTasks: pingedTasks,
236236
completedImportChunks: ([]: Array<Chunk>),
237237
completedHintChunks: ([]: Array<Chunk>),
238-
completedRegularChunks: ([]: Array<Chunk>),
238+
completedRegularChunks: ([]: Array<Chunk | BinaryChunk>),
239239
completedErrorChunks: ([]: Array<Chunk>),
240240
writtenSymbols: new Map(),
241241
writtenClientReferences: new Map(),

packages/react-server/src/ReactServerStreamConfigBrowser.js

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type Destination = ReadableStreamController;
1111

1212
export type PrecomputedChunk = Uint8Array;
1313
export opaque type Chunk = Uint8Array;
14+
export type BinaryChunk = Uint8Array;
1415

1516
export function scheduleWork(callback: () => void) {
1617
callback();
@@ -32,13 +33,13 @@ export function beginWriting(destination: Destination) {
3233

3334
export function writeChunk(
3435
destination: Destination,
35-
chunk: PrecomputedChunk | Chunk,
36+
chunk: PrecomputedChunk | Chunk | BinaryChunk,
3637
): void {
37-
if (chunk.length === 0) {
38+
if (chunk.byteLength === 0) {
3839
return;
3940
}
4041

41-
if (chunk.length > VIEW_SIZE) {
42+
if (chunk.byteLength > VIEW_SIZE) {
4243
if (__DEV__) {
4344
if (precomputedChunkSet.has(chunk)) {
4445
console.error(
@@ -68,7 +69,7 @@ export function writeChunk(
6869

6970
let bytesToWrite = chunk;
7071
const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes;
71-
if (allowableBytes < bytesToWrite.length) {
72+
if (allowableBytes < bytesToWrite.byteLength) {
7273
// this chunk would overflow the current view. We enqueue a full view
7374
// and start a new view with the remaining chunk
7475
if (allowableBytes === 0) {
@@ -89,12 +90,12 @@ export function writeChunk(
8990
writtenBytes = 0;
9091
}
9192
((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes);
92-
writtenBytes += bytesToWrite.length;
93+
writtenBytes += bytesToWrite.byteLength;
9394
}
9495

9596
export function writeChunkAndReturn(
9697
destination: Destination,
97-
chunk: PrecomputedChunk | Chunk,
98+
chunk: PrecomputedChunk | Chunk | BinaryChunk,
9899
): boolean {
99100
writeChunk(destination, chunk);
100101
// in web streams there is no backpressure so we can alwas write more
@@ -119,7 +120,9 @@ export function stringToChunk(content: string): Chunk {
119120
return textEncoder.encode(content);
120121
}
121122

122-
const precomputedChunkSet: Set<Chunk> = __DEV__ ? new Set() : (null: any);
123+
const precomputedChunkSet: Set<Chunk | BinaryChunk> = __DEV__
124+
? new Set()
125+
: (null: any);
123126

124127
export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
125128
const precomputedChunk = textEncoder.encode(content);
@@ -131,10 +134,27 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
131134
return precomputedChunk;
132135
}
133136

137+
export function typedArrayToBinaryChunk(
138+
content: $ArrayBufferView,
139+
): BinaryChunk {
140+
// Convert any non-Uint8Array array to Uint8Array. We could avoid this for Uint8Arrays.
141+
// If we passed through this straight to enqueue we wouldn't have to convert it but since
142+
// we need to copy the buffer in that case, we need to convert it to copy it.
143+
// When we copy it into another array using set() it needs to be a Uint8Array.
144+
const buffer = new Uint8Array(
145+
content.buffer,
146+
content.byteOffset,
147+
content.byteLength,
148+
);
149+
// We clone large chunks so that we can transfer them when we write them.
150+
// Others get copied into the target buffer.
151+
return content.byteLength > VIEW_SIZE ? buffer.slice() : buffer;
152+
}
153+
134154
export function clonePrecomputedChunk(
135155
precomputedChunk: PrecomputedChunk,
136156
): PrecomputedChunk {
137-
return precomputedChunk.length > VIEW_SIZE
157+
return precomputedChunk.byteLength > VIEW_SIZE
138158
? precomputedChunk.slice()
139159
: precomputedChunk;
140160
}
@@ -143,6 +163,10 @@ export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
143163
return chunk.byteLength;
144164
}
145165

166+
export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
167+
return chunk.byteLength;
168+
}
169+
146170
export function closeWithError(destination: Destination, error: mixed): void {
147171
// $FlowFixMe[method-unbinding]
148172
if (typeof destination.error === 'function') {

packages/react-server/src/ReactServerStreamConfigBun.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99

1010
type BunReadableStreamController = ReadableStreamController & {
1111
end(): mixed,
12-
write(data: Chunk): void,
12+
write(data: Chunk | BinaryChunk): void,
1313
error(error: Error): void,
1414
};
1515
export type Destination = BunReadableStreamController;
1616

1717
export type PrecomputedChunk = string;
1818
export opaque type Chunk = string;
19+
export type BinaryChunk = $ArrayBufferView;
1920

2021
export function scheduleWork(callback: () => void) {
2122
callback();
@@ -30,7 +31,7 @@ export function beginWriting(destination: Destination) {}
3031

3132
export function writeChunk(
3233
destination: Destination,
33-
chunk: PrecomputedChunk | Chunk,
34+
chunk: PrecomputedChunk | Chunk | BinaryChunk,
3435
): void {
3536
if (chunk.length === 0) {
3637
return;
@@ -41,7 +42,7 @@ export function writeChunk(
4142

4243
export function writeChunkAndReturn(
4344
destination: Destination,
44-
chunk: PrecomputedChunk | Chunk,
45+
chunk: PrecomputedChunk | Chunk | BinaryChunk,
4546
): boolean {
4647
return !!destination.write(chunk);
4748
}
@@ -60,6 +61,13 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
6061
return content;
6162
}
6263

64+
export function typedArrayToBinaryChunk(
65+
content: $ArrayBufferView,
66+
): BinaryChunk {
67+
// TODO: Does this needs to be cloned if it's transferred in enqueue()?
68+
return content;
69+
}
70+
6371
export function clonePrecomputedChunk(
6472
chunk: PrecomputedChunk,
6573
): PrecomputedChunk {
@@ -70,6 +78,10 @@ export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
7078
return Buffer.byteLength(chunk, 'utf8');
7179
}
7280

81+
export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
82+
return chunk.byteLength;
83+
}
84+
7385
export function closeWithError(destination: Destination, error: mixed): void {
7486
if (typeof destination.error === 'function') {
7587
// $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.

packages/react-server/src/ReactServerStreamConfigEdge.js

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type Destination = ReadableStreamController;
1111

1212
export type PrecomputedChunk = Uint8Array;
1313
export opaque type Chunk = Uint8Array;
14+
export type BinaryChunk = Uint8Array;
1415

1516
export function scheduleWork(callback: () => void) {
1617
setTimeout(callback, 0);
@@ -32,13 +33,13 @@ export function beginWriting(destination: Destination) {
3233

3334
export function writeChunk(
3435
destination: Destination,
35-
chunk: PrecomputedChunk | Chunk,
36+
chunk: PrecomputedChunk | Chunk | BinaryChunk,
3637
): void {
37-
if (chunk.length === 0) {
38+
if (chunk.byteLength === 0) {
3839
return;
3940
}
4041

41-
if (chunk.length > VIEW_SIZE) {
42+
if (chunk.byteLength > VIEW_SIZE) {
4243
if (__DEV__) {
4344
if (precomputedChunkSet.has(chunk)) {
4445
console.error(
@@ -68,7 +69,7 @@ export function writeChunk(
6869

6970
let bytesToWrite = chunk;
7071
const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes;
71-
if (allowableBytes < bytesToWrite.length) {
72+
if (allowableBytes < bytesToWrite.byteLength) {
7273
// this chunk would overflow the current view. We enqueue a full view
7374
// and start a new view with the remaining chunk
7475
if (allowableBytes === 0) {
@@ -89,12 +90,12 @@ export function writeChunk(
8990
writtenBytes = 0;
9091
}
9192
((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes);
92-
writtenBytes += bytesToWrite.length;
93+
writtenBytes += bytesToWrite.byteLength;
9394
}
9495

9596
export function writeChunkAndReturn(
9697
destination: Destination,
97-
chunk: PrecomputedChunk | Chunk,
98+
chunk: PrecomputedChunk | Chunk | BinaryChunk,
9899
): boolean {
99100
writeChunk(destination, chunk);
100101
// in web streams there is no backpressure so we can alwas write more
@@ -119,7 +120,9 @@ export function stringToChunk(content: string): Chunk {
119120
return textEncoder.encode(content);
120121
}
121122

122-
const precomputedChunkSet: Set<Chunk> = __DEV__ ? new Set() : (null: any);
123+
const precomputedChunkSet: Set<Chunk | BinaryChunk> = __DEV__
124+
? new Set()
125+
: (null: any);
123126

124127
export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
125128
const precomputedChunk = textEncoder.encode(content);
@@ -131,10 +134,27 @@ export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
131134
return precomputedChunk;
132135
}
133136

137+
export function typedArrayToBinaryChunk(
138+
content: $ArrayBufferView,
139+
): BinaryChunk {
140+
// Convert any non-Uint8Array array to Uint8Array. We could avoid this for Uint8Arrays.
141+
// If we passed through this straight to enqueue we wouldn't have to convert it but since
142+
// we need to copy the buffer in that case, we need to convert it to copy it.
143+
// When we copy it into another array using set() it needs to be a Uint8Array.
144+
const buffer = new Uint8Array(
145+
content.buffer,
146+
content.byteOffset,
147+
content.byteLength,
148+
);
149+
// We clone large chunks so that we can transfer them when we write them.
150+
// Others get copied into the target buffer.
151+
return content.byteLength > VIEW_SIZE ? buffer.slice() : buffer;
152+
}
153+
134154
export function clonePrecomputedChunk(
135155
precomputedChunk: PrecomputedChunk,
136156
): PrecomputedChunk {
137-
return precomputedChunk.length > VIEW_SIZE
157+
return precomputedChunk.byteLength > VIEW_SIZE
138158
? precomputedChunk.slice()
139159
: precomputedChunk;
140160
}
@@ -143,6 +163,10 @@ export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
143163
return chunk.byteLength;
144164
}
145165

166+
export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
167+
return chunk.byteLength;
168+
}
169+
146170
export function closeWithError(destination: Destination, error: mixed): void {
147171
// $FlowFixMe[method-unbinding]
148172
if (typeof destination.error === 'function') {

0 commit comments

Comments
 (0)