UNPKG

webpack-dev-middleware

Version:
943 lines (860 loc) 30.3 kB
"use strict"; const fs = require("node:fs"); const path = require("node:path"); const memfs = require("memfs"); const mime = require("mime-types"); const middleware = require("./middleware"); const noop = () => {}; /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ /** @typedef {import("webpack").Compiler} Compiler */ /** @typedef {import("webpack").MultiCompiler} MultiCompiler */ /** @typedef {import("webpack").Configuration} Configuration */ /** @typedef {import("webpack").Stats} Stats */ /** @typedef {import("webpack").MultiStats} MultiStats */ /** @typedef {import("fs").ReadStream} ReadStream */ /** @typedef {import("./middleware").FilenameWithExtra} FilenameWithExtra */ // eslint-disable-next-line jsdoc/reject-any-type /** @typedef {any} EXPECTED_ANY */ // eslint-disable-next-line jsdoc/reject-function-type /** @typedef {Function} EXPECTED_FUNCTION */ /** * @typedef {object} ExtendedServerResponse * @property {{ webpack?: { devMiddleware?: Context<IncomingMessage, ServerResponse> } }=} locals locals */ /** @typedef {import("http").IncomingMessage} IncomingMessage */ /** @typedef {import("http").ServerResponse & ExtendedServerResponse} ServerResponse */ /** * @callback NextFunction * @param {EXPECTED_ANY=} err error * @returns {void} */ /** @typedef {NonNullable<Configuration["watchOptions"]>} WatchOptions */ /** @typedef {Compiler["watching"]} Watching */ /** @typedef {ReturnType<MultiCompiler["watch"]>} MultiWatching */ /** @typedef {import("webpack").OutputFileSystem & { createReadStream?: import("fs").createReadStream, statSync: import("fs").statSync, readFileSync: import("fs").readFileSync }} OutputFileSystem */ /** @typedef {ReturnType<Compiler["getInfrastructureLogger"]>} Logger */ /** * @callback Callback * @param {(Stats | MultiStats)=} stats */ /** * @typedef {object} ResponseData * @property {Buffer | ReadStream} data data * @property {number} byteLength byte length */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @callback ModifyResponseData * @param {RequestInternal} req req * @param {ResponseInternal} res res * @param {Buffer | ReadStream} data data * @param {number} byteLength byte length * @returns {ResponseData} */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @typedef {object} Context * @property {boolean} state state * @property {Stats | MultiStats | undefined} stats stats * @property {Callback[]} callbacks callbacks * @property {Options<RequestInternal, ResponseInternal>} options options * @property {Compiler | MultiCompiler} compiler compiler * @property {Watching | MultiWatching} watching watching * @property {Logger} logger logger * @property {OutputFileSystem} outputFileSystem output file system */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @typedef {WithoutUndefined<Context<RequestInternal, ResponseInternal>, "watching">} FilledContext */ /** @typedef {Record<string, string | number> | { key: string, value: number | string }[]} NormalizedHeaders */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @typedef {NormalizedHeaders | ((req: RequestInternal, res: ResponseInternal, context: Context<RequestInternal, ResponseInternal>) => void | undefined | NormalizedHeaders) | undefined} Headers */ /** * @template {IncomingMessage} [RequestInternal = IncomingMessage] * @template {ServerResponse} [ResponseInternal = ServerResponse] * @typedef {object} Options * @property {{ [key: string]: string }=} mimeTypes mime types * @property {(string | undefined)=} mimeTypeDefault mime type default * @property {(boolean | ((targetPath: string) => boolean))=} writeToDisk write to disk * @property {string[]=} methods methods * @property {Headers<RequestInternal, ResponseInternal>=} headers headers * @property {NonNullable<Configuration["output"]>["publicPath"]=} publicPath public path * @property {Configuration["stats"]=} stats stats * @property {boolean=} serverSideRender is server side render * @property {OutputFileSystem=} outputFileSystem output file system * @property {(boolean | string)=} index index * @property {ModifyResponseData<RequestInternal, ResponseInternal>=} modifyResponseData modify response data * @property {"weak" | "strong"=} etag options to generate etag header * @property {boolean=} lastModified options to generate last modified header * @property {(boolean | number | string | { maxAge?: number, immutable?: boolean })=} cacheControl options to generate cache headers * @property {boolean=} cacheImmutable is cache immutable * @property {boolean=} forwardError forward error to next middleware */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @callback Middleware * @param {RequestInternal} req request * @param {ResponseInternal} res response * @param {NextFunction} next next function * @returns {Promise<void>} */ /** * @callback GetFilenameFromUrl * @param {string} url request URL * @returns {Promise<FilenameWithExtra | undefined>} a filename with additional information, or `undefined` if nothing is found */ /** * @callback WaitUntilValid * @param {Callback} callback */ /** * @callback Invalidate * @param {Callback} callback */ /** * @callback Close * @param {(err: Error | null | undefined) => void} callback */ /** * @template {IncomingMessage} RequestInternal * @template {ServerResponse} ResponseInternal * @typedef {object} AdditionalMethods * @property {GetFilenameFromUrl} getFilenameFromUrl get filename from url * @property {WaitUntilValid} waitUntilValid wait until valid * @property {Invalidate} invalidate invalidate * @property {Close} close close * @property {Context<RequestInternal, ResponseInternal>} context context */ /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @typedef {Middleware<RequestInternal, ResponseInternal> & AdditionalMethods<RequestInternal, ResponseInternal>} API */ /** * @template T * @template {keyof T} K * @typedef {Omit<T, K> & Partial<T>} WithOptional */ /** * @template T * @template {keyof T} K * @typedef {T & { [P in K]: NonNullable<T[P]> }} WithoutUndefined */ /** * @param {Compiler | MultiCompiler} compiler compiler * @returns {compiler is MultiCompiler} true when is multi compiler, otherwise false */ function isMultipleCompiler(compiler) { return typeof (/** @type {MultiCompiler} */compiler.compilers) !== "undefined"; } /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options<RequestInternal, ResponseInternal>} options options */ const internalValidate = (compiler, options) => { const schema = require("./options.json"); const firstCompiler = /** @type {Compiler & { validate: EXPECTED_ANY }} */ isMultipleCompiler(compiler) ? compiler.compilers[0] : compiler; if (typeof firstCompiler.validate === "function") { firstCompiler.validate(schema, options, { name: "Dev Middleware", baseDataPath: "options" }); return; } // TODO in the next major release bump minimum supported webpack version and remove it in favor of `compiler.validate` (above) const { validate } = require("schema-utils"); validate(/** @type {Schema} */schema, options, { name: "Dev Middleware", baseDataPath: "options" }); }; /** @typedef {Configuration["stats"]} StatsOptions */ /** @typedef {{ children: Configuration["stats"][] }} MultiStatsOptions */ /** @typedef {Exclude<Configuration["stats"], boolean | string | undefined>} StatsObjectOptions */ /** * @param {StatsOptions} statsOptions stats options * @returns {StatsObjectOptions} object stats options */ function normalizeStatsOptions(statsOptions) { if (typeof statsOptions === "undefined") { statsOptions = { preset: "normal" }; } else if (typeof statsOptions === "boolean") { statsOptions = statsOptions ? { preset: "normal" } : { preset: "none" }; } else if (typeof statsOptions === "string") { statsOptions = { preset: statsOptions }; } return statsOptions; } // Compatibility with rspack /** * @returns {boolean} true when color supported, otherwise false */ function isColorSupported() { const { env = {}, argv = [], platform = "" } = typeof process === "undefined" ? {} : process; const isDisabled = "NO_COLOR" in env || argv.includes("--no-color"); const isForced = "FORCE_COLOR" in env || argv.includes("--color"); const isWindows = platform === "win32"; const isDumbTerminal = env.TERM === "dumb"; const tty = require("node:tty"); const isCompatibleTerminal = tty && tty.isatty && tty.isatty(1) && env.TERM && !isDumbTerminal; const isCI = "CI" in env && ("GITHUB_ACTIONS" in env || "GITLAB_CI" in env || "CIRCLECI" in env); return !isDisabled && (isForced || isWindows && !isDumbTerminal || isCompatibleTerminal || isCI); } /** * @template {IncomingMessage} Request * @template {ServerResponse} Response * @param {Stats | MultiStats} stats stats * @param {WithOptional<Context<Request, Response>, "watching" | "outputFileSystem">} context context */ function printStats(stats, context) { const { compiler, logger, options } = context; logger.log("Compilation finished"); const isMultiCompilerMode = isMultipleCompiler(compiler); /** * @type {StatsOptions | MultiStatsOptions | undefined} */ let statsOptions; if (typeof options.stats !== "undefined") { statsOptions = isMultiCompilerMode ? { children: /** @type {MultiCompiler} */ compiler.compilers.map(() => options.stats) } : options.stats; } else { statsOptions = isMultiCompilerMode ? { children: /** @type {MultiCompiler} */ compiler.compilers.map(child => child.options.stats) } : /** @type {Compiler} */compiler.options.stats; } if (isMultiCompilerMode) { /** @type {MultiStatsOptions} */ statsOptions.children = /** @type {MultiStatsOptions} */ statsOptions.children.map( /** * @param {StatsOptions} childStatsOptions child stats options * @returns {StatsObjectOptions} object child stats options */ childStatsOptions => { childStatsOptions = normalizeStatsOptions(childStatsOptions); if (typeof childStatsOptions.colors === "undefined") { const [firstCompiler] = /** @type {MultiCompiler} */ compiler.compilers; childStatsOptions.colors = // rspack compatibility firstCompiler.webpack.cli && typeof firstCompiler.webpack.cli.isColorSupported === "function" ? firstCompiler.webpack.cli.isColorSupported() : isColorSupported(); } return childStatsOptions; }); } else { statsOptions = normalizeStatsOptions(/** @type {StatsOptions} */statsOptions); if (typeof statsOptions.colors === "undefined") { const { compiler } = /** @type {{ compiler: Compiler }} */context; statsOptions.colors = // rspack compatibility compiler.webpack.cli && typeof compiler.webpack.cli.isColorSupported === "function" ? compiler.webpack.cli.isColorSupported() : isColorSupported(); } } const printedStats = stats.toString(/** @type {StatsObjectOptions} */ statsOptions); // Avoid extra empty line when `stats: 'none'` if (printedStats) { // eslint-disable-next-line no-console console.log(printedStats); } } const PLUGIN_NAME = "DevMiddleware"; /** * @template {IncomingMessage} Request * @template {ServerResponse} Response * @param {Compiler} compiler compiler * @param {WithOptional<Context<Request, Response>, "watching" | "outputFileSystem">} context context */ function hookForWriteToDisk(compiler, context) { compiler.hooks.emit.tap(PLUGIN_NAME, () => { // @ts-expect-error if (compiler.hasWebpackDevMiddlewareAssetEmittedCallback) { return; } compiler.hooks.assetEmitted.tapAsync(PLUGIN_NAME, (file, info, callback) => { const { targetPath, content } = info; const { writeToDisk: filter } = context.options; const allowWrite = filter && typeof filter === "function" ? filter(targetPath) : true; if (!allowWrite) { return callback(); } const dir = path.dirname(targetPath); const name = compiler.options.name ? `Child "${compiler.options.name}": ` : ""; return fs.mkdir(dir, { recursive: true }, mkdirError => { if (mkdirError) { context.logger.error(`${name}Unable to write "${dir}" directory to disk:\n${mkdirError}`); return callback(mkdirError); } return fs.writeFile(targetPath, content, writeFileError => { if (writeFileError) { context.logger.error(`${name}Unable to write "${targetPath}" asset to disk:\n${writeFileError}`); return callback(writeFileError); } context.logger.log(`${name}Asset written to disk: "${targetPath}"`); return callback(); }); }); }); // @ts-expect-error compiler.hasWebpackDevMiddlewareAssetEmittedCallback = true; }); } /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options<RequestInternal, ResponseInternal>=} options options * @param {boolean} isPlugin true when will use as a plugin, otherwise false * @returns {API<RequestInternal, ResponseInternal>} webpack dev middleware */ function wdm(compiler, options = {}, isPlugin = false) { internalValidate(compiler, options); const { mimeTypes } = options; if (mimeTypes) { const { types } = mime; // mimeTypes from user provided options should take priority // over existing, known types // @ts-expect-error mime.types = { ...types, ...mimeTypes }; } /** * @type {WithOptional<Context<RequestInternal, ResponseInternal>, "watching" | "outputFileSystem">} */ const context = { state: false, stats: undefined, callbacks: [], options, compiler, logger: compiler.getInfrastructureLogger("webpack-dev-middleware") }; // Adding hooks /** * @returns {void} */ const invalid = () => { if (context.state) { context.logger.log("Compilation starting..."); } // We are now in invalid state context.state = false; context.stats = undefined; }; /** * @param {Stats | MultiStats} stats stats * @returns {void} */ const done = stats => { // We are now on valid state context.state = true; context.stats = stats; // Do the stuff in nextTick, because bundle may be invalidated if a change happened while compiling process.nextTick(() => { const { state, callbacks } = context; // Check if still in valid state if (!state) { return; } // For plugin support we should print nothing, because webpack/webpack-cli/webpack-dev-server will print them on using `stats.toString()` if (!isPlugin) { printStats(stats, context); } context.callbacks = []; // Execute callback that are delayed for (const callback of callbacks) { callback(stats); } }); }; compiler.hooks.watchRun.tap(PLUGIN_NAME, invalid); compiler.hooks.invalid.tap(PLUGIN_NAME, invalid); compiler.hooks.done.tap(PLUGIN_NAME, done); const compilersToModify = isMultipleCompiler(compiler) ? compiler.compilers.filter(item => item.options.devServer !== false) : [compiler]; if (typeof options.writeToDisk === "function") { for (const compiler of compilersToModify) { hookForWriteToDisk(compiler, context); } } // Modify output file system /** @type {OutputFileSystem} */ let outputFileSystem; if (context.options.outputFileSystem) { const { outputFileSystem: outputFileSystemFromOptions } = context.options; outputFileSystem = outputFileSystemFromOptions; } // Don't use `memfs` when developer wants to write everything to a disk, because it doesn't make sense. else if (context.options.writeToDisk === true) { // Prefer compiler with `devServer` option or fallback to the first one ({ outputFileSystem } = /** @type {Compiler & { outputFileSystem: OutputFileSystem }} */ isMultipleCompiler(compiler) ? compilersToModify[0] || compiler.compilers[0] : compiler); } else { outputFileSystem = /** @type {OutputFileSystem} */ /** @type {unknown} */memfs.createFsFromVolume(new memfs.Volume()); } context.outputFileSystem = outputFileSystem; for (const compiler of compilersToModify) { compiler.outputFileSystem = outputFileSystem; } // Start watching, but only for standalone usage, for plugin usage stats will be printed by external code, for example - webpack-cli if (!isPlugin) { /** * @param {Error | null} err err */ const errorHandler = err => { if (err) { // For example - `writeToDisk` can throw an error and right now it is ends watching. // We can improve that and keep watching active, but it is require API on webpack side. // Let's implement that in webpack@5 because it is rare case. context.logger.error(err); } }; if (isMultipleCompiler(compiler)) { // TODO improve on webpack side - add an option `watching(s)` for MultiCompiler context.watching = compiler.watch(compiler.compilers.map(compiler => compiler.options.watchOptions || {}), errorHandler); } else if (compiler.watching) { context.watching = compiler.watching; } else { context.watching = compiler.watch(compiler.options.watchOptions || {}, errorHandler); } } const filledContext = /** @type {FilledContext<RequestInternal, ResponseInternal>} */ context; const instance = /** @type {API<RequestInternal, ResponseInternal>} */ middleware(filledContext); // API instance.getFilenameFromUrl = url => middleware.getFilenameFromUrl(filledContext, url); instance.waitUntilValid = (callback = noop) => { middleware.ready(filledContext, callback); }; instance.invalidate = (callback = noop) => { middleware.ready(filledContext, callback); filledContext.watching.invalidate(); }; instance.close = (callback = noop) => { filledContext.watching.close(callback); }; instance.context = filledContext; return instance; } /** * @template S * @template O * @typedef {object} HapiPluginBase * @property {(server: S, options: O) => void | Promise<void>} register register */ /** * @template S * @template O * @typedef {HapiPluginBase<S, O> & { pkg: { name: string }, multiple: boolean }} HapiPlugin */ /** * @typedef {Options & { compiler: Compiler | MultiCompiler }} HapiOptions */ /** * @template HapiServer * @template {HapiOptions} HapiOptionsInternal * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false * @returns {HapiPlugin<HapiServer, HapiOptionsInternal>} hapi wrapper */ function hapiWrapper(usePlugin = false) { return { pkg: { name: "webpack-dev-middleware" }, // Allow to have multiple middleware multiple: true, register(server, options) { const { compiler, ...rest } = options; if (!compiler) { throw new Error("The compiler options is required."); } const devMiddleware = wdm(compiler, rest, usePlugin); // @ts-expect-error if (!server.decorations.server.includes("webpackDevMiddleware")) { // @ts-expect-error server.decorate("server", "webpackDevMiddleware", devMiddleware); } // @ts-expect-error // eslint-disable-next-line id-length server.ext("onRequest", (request, h) => new Promise((resolve, reject) => { let isFinished = false; /** * @param {(string | Buffer)=} data */ request.raw.res.send = data => { isFinished = true; request.raw.res.end(data); }; /** * @param {(string | Buffer)=} data */ request.raw.res.finish = data => { isFinished = true; request.raw.res.end(data); }; devMiddleware(request.raw.req, request.raw.res, error => { if (error) { reject(error); return; } if (!isFinished) { resolve(request); } }); }).then(() => h.continue).catch(error => { throw error; })); } }; } wdm.hapiWrapper = hapiWrapper; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options<RequestInternal, ResponseInternal>=} options options * @param {boolean=} usePlugin whether to use as webpack plugin * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} kow wrapper */ function koaWrapper(compiler, options = {}, usePlugin = false) { const devMiddleware = wdm(compiler, options, usePlugin); /** * @param {{ req: RequestInternal, res: ResponseInternal & import("./utils").ExpectedServerResponse, status: number, body: string | Buffer | import("fs").ReadStream | { message: string }, state: object }} ctx context * @param {EXPECTED_FUNCTION} next next * @returns {Promise<void>} */ async function webpackDevMiddleware(ctx, next) { const { req, res } = ctx; res.locals = ctx.state; let { status } = ctx; /** * @returns {number} code */ res.getStatusCode = () => status; /** * @param {number} statusCode status code */ res.setStatusCode = statusCode => { status = statusCode; ctx.status = statusCode; }; let isFinished = false; let needNext = false; try { await new Promise( /** * @param {(value: void) => void} resolve resolve * @param {(reason?: Error) => void} reject reject */ (resolve, reject) => { /** * @param {import("fs").ReadStream} stream readable stream */ res.stream = stream => { let resolved = false; /** * @param {Error=} err error */ const onEvent = err => { if (resolved) return; resolved = true; stream.removeListener("error", onEvent); stream.removeListener("readable", onEvent); if (err) { reject(err); return; } ctx.body = stream; isFinished = true; resolve(); }; stream.once("error", onEvent); stream.once("readable", onEvent); // Empty stream stream.once("end", onEvent); }; /** * @param {string | Buffer} data data */ res.send = data => { ctx.body = data; isFinished = true; resolve(); }; /** * @param {(string | Buffer)=} data data */ res.finish = data => { ctx.status = status; res.end(data); isFinished = true; resolve(); }; devMiddleware(req, res, err => { if (err) { reject(err); return; } needNext = true; if (!isFinished) { resolve(); } }); }); } catch (err) { if (options?.forwardError) { await next(); // need the return for prevent to execute the code below and override the status and body set by user in the next middleware return; } ctx.status = /** @type {Error & { statusCode: number }} */err.statusCode || /** @type {Error & { status: number }} */err.status || 500; ctx.body = { message: /** @type {Error} */err.message }; } if (needNext) { await next(); } } webpackDevMiddleware.devMiddleware = devMiddleware; return webpackDevMiddleware; } wdm.koaWrapper = koaWrapper; /** * @template {IncomingMessage} [RequestInternal=IncomingMessage] * @template {ServerResponse} [ResponseInternal=ServerResponse] * @param {Compiler | MultiCompiler} compiler compiler * @param {Options<RequestInternal, ResponseInternal>=} options options * @param {boolean=} usePlugin true when need to use as a plugin, otherwise false * @returns {(ctx: EXPECTED_ANY, next: EXPECTED_FUNCTION) => Promise<void> | void} hono wrapper */ function honoWrapper(compiler, options = {}, usePlugin = false) { const devMiddleware = wdm(compiler, options, usePlugin); /** * @param {{ env: EXPECTED_ANY, body: EXPECTED_ANY, json: EXPECTED_ANY, status: EXPECTED_ANY, set: EXPECTED_ANY, req: RequestInternal & import("./utils").ExpectedIncomingMessage & { header: (name: string) => string }, res: ResponseInternal & import("./utils").ExpectedServerResponse & { headers: EXPECTED_ANY, status: EXPECTED_ANY } }} context context * @param {EXPECTED_FUNCTION} next next function * @returns {Promise<void>} */ async function webpackDevMiddleware(context, next) { const { req, res } = context; context.set("webpack", { devMiddleware: devMiddleware.context }); /** * @returns {string | undefined} method */ req.getMethod = () => context.req.method; /** * @param {string} name name * @returns {string | string[] | undefined} header value */ req.getHeader = name => context.req.header(name); /** * @returns {string | undefined} URL */ req.getURL = () => context.req.url; let { status } = context.res; /** * @returns {number} code code */ res.getStatusCode = () => status; /** * @param {number} code code */ res.setStatusCode = code => { status = code; }; /** * @param {string} name header name * @returns {string | string[] | undefined} header */ res.getHeader = name => context.res.headers.get(name); /** * @param {string} name header name * @param {string | number | Readonly<string[]>} value value * @returns {ResponseInternal & import("./utils").ExpectedServerResponse & { headers: EXPECTED_ANY, status: EXPECTED_ANY }} response */ res.setHeader = (name, value) => { context.res.headers.append(name, value); return context.res; }; /** * @param {string} name header name */ res.removeHeader = name => { context.res.headers.delete(name); }; /** * @returns {string[]} response headers */ res.getResponseHeaders = () => [...context.res.headers.keys()]; /** * @returns {ServerResponse} server response */ res.getOutgoing = () => context.env.outgoing; res.setState = () => { // Do nothing, because we set it before }; res.getHeadersSent = () => context.env.outgoing.headersSent; let body; let isFinished = false; try { await new Promise( /** * @param {(value: void) => void} resolve resolve * @param {(reason?: Error) => void} reject reject */ (resolve, reject) => { /** * @param {import("fs").ReadStream} stream readable stream */ res.stream = stream => { let isResolved = false; /** * @param {Error=} err err */ const onEvent = err => { if (isResolved) return; isResolved = true; stream.removeListener("error", onEvent); stream.removeListener("readable", onEvent); stream.removeListener("end", onEvent); if (err) { stream.destroy(); reject(err); return; } body = stream; isFinished = true; resolve(); }; stream.once("error", onEvent); stream.once("readable", onEvent); // Empty stream stream.once("end", onEvent); if (stream.pending === false) { onEvent(); } }; /** * @param {string | Buffer} data data */ res.send = data => { // Hono sets `Content-Length` by default context.res.headers.delete("Content-Length"); body = data; isFinished = true; resolve(); }; /** * @param {(string | Buffer)=} data data */ res.finish = data => { const isDataExist = typeof data !== "undefined"; // Hono sets `Content-Length` by default if (isDataExist) { context.res.headers.delete("Content-Length"); } body = isDataExist ? data : null; isFinished = true; resolve(); }; devMiddleware(req, res, err => { if (err) { reject(err); return; } if (!isFinished) { resolve(); } }); }); } catch (err) { if (options?.forwardError) { await next(); // need the return for prevent to execute the code below and override the status and body set by user in the next middleware return; } context.status(500); return context.json({ message: /** @type {Error} */err.message }); } if (typeof body !== "undefined") { return context.body(body, status); } await next(); } webpackDevMiddleware.devMiddleware = devMiddleware; return webpackDevMiddleware; } wdm.honoWrapper = honoWrapper; module.exports = wdm;