@@ -47,6 +47,24 @@ const getThreadsCount = () => {
4747 return 3 ; // Default (like the workerpool do)
4848} ;
4949
50+ const splitFilesToChunks = ( files : FileContent [ ] , chunksCount : number ) : FileContent [ ] [ ] => {
51+ let chunks : FileContent [ ] [ ] ;
52+ if ( files . length < chunksCount * chunksCount ) {
53+ chunks = [ files ] ;
54+ } else {
55+ const baseSize = Math . floor ( files . length / chunksCount ) ;
56+ chunks = [ ] ;
57+ for ( let i = 0 ; i < chunksCount ; i ++ ) {
58+ // For the last part, we take the remaining files so we don't lose the extra ones.
59+ const start = i * baseSize ;
60+ const end = ( i === chunksCount - 1 ) ? files . length : start + baseSize ;
61+ chunks . push ( files . slice ( start , end ) ) ;
62+ }
63+ }
64+
65+ return chunks ;
66+ } ;
67+
5068export type DataSchemaCompilerOptions = {
5169 compilerCache : CompilerCache ;
5270 omitErrors ?: boolean ;
@@ -258,32 +276,58 @@ export class DataSchemaCompiler {
258276 ) ;
259277 }
260278
261- const transpile = async ( stage : CompileStage ) : Promise < FileContent [ ] > => {
279+ const transpilePhaseFirst = async ( stage : CompileStage ) : Promise < FileContent [ ] > => {
262280 let cubeNames : string [ ] = [ ] ;
263281 let cubeSymbols : Record < string , Record < string , boolean > > = { } ;
264282 let transpilerNames : string [ ] = [ ] ;
265283 let results : ( FileContent | undefined ) [ ] ;
266284
267285 if ( transpilationNative || transpilationWorkerThreads ) {
268- cubeNames = Object . keys ( this . cubeDictionary . byId ) ;
269- // We need only cubes and all its member names for transpiling.
270- // Cubes doesn't change during transpiling, but are changed during compilation phase,
271- // so we can prepare them once for every phase.
272- // Communication between main and worker threads uses
273- // The structured clone algorithm (@see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
274- // which doesn't allow passing any function objects, so we need to sanitize the symbols.
275- // Communication with native backend also involves deserialization.
276- cubeSymbols = Object . fromEntries (
277- Object . entries ( this . cubeSymbols . symbols as Record < string , Record < string , any > > )
278- . map (
279- ( [ key , value ] : [ string , Record < string , any > ] ) => [ key , Object . fromEntries (
280- Object . keys ( value ) . map ( ( k ) => [ k , true ] ) ,
281- ) ] ,
282- ) ,
283- ) ;
286+ ( { cubeNames, cubeSymbols, transpilerNames } = this . prepareTranspileSymbols ( ) ) ;
287+ }
288+
289+ if ( transpilationNative ) {
290+ const nonJsFilesTasks = toCompile . filter ( file => ! file . fileName . endsWith ( '.js' ) )
291+ . map ( f => this . transpileFile ( f , errorsReport , { transpilerNames, compilerId } ) ) ;
292+
293+ const jsFiles = toCompile . filter ( file => file . fileName . endsWith ( '.js' ) ) ;
294+ let jsFilesTasks : Promise < ( FileContent | undefined ) [ ] > [ ] = [ ] ;
295+
296+ if ( jsFiles . length > 0 ) {
297+ // Warming up swc compiler cache
298+ const dummyFile = {
299+ fileName : 'dummy.js' ,
300+ content : ';' ,
301+ } ;
302+
303+ await this . transpileJsFile ( dummyFile , errorsReport , { cubeNames, cubeSymbols, transpilerNames, contextSymbols : CONTEXT_SYMBOLS , compilerId, stage } ) ;
304+
305+ const jsChunks = splitFilesToChunks ( jsFiles , transpilationNativeThreadsCount ) ;
306+ jsFilesTasks = jsChunks . map ( chunk => this . transpileJsFilesBulk ( chunk , errorsReport , { transpilerNames, compilerId } ) ) ;
307+ }
308+
309+ results = ( await Promise . all ( [ ...nonJsFilesTasks , ...jsFilesTasks ] ) ) . flat ( ) ;
310+ } else if ( transpilationWorkerThreads ) {
311+ results = await Promise . all ( toCompile . map ( f => this . transpileFile ( f , errorsReport , { cubeNames, cubeSymbols, transpilerNames } ) ) ) ;
312+ } else {
313+ results = await Promise . all ( toCompile . map ( f => this . transpileFile ( f , errorsReport , { } ) ) ) ;
314+ }
315+
316+ return results . filter ( f => ! ! f ) as FileContent [ ] ;
317+ } ;
284318
285- // Transpilers are the same for all files within phase.
286- transpilerNames = this . transpilers . map ( t => t . constructor . name ) ;
319+ const transpilePhase = async ( stage : CompileStage ) : Promise < FileContent [ ] > => {
320+ let cubeNames : string [ ] = [ ] ;
321+ let cubeSymbols : Record < string , Record < string , boolean > > = { } ;
322+ let transpilerNames : string [ ] = [ ] ;
323+ let results : ( FileContent | undefined ) [ ] ;
324+
325+ if ( toCompile . length === 0 ) {
326+ return [ ] ;
327+ }
328+
329+ if ( transpilationNative || transpilationWorkerThreads ) {
330+ ( { cubeNames, cubeSymbols, transpilerNames } = this . prepareTranspileSymbols ( ) ) ;
287331 }
288332
289333 if ( transpilationNative ) {
@@ -295,34 +339,14 @@ export class DataSchemaCompiler {
295339
296340 await this . transpileJsFile ( dummyFile , errorsReport , { cubeNames, cubeSymbols, transpilerNames, contextSymbols : CONTEXT_SYMBOLS , compilerId, stage } ) ;
297341
298- const nonJsFilesTasks = toCompile . filter ( file => ! file . fileName . endsWith ( '.js' ) && ! file . convertedToJs )
299- . map ( f => this . transpileFile ( f , errorsReport , { transpilerNames, compilerId } ) ) ;
300-
301- const jsFiles = toCompile . filter ( file => file . fileName . endsWith ( '.js' ) || file . convertedToJs ) ;
302- let JsFilesTasks = [ ] ;
342+ const jsChunks = splitFilesToChunks ( toCompile , transpilationNativeThreadsCount ) ;
343+ const jsFilesTasks = jsChunks . map ( chunk => this . transpileJsFilesBulk ( chunk , errorsReport , { transpilerNames, compilerId } ) ) ;
303344
304- if ( jsFiles . length > 0 ) {
305- let jsChunks ;
306- if ( jsFiles . length < transpilationNativeThreadsCount * transpilationNativeThreadsCount ) {
307- jsChunks = [ jsFiles ] ;
308- } else {
309- const baseSize = Math . floor ( jsFiles . length / transpilationNativeThreadsCount ) ;
310- jsChunks = [ ] ;
311- for ( let i = 0 ; i < transpilationNativeThreadsCount ; i ++ ) {
312- // For the last part, we take the remaining files so we don't lose the extra ones.
313- const start = i * baseSize ;
314- const end = ( i === transpilationNativeThreadsCount - 1 ) ? jsFiles . length : start + baseSize ;
315- jsChunks . push ( jsFiles . slice ( start , end ) ) ;
316- }
317- }
318- JsFilesTasks = jsChunks . map ( chunk => this . transpileJsFilesBulk ( chunk , errorsReport , { transpilerNames, compilerId } ) ) ;
319- }
320-
321- results = ( await Promise . all ( [ ...nonJsFilesTasks , ...JsFilesTasks ] ) ) . flat ( ) ;
345+ results = ( await Promise . all ( jsFilesTasks ) ) . flat ( ) ;
322346 } else if ( transpilationWorkerThreads ) {
323- results = await Promise . all ( toCompile . map ( f => this . transpileFile ( f , errorsReport , { cubeNames, cubeSymbols, transpilerNames } ) ) ) ;
347+ results = await Promise . all ( toCompile . map ( f => this . transpileJsFile ( f , errorsReport , { cubeNames, cubeSymbols, transpilerNames } ) ) ) ;
324348 } else {
325- results = await Promise . all ( toCompile . map ( f => this . transpileFile ( f , errorsReport , { } ) ) ) ;
349+ results = await Promise . all ( toCompile . map ( f => this . transpileJsFile ( f , errorsReport , { } ) ) ) ;
326350 }
327351
328352 return results . filter ( f => ! ! f ) as FileContent [ ] ;
@@ -335,6 +359,14 @@ export class DataSchemaCompiler {
335359 let asyncModules : CallableFunction [ ] = [ ] ;
336360 let transpiledFiles : FileContent [ ] = [ ] ;
337361
362+ const cleanup = ( ) => {
363+ cubes = [ ] ;
364+ exports = { } ;
365+ contexts = [ ] ;
366+ compiledFiles = { } ;
367+ asyncModules = [ ] ;
368+ } ;
369+
338370 this . compileV8ContextCache = vm . createContext ( {
339371 view : ( name , cube ) => {
340372 const file = ctxFileStorage . getStore ( ) ;
@@ -420,26 +452,28 @@ export class DataSchemaCompiler {
420452 COMPILE_CONTEXT : this . standalone ? this . standaloneCompileContextProxy ( ) : this . cloneCompileContextWithGetterAlias ( this . compileContext || { } ) ,
421453 } ) ;
422454
423- const compilePhase = async ( compilers : CompileCubeFilesCompilers , stage : 0 | 1 | 2 | 3 ) => {
455+ const compilePhaseFirst = async ( compilers : CompileCubeFilesCompilers , stage : 0 | 1 | 2 | 3 ) => {
424456 // clear the objects for the next phase
425- cubes = [ ] ;
426- exports = { } ;
427- contexts = [ ] ;
428- compiledFiles = { } ;
429- asyncModules = [ ] ;
430- transpiledFiles = await transpile ( stage ) ;
457+ cleanup ( ) ;
458+ transpiledFiles = await transpilePhaseFirst ( stage ) ;
431459
432- if ( stage === 0 ) {
433- // We render jinja and transpile yaml only once on first phase and then use resulting JS for these files
434- // afterward avoiding costly YAML/Python parsing again. Original JS files are preserved as is for cache hits.
435- const convertedToJsFiles = transpiledFiles . filter ( f => f . convertedToJs ) ;
436- toCompile = [ ...originalJsFiles , ...convertedToJsFiles ] ;
437- }
460+ // We render jinja and transpile yaml only once on first phase and then use resulting JS for these files
461+ // afterward avoiding costly YAML/Python parsing again. Original JS files are preserved as is for cache hits.
462+ const convertedToJsFiles = transpiledFiles . filter ( f => ! f . fileName . endsWith ( '.js' ) ) ;
463+ toCompile = [ ...originalJsFiles , ...convertedToJsFiles ] ;
464+
465+ return this . compileCubeFiles ( cubes , contexts , compiledFiles , asyncModules , compilers , transpiledFiles , errorsReport ) ;
466+ } ;
467+
468+ const compilePhase = async ( compilers : CompileCubeFilesCompilers , stage : 0 | 1 | 2 | 3 ) => {
469+ // clear the objects for the next phase
470+ cleanup ( ) ;
471+ transpiledFiles = await transpilePhase ( stage ) ;
438472
439473 return this . compileCubeFiles ( cubes , contexts , compiledFiles , asyncModules , compilers , transpiledFiles , errorsReport ) ;
440474 } ;
441475
442- return compilePhase ( { cubeCompilers : this . cubeNameCompilers } , 0 )
476+ return compilePhaseFirst ( { cubeCompilers : this . cubeNameCompilers } , 0 )
443477 . then ( ( ) => compilePhase ( { cubeCompilers : this . preTranspileCubeCompilers . concat ( [ this . viewCompilationGate ] ) } , 1 ) )
444478 . then ( ( ) => ( this . viewCompilationGate . shouldCompileViews ( ) ?
445479 compilePhase ( { cubeCompilers : this . viewCompilers } , 2 )
@@ -450,11 +484,7 @@ export class DataSchemaCompiler {
450484 } , 3 ) )
451485 . then ( ( ) => {
452486 // Free unneeded resources
453- cubes = [ ] ;
454- exports = { } ;
455- contexts = [ ] ;
456- compiledFiles = { } ;
457- asyncModules = [ ] ;
487+ cleanup ( ) ;
458488 transpiledFiles = [ ] ;
459489 toCompile = [ ] ;
460490
@@ -508,36 +538,49 @@ export class DataSchemaCompiler {
508538 } ) ;
509539 }
510540
541+ private prepareTranspileSymbols ( ) {
542+ const cubeNames : string [ ] = Object . keys ( this . cubeDictionary . byId ) ;
543+ // We need only cubes and all its member names for transpiling.
544+ // Cubes doesn't change during transpiling, but are changed during compilation phase,
545+ // so we can prepare them once for every phase.
546+ // Communication between main and worker threads uses
547+ // The structured clone algorithm (@see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
548+ // which doesn't allow passing any function objects, so we need to sanitize the symbols.
549+ // Communication with native backend also involves deserialization.
550+ const cubeSymbols : Record < string , Record < string , boolean > > = Object . fromEntries (
551+ Object . entries ( this . cubeSymbols . symbols as Record < string , Record < string , any > > )
552+ . map (
553+ ( [ key , value ] : [ string , Record < string , any > ] ) => [ key , Object . fromEntries (
554+ Object . keys ( value ) . map ( ( k ) => [ k , true ] ) ,
555+ ) ] ,
556+ ) ,
557+ ) ;
558+
559+ // Transpilers are the same for all files within phase.
560+ const transpilerNames : string [ ] = this . transpilers . map ( t => t . constructor . name ) ;
561+
562+ return { cubeNames, cubeSymbols, transpilerNames } ;
563+ }
564+
511565 private async transpileFile (
512566 file : FileContent ,
513567 errorsReport : ErrorReporter ,
514568 options : TranspileOptions = { }
515569 ) : Promise < ( FileContent | undefined ) > {
516- if ( file . fileName . endsWith ( '.js' ) || file . convertedToJs ) {
570+ if ( file . fileName . endsWith ( '.js' ) ) {
517571 return this . transpileJsFile ( file , errorsReport , options ) ;
518572 } else if ( file . fileName . endsWith ( '.jinja' ) ||
519573 ( file . fileName . endsWith ( '.yml' ) || file . fileName . endsWith ( '.yaml' ) )
520574 && file . content . match ( JINJA_SYNTAX )
521575 ) {
522- const transpiledFile = await this . yamlCompiler . compileYamlWithJinjaFile (
576+ return this . yamlCompiler . compileYamlWithJinjaFile (
523577 file ,
524578 errorsReport ,
525579 this . standalone ? { } : this . cloneCompileContextWithGetterAlias ( this . compileContext ) ,
526580 this . pythonContext !
527581 ) ;
528- if ( transpiledFile ) {
529- transpiledFile . convertedToJs = true ;
530- }
531-
532- return transpiledFile ;
533582 } else if ( file . fileName . endsWith ( '.yml' ) || file . fileName . endsWith ( '.yaml' ) ) {
534- const transpiledFile = this . yamlCompiler . transpileYamlFile ( file , errorsReport ) ;
535-
536- if ( transpiledFile ) {
537- transpiledFile . convertedToJs = true ;
538- }
539-
540- return transpiledFile ;
583+ return this . yamlCompiler . transpileYamlFile ( file , errorsReport ) ;
541584 } else {
542585 return file ;
543586 }
0 commit comments