You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
7.6 KiB
305 lines
7.6 KiB
"use strict"; |
|
|
|
const { |
|
validate |
|
} = require("schema-utils"); |
|
|
|
const mime = require("mime-types"); |
|
|
|
const middleware = require("./middleware"); |
|
|
|
const getFilenameFromUrl = require("./utils/getFilenameFromUrl"); |
|
|
|
const setupHooks = require("./utils/setupHooks"); |
|
|
|
const setupWriteToDisk = require("./utils/setupWriteToDisk"); |
|
|
|
const setupOutputFileSystem = require("./utils/setupOutputFileSystem"); |
|
|
|
const ready = require("./utils/ready"); |
|
|
|
const schema = require("./options.json"); |
|
|
|
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 {Object} ExtendedServerResponse |
|
* @property {{ webpack?: { devMiddleware?: Context<IncomingMessage, ServerResponse> } }} [locals] |
|
*/ |
|
|
|
/** @typedef {import("http").IncomingMessage} IncomingMessage */ |
|
|
|
/** @typedef {import("http").ServerResponse & ExtendedServerResponse} ServerResponse */ |
|
|
|
/** |
|
* @callback NextFunction |
|
* @param {any} [err] |
|
* @return {void} |
|
*/ |
|
|
|
/** |
|
* @typedef {NonNullable<Configuration["watchOptions"]>} WatchOptions |
|
*/ |
|
|
|
/** |
|
* @typedef {Compiler["watching"]} Watching |
|
*/ |
|
|
|
/** |
|
* @typedef {ReturnType<Compiler["watch"]>} MultiWatching |
|
*/ |
|
|
|
/** |
|
* @typedef {Compiler["outputFileSystem"] & { createReadStream?: import("fs").createReadStream, statSync?: import("fs").statSync, lstat?: import("fs").lstat, readFileSync?: import("fs").readFileSync }} OutputFileSystem |
|
*/ |
|
|
|
/** @typedef {ReturnType<Compiler["getInfrastructureLogger"]>} Logger */ |
|
|
|
/** |
|
* @callback Callback |
|
* @param {Stats | MultiStats} [stats] |
|
*/ |
|
|
|
/** |
|
* @template {IncomingMessage} Request |
|
* @template {ServerResponse} Response |
|
* @typedef {Object} Context |
|
* @property {boolean} state |
|
* @property {Stats | MultiStats | undefined} stats |
|
* @property {Callback[]} callbacks |
|
* @property {Options<Request, Response>} options |
|
* @property {Compiler | MultiCompiler} compiler |
|
* @property {Watching | MultiWatching} watching |
|
* @property {Logger} logger |
|
* @property {OutputFileSystem} outputFileSystem |
|
*/ |
|
|
|
/** |
|
* @template {IncomingMessage} Request |
|
* @template {ServerResponse} Response |
|
* @typedef {Record<string, string | number> | Array<{ key: string, value: number | string }> | ((req: Request, res: Response, context: Context<Request, Response>) => void | undefined | Record<string, string | number>) | undefined} Headers |
|
*/ |
|
|
|
/** |
|
* @template {IncomingMessage} Request |
|
* @template {ServerResponse} Response |
|
* @typedef {Object} Options |
|
* @property {{[key: string]: string}} [mimeTypes] |
|
* @property {boolean | ((targetPath: string) => boolean)} [writeToDisk] |
|
* @property {string} [methods] |
|
* @property {Headers<Request, Response>} [headers] |
|
* @property {NonNullable<Configuration["output"]>["publicPath"]} [publicPath] |
|
* @property {Configuration["stats"]} [stats] |
|
* @property {boolean} [serverSideRender] |
|
* @property {OutputFileSystem} [outputFileSystem] |
|
* @property {boolean | string} [index] |
|
*/ |
|
|
|
/** |
|
* @template {IncomingMessage} Request |
|
* @template {ServerResponse} Response |
|
* @callback Middleware |
|
* @param {Request} req |
|
* @param {Response} res |
|
* @param {NextFunction} next |
|
* @return {Promise<void>} |
|
*/ |
|
|
|
/** |
|
* @callback GetFilenameFromUrl |
|
* @param {string} url |
|
* @returns {string | undefined} |
|
*/ |
|
|
|
/** |
|
* @callback WaitUntilValid |
|
* @param {Callback} callback |
|
*/ |
|
|
|
/** |
|
* @callback Invalidate |
|
* @param {Callback} callback |
|
*/ |
|
|
|
/** |
|
* @callback Close |
|
* @param {(err: Error | null | undefined) => void} callback |
|
*/ |
|
|
|
/** |
|
* @template {IncomingMessage} Request |
|
* @template {ServerResponse} Response |
|
* @typedef {Object} AdditionalMethods |
|
* @property {GetFilenameFromUrl} getFilenameFromUrl |
|
* @property {WaitUntilValid} waitUntilValid |
|
* @property {Invalidate} invalidate |
|
* @property {Close} close |
|
* @property {Context<Request, Response>} context |
|
*/ |
|
|
|
/** |
|
* @template {IncomingMessage} Request |
|
* @template {ServerResponse} Response |
|
* @typedef {Middleware<Request, Response> & AdditionalMethods<Request, Response>} API |
|
*/ |
|
|
|
/** |
|
* @template {IncomingMessage} Request |
|
* @template {ServerResponse} Response |
|
* @param {Compiler | MultiCompiler} compiler |
|
* @param {Options<Request, Response>} [options] |
|
* @returns {API<Request, Response>} |
|
*/ |
|
|
|
|
|
function wdm(compiler, options = {}) { |
|
validate( |
|
/** @type {Schema} */ |
|
schema, options, { |
|
name: "Dev Middleware", |
|
baseDataPath: "options" |
|
}); |
|
const { |
|
mimeTypes |
|
} = options; |
|
|
|
if (mimeTypes) { |
|
const { |
|
types |
|
} = mime; // mimeTypes from user provided options should take priority |
|
// over existing, known types |
|
// @ts-ignore |
|
|
|
mime.types = { ...types, |
|
...mimeTypes |
|
}; |
|
} |
|
/** |
|
* @type {Context<Request, Response>} |
|
*/ |
|
|
|
|
|
const context = { |
|
state: false, |
|
// eslint-disable-next-line no-undefined |
|
stats: undefined, |
|
callbacks: [], |
|
options, |
|
compiler, |
|
// @ts-ignore |
|
// eslint-disable-next-line no-undefined |
|
watching: undefined, |
|
logger: compiler.getInfrastructureLogger("webpack-dev-middleware"), |
|
// @ts-ignore |
|
// eslint-disable-next-line no-undefined |
|
outputFileSystem: undefined |
|
}; |
|
setupHooks(context); |
|
|
|
if (options.writeToDisk) { |
|
setupWriteToDisk(context); |
|
} |
|
|
|
setupOutputFileSystem(context); // Start watching |
|
|
|
if ( |
|
/** @type {Compiler} */ |
|
context.compiler.watching) { |
|
context.watching = |
|
/** @type {Compiler} */ |
|
context.compiler.watching; |
|
} else { |
|
/** |
|
* @type {WatchOptions | WatchOptions[]} |
|
*/ |
|
let watchOptions; |
|
/** |
|
* @param {Error | null | undefined} error |
|
*/ |
|
|
|
const errorHandler = error => { |
|
if (error) { |
|
// TODO: improve that in future |
|
// 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(error); |
|
} |
|
}; |
|
|
|
if (Array.isArray( |
|
/** @type {MultiCompiler} */ |
|
context.compiler.compilers)) { |
|
watchOptions = |
|
/** @type {MultiCompiler} */ |
|
context.compiler.compilers.map( |
|
/** |
|
* @param {Compiler} childCompiler |
|
* @returns {WatchOptions} |
|
*/ |
|
childCompiler => childCompiler.options.watchOptions || {}); |
|
context.watching = |
|
/** @type {MultiWatching} */ |
|
context.compiler.watch( |
|
/** @type {WatchOptions}} */ |
|
watchOptions, errorHandler); |
|
} else { |
|
watchOptions = |
|
/** @type {Compiler} */ |
|
context.compiler.options.watchOptions || {}; |
|
context.watching = |
|
/** @type {Watching} */ |
|
context.compiler.watch(watchOptions, errorHandler); |
|
} |
|
} |
|
|
|
const instance = |
|
/** @type {API<Request, Response>} */ |
|
middleware(context); // API |
|
|
|
/** @type {API<Request, Response>} */ |
|
|
|
instance.getFilenameFromUrl = |
|
/** |
|
* @param {string} url |
|
* @returns {string|undefined} |
|
*/ |
|
url => getFilenameFromUrl(context, url); |
|
/** @type {API<Request, Response>} */ |
|
|
|
|
|
instance.waitUntilValid = (callback = noop) => { |
|
ready(context, callback); |
|
}; |
|
/** @type {API<Request, Response>} */ |
|
|
|
|
|
instance.invalidate = (callback = noop) => { |
|
ready(context, callback); |
|
context.watching.invalidate(); |
|
}; |
|
/** @type {API<Request, Response>} */ |
|
|
|
|
|
instance.close = (callback = noop) => { |
|
context.watching.close(callback); |
|
}; |
|
/** @type {API<Request, Response>} */ |
|
|
|
|
|
instance.context = context; |
|
return instance; |
|
} |
|
|
|
module.exports = wdm; |