Skip to content

Commit

Permalink
fix(types): make types better (#1786)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait authored Mar 21, 2024
1 parent f23ed7c commit e4d183e
Show file tree
Hide file tree
Showing 19 changed files with 420 additions and 316 deletions.
462 changes: 264 additions & 198 deletions package-lock.json

Large diffs are not rendered by default.

112 changes: 50 additions & 62 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ const noop = () => {};
*/

/**
* @typedef {ReturnType<Compiler["watch"]>} MultiWatching
* @typedef {ReturnType<MultiCompiler["watch"]>} MultiWatching
*/

// TODO fix me after the next webpack release
/**
* @typedef {Compiler["outputFileSystem"] & { createReadStream?: import("fs").createReadStream, statSync?: import("fs").statSync, lstat?: import("fs").lstat, readFileSync?: import("fs").readFileSync }} OutputFileSystem
* @typedef {Object & { createReadStream?: import("fs").createReadStream, statSync?: import("fs").statSync, lstat?: import("fs").lstat, readFileSync?: import("fs").readFileSync }} OutputFileSystem
*/

/** @typedef {ReturnType<Compiler["getInfrastructureLogger"]>} Logger */
Expand Down Expand Up @@ -82,15 +83,23 @@ const noop = () => {};
* @property {Callback[]} callbacks
* @property {Options<RequestInternal, ResponseInternal>} options
* @property {Compiler | MultiCompiler} compiler
* @property {Watching | MultiWatching} watching
* @property {Watching | MultiWatching | undefined} watching
* @property {Logger} logger
* @property {OutputFileSystem} outputFileSystem
*/

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @typedef {Record<string, string | number> | Array<{ key: string, value: number | string }> | ((req: RequestInternal, res: ResponseInternal, context: Context<RequestInternal, ResponseInternal>) => void | undefined | Record<string, string | number>) | undefined} Headers
* @typedef {WithoutUndefined<Context<RequestInternal, ResponseInternal>, "watching">} FilledContext
*/

/** @typedef {Record<string, string | number> | Array<{ key: string, value: number | string }>} NormalizedHeaders */

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
* @typedef {NormalizedHeaders | ((req: RequestInternal, res: ResponseInternal, context: Context<RequestInternal, ResponseInternal>) => void | undefined | NormalizedHeaders) | undefined} Headers
*/

/**
Expand Down Expand Up @@ -161,6 +170,18 @@ const noop = () => {};
* @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
*/

/**
* @template {IncomingMessage} RequestInternal
* @template {ServerResponse} ResponseInternal
Expand All @@ -186,7 +207,7 @@ function wdm(compiler, options = {}) {
}

/**
* @type {Context<RequestInternal, ResponseInternal>}
* @type {WithOptional<Context<RequestInternal, ResponseInternal>, "watching" | "outputFileSystem">}
*/
const context = {
state: false,
Expand All @@ -195,13 +216,7 @@ function wdm(compiler, options = {}) {
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);
Expand All @@ -216,11 +231,6 @@ function wdm(compiler, options = {}) {
if (/** @type {Compiler} */ (context.compiler).watching) {
context.watching = /** @type {Compiler} */ (context.compiler).watching;
} else {
/**
* @type {WatchOptions | WatchOptions[]}
*/
let watchOptions;

/**
* @param {Error | null | undefined} error
*/
Expand All @@ -237,69 +247,47 @@ function wdm(compiler, options = {}) {
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,
)
);
const compiler = /** @type {MultiCompiler} */ (context.compiler);
const watchOptions = compiler.compilers.map(
(childCompiler) => childCompiler.options.watchOptions || {},
);

context.watching = compiler.watch(watchOptions, errorHandler);
} else {
watchOptions =
/** @type {Compiler} */ (context.compiler).options.watchOptions || {};
const compiler = /** @type {Compiler} */ (context.compiler);
const watchOptions = compiler.options.watchOptions || {};

context.watching = /** @type {Watching} */ (
context.compiler.watch(watchOptions, errorHandler)
);
context.watching = compiler.watch(watchOptions, errorHandler);
}
}

const filledContext =
/** @type {FilledContext<RequestInternal, ResponseInternal>} */
(context);

const instance =
/** @type {API<RequestInternal, ResponseInternal>} */
(middleware(context));
(middleware(filledContext));

// API
/** @type {API<RequestInternal, ResponseInternal>} */
(instance).getFilenameFromUrl = (url, extra) =>
getFilenameFromUrl(context, url, extra);
instance.getFilenameFromUrl = (url, extra) =>
getFilenameFromUrl(filledContext, url, extra);

/** @type {API<RequestInternal, ResponseInternal>} */
(instance).waitUntilValid = (callback = noop) => {
ready(context, callback);
instance.waitUntilValid = (callback = noop) => {
ready(filledContext, callback);
};

/** @type {API<RequestInternal, ResponseInternal>} */
(instance).invalidate = (callback = noop) => {
ready(context, callback);
instance.invalidate = (callback = noop) => {
ready(filledContext, callback);

/**
* @type {NonNullable<Context<RequestInternal, ResponseInternal>["watching"]>}
*/
(context.watching).invalidate();
filledContext.watching.invalidate();
};

/** @type {API<RequestInternal, ResponseInternal>} */
(instance).close = (callback = noop) => {
/**
* @type {NonNullable<Context<RequestInternal, ResponseInternal>["watching"]>}
*/
(context.watching).close(callback);
instance.close = (callback = noop) => {
filledContext.watching.close(callback);
};

/** @type {API<RequestInternal, ResponseInternal>} */
(instance).context = context;
instance.context = filledContext;

return instance;
}
Expand Down
22 changes: 9 additions & 13 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ready = require("./utils/ready");
/** @typedef {import("./index.js").NextFunction} NextFunction */
/** @typedef {import("./index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("./index.js").ServerResponse} ServerResponse */
/** @typedef {import("./index.js").NormalizedHeaders} NormalizedHeaders */

/**
* @param {string} type
Expand All @@ -32,7 +33,7 @@ const BYTES_RANGE_REGEXP = /^ *bytes/i;
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("./index.js").Context<Request, Response>} context
* @param {import("./index.js").FilledContext<Request, Response>} context
* @return {import("./index.js").Middleware<Request, Response>}
*/
function wrapper(context) {
Expand Down Expand Up @@ -101,8 +102,7 @@ function wrapper(context) {
let { headers } = context.options;

if (typeof headers === "function") {
// @ts-ignore
headers = headers(req, res, context);
headers = /** @type {NormalizedHeaders} */ (headers(req, res, context));
}

/**
Expand All @@ -114,21 +114,15 @@ function wrapper(context) {
if (!Array.isArray(headers)) {
// eslint-disable-next-line guard-for-in
for (const name in headers) {
// @ts-ignore
allHeaders.push({ key: name, value: headers[name] });
}

headers = allHeaders;
}

headers.forEach(
/**
* @param {{key: string, value: any}} header
*/
(header) => {
setHeaderForResponse(res, header.key, header.value);
},
);
headers.forEach((header) => {
setHeaderForResponse(res, header.key, header.value);
});
}

if (!getHeaderFromResponse(res, "Content-Type")) {
Expand All @@ -152,7 +146,9 @@ function wrapper(context) {
setHeaderForResponse(res, "Accept-Ranges", "bytes");
}

const rangeHeader = getHeaderFromRequest(req, "range");
const rangeHeader =
/** @type {string} */
(getHeaderFromRequest(req, "range"));

let len = /** @type {import("fs").Stats} */ (extra.stats).size;
let offset = 0;
Expand Down
5 changes: 3 additions & 2 deletions src/utils/compatibleAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const escapeHtml = require("./escapeHtml");

/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/** @typedef {import("fs").ReadStream} ReadStream */

/**
* @typedef {Object} ExpectedRequest
Expand Down Expand Up @@ -37,7 +38,7 @@ function getHeaderNames(res) {
* @template {IncomingMessage} Request
* @param {Request} req
* @param {string} name
* @returns {string | undefined}
* @returns {string | string[] | undefined}
*/
function getHeaderFromRequest(req, name) {
// Express API
Expand All @@ -48,7 +49,6 @@ function getHeaderFromRequest(req, name) {
}

// Node.js API
// @ts-ignore
return req.headers[name];
}

Expand Down Expand Up @@ -256,6 +256,7 @@ async function send(req, res, filename, start, end, goNext, options) {
const isFsSupportsStream =
typeof options.outputFileSystem.createReadStream === "function";

/** @type {string | Buffer | ReadStream} */
let bufferOrStream;
let byteLength;

Expand Down
3 changes: 1 addition & 2 deletions src/utils/getFilenameFromUrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const cacheStore = new WeakMap();
* @param {(value: T) => T} callback
* @returns {any}
*/
// @ts-ignore
const mem = (fn, { cache = new Map() } = {}, callback) => {
/**
* @param {any} arguments_
Expand Down Expand Up @@ -79,7 +78,7 @@ function decode(input) {
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").Context<Request, Response>} context
* @param {import("../index.js").FilledContext<Request, Response>} context
* @param {string} url
* @param {Extra=} extra
* @returns {string | undefined}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/getPaths.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").Context<Request, Response>} context
* @param {import("../index.js").FilledContext<Request, Response>} context
*/
function getPaths(context) {
const { stats, options } = context;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/ready.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").Context<Request, Response>} context
* @param {import("../index.js").FilledContext<Request, Response>} context
* @param {(...args: any[]) => any} callback
* @param {Request} [req]
* @returns {void}
Expand Down
15 changes: 10 additions & 5 deletions src/utils/setupHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
/** @typedef {import("webpack").MultiCompiler} MultiCompiler */
/** @typedef {import("webpack").Stats} Stats */
/** @typedef {import("webpack").MultiStats} MultiStats */

/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */

/** @typedef {Configuration["stats"]} StatsOptions */
/** @typedef {{ children: Configuration["stats"][] }} MultiStatsOptions */
/** @typedef {Exclude<Configuration["stats"], boolean | string | undefined>} StatsObjectOptions */

/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").Context<Request, Response>} context
* @param {import("../index.js").WithOptional<import("../index.js").Context<Request, Response>, "watching" | "outputFileSystem">} context
*/
function setupHooks(context) {
function invalid() {
Expand Down Expand Up @@ -155,9 +155,14 @@ function setupHooks(context) {
});
}

context.compiler.hooks.watchRun.tap("webpack-dev-middleware", invalid);
context.compiler.hooks.invalid.tap("webpack-dev-middleware", invalid);
context.compiler.hooks.done.tap("webpack-dev-middleware", done);
// eslint-disable-next-line prefer-destructuring
const compiler =
/** @type {import("../index.js").Context<Request, Response>} */
(context).compiler;

compiler.hooks.watchRun.tap("webpack-dev-middleware", invalid);
compiler.hooks.invalid.tap("webpack-dev-middleware", invalid);
compiler.hooks.done.tap("webpack-dev-middleware", done);
}

module.exports = setupHooks;
3 changes: 2 additions & 1 deletion src/utils/setupOutputFileSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const memfs = require("memfs");
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").Context<Request, Response>} context
* @param {import("../index.js").WithOptional<import("../index.js").Context<Request, Response>, "watching" | "outputFileSystem">} context
*/
function setupOutputFileSystem(context) {
let outputFileSystem;
Expand Down Expand Up @@ -48,6 +48,7 @@ function setupOutputFileSystem(context) {
(context.compiler).compilers || [context.compiler];

for (const compiler of compilers) {
// @ts-ignore
compiler.outputFileSystem = outputFileSystem;
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/setupWriteToDisk.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const path = require("path");
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").Context<Request, Response>} context
* @param {import("../index.js").WithOptional<import("../index.js").Context<Request, Response>, "watching" | "outputFileSystem">} context
*/
function setupWriteToDisk(context) {
/**
Expand Down
Loading

0 comments on commit e4d183e

Please sign in to comment.