Initial commit: notification-elements-demo app

Interactive Angular 19 demo for @sda/notification-elements-ui with
6 sections: Bell & Feed, Notification Center, Inbox, Comments &
Threads, Mention Input, and Full-Featured layout. Includes mock
data, dark mode toggle, and real-time event log.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Giuliano Silvestro
2026-02-13 21:49:19 +10:00
commit 5d0c9ec7eb
36473 changed files with 3778146 additions and 0 deletions

603
node_modules/webpack-dev-middleware/dist/index.js generated vendored Normal file
View File

@@ -0,0 +1,603 @@
"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 {import("fs").ReadStream} ReadStream */
/**
* @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<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
* @property {number} byteLength
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback ModifyResponseData
* @param {RequestInternal} req
* @param {ResponseInternal} res
* @param {Buffer | ReadStream} data
* @param {number} byteLength
* @return {ResponseData}
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {Object} Context
* @property {boolean} state
* @property {Stats | MultiStats | undefined} stats
* @property {Callback[]} callbacks
* @property {Options<RequestInternal, ResponseInternal>} options
* @property {Compiler | MultiCompiler} compiler
* @property {Watching | MultiWatching | undefined} watching
* @property {Logger} logger
* @property {OutputFileSystem} outputFileSystem
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @typedef {WithoutUndefined<Context<RequestInternal, ResponseInternal>, "watching">} FilledContext
*/
/** @typedef {Record<string, string | number> | Array<{ 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]
* @property {string | undefined} [mimeTypeDefault]
* @property {boolean | ((targetPath: string) => boolean)} [writeToDisk]
* @property {string[]} [methods]
* @property {Headers<RequestInternal, ResponseInternal>} [headers]
* @property {NonNullable<Configuration["output"]>["publicPath"]} [publicPath]
* @property {Configuration["stats"]} [stats]
* @property {boolean} [serverSideRender]
* @property {OutputFileSystem} [outputFileSystem]
* @property {boolean | string} [index]
* @property {ModifyResponseData<RequestInternal, ResponseInternal>} [modifyResponseData]
* @property {"weak" | "strong"} [etag]
* @property {boolean} [lastModified]
* @property {boolean | number | string | { maxAge?: number, immutable?: boolean }} [cacheControl]
* @property {boolean} [cacheImmutable]
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback Middleware
* @param {RequestInternal} req
* @param {ResponseInternal} res
* @param {NextFunction} next
* @return {Promise<void>}
*/
/** @typedef {import("./utils/getFilenameFromUrl").Extra} Extra */
/**
* @callback GetFilenameFromUrl
* @param {string} url
* @param {Extra=} extra
* @returns {string | undefined}
*/
/**
* @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
* @property {WaitUntilValid} waitUntilValid
* @property {Invalidate} invalidate
* @property {Close} close
* @property {Context<RequestInternal, ResponseInternal>} 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
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler
* @param {Options<RequestInternal, ResponseInternal>} [options]
* @returns {API<RequestInternal, ResponseInternal>}
*/
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 {WithOptional<Context<RequestInternal, ResponseInternal>, "watching" | "outputFileSystem">}
*/
const context = {
state: false,
// eslint-disable-next-line no-undefined
stats: undefined,
callbacks: [],
options,
compiler,
logger: compiler.getInfrastructureLogger("webpack-dev-middleware")
};
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 {
/**
* @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)) {
const c = /** @type {MultiCompiler} */context.compiler;
const watchOptions = c.compilers.map(childCompiler => childCompiler.options.watchOptions || {});
context.watching = compiler.watch(watchOptions, errorHandler);
} else {
const c = /** @type {Compiler} */context.compiler;
const watchOptions = c.options.watchOptions || {};
context.watching = compiler.watch(watchOptions, errorHandler);
}
}
const filledContext = /** @type {FilledContext<RequestInternal, ResponseInternal>} */
context;
const instance = /** @type {API<RequestInternal, ResponseInternal>} */
middleware(filledContext);
// API
instance.getFilenameFromUrl = (url, extra) => getFilenameFromUrl(filledContext, url, extra);
instance.waitUntilValid = (callback = noop) => {
ready(filledContext, callback);
};
instance.invalidate = (callback = noop) => {
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
*/
/**
* @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
* @returns {HapiPlugin<HapiServer, HapiOptionsInternal>}
*/
function hapiWrapper() {
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);
// @ts-ignore
if (!server.decorations.server.includes("webpackDevMiddleware")) {
// @ts-ignore
server.decorate("server", "webpackDevMiddleware", devMiddleware);
}
// @ts-ignore
server.ext("onRequest", (request, h) => new Promise((resolve, reject) => {
let isFinished = false;
/**
* @param {string | Buffer} [data]
*/
// eslint-disable-next-line no-param-reassign
request.raw.res.send = data => {
isFinished = true;
request.raw.res.end(data);
};
/**
* @param {string | Buffer} [data]
*/
// eslint-disable-next-line no-param-reassign
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
* @param {Options<RequestInternal, ResponseInternal>} [options]
* @returns {(ctx: any, next: Function) => Promise<void> | void}
*/
function koaWrapper(compiler, options) {
const devMiddleware = wdm(compiler, options);
/**
* @param {{ req: RequestInternal, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse, status: number, body: string | Buffer | import("fs").ReadStream | { message: string }, state: Object }} ctx
* @param {Function} next
* @returns {Promise<void>}
*/
const wrapper = 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;
// eslint-disable-next-line no-param-reassign
ctx.status = statusCode;
};
res.getReadyReadableStreamState = () => "open";
try {
await new Promise(
/**
* @param {(value: void) => void} resolve
* @param {(reason?: any) => void} reject
*/
(resolve, reject) => {
/**
* @param {import("fs").ReadStream} stream readable stream
*/
res.stream = stream => {
// eslint-disable-next-line no-param-reassign
ctx.body = stream;
};
/**
* @param {string | Buffer} data data
*/
res.send = data => {
// eslint-disable-next-line no-param-reassign
ctx.body = data;
};
/**
* @param {string | Buffer} [data] data
*/
res.finish = data => {
// eslint-disable-next-line no-param-reassign
ctx.status = status;
res.end(data);
};
devMiddleware(req, res, err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
} catch (err) {
// eslint-disable-next-line no-param-reassign
ctx.status = /** @type {Error & { statusCode: number }} */err.statusCode || /** @type {Error & { status: number }} */err.status || 500;
// eslint-disable-next-line no-param-reassign
ctx.body = {
message: /** @type {Error} */err.message
};
}
await next();
};
wrapper.devMiddleware = devMiddleware;
return wrapper;
}
wdm.koaWrapper = koaWrapper;
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler
* @param {Options<RequestInternal, ResponseInternal>} [options]
* @returns {(ctx: any, next: Function) => Promise<void> | void}
*/
function honoWrapper(compiler, options) {
const devMiddleware = wdm(compiler, options);
/**
* @param {{ env: any, body: any, json: any, status: any, set:any, req: RequestInternal & import("./utils/compatibleAPI").ExpectedIncomingMessage & { header: (name: string) => string }, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: any, status: any } }} c
* @param {Function} next
* @returns {Promise<void>}
*/
// eslint-disable-next-line consistent-return
const wrapper = async function webpackDevMiddleware(c, next) {
const {
req,
res
} = c;
c.set("webpack", {
devMiddleware: devMiddleware.context
});
/**
* @returns {string | undefined}
*/
req.getMethod = () => c.req.method;
/**
* @param {string} name
* @returns {string | string[] | undefined}
*/
req.getHeader = name => c.req.header(name);
/**
* @returns {string | undefined}
*/
req.getURL = () => c.req.url;
let {
status
} = c.res;
/**
* @returns {number} code
*/
res.getStatusCode = () => status;
/**
* @param {number} code
*/
res.setStatusCode = code => {
status = code;
};
/**
* @param {string} name header name
*/
res.getHeader = name => c.res.headers.get(name);
/**
* @param {string} name
* @param {string | number | Readonly<string[]>} value
*/
res.setHeader = (name, value) => {
c.res.headers.append(name, value);
return c.res;
};
/**
* @param {string} name
*/
res.removeHeader = name => {
c.res.headers.delete(name);
};
/**
* @returns {string[]}
*/
res.getResponseHeaders = () => Array.from(c.res.headers.keys());
/**
* @returns {ServerResponse}
*/
res.getOutgoing = () => c.env.outgoing;
res.setState = () => {
// Do nothing, because we set it before
};
res.getReadyReadableStreamState = () => "readable";
res.getHeadersSent = () => c.env.outgoing.headersSent;
let body;
try {
await new Promise(
/**
* @param {(value: void) => void} resolve
* @param {(reason?: any) => void} reject
*/
(resolve, reject) => {
/**
* @param {import("fs").ReadStream} stream readable stream
*/
res.stream = stream => {
body = stream;
// responseHandler(stream);
};
/**
* @param {string | Buffer} data data
*/
res.send = data => {
body = data;
};
/**
* @param {string | Buffer} [data] data
*/
res.finish = data => {
body = typeof data !== "undefined" ? data : null;
};
devMiddleware(req, res, err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
} catch (err) {
c.status(500);
return c.json({
message: /** @type {Error} */err.message
});
}
if (typeof body !== "undefined") {
return c.body(body, status);
}
await next();
};
wrapper.devMiddleware = devMiddleware;
return wrapper;
}
wdm.honoWrapper = honoWrapper;
module.exports = wdm;

677
node_modules/webpack-dev-middleware/dist/middleware.js generated vendored Normal file
View File

@@ -0,0 +1,677 @@
"use strict";
const path = require("path");
const mime = require("mime-types");
const onFinishedStream = require("on-finished");
const getFilenameFromUrl = require("./utils/getFilenameFromUrl");
const {
setStatusCode,
getStatusCode,
getRequestHeader,
getRequestMethod,
getRequestURL,
getResponseHeader,
setResponseHeader,
removeResponseHeader,
getResponseHeaders,
getHeadersSent,
send,
finish,
pipe,
createReadStreamOrReadFileSync,
getOutgoing,
initState,
setState,
getReadyReadableStreamState
} = require("./utils/compatibleAPI");
const ready = require("./utils/ready");
const parseTokenList = require("./utils/parseTokenList");
const memorize = require("./utils/memorize");
/** @typedef {import("./index.js").NextFunction} NextFunction */
/** @typedef {import("./index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("./index.js").ServerResponse} ServerResponse */
/** @typedef {import("./index.js").NormalizedHeaders} NormalizedHeaders */
/** @typedef {import("fs").ReadStream} ReadStream */
const BYTES_RANGE_REGEXP = /^ *bytes/i;
/**
* @param {string} type
* @param {number} size
* @param {import("range-parser").Range} [range]
* @returns {string}
*/
function getValueContentRangeHeader(type, size, range) {
return `${type} ${range ? `${range.start}-${range.end}` : "*"}/${size}`;
}
/**
* Parse an HTTP Date into a number.
*
* @param {string} date
* @returns {number}
*/
function parseHttpDate(date) {
const timestamp = date && Date.parse(date);
// istanbul ignore next: guard against date.js Date.parse patching
return typeof timestamp === "number" ? timestamp : NaN;
}
const CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/;
/**
* @param {import("fs").ReadStream} stream stream
* @param {boolean} suppress do need suppress?
* @returns {void}
*/
function destroyStream(stream, suppress) {
if (typeof stream.destroy === "function") {
stream.destroy();
}
if (typeof stream.close === "function") {
// Node.js core bug workaround
stream.on("open",
/**
* @this {import("fs").ReadStream}
*/
function onOpenClose() {
// @ts-ignore
if (typeof this.fd === "number") {
// actually close down the fd
this.close();
}
});
}
if (typeof stream.addListener === "function" && suppress) {
stream.removeAllListeners("error");
stream.addListener("error", () => {});
}
}
/** @type {Record<number, string>} */
const statuses = {
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
416: "Range Not Satisfiable",
500: "Internal Server Error"
};
const parseRangeHeaders = memorize(
/**
* @param {string} value
* @returns {import("range-parser").Result | import("range-parser").Ranges}
*/
value => {
const [len, rangeHeader] = value.split("|");
// eslint-disable-next-line global-require
return require("range-parser")(Number(len), rangeHeader, {
combine: true
});
});
const MAX_MAX_AGE = 31536000000;
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @typedef {Object} SendErrorOptions send error options
* @property {Record<string, number | string | string[] | undefined>=} headers headers
* @property {import("./index").ModifyResponseData<Request, Response>=} modifyResponseData modify response data callback
*/
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("./index.js").FilledContext<Request, Response>} context
* @return {import("./index.js").Middleware<Request, Response>}
*/
function wrapper(context) {
return async function middleware(req, res, next) {
const acceptedMethods = context.options.methods || ["GET", "HEAD"];
initState(res);
async function goNext() {
if (!context.options.serverSideRender) {
return next();
}
return new Promise(resolve => {
ready(context, () => {
setState(res, "webpack", {
devMiddleware: context
});
resolve(next());
}, req);
});
}
const method = getRequestMethod(req);
if (method && !acceptedMethods.includes(method)) {
await goNext();
return;
}
/**
* @param {number} status status
* @param {Partial<SendErrorOptions<Request, Response>>=} options options
* @returns {void}
*/
function sendError(status, options) {
// eslint-disable-next-line global-require
const escapeHtml = require("./utils/escapeHtml");
const content = statuses[status] || String(status);
let document = Buffer.from(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>${escapeHtml(content)}</pre>
</body>
</html>`, "utf-8");
// Clear existing headers
const headers = getResponseHeaders(res);
for (let i = 0; i < headers.length; i++) {
removeResponseHeader(res, headers[i]);
}
if (options && options.headers) {
const keys = Object.keys(options.headers);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = options.headers[key];
if (typeof value !== "undefined") {
setResponseHeader(res, key, value);
}
}
}
// Send basic response
setStatusCode(res, status);
setResponseHeader(res, "Content-Type", "text/html; charset=utf-8");
setResponseHeader(res, "Content-Security-Policy", "default-src 'none'");
setResponseHeader(res, "X-Content-Type-Options", "nosniff");
let byteLength = Buffer.byteLength(document);
if (options && options.modifyResponseData) {
({
data: document,
byteLength
} = /** @type {{ data: Buffer, byteLength: number }} */
options.modifyResponseData(req, res, document, byteLength));
}
setResponseHeader(res, "Content-Length", byteLength);
finish(res, document);
}
/**
* @param {NodeJS.ErrnoException} error
*/
function errorHandler(error) {
switch (error.code) {
case "ENAMETOOLONG":
case "ENOENT":
case "ENOTDIR":
sendError(404, {
modifyResponseData: context.options.modifyResponseData
});
break;
default:
sendError(500, {
modifyResponseData: context.options.modifyResponseData
});
break;
}
}
function isConditionalGET() {
return getRequestHeader(req, "if-match") || getRequestHeader(req, "if-unmodified-since") || getRequestHeader(req, "if-none-match") || getRequestHeader(req, "if-modified-since");
}
function isPreconditionFailure() {
// if-match
const ifMatch = /** @type {string} */getRequestHeader(req, "if-match");
// A recipient MUST ignore If-Unmodified-Since if the request contains
// an If-Match header field; the condition in If-Match is considered to
// be a more accurate replacement for the condition in
// If-Unmodified-Since, and the two are only combined for the sake of
// interoperating with older intermediaries that might not implement If-Match.
if (ifMatch) {
const etag = getResponseHeader(res, "ETag");
return !etag || ifMatch !== "*" && parseTokenList(ifMatch).every(match => match !== etag && match !== `W/${etag}` && `W/${match}` !== etag);
}
// if-unmodified-since
const ifUnmodifiedSince = /** @type {string} */
getRequestHeader(req, "if-unmodified-since");
if (ifUnmodifiedSince) {
const unmodifiedSince = parseHttpDate(ifUnmodifiedSince);
// A recipient MUST ignore the If-Unmodified-Since header field if the
// received field-value is not a valid HTTP-date.
if (!isNaN(unmodifiedSince)) {
const lastModified = parseHttpDate( /** @type {string} */getResponseHeader(res, "Last-Modified"));
return isNaN(lastModified) || lastModified > unmodifiedSince;
}
}
return false;
}
/**
* @returns {boolean} is cachable
*/
function isCachable() {
const statusCode = getStatusCode(res);
return statusCode >= 200 && statusCode < 300 || statusCode === 304 ||
// For Koa and Hono, because by default status code is 404, but we already found a file
statusCode === 404;
}
/**
* @param {import("http").OutgoingHttpHeaders} resHeaders
* @returns {boolean}
*/
function isFresh(resHeaders) {
// Always return stale when Cache-Control: no-cache to support end-to-end reload requests
// https://tools.ietf.org/html/rfc2616#section-14.9.4
const cacheControl = /** @type {string} */
getRequestHeader(req, "cache-control");
if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {
return false;
}
// fields
const noneMatch = /** @type {string} */
getRequestHeader(req, "if-none-match");
const modifiedSince = /** @type {string} */
getRequestHeader(req, "if-modified-since");
// unconditional request
if (!noneMatch && !modifiedSince) {
return false;
}
// if-none-match
if (noneMatch && noneMatch !== "*") {
if (!resHeaders.etag) {
return false;
}
const matches = parseTokenList(noneMatch);
let etagStale = true;
for (let i = 0; i < matches.length; i++) {
const match = matches[i];
if (match === resHeaders.etag || match === `W/${resHeaders.etag}` || `W/${match}` === resHeaders.etag) {
etagStale = false;
break;
}
}
if (etagStale) {
return false;
}
}
// A recipient MUST ignore If-Modified-Since if the request contains an If-None-Match header field;
// the condition in If-None-Match is considered to be a more accurate replacement for the condition in If-Modified-Since,
// and the two are only combined for the sake of interoperating with older intermediaries that might not implement If-None-Match.
if (noneMatch) {
return true;
}
// if-modified-since
if (modifiedSince) {
const lastModified = resHeaders["last-modified"];
// A recipient MUST ignore the If-Modified-Since header field if the
// received field-value is not a valid HTTP-date, or if the request
// method is neither GET nor HEAD.
const modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince));
if (modifiedStale) {
return false;
}
}
return true;
}
function isRangeFresh() {
const ifRange = /** @type {string | undefined} */
getRequestHeader(req, "if-range");
if (!ifRange) {
return true;
}
// if-range as etag
if (ifRange.indexOf('"') !== -1) {
const etag = /** @type {string | undefined} */
getResponseHeader(res, "ETag");
if (!etag) {
return true;
}
return Boolean(etag && ifRange.indexOf(etag) !== -1);
}
// if-range as modified date
const lastModified = /** @type {string | undefined} */
getResponseHeader(res, "Last-Modified");
if (!lastModified) {
return true;
}
return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
}
/**
* @returns {string | undefined}
*/
function getRangeHeader() {
const range = /** @type {string} */getRequestHeader(req, "range");
if (range && BYTES_RANGE_REGEXP.test(range)) {
return range;
}
// eslint-disable-next-line no-undefined
return undefined;
}
/**
* @param {import("range-parser").Range} range
* @returns {[number, number]}
*/
function getOffsetAndLenFromRange(range) {
const offset = range.start;
const len = range.end - range.start + 1;
return [offset, len];
}
/**
* @param {number} offset
* @param {number} len
* @returns {[number, number]}
*/
function calcStartAndEnd(offset, len) {
const start = offset;
const end = Math.max(offset, offset + len - 1);
return [start, end];
}
async function processRequest() {
// Pipe and SendFile
/** @type {import("./utils/getFilenameFromUrl").Extra} */
const extra = {};
const filename = getFilenameFromUrl(context, /** @type {string} */getRequestURL(req), extra);
if (extra.errorCode) {
if (extra.errorCode === 403) {
context.logger.error(`Malicious path "${filename}".`);
}
sendError(extra.errorCode, {
modifyResponseData: context.options.modifyResponseData
});
await goNext();
return;
}
if (!filename) {
await goNext();
return;
}
if (getHeadersSent(res)) {
await goNext();
return;
}
const {
size
} = /** @type {import("fs").Stats} */extra.stats;
let len = size;
let offset = 0;
// Send logic
if (context.options.headers) {
let {
headers
} = context.options;
if (typeof headers === "function") {
headers = /** @type {NormalizedHeaders} */
headers(req, res, context);
}
/**
* @type {{key: string, value: string | number}[]}
*/
const allHeaders = [];
if (typeof headers !== "undefined") {
if (!Array.isArray(headers)) {
// eslint-disable-next-line guard-for-in
for (const name in headers) {
allHeaders.push({
key: name,
value: headers[name]
});
}
headers = allHeaders;
}
for (const {
key,
value
} of headers) {
setResponseHeader(res, key, value);
}
}
}
if (!getResponseHeader(res, "Accept-Ranges")) {
setResponseHeader(res, "Accept-Ranges", "bytes");
}
if (!getResponseHeader(res, "Cache-Control")) {
// TODO enable the `cacheImmutable` by default for the next major release
const cacheControl = context.options.cacheImmutable && extra.immutable ? {
immutable: true
} : context.options.cacheControl;
if (cacheControl) {
let cacheControlValue;
if (typeof cacheControl === "boolean") {
cacheControlValue = "public, max-age=31536000";
} else if (typeof cacheControl === "number") {
const maxAge = Math.floor(Math.min(Math.max(0, cacheControl), MAX_MAX_AGE) / 1000);
cacheControlValue = `public, max-age=${maxAge}`;
} else if (typeof cacheControl === "string") {
cacheControlValue = cacheControl;
} else {
const maxAge = cacheControl.maxAge ? Math.floor(Math.min(Math.max(0, cacheControl.maxAge), MAX_MAX_AGE) / 1000) : MAX_MAX_AGE / 1000;
cacheControlValue = `public, max-age=${maxAge}`;
if (cacheControl.immutable) {
cacheControlValue += ", immutable";
}
}
setResponseHeader(res, "Cache-Control", cacheControlValue);
}
}
if (context.options.lastModified && !getResponseHeader(res, "Last-Modified")) {
const modified = /** @type {import("fs").Stats} */
extra.stats.mtime.toUTCString();
setResponseHeader(res, "Last-Modified", modified);
}
/** @type {number} */
let start;
/** @type {number} */
let end;
/** @type {undefined | Buffer | ReadStream} */
let bufferOrStream;
/** @type {number | undefined} */
let byteLength;
const rangeHeader = getRangeHeader();
if (context.options.etag && !getResponseHeader(res, "ETag")) {
/** @type {import("fs").Stats | Buffer | ReadStream | undefined} */
let value;
// TODO cache etag generation?
if (context.options.etag === "weak") {
value = /** @type {import("fs").Stats} */extra.stats;
} else {
if (rangeHeader) {
const parsedRanges = /** @type {import("range-parser").Ranges | import("range-parser").Result} */
parseRangeHeaders(`${size}|${rangeHeader}`);
if (parsedRanges !== -2 && parsedRanges !== -1 && parsedRanges.length === 1) {
[offset, len] = getOffsetAndLenFromRange(parsedRanges[0]);
}
}
[start, end] = calcStartAndEnd(offset, len);
try {
const result = createReadStreamOrReadFileSync(filename, context.outputFileSystem, start, end);
value = result.bufferOrStream;
({
bufferOrStream,
byteLength
} = result);
} catch (error) {
errorHandler( /** @type {NodeJS.ErrnoException} */error);
await goNext();
return;
}
}
if (value) {
// eslint-disable-next-line global-require
const result = await require("./utils/etag")(value);
// Because we already read stream, we can cache buffer to avoid extra read from fs
if (result.buffer) {
bufferOrStream = result.buffer;
}
setResponseHeader(res, "ETag", result.hash);
}
}
if (!getResponseHeader(res, "Content-Type") || getStatusCode(res) === 404) {
removeResponseHeader(res, "Content-Type");
// content-type name(like application/javascript; charset=utf-8) or false
const contentType = mime.contentType(path.extname(filename));
// Only set content-type header if media type is known
// https://tools.ietf.org/html/rfc7231#section-3.1.1.5
if (contentType) {
setResponseHeader(res, "Content-Type", contentType);
} else if (context.options.mimeTypeDefault) {
setResponseHeader(res, "Content-Type", context.options.mimeTypeDefault);
}
}
// Conditional GET support
if (isConditionalGET()) {
if (isPreconditionFailure()) {
sendError(412, {
modifyResponseData: context.options.modifyResponseData
});
await goNext();
return;
}
if (isCachable() && isFresh({
etag: ( /** @type {string | undefined} */
getResponseHeader(res, "ETag")),
"last-modified": ( /** @type {string | undefined} */
getResponseHeader(res, "Last-Modified"))
})) {
setStatusCode(res, 304);
// Remove content header fields
removeResponseHeader(res, "Content-Encoding");
removeResponseHeader(res, "Content-Language");
removeResponseHeader(res, "Content-Length");
removeResponseHeader(res, "Content-Range");
removeResponseHeader(res, "Content-Type");
finish(res);
await goNext();
return;
}
}
let isPartialContent = false;
if (rangeHeader) {
let parsedRanges = /** @type {import("range-parser").Ranges | import("range-parser").Result | []} */
parseRangeHeaders(`${size}|${rangeHeader}`);
// If-Range support
if (!isRangeFresh()) {
parsedRanges = [];
}
if (parsedRanges === -1) {
context.logger.error("Unsatisfiable range for 'Range' header.");
setResponseHeader(res, "Content-Range", getValueContentRangeHeader("bytes", size));
sendError(416, {
headers: {
"Content-Range": getResponseHeader(res, "Content-Range")
},
modifyResponseData: context.options.modifyResponseData
});
await goNext();
return;
} else if (parsedRanges === -2) {
context.logger.error("A malformed 'Range' header was provided. A regular response will be sent for this request.");
} else if (parsedRanges.length > 1) {
context.logger.error("A 'Range' header with multiple ranges was provided. Multiple ranges are not supported, so a regular response will be sent for this request.");
}
if (parsedRanges !== -2 && parsedRanges.length === 1) {
// Content-Range
setStatusCode(res, 206);
setResponseHeader(res, "Content-Range", getValueContentRangeHeader("bytes", size, /** @type {import("range-parser").Ranges} */parsedRanges[0]));
isPartialContent = true;
[offset, len] = getOffsetAndLenFromRange(parsedRanges[0]);
}
}
// When strong Etag generation is enabled we already read file, so we can skip extra fs call
if (!bufferOrStream) {
[start, end] = calcStartAndEnd(offset, len);
try {
({
bufferOrStream,
byteLength
} = createReadStreamOrReadFileSync(filename, context.outputFileSystem, start, end));
} catch (error) {
errorHandler( /** @type {NodeJS.ErrnoException} */error);
await goNext();
return;
}
}
if (context.options.modifyResponseData) {
({
data: bufferOrStream,
byteLength
} = context.options.modifyResponseData(req, res, bufferOrStream, /** @type {number} */
byteLength));
}
setResponseHeader(res, "Content-Length", /** @type {number} */
byteLength);
if (method === "HEAD") {
if (!isPartialContent) {
setStatusCode(res, 200);
}
finish(res);
await goNext();
return;
}
if (!isPartialContent) {
setStatusCode(res, 200);
}
const isPipeSupports = typeof ( /** @type {import("fs").ReadStream} */bufferOrStream.pipe) === "function";
if (!isPipeSupports) {
send(res, /** @type {Buffer} */bufferOrStream);
await goNext();
return;
}
// Cleanup
const cleanup = () => {
destroyStream( /** @type {import("fs").ReadStream} */bufferOrStream, true);
};
// Error handling
/** @type {import("fs").ReadStream} */
bufferOrStream.on("error", error => {
// clean up stream early
cleanup();
errorHandler(error);
goNext();
}).on(getReadyReadableStreamState(res), () => {
goNext();
});
pipe(res, /** @type {ReadStream} */bufferOrStream);
const outgoing = getOutgoing(res);
if (outgoing) {
// Response finished, cleanup
onFinishedStream(outgoing, cleanup);
}
}
ready(context, processRequest, req);
};
}
module.exports = wrapper;

178
node_modules/webpack-dev-middleware/dist/options.json generated vendored Normal file
View File

@@ -0,0 +1,178 @@
{
"type": "object",
"properties": {
"mimeTypes": {
"description": "Allows a user to register custom mime types or extension mappings.",
"link": "https://github.com/webpack/webpack-dev-middleware#mimetypes",
"type": "object"
},
"mimeTypeDefault": {
"description": "Allows a user to register a default mime type when we can't determine the content type.",
"link": "https://github.com/webpack/webpack-dev-middleware#mimetypedefault",
"type": "string"
},
"writeToDisk": {
"description": "Allows to write generated files on disk.",
"link": "https://github.com/webpack/webpack-dev-middleware#writetodisk",
"anyOf": [
{
"type": "boolean"
},
{
"instanceof": "Function"
}
]
},
"methods": {
"description": "Allows to pass the list of HTTP request methods accepted by the middleware.",
"link": "https://github.com/webpack/webpack-dev-middleware#methods",
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"headers": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"description": "key of header.",
"type": "string"
},
"value": {
"description": "value of header.",
"type": "string"
}
}
},
"minItems": 1
},
{
"type": "object"
},
{
"instanceof": "Function"
}
],
"description": "Allows to pass custom HTTP headers on each request",
"link": "https://github.com/webpack/webpack-dev-middleware#headers"
},
"publicPath": {
"description": "The `publicPath` specifies the public URL address of the output files when referenced in a browser.",
"link": "https://github.com/webpack/webpack-dev-middleware#publicpath",
"anyOf": [
{
"enum": ["auto"]
},
{
"type": "string"
},
{
"instanceof": "Function"
}
]
},
"stats": {
"description": "Stats options object or preset name.",
"link": "https://github.com/webpack/webpack-dev-middleware#stats",
"anyOf": [
{
"enum": [
"none",
"summary",
"errors-only",
"errors-warnings",
"minimal",
"normal",
"detailed",
"verbose"
]
},
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": true
}
]
},
"serverSideRender": {
"description": "Instructs the module to enable or disable the server-side rendering mode.",
"link": "https://github.com/webpack/webpack-dev-middleware#serversiderender",
"type": "boolean"
},
"outputFileSystem": {
"description": "Set the default file system which will be used by webpack as primary destination of generated files.",
"link": "https://github.com/webpack/webpack-dev-middleware#outputfilesystem",
"type": "object"
},
"index": {
"description": "Allows to serve an index of the directory.",
"link": "https://github.com/webpack/webpack-dev-middleware#index",
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"minLength": 1
}
]
},
"modifyResponseData": {
"description": "Allows to set up a callback to change the response data.",
"link": "https://github.com/webpack/webpack-dev-middleware#modifyresponsedata",
"instanceof": "Function"
},
"etag": {
"description": "Enable or disable etag generation.",
"link": "https://github.com/webpack/webpack-dev-middleware#etag",
"enum": ["weak", "strong"]
},
"lastModified": {
"description": "Enable or disable `Last-Modified` header. Uses the file system's last modified value.",
"link": "https://github.com/webpack/webpack-dev-middleware#lastmodified",
"type": "boolean"
},
"cacheControl": {
"description": "Enable or disable setting `Cache-Control` response header.",
"link": "https://github.com/webpack/webpack-dev-middleware#cachecontrol",
"anyOf": [
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string",
"minLength": 1
},
{
"type": "object",
"properties": {
"maxAge": {
"type": "number"
},
"immutable": {
"type": "boolean"
}
},
"additionalProperties": false
}
]
},
"cacheImmutable": {
"description": "Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash in file name like `image.a4c12bde.jpg`).",
"link": "https://github.com/webpack/webpack-dev-middleware#cacheimmutable",
"type": "boolean"
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,318 @@
"use strict";
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/** @typedef {import("../index").OutputFileSystem} OutputFileSystem */
/**
* @typedef {Object} ExpectedIncomingMessage
* @property {(name: string) => string | string[] | undefined} [getHeader]
* @property {() => string | undefined} [getMethod]
* @property {() => string | undefined} [getURL]
*/
/**
* @typedef {Object} ExpectedServerResponse
* @property {(status: number) => void} [setStatusCode]
* @property {() => number} [getStatusCode]
* @property {(name: string) => string | string[] | undefined | number} [getHeader]
* @property {(name: string, value: number | string | Readonly<string[]>) => ExpectedServerResponse} [setHeader]
* @property {(name: string) => void} [removeHeader]
* @property {(data: string | Buffer) => void} [send]
* @property {(data?: string | Buffer) => void} [finish]
* @property {() => string[]} [getResponseHeaders]
* @property {() => boolean} [getHeadersSent]
* @property {(data: any) => void} [stream]
* @property {() => any} [getOutgoing]
* @property {(name: string, value: any) => void} [setState]
* @property {() => "ready" | "open" | "readable"} [getReadyReadableStreamState]
*/
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req
* @param {string} name
* @returns {string | string[] | undefined}
*/
function getRequestHeader(req, name) {
// Pseudo API
if (typeof req.getHeader === "function") {
return req.getHeader(name);
}
return req.headers[name];
}
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req
* @returns {string | undefined}
*/
function getRequestMethod(req) {
// Pseudo API
if (typeof req.getMethod === "function") {
return req.getMethod();
}
return req.method;
}
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req
* @returns {string | undefined}
*/
function getRequestURL(req) {
// Pseudo API
if (typeof req.getURL === "function") {
return req.getURL();
}
return req.url;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {number} code
*/
function setStatusCode(res, code) {
// Pseudo API
if (typeof res.setStatusCode === "function") {
res.setStatusCode(code);
return;
}
// Node.js API
// eslint-disable-next-line no-param-reassign
res.statusCode = code;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @returns {number}
*/
function getStatusCode(res) {
// Pseudo API
if (typeof res.getStatusCode === "function") {
return res.getStatusCode();
}
return res.statusCode;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {string} name
* @returns {string | string[] | undefined | number}
*/
function getResponseHeader(res, name) {
// Real and Pseudo API
return res.getHeader(name);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {string} name
* @param {number | string | Readonly<string[]>} value
* @returns {Response}
*/
function setResponseHeader(res, name, value) {
// Real and Pseudo API
return res.setHeader(name, value);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {string} name
*/
function removeResponseHeader(res, name) {
// Real and Pseudo API
res.removeHeader(name);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @returns {string[]}
*/
function getResponseHeaders(res) {
// Pseudo API
if (typeof res.getResponseHeaders === "function") {
return res.getResponseHeaders();
}
return res.getHeaderNames();
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @returns {boolean}
*/
function getHeadersSent(res) {
// Pseudo API
if (typeof res.getHeadersSent === "function") {
return res.getHeadersSent();
}
return res.headersSent;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {import("fs").ReadStream} bufferOrStream
*/
function pipe(res, bufferOrStream) {
// Pseudo API and Koa API
if (typeof res.stream === "function") {
// Writable stream into Readable stream
res.stream(bufferOrStream);
return;
}
// Node.js API and Express API and Hapi API
bufferOrStream.pipe(res);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {string | Buffer} bufferOrString
*/
function send(res, bufferOrString) {
// Pseudo API and Express API and Koa API
if (typeof res.send === "function") {
res.send(bufferOrString);
return;
}
res.end(bufferOrString);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {string | Buffer} [data]
*/
function finish(res, data) {
// Pseudo API and Express API and Koa API
if (typeof res.finish === "function") {
res.finish(data);
return;
}
// Pseudo API and Express API and Koa API
res.end(data);
}
/**
* @param {string} filename
* @param {OutputFileSystem} outputFileSystem
* @param {number} start
* @param {number} end
* @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }}
*/
function createReadStreamOrReadFileSync(filename, outputFileSystem, start, end) {
/** @type {string | Buffer | import("fs").ReadStream} */
let bufferOrStream;
/** @type {number} */
let byteLength;
// Stream logic
const isFsSupportsStream = typeof outputFileSystem.createReadStream === "function";
if (isFsSupportsStream) {
bufferOrStream = /** @type {import("fs").createReadStream} */
outputFileSystem.createReadStream(filename, {
start,
end
});
// Handle files with zero bytes
byteLength = end === 0 ? 0 : end - start + 1;
} else {
bufferOrStream = outputFileSystem.readFileSync(filename);
({
byteLength
} = bufferOrStream);
}
return {
bufferOrStream,
byteLength
};
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @returns {Response} res
*/
function getOutgoing(res) {
// Pseudo API and Express API and Koa API
if (typeof res.getOutgoing === "function") {
return res.getOutgoing();
}
return res;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
*/
function initState(res) {
if (typeof res.setState === "function") {
return;
}
// fixes #282. credit @cexoso. in certain edge situations res.locals is undefined.
// eslint-disable-next-line no-param-reassign
res.locals = res.locals || {};
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @param {string} name
* @param {any} value
*/
function setState(res, name, value) {
if (typeof res.setState === "function") {
res.setState(name, value);
return;
}
/** @type {any} */
// eslint-disable-next-line no-param-reassign
res.locals[name] = value;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res
* @returns {"ready" | "open" | "readable"}
*/
function getReadyReadableStreamState(res) {
// Pseudo API and Express API and Koa API
if (typeof res.getReadyReadableStreamState === "function") {
return res.getReadyReadableStreamState();
}
return "ready";
}
module.exports = {
setStatusCode,
getStatusCode,
getRequestHeader,
getRequestMethod,
getRequestURL,
getResponseHeader,
setResponseHeader,
removeResponseHeader,
getResponseHeaders,
getHeadersSent,
pipe,
send,
finish,
createReadStreamOrReadFileSync,
getOutgoing,
initState,
setState,
getReadyReadableStreamState
};

View File

@@ -0,0 +1,55 @@
"use strict";
const matchHtmlRegExp = /["'&<>]/;
/**
* @param {string} string raw HTML
* @returns {string} escaped HTML
*/
function escapeHtml(string) {
const str = `${string}`;
const match = matchHtmlRegExp.exec(str);
if (!match) {
return str;
}
let escape;
let html = "";
let index = 0;
let lastIndex = 0;
for (({
index
} = match); index < str.length; index++) {
switch (str.charCodeAt(index)) {
// "
case 34:
escape = "&quot;";
break;
// &
case 38:
escape = "&amp;";
break;
// '
case 39:
escape = "&#39;";
break;
// <
case 60:
escape = "&lt;";
break;
// >
case 62:
escape = "&gt;";
break;
default:
// eslint-disable-next-line no-continue
continue;
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index);
}
lastIndex = index + 1;
html += escape;
}
return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
}
module.exports = escapeHtml;

78
node_modules/webpack-dev-middleware/dist/utils/etag.js generated vendored Normal file
View File

@@ -0,0 +1,78 @@
"use strict";
const crypto = require("crypto");
/** @typedef {import("fs").Stats} Stats */
/** @typedef {import("fs").ReadStream} ReadStream */
/**
* Generate a tag for a stat.
*
* @param {Stats} stat
* @return {{ hash: string, buffer?: Buffer }}
*/
function statTag(stat) {
const mtime = stat.mtime.getTime().toString(16);
const size = stat.size.toString(16);
return {
hash: `W/"${size}-${mtime}"`
};
}
/**
* Generate an entity tag.
*
* @param {Buffer | ReadStream} entity
* @return {Promise<{ hash: string, buffer?: Buffer }>}
*/
async function entityTag(entity) {
const sha1 = crypto.createHash("sha1");
if (!Buffer.isBuffer(entity)) {
let byteLength = 0;
/** @type {Buffer[]} */
const buffers = [];
await new Promise((resolve, reject) => {
entity.on("data", chunk => {
sha1.update(chunk);
buffers.push( /** @type {Buffer} */chunk);
byteLength += /** @type {Buffer} */chunk.byteLength;
}).on("end", () => {
resolve(sha1);
}).on("error", reject);
});
return {
buffer: Buffer.concat(buffers),
hash: `"${byteLength.toString(16)}-${sha1.digest("base64").substring(0, 27)}"`
};
}
if (entity.byteLength === 0) {
// Fast-path empty
return {
hash: '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'
};
}
// Compute hash of entity
const hash = sha1.update(entity).digest("base64").substring(0, 27);
// Compute length of entity
const {
byteLength
} = entity;
return {
hash: `"${byteLength.toString(16)}-${hash}"`
};
}
/**
* Create a simple ETag.
*
* @param {Buffer | ReadStream | Stats} entity
* @return {Promise<{ hash: string, buffer?: Buffer }>}
*/
async function etag(entity) {
const isStrong = Buffer.isBuffer(entity) || typeof ( /** @type {ReadStream} */entity.pipe) === "function";
return isStrong ? entityTag( /** @type {Buffer | ReadStream} */entity) : statTag( /** @type {import("fs").Stats} */entity);
}
module.exports = etag;

View File

@@ -0,0 +1,150 @@
"use strict";
const path = require("path");
const {
parse
} = require("url");
const querystring = require("querystring");
const getPaths = require("./getPaths");
const memorize = require("./memorize");
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
// eslint-disable-next-line no-undefined
const memoizedParse = memorize(parse, undefined, value => {
if (value.pathname) {
// eslint-disable-next-line no-param-reassign
value.pathname = decode(value.pathname);
}
return value;
});
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
/**
* @typedef {Object} Extra
* @property {import("fs").Stats=} stats
* @property {number=} errorCode
* @property {boolean=} immutable
*/
/**
* decodeURIComponent.
*
* Allows V8 to only deoptimize this fn instead of all of send().
*
* @param {string} input
* @returns {string}
*/
function decode(input) {
return querystring.unescape(input);
}
// TODO refactor me in the next major release, this function should return `{ filename, stats, error }`
// TODO fix redirect logic when `/` at the end, like https://github.com/pillarjs/send/blob/master/index.js#L586
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").FilledContext<Request, Response>} context
* @param {string} url
* @param {Extra=} extra
* @returns {string | undefined}
*/
function getFilenameFromUrl(context, url, extra = {}) {
const {
options
} = context;
const paths = getPaths(context);
/** @type {string | undefined} */
let foundFilename;
/** @type {URL} */
let urlObject;
try {
// The `url` property of the `request` is contains only `pathname`, `search` and `hash`
urlObject = memoizedParse(url, false, true);
} catch (_ignoreError) {
return;
}
for (const {
publicPath,
outputPath,
assetsInfo
} of paths) {
/** @type {string | undefined} */
let filename;
/** @type {URL} */
let publicPathObject;
try {
publicPathObject = memoizedParse(publicPath !== "auto" && publicPath ? publicPath : "/", false, true);
} catch (_ignoreError) {
// eslint-disable-next-line no-continue
continue;
}
const {
pathname
} = urlObject;
const {
pathname: publicPathPathname
} = publicPathObject;
if (pathname && pathname.startsWith(publicPathPathname)) {
// Null byte(s)
if (pathname.includes("\0")) {
// eslint-disable-next-line no-param-reassign
extra.errorCode = 400;
return;
}
// ".." is malicious
if (UP_PATH_REGEXP.test(path.normalize(`./${pathname}`))) {
// eslint-disable-next-line no-param-reassign
extra.errorCode = 403;
return;
}
// Strip the `pathname` property from the `publicPath` option from the start of requested url
// `/complex/foo.js` => `foo.js`
// and add outputPath
// `foo.js` => `/home/user/my-project/dist/foo.js`
filename = path.join(outputPath, pathname.slice(publicPathPathname.length));
try {
// eslint-disable-next-line no-param-reassign
extra.stats = context.outputFileSystem.statSync(filename);
} catch (_ignoreError) {
// eslint-disable-next-line no-continue
continue;
}
if (extra.stats.isFile()) {
foundFilename = filename;
// Rspack does not yet support `assetsInfo`, so we need to check if `assetsInfo` exists here
if (assetsInfo) {
const assetInfo = assetsInfo.get(pathname.slice(publicPathObject.pathname.length));
// eslint-disable-next-line no-param-reassign
extra.immutable = assetInfo ? assetInfo.immutable : false;
}
break;
} else if (extra.stats.isDirectory() && (typeof options.index === "undefined" || options.index)) {
const indexValue = typeof options.index === "undefined" || typeof options.index === "boolean" ? "index.html" : options.index;
filename = path.join(filename, indexValue);
try {
// eslint-disable-next-line no-param-reassign
extra.stats = context.outputFileSystem.statSync(filename);
} catch (__ignoreError) {
// eslint-disable-next-line no-continue
continue;
}
if (extra.stats.isFile()) {
foundFilename = filename;
break;
}
}
}
}
// eslint-disable-next-line consistent-return
return foundFilename;
}
module.exports = getFilenameFromUrl;

View File

@@ -0,0 +1,42 @@
"use strict";
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").Stats} Stats */
/** @typedef {import("webpack").MultiStats} MultiStats */
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").FilledContext<Request, Response>} context
*/
function getPaths(context) {
const {
stats,
options
} = context;
/** @type {Stats[]} */
const childStats = /** @type {MultiStats} */
stats.stats ? /** @type {MultiStats} */stats.stats : [( /** @type {Stats} */stats)];
const publicPaths = [];
for (const {
compilation
} of childStats) {
if (compilation.options.devServer === false) {
// eslint-disable-next-line no-continue
continue;
}
// The `output.path` is always present and always absolute
const outputPath = compilation.getPath(compilation.outputOptions.path || "");
const publicPath = options.publicPath ? compilation.getPath(options.publicPath) : compilation.outputOptions.publicPath ? compilation.getPath(compilation.outputOptions.publicPath) : "";
publicPaths.push({
outputPath,
publicPath,
assetsInfo: compilation.assetsInfo
});
}
return publicPaths;
}
module.exports = getPaths;

View File

@@ -0,0 +1,39 @@
"use strict";
const cacheStore = new WeakMap();
/**
* @template T
* @param {Function} fn
* @param {{ cache?: Map<string, { data: T }> } | undefined} cache
* @param {((value: T) => T)=} callback
* @returns {any}
*/
function memorize(fn, {
cache = new Map()
} = {}, callback) {
/**
* @param {any} arguments_
* @return {any}
*/
const memoized = (...arguments_) => {
const [key] = arguments_;
const cacheItem = cache.get(key);
if (cacheItem) {
return cacheItem.data;
}
// @ts-ignore
let result = fn.apply(this, arguments_);
if (callback) {
result = callback(result);
}
cache.set(key, {
data: result
});
return result;
};
cacheStore.set(memoized, cache);
return memoized;
}
module.exports = memorize;

View File

@@ -0,0 +1,42 @@
"use strict";
/**
* Parse a HTTP token list.
*
* @param {string} str
* @returns {string[]} tokens
*/
function parseTokenList(str) {
let end = 0;
let start = 0;
const list = [];
// gather tokens
for (let i = 0, len = str.length; i < len; i++) {
switch (str.charCodeAt(i)) {
case 0x20 /* */:
if (start === end) {
end = i + 1;
start = end;
}
break;
case 0x2c /* , */:
if (start !== end) {
list.push(str.substring(start, end));
}
end = i + 1;
start = end;
break;
default:
end = i + 1;
break;
}
}
// final token
if (start !== end) {
list.push(str.substring(start, end));
}
return list;
}
module.exports = parseTokenList;

View File

@@ -0,0 +1,23 @@
"use strict";
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").FilledContext<Request, Response>} context
* @param {(...args: any[]) => any} callback
* @param {Request} [req]
* @returns {void}
*/
function ready(context, callback, req) {
if (context.state) {
callback(context.stats);
return;
}
const name = req && req.url || callback.name;
context.logger.info(`wait until bundle finished${name ? `: ${name}` : ""}`);
context.callbacks.push(callback);
}
module.exports = ready;

View File

@@ -0,0 +1,153 @@
"use strict";
/** @typedef {import("webpack").Configuration} Configuration */
/** @typedef {import("webpack").Compiler} Compiler */
/** @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").WithOptional<import("../index.js").Context<Request, Response>, "watching" | "outputFileSystem">} context
*/
function setupHooks(context) {
function invalid() {
if (context.state) {
context.logger.log("Compilation starting...");
}
// We are now in invalid state
// eslint-disable-next-line no-param-reassign
context.state = false;
// eslint-disable-next-line no-param-reassign, no-undefined
context.stats = undefined;
}
/**
* @param {StatsOptions} statsOptions
* @returns {StatsObjectOptions}
*/
function normalizeStatsOptions(statsOptions) {
if (typeof statsOptions === "undefined") {
// eslint-disable-next-line no-param-reassign
statsOptions = {
preset: "normal"
};
} else if (typeof statsOptions === "boolean") {
// eslint-disable-next-line no-param-reassign
statsOptions = statsOptions ? {
preset: "normal"
} : {
preset: "none"
};
} else if (typeof statsOptions === "string") {
// eslint-disable-next-line no-param-reassign
statsOptions = {
preset: statsOptions
};
}
return statsOptions;
}
/**
* @param {Stats | MultiStats} stats
*/
function done(stats) {
// We are now on valid state
// eslint-disable-next-line no-param-reassign
context.state = true;
// eslint-disable-next-line no-param-reassign
context.stats = stats;
// Do the stuff in nextTick, because bundle may be invalidated if a change happened while compiling
process.nextTick(() => {
const {
compiler,
logger,
options,
state,
callbacks
} = context;
// Check if still in valid state
if (!state) {
return;
}
logger.log("Compilation finished");
const isMultiCompilerMode = Boolean( /** @type {MultiCompiler} */
compiler.compilers);
/**
* @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
* @return {StatsObjectOptions}
*/
childStatsOptions => {
// eslint-disable-next-line no-param-reassign
childStatsOptions = normalizeStatsOptions(childStatsOptions);
if (typeof childStatsOptions.colors === "undefined") {
// eslint-disable-next-line no-param-reassign
childStatsOptions.colors =
// eslint-disable-next-line global-require
require("colorette").isColorSupported;
}
return childStatsOptions;
});
} else {
statsOptions = normalizeStatsOptions( /** @type {StatsOptions} */statsOptions);
if (typeof statsOptions.colors === "undefined") {
// eslint-disable-next-line global-require
statsOptions.colors = require("colorette").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);
}
// eslint-disable-next-line no-param-reassign
context.callbacks = [];
// Execute callback that are delayed
for (const callback of callbacks) {
callback(stats);
}
});
}
// 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;

View File

@@ -0,0 +1,59 @@
"use strict";
const memfs = require("memfs");
/** @typedef {import("webpack").MultiCompiler} MultiCompiler */
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").WithOptional<import("../index.js").Context<Request, Response>, "watching" | "outputFileSystem">} context
*/
function setupOutputFileSystem(context) {
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) {
outputFileSystem = memfs.createFsFromVolume(new memfs.Volume());
} else {
const isMultiCompiler = /** @type {MultiCompiler} */
context.compiler.compilers;
if (isMultiCompiler) {
// Prefer compiler with `devServer` option or fallback on the first
// TODO we need to support webpack-dev-server as a plugin or revisit it
const compiler = /** @type {MultiCompiler} */
context.compiler.compilers.filter(item => Object.prototype.hasOwnProperty.call(item.options, "devServer") && item.options.devServer !== false);
({
outputFileSystem
} = compiler[0] || /** @type {MultiCompiler} */
context.compiler.compilers[0]);
} else {
({
outputFileSystem
} = context.compiler);
}
}
const compilers = /** @type {MultiCompiler} */
context.compiler.compilers || [context.compiler];
for (const compiler of compilers) {
if (compiler.options.devServer === false) {
// eslint-disable-next-line no-continue
continue;
}
// @ts-ignore
compiler.outputFileSystem = outputFileSystem;
}
// @ts-ignore
// eslint-disable-next-line no-param-reassign
context.outputFileSystem = outputFileSystem;
}
module.exports = setupOutputFileSystem;

View File

@@ -0,0 +1,70 @@
"use strict";
const fs = require("fs");
const path = require("path");
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").MultiCompiler} MultiCompiler */
/** @typedef {import("webpack").Compilation} Compilation */
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").WithOptional<import("../index.js").Context<Request, Response>, "watching" | "outputFileSystem">} context
*/
function setupWriteToDisk(context) {
/**
* @type {Compiler[]}
*/
const compilers = /** @type {MultiCompiler} */
context.compiler.compilers || [context.compiler];
for (const compiler of compilers) {
if (compiler.options.devServer === false) {
// eslint-disable-next-line no-continue
continue;
}
compiler.hooks.emit.tap("DevMiddleware", () => {
// @ts-ignore
if (compiler.hasWebpackDevMiddlewareAssetEmittedCallback) {
return;
}
compiler.hooks.assetEmitted.tapAsync("DevMiddleware", (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-ignore
compiler.hasWebpackDevMiddlewareAssetEmittedCallback = true;
});
}
}
module.exports = setupWriteToDisk;