1414 * limitations under the License.
1515 */
1616
17- import { Context , Compression , OrderedCompressionValues , maxSize } from './validation/Condition' ;
1817import { cpus } from 'os' ;
19- import { constants as brotliConstants , brotliCompress , gzip , ZlibOptions } from 'zlib ' ;
18+ import { Context , Compression , OrderedCompressionValues , maxSize } from './validation/Condition ' ;
2019import { readFile } from './helpers/fs' ;
21- import { LogError } from './log/helpers/error' ;
2220import { Report } from './log/report' ;
23- import { TTYReport } from './log/tty-report' ;
24- import { stdout } from 'process' ;
21+ import { compressor } from './compressor' ;
2522
2623const 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- } ;
3724
38- interface CompressionItem {
25+ export interface CompressionItem {
3926 path : string ;
4027 compression : Compression ;
4128 maxSize : maxSize ;
4229}
4330
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-
10831/**
10932 * Store the original content so it isn't retrieved from FileSystem for each compression.
11033 * @param context
@@ -125,15 +48,15 @@ async function storeOriginalFileContents(context: Context, path: string): Promis
12548 * @param context
12649 * @param findDefaultSize
12750 */
128- async function findItemsToCompress ( context : Context , findDefaultSize : boolean ) : Promise < Array < CompressionItem > > {
51+ export async function findItemsToCompress ( context : Context , findDefaultSize : boolean ) : Promise < Array < CompressionItem > > {
12952 const toCompress : Array < CompressionItem > = [ ] ;
13053 for ( const [ path , sizeMapValue ] of context . compressed ) {
13154 for ( let iterator : number = 0 ; iterator < OrderedCompressionValues . length ; iterator ++ ) {
13255 const compression : Compression = OrderedCompressionValues [ iterator ] as Compression ;
13356 const [ size , maxSize ] = sizeMapValue [ iterator ] ;
13457 await storeOriginalFileContents ( context , path ) ;
13558 if ( findDefaultSize && compression === 'none' ) {
136- await compressor ( null , context , { path, compression, maxSize } ) ;
59+ await compressor ( context , null , { path, compression, maxSize } ) ;
13760 }
13861 if ( size !== undefined ) {
13962 toCompress . push ( {
@@ -152,28 +75,40 @@ async function findItemsToCompress(context: Context, findDefaultSize: boolean):
15275 * Given a context, compress all Items within splitting work eagly per cpu core to achieve some concurrency.
15376 * @param context Finalized Valid Context from Configuration
15477 */
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+ }
16386
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+ }
173106 }
174- yield success ;
107+ }
108+ if ( ( await Promise . all ( returnable ) ) . includes ( false ) ) {
109+ success = false ;
175110 }
176111
177- report ?. end ( ) ;
112+ reportInstance ?. end ( ) ;
178113 return success ;
179114}
0 commit comments