Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
@skava/bs / src / scripts / development / configs.ts
Size: Mime:
/* eslint-disable import/no-dynamic-require */
/**
 * @todo serve the app on 3000 export the app from dist server...
 */
import './safety'
import * as path from 'path'
import { config } from '@skava/di'
import fwf from 'funwithflags'
import { exists, read, write } from 'flipfile'
import express from 'express'
import webpack from 'webpack'
import createWebpackMiddleware from 'webpack-dev-middleware'
import createWebpackHotMiddleware from 'webpack-hot-middleware'
import { webpackConfigFactory } from '../bundle/webpackConfigFactory'
import { getConfig } from '../../config/oneConfig/getConfigForEnv'
import { resolveToRoot } from '../../config/resolveToRoot'
import { create, inject } from './dll'

const argv = fwf(process.argv.slice(2), {
  camel: true,
})

// @todo
// const buildMeta = write('./dist/meta.json', { lastBuild: Date.now() })
const serverOutputPath = process.env.BUNDLE_SERVER_OUTPUT_PATH_ABSOLUTE

/**
 * @todo https://github.com/webpack-contrib/webpack-hot-middleware#multi-compiler-mode
 */
const devServer = express()

const initializeBundle = (name, bundleConfig) => {
  const toConfig = () => {
    const webpackConfig = webpackConfigFactory({
      target: name,
      mode: 'development',
    })

    if (config.has('buildSystemTap') === true) {
      const tapConfig = config.get('buildSystemTap')
      tapConfig.tap(webpackConfig, bundleConfig)
    }

    return webpackConfig
  }

  const createCompiler = () => {
    const webpackConfig = toConfig()
    decorateWithDll(name, webpackConfig)
    return webpack(webpackConfig)
  }

  return { name, bundleConfig, createCompiler, toConfig }
}

function decorateWithDll(name, bundleConfig) {
  if (!process.env.BUILD_USE_DLL) {
    console.log('[devserver] not using dll')
    return
  }
  create(name, bundleConfig)
  inject(name, bundleConfig)
}

// eslint-disable-next-line
function toConfigList() {
  const clientBundleConfig = getConfig('bundles.client')
  const serverBundleConfig = getConfig('bundles.server')
  const clientBundle = initializeBundle('client', clientBundleConfig)
  const serverBundle = initializeBundle('server', serverBundleConfig)

  const clientConfig = clientBundle.toConfig()
  const serverConfig = serverBundle.toConfig()

  const configList = [clientConfig, serverConfig]

  return configList
}

function toCompiler(configList = Array) {
  if (process.env.IS_SEQUENTIAL_BUILD) {
    // @note turned this on to debug the build-loop...
    const shouldCompileServer =
      process.env.FORCE_BUILD_SERVER || exists(serverOutputPath === false)

    const clientCompiler = webpack(configList[0])
    const serverCompiler = shouldCompileServer ? webpack(configList[1]) : false
    return [clientCompiler, serverCompiler]
  } else {
    const multiCompiler = webpack(configList)
    const [clientCompiler, serverCompiler] = multiCompiler.compilers
    return multiCompiler
  }
}

const dynamic = {
  app: undefined,
  hotMiddleware: undefined,
  webpackServe: undefined,
}

function requireUncached(pathTo: string) {
  const absolutePath = require.resolve(pathTo)
  delete require.cache[absolutePath]
  try {
    const dep = require(absolutePath)
    return dep
  } catch (requireException) {
    return requireException
  }
}

/**
 * @tutorial https://derickbailey.com/2016/02/17/using-express-sub-apps-to-keep-your-code-clean/
 * @tutorial https://medium.com/@gattermeier/invalidate-node-js-require-cache-c2989af8f8b0
 *
 * @todo can also import gql here if we wanted...
 *
 * @todo when we use webpack hmr, file isn't written to disk...
 */
