Repository URL to install this package:
|
Version:
1.1.10 ▾
|
/* 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,
}