import { sync as delSync } from 'del' ; import { Application } from 'express' ; import { PathParams } from 'express-serve-static-core' ; import openBrowser from 'opn' ; import path from 'path' ; import webpack from 'webpack' ; import { MultiCompiler } from 'webpack-dev-server/node_modules/webpack/types' ; import WebpackDevMiddleware from 'webpack-dev-middleware' ; import WebpackDevServer from 'webpack-dev-server' ; import { ssrMiddleware as defaultSSRMiddleware , AppInvocationInfoResolver , SSRMiddleware , } from './ssrMiddleware' ; import { getDefaultAppInvocationInfoResolver } from './defaultAppInvocationInfoResolver' ; export interface ConfigFactoryResult { ssrWebpackConfig : webpack . Configuration ; clientWebpackConfig : webpack . Configuration ; devServerConfig : WebpackDevServer . Configuration ; } export type ConfigFactory = ( nodeEnv ? : string , tunnelUrl ? : string ) => ConfigFactoryResult ; export interface DevServerOptions { configFactory : ConfigFactory ; /**
     * Path to the build artifacts from your SSR Webpack configuration. By default,
     * we'll use the 'ssrWebpackConfig.output.path' value. But you can specify a different
     * value with the 'buildArtifactsPath' property. Note: this does _not_ replace the
     * value specified in your Webpack configuration, instead this value is used for resolving
     * the path to your app's server bundle.
     */ buildArtifactsPath ? : string ; serverBundleFileName ? : string ; default port ? : number ; default hostname ? : string ; tunnelUrl ? : string ; urlToOpenOnStart ? : string ; hooks ? : { beforeWebpackCompilerCreation ? : ( clientWebpackConfig : webpack . Configuration , ssrWebpackConfig : webpack . Configuration ) => void ; beforeSSRMiddlewareRegistered ? : ( app : Application , server : WebpackDevServer ) => void ; beforeDevServerCreated ? : ( compiler : webpack . MultiCompiler , devServerOptions : WebpackDevServer . Configuration & WebpackDevMiddleware . Options ) => void ; beforeDevServerStarted ? : ( devServer : WebpackDevServer ) => void ; afterDevServerStarted ? : ( devServer : WebpackDevServer ) => void ; } ; ssrMiddleware ? : SSRMiddleware ; default ssrMiddlewarePath ? : PathParams ; appInvocationInfoResolver ? : AppInvocationInfoResolver ; clean ? : boolean ; } param { DevServerOptions } config export function startDevServer ( { port = 0 , tunnelUrl , configFactory , buildArtifactsPath : customBuildArtifactsPath , serverBundleFileName : customServerBundleFileName , urlToOpenOnStart , hooks = { } , ssrMiddleware , ssrMiddlewarePath , appInvocationInfoResolver : customAppInvocationInfoResolver , clean = true , } ) { const configs = configFactory ( process . env . NODE_ENV , tunnelUrl ) ; if ( ! configs . ssrWebpackConfig . output ) { throw new Error ( `The SSR webpack config doesn't have an 'output' property defined.
      Ensure that your config factory is returning a valid webpack configuration
      for the 'ssrWebpackConfig' property.` ) ; } if ( ! configs . clientWebpackConfig . output ) { throw new Error ( `The client webpack config doesn't have an 'output' property defined.
      Ensure that your config factory is returning a valid webpack configuration
      for the 'clientWebpackConfig' property.` ) ; } const buildArtifactsPath = customBuildArtifactsPath || configs . ssrWebpackConfig . output . path || configs . clientWebpackConfig . output . path ; if ( ! buildArtifactsPath ) { throw new Error ( `No path was specified for your webpack build artifacts. The SSR Middleware needs to know where your
      webpack build artifacts are located in order to fulfill it's glorious destiny. You can specify a path via the options provided to
      the 'startDevServer' function, otherwise we'll attempt to use the value specified in your SSR webpack config or client webpack config,
      respectively.` ) ; } const serverBundleFileName = customServerBundleFileName || configs . ssrWebpackConfig . output . filename || 'server.bundle.js' ; if ( tunnelUrl ) { console . log ( `Setting client webpack configuration publicPath to tunnelUrl: ${ tunnelUrl }` ) ; configs . clientWebpackConfig . output . publicPath = tunnelUrl ; } invokeHook ( hooks . beforeWebpackCompilerCreation , configs . clientWebpackConfig , configs . ssrWebpackConfig ) ; const compiler = webpack ( [ configs . clientWebpackConfig , configs . ssrWebpackConfig ] ) ; if ( urlToOpenOnStart ) { let browserOpened = false ; compiler . compilers [ compiler . compilers . length - 1 ] . hooks . done . tap ( 'OpenBrowserAfterCompilationPlugin' , ( stats ) => { if ( ! browserOpened ) { console . log ( 'opening browser' , stats . compilation . compiler . name ) ; openBrowser ( urlToOpenOnStart ) . then ( ( ) => { browserOpened = true ; } ) ; } } ) ; } const webpackDevMiddlewareOptions = { serverSideRender : true , writeToDisk : true , logLevel : 'debug' , } ; const serverOptions = { publicPath : '/' , ... configs . devServerConfig , inline : true , contentBase : configs . ssrWebpackConfig . output . path , hot : true , quiet : false , stats : 'none' , clientLogLevel : 'info' , ... webpackDevMiddlewareOptions , } ; if ( tunnelUrl ) { serverOptions . public = tunnelUrl ; } const modulePath = path . join ( buildArtifactsPath , serverBundleFileName ) ; console . log ( 'Resolved server bundle path' , modulePath ) ; const appInvocationInfoResolver = customAppInvocationInfoResolver || getDefaultAppInvocationInfoResolver ( { appPathResolver : ( ) => modulePath } ) ; const originalAfter = serverOptions . after ; serverOptions . after = ( app , server ) => { const middleware = ssrMiddleware ? ssrMiddleware ( { appInvocationInfoResolver , } ) : defaultSSRMiddleware ( { appInvocationInfoResolver , } ) ; invokeHook ( hooks . beforeSSRMiddlewareRegistered , app , server ) ; app . post ( ssrMiddlewarePath || '*' , middleware ) ; if ( originalAfter ) { originalAfter ( app , server ) ; } } ; if ( clean ) { const cleanPaths = [ `${ path . join ( buildArtifactsPath , '**' ) }` , `!${ buildArtifactsPath }` ] ; console . log ( 'cleaning paths' , cleanPaths ) ; const cleanedPaths = delSync ( cleanPaths ) ; console . log ( 'cleaned paths' , cleanedPaths ) ; } invokeHook ( hooks . beforeDevServerCreated , compiler , serverOptions ) ; const server = new WebpackDevServer ( ( compiler as unknown ) as MultiCompiler , serverOptions ) ; invokeHook ( hooks . beforeDevServerStarted , server ) ; server . listen ( port , 'localhost' , ( err ? ) => { if ( err ) { console . error ( err ) ; return ; } console . log ( `Starting webpack dev server at localhost:${ port }` ) ; invokeHook ( hooks . afterDevServerStarted , server ) ; } ) ; } param { Function | undefined } hook param { ... unknown } args function invokeHook ( hook , ... args ) { if ( hook && typeof hook === 'function' ) { hook ( ... args ) ; } }