8
8
*/
9
9
10
10
import type { Writable } from 'stream' ;
11
+ import { TextEncoder } from 'util' ;
11
12
12
13
type MightBeFlushable = {
13
14
flush ?: ( ) => void ,
@@ -17,7 +18,7 @@ type MightBeFlushable = {
17
18
export type Destination = Writable & MightBeFlushable ;
18
19
19
20
export type PrecomputedChunk = Uint8Array ;
20
- export type Chunk = string ;
21
+ export type Chunk = Uint8Array ;
21
22
22
23
export function scheduleWork ( callback : ( ) = > void ) {
23
24
setImmediate ( callback ) ;
@@ -33,46 +34,101 @@ export function flushBuffered(destination: Destination) {
33
34
}
34
35
}
35
36
37
+ const VIEW_SIZE = 2048 ;
38
+ let currentView = null ;
39
+ let writtenBytes = 0 ;
40
+ let destinationHasCapacity = true ;
41
+
36
42
export function beginWriting ( destination : Destination ) {
37
- // Older Node streams like http.createServer don't have this.
38
- if ( typeof destination . cork === 'function' ) {
39
- destination . cork ( ) ;
40
- }
43
+ currentView = new Uint8Array ( VIEW_SIZE ) ;
44
+ writtenBytes = 0 ;
45
+ destinationHasCapacity = true ;
41
46
}
42
47
43
48
export function writeChunk (
44
49
destination : Destination ,
45
- chunk : Chunk | PrecomputedChunk ,
50
+ chunk : PrecomputedChunk | Chunk ,
46
51
) : void {
47
- const nodeBuffer = ( ( chunk : any ) : Buffer | string ) ; // close enough
48
- destination . write ( nodeBuffer ) ;
52
+ if ( chunk . byteLength === 0 ) {
53
+ return ;
54
+ }
55
+
56
+ if ( chunk . byteLength > VIEW_SIZE ) {
57
+ // this chunk may overflow a single view which implies it was not
58
+ // one that is cached by the streaming renderer. We will enqueu
59
+ // it directly and expect it is not re-used
60
+ if ( writtenBytes > 0 ) {
61
+ writeToDestination (
62
+ destination ,
63
+ ( ( currentView : any ) : Uint8Array ) . subarray ( 0 , writtenBytes ) ,
64
+ ) ;
65
+ currentView = new Uint8Array ( VIEW_SIZE ) ;
66
+ writtenBytes = 0 ;
67
+ }
68
+ writeToDestination ( destination , chunk ) ;
69
+ return ;
70
+ }
71
+
72
+ let bytesToWrite = chunk ;
73
+ const allowableBytes = ( ( currentView : any ) : Uint8Array ) . length - writtenBytes ;
74
+ if ( allowableBytes < bytesToWrite . byteLength ) {
75
+ // this chunk would overflow the current view. We enqueue a full view
76
+ // and start a new view with the remaining chunk
77
+ if ( allowableBytes === 0 ) {
78
+ // the current view is already full, send it
79
+ writeToDestination ( destination , ( currentView : any ) ) ;
80
+ } else {
81
+ // fill up the current view and apply the remaining chunk bytes
82
+ // to a new view.
83
+ ( ( currentView : any ) : Uint8Array ) . set (
84
+ bytesToWrite . subarray ( 0 , allowableBytes ) ,
85
+ writtenBytes ,
86
+ ) ;
87
+ writtenBytes += allowableBytes ;
88
+ writeToDestination ( destination , ( currentView : any ) ) ;
89
+ bytesToWrite = bytesToWrite . subarray ( allowableBytes ) ;
90
+ }
91
+ currentView = new Uint8Array ( VIEW_SIZE ) ;
92
+ writtenBytes = 0 ;
93
+ }
94
+ ( ( currentView : any ) : Uint8Array ) . set ( bytesToWrite , writtenBytes ) ;
95
+ writtenBytes += bytesToWrite . byteLength ;
96
+ }
97
+
98
+ function writeToDestination ( destination : Destination , view : Uint8Array ) {
99
+ const currentHasCapacity = destination . write ( view ) ;
100
+ destinationHasCapacity = destinationHasCapacity && currentHasCapacity ;
49
101
}
50
102
51
103
export function writeChunkAndReturn (
52
104
destination : Destination ,
53
- chunk : Chunk | PrecomputedChunk ,
105
+ chunk : PrecomputedChunk | Chunk ,
54
106
) : boolean {
55
- const nodeBuffer = ( ( chunk : any ) : Buffer | string ) ; // close enough
56
- return destination . write ( nodeBuffer ) ;
107
+ writeChunk ( destination , chunk ) ;
108
+ return destinationHasCapacity ;
57
109
}
58
110
59
111
export function completeWriting ( destination : Destination ) {
60
- // Older Node streams like http.createServer don't have this.
61
- if ( typeof destination . uncork === 'function' ) {
62
- destination . uncork ( ) ;
112
+ if ( currentView && writtenBytes > 0 ) {
113
+ destination . write ( currentView . subarray ( 0 , writtenBytes ) ) ;
63
114
}
115
+ currentView = null ;
116
+ writtenBytes = 0 ;
117
+ destinationHasCapacity = true ;
64
118
}
65
119
66
120
export function close ( destination : Destination ) {
67
121
destination . end ( ) ;
68
122
}
69
123
124
+ const textEncoder = new TextEncoder ( ) ;
125
+
70
126
export function stringToChunk ( content : string ) : Chunk {
71
- return content ;
127
+ return textEncoder . encode ( content ) ;
72
128
}
73
129
74
130
export function stringToPrecomputedChunk ( content : string ) : PrecomputedChunk {
75
- return Buffer . from ( content , 'utf8' ) ;
131
+ return textEncoder . encode ( content ) ;
76
132
}
77
133
78
134
export function closeWithError ( destination : Destination , error : mixed ) : void {
0 commit comments