UNPKG

@roots/bud-server

Version:

Development server for @roots/bud

135 lines (107 loc) 3.42 kB
/* eslint-disable no-console */ import type {Bud} from '@roots/bud-framework' import type { MultiCompiler, StatsCompilation, StatsModule, } from '@roots/bud-framework/config' import type {MiddlewareFactory} from '@roots/bud-server/middleware' import type {Payload} from '@roots/bud-server/middleware/hot' import type {RequestHandler} from '@roots/bud-support/express' import type {Handler} from 'express-serve-static-core' import {HotEventStream} from '@roots/bud-server/middleware/hot' import loggerInstance from '@roots/bud-support/logger' const middlewarePath = `/bud/hot` let latestStats: null | StatsCompilation = null let closed = false let logger: typeof loggerInstance export const factory: MiddlewareFactory = (app: Bud) => { if (!app.compiler) return logger = loggerInstance.scope(app.label, `hmr`) as typeof logger return makeHandler(app.compiler.instance) } export const makeHandler = (compiler: MultiCompiler): Handler => { const stream = new HotEventStream() const onInvalid = () => { if (closed) return stream.publish({action: `building`}) } const onDone = (stats: StatsCompilation) => { if (closed) return latestStats = stats publish(`built`, latestStats, stream) } compiler.hooks.invalid.tap(`bud-hot-middleware`, onInvalid) compiler.hooks.done.tap(`bud-hot-middleware`, onDone) const middleware: RequestHandler = function (req, res, next) { if (closed) return next() if (!req.url.endsWith(middlewarePath)) return next() stream.handle(req, res) if (latestStats) { publish(`sync`, latestStats, stream) } } // @ts-ignore middleware.publish = function (payload) { if (closed) return stream.publish(payload) } // @ts-ignore middleware.close = function () { if (closed) return closed = true stream.close() // @ts-ignore https://github.com/webpack/tapable/issues/32#issuecomment-350644466 stream = null } return middleware } export const publish = ( action: Payload['action'], statsCompilation: StatsCompilation, stream: HotEventStream, ) => { const compilations = collectCompilations( statsCompilation.toJson({ all: false, assets: true, errorDetails: false, errors: true, hash: true, timings: true, warnings: true, }), ) compilations.forEach((stats: StatsCompilation) => { const name: string = stats.name ?? statsCompilation.name ?? `unnamed` const modules = collectModules(stats.modules) logger.log(`built`, name, `(${stats.hash})`, `in`, `${stats.time}ms`) stream.publish({ action, errors: stats.errors ?? [], hash: stats.hash, modules, name, time: stats.time, warnings: stats.warnings ?? [], }) }) } export const collectModules = (modules?: Array<StatsModule>) => { if (!modules) return {} return modules?.reduce((modules, module) => { if (!module.id || !module.name) return modules return {...modules, [module.id]: module.name} }, {}) } export const collectCompilations = ( stats: StatsCompilation, ): Array<StatsCompilation> => { let collection: Array<StatsCompilation> = [] // Stats has modules, single bundle if (stats.modules) collection.push(stats) // Stats has children, multiple bundles if (stats.children?.length) collection.push(...stats.children) // Not sure, assume single return collection }