import { resolve } from 'path' ; import { promises } from 'fs' ; import { performance } from 'perf_hooks' ; import type { OutputOptions , RollupOptions , RollupBuild } from 'rollup' ; import { rollup , watch } from 'rollup' ; import type { Options } from './options.h' ; import type { Build } from './builds/build.h' ; import { build as nodeCjsBuild } from './builds/node-cjs' ; import { build as nodeEsBuild } from './builds/node-es' ; import { build as browserBuild } from './builds/browser' ; import { createBuild as createMigrationsBuild } from './builds/migrations' ; import { testsBuild } from './builds/tests' ; import { clearOutput } from './clearOutput' ; import { copyStaticAssets } from './copyStaticAssets' ; import { changeTypings } from './changeTypings' ; import type { PackageJSON } from './packageJson' ; import { defaultSourceDir } from './fileNames.ts' ; import { logger } from './logger' ; export class TramvaiBuild { private cwd : string ; private options : Options ; private builds : Build [ ] ; private packageJSON : PackageJSON ; private rollupCache : Record < string , RollupBuild > = { } ; constructor ( options = { } , builds = [ createMigrationsBuild ( ) , nodeCjsBuild , nodeEsBuild , browserBuild , testsBuild ] ) { this . cwd = process . cwd ( ) ; this . options = options ; this . builds = builds ; this . applyDefaultOptions ( ) ; this . readPackageJson ( ) ; } async start ( ) { logger . info ( `build${ this . options . watchMode ? ' in watch mode' : '' } ${ this . packageJSON . name }` ) ; try { await this . clearOutput ( ) ; if ( this . options . copyStaticAssets ) { await this . copyStaticAssets ( ) ; } for ( const build of this . builds ) { await this . build ( build ) ; } await this . changeTypings ( ) ; if ( this . options . forPublish && ! this . options . watchMode ) { await this . writePackageJson ( ) ; } logger . info ( `successfully build ${ this . packageJSON . name }` ) ; } catch ( error ) { console . error ( error ) ; throw error ; } } async copy ( ) { return this . copyStaticAssets ( ) ; } private readPackageJson ( ) { const path = resolve ( this . cwd , 'package.json' ) ; this . packageJSON = require ( path ) ; if ( ! this . packageJSON . main ) { throw new Error ( `в package.json необходимо указать поле "main"!` ) ; } if ( ! this . packageJSON . typings ) { throw new Error ( `в package.json необходимо указать поле "typings"!` ) ; } } private applyDefaultOptions ( ) { if ( typeof this . options . sourceDir !== 'string' ) { this . options . sourceDir = defaultSourceDir ; } if ( ! ( 'copyStaticAssets' in this . options ) ) { this . options . copyStaticAssets = true ; } } private writePackageJson ( ) { logger . info ( `update package.json` ) ; const path = resolve ( this . cwd , 'package.json' ) ; return promises . writeFile ( path , JSON . stringify ( this . packageJSON , null , 2 ) ) ; } private async copyStaticAssets ( ) { return copyStaticAssets ( { cwd : this . cwd , options : this . options , packageJSON : this . packageJSON , } ) ; } private async clearOutput ( ) { return clearOutput ( { cwd : this . cwd , options : this . options , packageJSON : this . packageJSON , } ) ; } private async changeTypings ( ) { this . packageJSON = await changeTypings ( { cwd : this . cwd , options : this . options , packageJSON : this . packageJSON , } ) ; } private async build ( build ) { const params = { cwd : this . cwd , options : this . options , packageJSON : this . packageJSON , } ; const shouldExecute = await build . shouldExecute ( params ) ; if ( ! shouldExecute ) { return ; } const options = await build . getOptions ( params ) ; if ( this . options . watchMode ) { await this . watch ( build . name , options . input , options . output ) ; return ; } const start = performance . now ( ) ; logger . info ( `start build ${ build . name }` ) ; if ( typeof options . input . input === 'string' ) { logger . info ( `parse ${ options . input . input . replace ( this . cwd , '' ) . substring ( 1 ) }` ) ; } else { logger . info ( `parse ${ JSON . stringify ( options . input . input ) }` ) ; } const bundle = await this . getBundleWithCache ( build , options ) ; if ( typeof options . output . entryFileNames === 'string' ) { logger . info ( `write ${ options . output . dir }/${ options . output . entryFileNames }` ) ; } else { logger . info ( `write files to ${ options . output . dir }` ) ; } await this . write ( bundle , options . output ) ; const end = performance . now ( ) ; logger . info ( `end build ${ build . name } in ${ Math . round ( end - start ) }ms` ) ; if ( build . modifyPackageJSON ) { this . packageJSON = await build . modifyPackageJSON ( params ) ; } } private async rollup ( inputOptions ) { const bundle = await rollup ( inputOptions ) ; return bundle ; } private async write ( bundle , outputOptions ) { await bundle . write ( outputOptions ) ; } private async watch ( buildName , inputOptions , outputOptions ) { const watcher = watch ( { ... inputOptions , output : { ... outputOptions , } , } ) ; watcher . on ( 'event' , ( event ) => { switch ( event . code ) { case 'BUNDLE_START' : { logger . info ( `start build ${ buildName }` ) ; break ; } case 'BUNDLE_END' : { logger . info ( `build ${ buildName } is successful` ) ; break ; } case 'ERROR' : { logger . error ( `${ buildName } build error:` , event . error ) ; break ; } } } ) ; return watcher ; } private async getBundleWithCache ( build , options ) { const makeRollup = ( ) => this . rollup ( options . input ) ; let bundle ; if ( build . cacheName ) { if ( this . rollupCache [ build . cacheName ] ) { bundle = this . rollupCache [ build . cacheName ] ; } else { bundle = await makeRollup ( ) ; this . rollupCache . node = bundle ; } } else { bundle = await makeRollup ( ) ; } return bundle ; } }