14
14
* limitations under the License.
15
15
*/
16
16
17
- import { Context , Compression , OrderedCompressionValues , maxSize } from './validation/Condition' ;
18
17
import { cpus } from 'os' ;
19
- import { constants as brotliConstants , brotliCompress , gzip , ZlibOptions } from 'zlib ' ;
18
+ import { Context , Compression , OrderedCompressionValues , maxSize } from './validation/Condition ' ;
20
19
import { readFile } from './helpers/fs' ;
21
- import { LogError } from './log/helpers/error' ;
22
20
import { Report } from './log/report' ;
23
- import { TTYReport } from './log/tty-report' ;
24
- import { stdout } from 'process' ;
21
+ import { compressor } from './compressor' ;
25
22
26
23
const COMPRESSION_CONCURRENCY = cpus ( ) . length ;
27
- const BROTLI_OPTIONS = {
28
- params : {
29
- [ brotliConstants . BROTLI_PARAM_MODE ] : brotliConstants . BROTLI_DEFAULT_MODE ,
30
- [ brotliConstants . BROTLI_PARAM_QUALITY ] : brotliConstants . BROTLI_MAX_QUALITY ,
31
- [ brotliConstants . BROTLI_PARAM_SIZE_HINT ] : 0 ,
32
- } ,
33
- } ;
34
- const GZIP_OPTIONS : ZlibOptions = {
35
- level : 9 ,
36
- } ;
37
24
38
- interface CompressionItem {
25
+ export interface CompressionItem {
39
26
path : string ;
40
27
compression : Compression ;
41
28
maxSize : maxSize ;
42
29
}
43
30
44
- /**
45
- * Use the given configuration and actual size to report item filesize.
46
- * @param report Optional reporter to update with this value
47
- * @param item Configuration for an Item
48
- * @param error Error from compressing an Item
49
- * @param size actual size for this comparison
50
- */
51
- function store (
52
- report : Report | null ,
53
- context : Context ,
54
- item : CompressionItem ,
55
- error : Error | null ,
56
- size : number ,
57
- ) : boolean {
58
- if ( error !== null ) {
59
- LogError ( `Could not compress '${ item . path } ' with '${ item . compression } '.` ) ;
60
- return false ;
61
- }
62
-
63
- // Store the size of the item in the compression map.
64
- const sizeMap = context . compressed . get ( item . path ) ;
65
- if ( sizeMap === undefined ) {
66
- LogError ( `Could not find item '${ item . path } ' with '${ item . compression } ' in compression map.` ) ;
67
- return false ;
68
- }
69
- sizeMap [ OrderedCompressionValues . indexOf ( item . compression ) ] [ 0 ] = size ;
70
-
71
- report ?. update ( context ) ;
72
- if ( item . maxSize === undefined ) {
73
- return true ;
74
- }
75
- return size < item . maxSize ;
76
- }
77
-
78
- /**
79
- * Compress an Item and report status to the console.
80
- * @param item Configuration for an Item.
81
- */
82
- async function compressor ( report : Report | null , context : Context , item : CompressionItem ) : Promise < boolean > {
83
- const contents = context . fileContents . get ( item . path ) ;
84
- if ( contents ) {
85
- const buffer = Buffer . from ( contents , 'utf8' ) ;
86
-
87
- switch ( item . compression ) {
88
- case 'brotli' :
89
- return new Promise ( resolve =>
90
- brotliCompress ( buffer , BROTLI_OPTIONS , ( error : Error | null , result : Buffer ) =>
91
- resolve ( store ( report , context , item , error , result . byteLength ) ) ,
92
- ) ,
93
- ) ;
94
- case 'gzip' :
95
- return new Promise ( resolve =>
96
- gzip ( buffer , GZIP_OPTIONS , ( error : Error | null , result : Buffer ) =>
97
- resolve ( store ( report , context , item , error , result . byteLength ) ) ,
98
- ) ,
99
- ) ;
100
- default :
101
- return store ( report , context , item , null , buffer . byteLength ) ;
102
- }
103
- }
104
-
105
- return false ;
106
- }
107
-
108
31
/**
109
32
* Store the original content so it isn't retrieved from FileSystem for each compression.
110
33
* @param context
@@ -125,15 +48,15 @@ async function storeOriginalFileContents(context: Context, path: string): Promis
125
48
* @param context
126
49
* @param findDefaultSize
127
50
*/
128
- async function findItemsToCompress ( context : Context , findDefaultSize : boolean ) : Promise < Array < CompressionItem > > {
51
+ export async function findItemsToCompress ( context : Context , findDefaultSize : boolean ) : Promise < Array < CompressionItem > > {
129
52
const toCompress : Array < CompressionItem > = [ ] ;
130
53
for ( const [ path , sizeMapValue ] of context . compressed ) {
131
54
for ( let iterator : number = 0 ; iterator < OrderedCompressionValues . length ; iterator ++ ) {
132
55
const compression : Compression = OrderedCompressionValues [ iterator ] as Compression ;
133
56
const [ size , maxSize ] = sizeMapValue [ iterator ] ;
134
57
await storeOriginalFileContents ( context , path ) ;
135
58
if ( findDefaultSize && compression === 'none' ) {
136
- await compressor ( null , context , { path, compression, maxSize } ) ;
59
+ await compressor ( context , null , { path, compression, maxSize } ) ;
137
60
}
138
61
if ( size !== undefined ) {
139
62
toCompress . push ( {
@@ -152,28 +75,40 @@ async function findItemsToCompress(context: Context, findDefaultSize: boolean):
152
75
* Given a context, compress all Items within splitting work eagly per cpu core to achieve some concurrency.
153
76
* @param context Finalized Valid Context from Configuration
154
77
*/
155
- export default async function * compress ( context : Context , outputReport : boolean ) : AsyncGenerator < boolean , boolean > {
156
- const toCompress : Array < CompressionItem > = await findItemsToCompress ( context , true ) ;
157
- const report : Report | null = outputReport
158
- ? null
159
- : stdout . isTTY && toCompress . length < 30
160
- ? new TTYReport ( context )
161
- : new Report ( context ) ;
162
- let success : boolean = true ;
78
+ export default async function compress (
79
+ context : Context ,
80
+ toCompress : Array < CompressionItem > ,
81
+ report : typeof Report | null ,
82
+ ) : Promise < boolean > {
83
+ if ( toCompress . length === 0 ) {
84
+ return true ;
85
+ }
163
86
164
- for ( let iterator : number = 0 ; iterator < toCompress . length ; iterator += COMPRESSION_CONCURRENCY ) {
165
- if ( iterator === 0 ) {
166
- report ?. update ( context ) ;
167
- }
168
- let itemsSuccessful = await Promise . all (
169
- toCompress . slice ( iterator , iterator + COMPRESSION_CONCURRENCY ) . map ( item => compressor ( report , context , item ) ) ,
170
- ) ;
171
- if ( itemsSuccessful . includes ( false ) ) {
172
- success = false ;
87
+ const returnable : Array < Promise < boolean > > = [ ] ;
88
+ const executing : Array < Promise < boolean > > = [ ] ;
89
+ const reportInstance : Report | null = report ? new report ( context ) : null ;
90
+ let success = true ;
91
+ for ( const item of toCompress ) {
92
+ const promise : Promise < boolean > = Promise . resolve ( item ) . then ( ( item ) => compressor ( context , reportInstance , item ) ) ;
93
+ returnable . push ( promise ) ;
94
+
95
+ if ( COMPRESSION_CONCURRENCY <= toCompress . length ) {
96
+ const execute : any = promise . then ( ( successful ) => {
97
+ if ( ! successful ) {
98
+ success = successful ;
99
+ }
100
+ executing . splice ( executing . indexOf ( execute ) , 1 ) ;
101
+ } ) ;
102
+ executing . push ( execute ) ;
103
+ if ( executing . length >= COMPRESSION_CONCURRENCY ) {
104
+ await Promise . race ( executing ) ;
105
+ }
173
106
}
174
- yield success ;
107
+ }
108
+ if ( ( await Promise . all ( returnable ) ) . includes ( false ) ) {
109
+ success = false ;
175
110
}
176
111
177
- report ?. end ( ) ;
112
+ reportInstance ?. end ( ) ;
178
113
return success ;
179
114
}
0 commit comments