@@ -212,6 +212,8 @@ class CodeInjector implements ICodeInjector {
212212 }
213213
214214 async prepareSources ( ) {
215+ // collects all files and folders into SPA_TMP_DIR
216+
215217 // check spa tmp folder exists and create if not
216218 try {
217219 await fs . promises . access ( this . spaTmpPath ( ) , fs . constants . F_OK ) ;
@@ -734,6 +736,39 @@ class CodeInjector implements ICodeInjector {
734736 this . allWatchers . push ( watcher ) ;
735737 }
736738
739+ async tryReadFile ( filePath : string ) {
740+ try {
741+ const content = await fs . promises . readFile ( filePath , 'utf-8' ) ;
742+ return content ;
743+ } catch ( e ) {
744+ // file does not exist
745+ process . env . HEAVY_DEBUG && console . log ( `🪲File ${ filePath } does not exist, returning null` ) ;
746+ return null ;
747+ }
748+ }
749+
750+ async computeSourcesHash ( folderPath : string = this . spaTmpPath ( ) ) {
751+ const files = await fs . promises . readdir ( folderPath , { withFileTypes : true } ) ;
752+ const hashes = await Promise . all (
753+ files . map ( async ( file ) => {
754+ const filePath = path . join ( folderPath , file . name ) ;
755+
756+ // 🚫 Skip node_modules
757+ if ( file . name === 'node_modules' || file . name === 'dist' ) {
758+ return '' ;
759+ }
760+
761+ if ( file . isDirectory ( ) ) {
762+ return this . computeSourcesHash ( filePath ) ;
763+ } else {
764+ const content = await fs . promises . readFile ( filePath , 'utf-8' ) ;
765+ return md5hash ( content ) ;
766+ }
767+ } )
768+ ) ;
769+ return md5hash ( hashes . join ( '' ) ) ;
770+ }
771+
737772 async bundleNow ( { hotReload = false } : { hotReload : boolean } ) {
738773 console . log ( `${ this . adminforth . formatAdminForth ( ) } Bundling ${ hotReload ? 'and listening for changes (🔥 Hotreload)' : ' (no hot reload)' } ` ) ;
739774 this . adminforth . runningHotReload = hotReload ;
@@ -754,28 +789,55 @@ class CodeInjector implements ICodeInjector {
754789 }
755790
756791 const cwd = this . spaTmpPath ( ) ;
792+ const serveDir = this . getServeDir ( ) ;
757793
758794
759- await this . runNpmShell ( { command : 'run i18n:extract' , cwd} ) ;
795+ const sourcesHash = await this . computeSourcesHash ( this . spaTmpPath ( ) ) ;
796+
797+ const buildHash = await this . tryReadFile ( path . join ( serveDir , '.adminforth_build_hash' ) ) ;
798+ const messagesHash = await this . tryReadFile ( path . join ( serveDir , '.adminforth_messages_hash' ) ) ;
760799
761- // probably add option to build with tsh check (plain 'build')
762- const serveDir = this . getServeDir ( ) ;
763- // remove serveDir if exists
764- try {
765- await fs . promises . rm ( serveDir , { recursive : true } ) ;
766- } catch ( e ) {
767- // ignore
800+ const skipBuild = buildHash === sourcesHash ;
801+ const skipExtract = messagesHash === sourcesHash ;
802+
803+
804+
805+ if ( ! skipExtract ) {
806+ await this . runNpmShell ( { command : 'run i18n:extract' , cwd} ) ;
807+
808+ // create serveDir if not exists
809+ await fs . promises . mkdir ( serveDir , { recursive : true } ) ;
810+
811+ // copy i18n messages to serve dir
812+ await fsExtra . copy ( path . join ( cwd , 'i18n-messages.json' ) , path . join ( serveDir , 'i18n-messages.json' ) ) ;
813+
814+ // save hash
815+ await fs . promises . writeFile ( path . join ( serveDir , '.adminforth_messages_hash' ) , sourcesHash ) ;
816+ } else {
817+ console . log ( `Skipping AdminForth i18n messages extraction - it is already done for these sources set` ) ;
768818 }
769- await fs . promises . mkdir ( serveDir , { recursive : true } ) ;
770-
771- // copy i18n messages to serve dir
772- await fsExtra . copy ( path . join ( cwd , 'i18n-messages.json' ) , path . join ( serveDir , 'i18n-messages.json' ) ) ;
773819
774820 if ( ! hotReload ) {
775- await this . runNpmShell ( { command : 'run build-only' , cwd} ) ;
821+ if ( ! skipBuild ) {
822+ // remove serveDir if exists
823+ try {
824+ await fs . promises . rm ( serveDir , { recursive : true } ) ;
825+ } catch ( e ) {
826+ // ignore
827+ }
828+ await fs . promises . mkdir ( serveDir , { recursive : true } ) ;
829+
830+ // TODO probably add option to build with tsh check (plain 'build')
831+ await this . runNpmShell ( { command : 'run build-only' , cwd} ) ;
832+
833+ // coy dist to serveDir
834+ await fsExtra . copy ( path . join ( cwd , 'dist' ) , serveDir , { recursive : true } ) ;
776835
777- // coy dist to serveDir
778- await fsExtra . copy ( path . join ( cwd , 'dist' ) , serveDir , { recursive : true } ) ;
836+ // save hash
837+ await fs . promises . writeFile ( path . join ( serveDir , '.adminforth_build_hash' ) , sourcesHash ) ;
838+ } else {
839+ console . log ( `Skipping AdminForth SPA bundle - it is already done for these sources set` ) ;
840+ }
779841 } else {
780842
781843 const command = 'run dev' ;
0 commit comments