// eslint-disable-next-line
async function serve() {
  console.log('[devpoc] serving app server...')

  /**
   * @example typeof function() {} === 'function'
   * @description as long as we check typeof window === 'object'
   *              all IS_BROWSER still work
   *              it would also work with && typeof document
   *              this is a really bad way to patch `window` for webpack hmr...
   */
  // global.window = function window() {
  //   // server window
  // }

  const SERVER_OUTPUT_FOLDER = path.resolve(
    process.env.APP_ROOT_PATH,
    process.env.BUILD_CONFIG_SERVER_OUTPUT_PATH
  )
  const BUILD_CONFIG_SERVER_OUTPUT_ABSOLUTE = path.resolve(
    SERVER_OUTPUT_FOLDER,
    process.env.BUILD_CONFIG_SERVER_OUTPUT_FILENAME || 'index.js'
  )

  const server = requireUncached(BUILD_CONFIG_SERVER_OUTPUT_ABSOLUTE)

  if (process.env.LOG_BUNDLE_DIST || true) {
    console.log({ server })
  }

  // inject our dependencies
  // server.setConfig({
  //   // view
  //   App: client.AppLayoutView,
  //   // network/gql (apollo client)
  //   client: client.client,
  //   // data
  //   OmniStore: client.OmniStore,
  //   // for providing omnistore & contexts
  //   OmniContainer: client.OmniContainer,
  // })
  server.setupApp(devServer)

  // should not be needed?
  // const app = server.withKiller(devServer)

  // we want to do it before awaiting the other routes
  // if (dynamic.hotMiddleware) {
  //   // if (dynamic.app.use) {
  //   //   // this is async
  //   //   dynamic.app.use(dynamic.hotMiddleware)
  //   // } else
  //   if (app.use) {
  //     // this is the actual app without wrapper
  //     app.use(dynamic.hotMiddleware)
  //   } else {
  //     // maybe should throw
  //     console.warn('[devpoc] MISSING .USE IN APP')
  //   }
  // }

  // const appWithKiller = await server.setupApp()
  // dynamic.app = appWithKiller
  // dynamic.app = dynamic.app.listen(process.env.PORT || 3000)
  dynamic.app = devServer.listen(process.env.PORT || 3000)
}

/**
 * @description used to close the previous app so we can start the process again
 * previously, this was handled by killing the subprocess
 *
 * we could also do await isPortRunning(3000) => kill process on that port
 */
function closePreviousApp() {
  if (dynamic.app) {
    dynamic.app.close()
  }
  if (global.gqlApp) {
    global.gqlApp.close()
  }
}

/**
 * @param {null | Error} [bundleError=null]
 * @param {Object} info
 */
function onCompiled(bundleError, info) {
  console.log('compiled...')
  if (bundleError) {
    throw bundleError
  }
  if (info) {
    console.log(info.toString({ colors: true }))
  }
  console.timeEnd('bundle')

  // const [clientStats, serverStats] = info.stats
  // console.log(clientStats)
  // console.log(serverStats)

  closePreviousApp()
  serve()
}

/**
 * @see https://github.com/webpack/webpack/issues/6693
 */
function toWatchOptions() {
  const IS_HOT = process.env.HOT !== undefined
  const defaultIgnored = /node_modules|modules|scripts|config|docs/
  const hotIgnored = /node_modules/
  const ignored = IS_HOT ? hotIgnored : defaultIgnored

  const watchOptions = {
    aggregateTimeout: 3000,
    poll: 2000,
    ignored,
  }
  return watchOptions
}

/**
 * currently unused, trying webpack serve
 */
