@@ -6,8 +6,9 @@ import { parse } from '@babel/parser';
66import babelGenerator from '@babel/generator' ;
77import babelTraverse from '@babel/traverse' ;
88import R from 'ramda' ;
9+ import workerpool from 'workerpool' ;
910
10- import { isNativeSupported } from '@cubejs-backend/shared' ;
11+ import { getEnv , isNativeSupported } from '@cubejs-backend/shared' ;
1112import { UserError } from './UserError' ;
1213import { ErrorReporter } from './ErrorReporter' ;
1314
@@ -28,6 +29,8 @@ export class DataSchemaCompiler {
2829 this . viewCompilationGate = options . viewCompilationGate ;
2930 this . cubeNameCompilers = options . cubeNameCompilers || [ ] ;
3031 this . extensions = options . extensions || { } ;
32+ this . cubeDictionary = options . cubeDictionary ;
33+ this . cubeSymbols = options . cubeSymbols ;
3134 this . cubeFactory = options . cubeFactory ;
3235 this . filesToCompile = options . filesToCompile ;
3336 this . omitErrors = options . omitErrors ;
@@ -40,6 +43,7 @@ export class DataSchemaCompiler {
4043 this . yamlCompiler = options . yamlCompiler ;
4144 this . yamlCompiler . dataSchemaCompiler = this ;
4245 this . pythonContext = null ;
46+ this . workerPool = null ;
4347 }
4448
4549 compileObjects ( compileServices , objects , errorsReport ) {
@@ -89,10 +93,40 @@ export class DataSchemaCompiler {
8993 const errorsReport = new ErrorReporter ( null , [ ] , this . errorReport ) ;
9094 this . errorsReport = errorsReport ;
9195
92- // TODO: required in order to get pre transpile compilation work
93- const transpile = ( ) => toCompile . map ( f => this . transpileFile ( f , errorsReport ) ) . filter ( f => ! ! f ) ;
96+ if ( getEnv ( 'transpilationWorkerThreads' ) ) {
97+ const wc = getEnv ( 'transpilationWorkerThreadsCount' ) ;
98+ this . workerPool = workerpool . pool (
99+ path . join ( __dirname , 'transpilers/transpiler_worker' ) ,
100+ wc > 0 ? { maxWorkers : wc } : undefined ,
101+ ) ;
102+ }
103+
104+ const transpile = async ( ) => {
105+ let cubeNames ;
106+ let cubeSymbolsNames ;
107+
108+ if ( getEnv ( 'transpilationWorkerThreads' ) ) {
109+ cubeNames = Object . keys ( this . cubeDictionary . byId ) ;
110+ // We need only cubes and all its member names for transpiling.
111+ // Cubes doesn't change during transpiling, but are changed during compilation phase,
112+ // so we can prepare them once for every phase.
113+ // Communication between main and worker threads uses
114+ // The structured clone algorithm (@see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
115+ // which doesn't allow passing any function objects, so we need to sanitize the symbols.
116+ cubeSymbolsNames = Object . fromEntries (
117+ Object . entries ( this . cubeSymbols . symbols )
118+ . map (
119+ ( [ key , value ] ) => [ key , Object . fromEntries (
120+ Object . keys ( value ) . map ( ( k ) => [ k , true ] ) ,
121+ ) ] ,
122+ ) ,
123+ ) ;
124+ }
125+ const results = await Promise . all ( toCompile . map ( f => this . transpileFile ( f , errorsReport , { cubeNames, cubeSymbolsNames } ) ) ) ;
126+ return results . filter ( f => ! ! f ) ;
127+ } ;
94128
95- const compilePhase = ( compilers ) => this . compileCubeFiles ( compilers , transpile ( ) , errorsReport ) ;
129+ const compilePhase = async ( compilers ) => this . compileCubeFiles ( compilers , await transpile ( ) , errorsReport ) ;
96130
97131 return compilePhase ( { cubeCompilers : this . cubeNameCompilers } )
98132 . then ( ( ) => compilePhase ( { cubeCompilers : this . preTranspileCubeCompilers . concat ( [ this . viewCompilationGate ] ) } ) )
@@ -102,7 +136,12 @@ export class DataSchemaCompiler {
102136 . then ( ( ) => compilePhase ( {
103137 cubeCompilers : this . cubeCompilers ,
104138 contextCompilers : this . contextCompilers ,
105- } ) ) ;
139+ } ) )
140+ . then ( ( ) => {
141+ if ( this . workerPool ) {
142+ this . workerPool . terminate ( ) ;
143+ }
144+ } ) ;
106145 }
107146
108147 compile ( ) {
@@ -118,7 +157,7 @@ export class DataSchemaCompiler {
118157 return this . compilePromise ;
119158 }
120159
121- transpileFile ( file , errorsReport ) {
160+ async transpileFile ( file , errorsReport , options ) {
122161 if ( R . endsWith ( '.jinja' , file . fileName ) ||
123162 ( R . endsWith ( '.yml' , file . fileName ) || R . endsWith ( '.yaml' , file . fileName ) )
124163 // TODO do Jinja syntax check with jinja compiler
@@ -137,31 +176,47 @@ export class DataSchemaCompiler {
137176 } else if ( R . endsWith ( '.yml' , file . fileName ) || R . endsWith ( '.yaml' , file . fileName ) ) {
138177 return file ;
139178 } else if ( R . endsWith ( '.js' , file . fileName ) ) {
140- return this . transpileJsFile ( file , errorsReport ) ;
179+ return this . transpileJsFile ( file , errorsReport , options ) ;
141180 } else {
142181 return file ;
143182 }
144183 }
145184
146- transpileJsFile ( file , errorsReport ) {
185+ async transpileJsFile ( file , errorsReport , { cubeNames , cubeSymbolsNames } ) {
147186 try {
148- const ast = parse (
149- file . content ,
150- {
151- sourceFilename : file . fileName ,
152- sourceType : 'module' ,
153- plugins : [ 'objectRestSpread' ]
154- } ,
155- ) ;
187+ if ( getEnv ( 'transpilationWorkerThreads' ) ) {
188+ const data = {
189+ fileName : file . fileName ,
190+ content : file . content ,
191+ transpilers : this . transpilers . map ( t => t . constructor . name ) ,
192+ cubeNames,
193+ cubeSymbolsNames,
194+ } ;
195+
196+ const res = await this . workerPool . exec ( 'transpile' , [ data ] ) ;
197+ errorsReport . addErrors ( res . errors ) ;
198+ errorsReport . addWarnings ( res . warnings ) ;
199+
200+ return Object . assign ( { } , file , { content : res . content } ) ;
201+ } else {
202+ const ast = parse (
203+ file . content ,
204+ {
205+ sourceFilename : file . fileName ,
206+ sourceType : 'module' ,
207+ plugins : [ 'objectRestSpread' ] ,
208+ } ,
209+ ) ;
156210
157- this . transpilers . forEach ( ( t ) => {
158- errorsReport . inFile ( file ) ;
159- babelTraverse ( ast , t . traverseObject ( errorsReport ) ) ;
160- errorsReport . exitFile ( ) ;
161- } ) ;
211+ this . transpilers . forEach ( ( t ) => {
212+ errorsReport . inFile ( file ) ;
213+ babelTraverse ( ast , t . traverseObject ( errorsReport ) ) ;
214+ errorsReport . exitFile ( ) ;
215+ } ) ;
162216
163- const content = babelGenerator ( ast , { } , file . content ) . code ;
164- return Object . assign ( { } , file , { content } ) ;
217+ const content = babelGenerator ( ast , { } , file . content ) . code ;
218+ return Object . assign ( { } , file , { content } ) ;
219+ }
165220 } catch ( e ) {
166221 if ( e . toString ( ) . indexOf ( 'SyntaxError' ) !== - 1 ) {
167222 const line = file . content . split ( '\n' ) [ e . loc . line - 1 ] ;
0 commit comments