function startCompiling(configList) {
  const multiCompiler = toCompiler(configList)
  const [clientConfig, serverConfig] = configList
  const clientCompiler = multiCompiler.compilers
    ? multiCompiler.compilers[0]
    : multiCompiler[0]

  const hotMiddlewareConfig = {
    // publicPath: '/client',
    path: '/__webpack_hmr',
  }
  const devMiddlewareConfig = {
    // noInfo: true,
    // publicPath: clientConfig.output.publicPath,
    publicPath: '/',
  }

  /**
   * @todo webpack-dev-server uses memory-fs, should use the same to respond...
   * @see https://webpack.js.org/guides/development/#webpack-dev-middleware
   */
  // dynamic.devMiddleware = createWebpackMiddleware(clientCompiler, devMiddlewareConfig)
  dynamic.hotMiddleware = createWebpackHotMiddleware(
    clientCompiler,
    hotMiddlewareConfig
  )

  // use as middleware
  // before we tried to add to application
  // now we use application in dev...
  // devServer.use(dynamic.devMiddleware)
  devServer.use(dynamic.hotMiddleware)
  // devServer.use(hmrDecorator)

  return multiCompiler
}

/**
 * @todo webpack-dev-server alone instead... ?????
 *
 * 1 at a time
 */
function compileSequentially() {
  // could time this too
  const watchOptions = toWatchOptions()
  const configList = toConfigList()

  const multiCompiler = startCompiling(configList)
  const [clientCompiler, serverCompiler] = multiCompiler

  // const promises = []

  // @todo promisify
  const serverPromise = new Promise(resolve => {
    // @todo inject again, for now watching server -.-
    serverCompiler.watch(watchOptions, (clientError, serverStats) => {
      if (clientError) {
        console.log(clientError)
      }
      console.log(serverStats.toString({ colors: true }))
      resolve(serverStats)

      // could also check if server exists here to double run onCompiled
      // promises.push('server')
    })
  })

  // ?????? back to run...
  // serverCompiler.run((clientError, serverStats) => {
  //   if (clientError) {
  //     console.log(clientError)
  //   }
  //   console.log(serverStats.toString({ colors: true }))
  //   resolve(serverStats)
  // })
  const clientPromise = new Promise(resolve => {
    clientCompiler.watch(watchOptions, (serverError, clientStats) => {
      console.log(serverError)
      if (serverError) {
        console.log(serverError)
      }
      console.log(clientStats.toString({ colors: true }))
      resolve(clientStats)
      // promises.push('client')

      // this one is needed if we are doing multi and not injecting...
      // onCompiled()
    })
  })

  // @todo every x time ensure both are done
  // setInterval(() => {}, 10000)
  // setTimeout(() => {
  //   promises.pop()
  //   promises.pop()
  // })

  Promise.all([serverPromise, clientPromise]).then(done => {
    console.log('[devpoc] compiled both')
    onCompiled()
  })
}

/**
 * 2+ at a time
 */
function compileParallel() {
  const watchOptions = toWatchOptions()
  const configList = toConfigList()
  const multiCompiler = startCompiling(configList)
  multiCompiler.watch(watchOptions, onCompiled)
}

/**
 * @todo can also serve & then bundle?
 *
 * @api https://webpack.js.org/api/node/
 * @see https://github.com/webpack/webpack-dev-middleware/blob/master/lib/fs.js
 * @see https://github.com/gajus/write-file-webpack-plugin/blob/master/src/index.js
 * @see https://github.com/webpack/webpack-dev-server/blob/master/lib/Server.js
 * @see https://github.com/webpack-contrib/webpack-serve
 *
 * (for fake https)
 * @todo https://github.com/webpack/webpack-dev-server/blob/master/lib/Server.js#L386
 */
function startBundling() {
  console.log('[devpoc] starting bundle...')
  console.time('bundle')

  if (process.env.IS_SEQUENTIAL_BUILD) {
    compileSequentially()
  } else {
    compileParallel()
  }
}

/**
 * @see https://github.com/fluents/funwithflags
 */
function startUsingFlags() {
  // console.log('[devpoc] argv:')
  // console.log(argv)

  /**
   * @example --only-serve
   */
  if (argv.onlyServe) {
    console.log('[devpoc] only serve...')
    serve()
  } else {
    startBundling()
  }
}

export {
  decorateWithDll,
  initializeBundle,
  toConfigList,
  toCompiler,
  dynamic,
  serve,
  closePreviousApp,
  onCompiled,
  toWatchOptions,
  startCompiling,
  startBundling,
  startUsingFlags,
}