first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+788
View File
@@ -0,0 +1,788 @@
<div align="center">
<a href="https://github.com/webpack/webpack">
<img width="200" height="200" src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
</div>
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![tests][tests]][tests-url]
[![coverage][cover]][cover-url]
[![discussion][discussion]][discussion-url]
[![size][size]][size-url]
# webpack-dev-middleware
An express-style development middleware for use with [webpack](https://webpack.js.org)
bundles and allows for serving of the files emitted from webpack.
This should be used for **development only**.
Some of the benefits of using this middleware include:
- No files are written to disk, rather it handles files in memory
- If files changed in watch mode, the middleware delays requests until compiling
has completed.
- Supports hot module reload (HMR).
## Getting Started
First thing's first, install the module:
```console
npm install webpack-dev-middleware --save-dev
```
> [!WARNING]
>
> _We do not recommend installing this module globally._
## Usage
```js
const express = require("express");
const webpack = require("webpack");
const middleware = require("webpack-dev-middleware");
const compiler = webpack({
// webpack options
});
const app = express();
app.use(
middleware(compiler, {
// webpack-dev-middleware options
}),
);
app.listen(3000, () => console.log("Example app listening on port 3000!"));
```
See [below](#other-servers) for an example of use with fastify.
## Options
| Name | Type | Default | Description |
| :---------------------------------------------: | :-------------------------------: | :-------------------------------------------: | :------------------------------------------------------------------------------------------------------------------- |
| **[`methods`](#methods)** | `Array` | `[ 'GET', 'HEAD' ]` | Allows to pass the list of HTTP request methods accepted by the middleware |
| **[`headers`](#headers)** | `Array\|Object\|Function` | `undefined` | Allows to pass custom HTTP headers on each request. |
| **[`index`](#index)** | `boolean\|string` | `index.html` | If `false` (but not `undefined`), the server will not respond to requests to the root URL. |
| **[`mimeTypes`](#mimetypes)** | `Object` | `undefined` | Allows to register custom mime types or extension mappings. |
| **[`mimeTypeDefault`](#mimetypedefault)** | `string` | `undefined` | Allows to register a default mime type when we can't determine the content type. |
| **[`etag`](#tag)** | `boolean\| "weak"\| "strong"` | `undefined` | Enable or disable etag generation. |
| **[`lastModified`](#lastmodified)** | `boolean` | `undefined` | Enable or disable `Last-Modified` header. Uses the file system's last modified value. |
| **[`cacheControl`](#cachecontrol)** | `boolean\|number\|string\|Object` | `undefined` | Enable or disable setting `Cache-Control` response header. |
| **[`cacheImmutable`](#cacheimmutable)** | `boolean\` | `undefined` | Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets. |
| **[`publicPath`](#publicpath)** | `string` | `undefined` | The public path that the middleware is bound to. |
| **[`stats`](#stats)** | `boolean\|string\|Object` | `stats` (from a configuration) | Stats options object or preset name. |
| **[`serverSideRender`](#serversiderender)** | `boolean` | `undefined` | Instructs the module to enable or disable the server-side rendering mode. |
| **[`writeToDisk`](#writetodisk)** | `boolean\|Function` | `false` | Instructs the module to write files to the configured location on disk as specified in your `webpack` configuration. |
| **[`outputFileSystem`](#outputfilesystem)** | `Object` | [`memfs`](https://github.com/streamich/memfs) | Set the default file system which will be used by webpack as primary destination of generated files. |
| **[`modifyResponseData`](#modifyresponsedata)** | `Function` | `undefined` | Allows to set up a callback to change the response data. |
The middleware accepts an `options` Object. The following is a property reference for the Object.
### methods
Type: `Array`
Default: `[ 'GET', 'HEAD' ]`
This property allows a user to pass the list of HTTP request methods accepted by the middleware\*\*.
### headers
Type: `Array|Object|Function`
Default: `undefined`
This property allows a user to pass custom HTTP headers on each request.
eg. `{ "X-Custom-Header": "yes" }`
or
```js
webpackDevMiddleware(compiler, {
headers: () => ({
"Last-Modified": new Date(),
}),
});
```
or
```js
webpackDevMiddleware(compiler, {
headers: (req, res, context) => {
res.setHeader("Last-Modified", new Date());
},
});
```
or
```js
webpackDevMiddleware(compiler, {
headers: [
{
key: "X-custom-header",
value: "foo",
},
{
key: "Y-custom-header",
value: "bar",
},
],
});
```
or
```js
webpackDevMiddleware(compiler, {
headers: () => [
{
key: "X-custom-header",
value: "foo",
},
{
key: "Y-custom-header",
value: "bar",
},
],
});
```
### index
Type: `Boolean|String`
Default: `index.html`
If `false` (but not `undefined`), the server will not respond to requests to the root URL.
### mimeTypes
Type: `Object`
Default: `undefined`
This property allows a user to register custom mime types or extension mappings.
eg. `mimeTypes: { phtml: 'text/html' }`.
Please see the documentation for [`mime-types`](https://github.com/jshttp/mime-types) for more information.
### mimeTypeDefault
Type: `String`
Default: `undefined`
This property allows a user to register a default mime type when we can't determine the content type.
### etag
Type: `"weak" | "strong"`
Default: `undefined`
Enable or disable etag generation. Boolean value use
### lastModified
Type: `Boolean`
Default: `undefined`
Enable or disable `Last-Modified` header. Uses the file system's last modified value.
### cacheControl
Type: `Boolean | Number | String | { maxAge?: number, immutable?: boolean }`
Default: `undefined`
Depending on the setting, the following headers will be generated:
- `Boolean` - `Cache-Control: public, max-age=31536000000`
- `Number` - `Cache-Control: public, max-age=YOUR_NUMBER`
- `String` - `Cache-Control: YOUR_STRING`
- `{ maxAge?: number, immutable?: boolean }` - `Cache-Control: public, max-age=YOUR_MAX_AGE_or_31536000000`, also `, immutable` can be added if you set the `immutable` option to `true`
Enable or disable setting `Cache-Control` response header.
### cacheImmutable
Type: `Boolean`
Default: `undefined`
Enable or disable setting `Cache-Control: public, max-age=31536000, immutable` response header for immutable assets (i.e. asset with a hash like `image.a4c12bde.jpg`).
Immutable assets are assets that have their hash in the file name therefore they can be cached, because if you change their contents the file name will be changed.
Take preference over the `cacheControl` option if the asset was defined as immutable.
### publicPath
Type: `String`
Default: `output.publicPath` (from a configuration)
The public path that the middleware is bound to.
_Best Practice: use the same `publicPath` defined in your webpack config. For more information about `publicPath`, please see [the webpack documentation](https://webpack.js.org/guides/public-path)._
### stats
Type: `Boolean|String|Object`
Default: `stats` (from a configuration)
Stats options object or preset name.
### serverSideRender
Type: `Boolean`
Default: `undefined`
Instructs the module to enable or disable the server-side rendering mode.
Please see [Server-Side Rendering](#server-side-rendering) for more information.
### writeToDisk
Type: `Boolean|Function`
Default: `false`
If `true`, the option will instruct the module to write files to the configured location on disk as specified in your `webpack` config file.
_Setting `writeToDisk: true` won't change the behavior of the `webpack-dev-middleware`, and bundle files accessed through the browser will still be served from memory._
This option provides the same capabilities as the [`WriteFilePlugin`](https://github.com/gajus/write-file-webpack-plugin/pulls).
This option also accepts a `Function` value, which can be used to filter which files are written to disk.
The function follows the same premise as [`Array#filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) in which a return value of `false` _will not_ write the file, and a return value of `true` _will_ write the file to disk. eg.
```js
const webpack = require("webpack");
const configuration = {
/* Webpack configuration */
};
const compiler = webpack(configuration);
middleware(compiler, {
writeToDisk: (filePath) => /superman\.css$/.test(filePath),
});
```
### outputFileSystem
Type: `Object`
Default: [memfs](https://github.com/streamich/memfs)
Set the default file system which will be used by webpack as primary destination of generated files.
This option isn't affected by the [writeToDisk](#writeToDisk) option.
You have to provide `.join()` and `mkdirp` method to the `outputFileSystem` instance manually for compatibility with `webpack@4`.
This can be done simply by using `path.join`:
```js
const path = require("node:path");
const mkdirp = require("mkdirp");
const myOutputFileSystem = require("my-fs");
const webpack = require("webpack");
myOutputFileSystem.join = path.join.bind(path); // no need to bind
myOutputFileSystem.mkdirp = mkdirp.bind(mkdirp); // no need to bind
const compiler = webpack({
/* Webpack configuration */
});
middleware(compiler, { outputFileSystem: myOutputFileSystem });
```
### modifyResponseData
Allows to set up a callback to change the response data.
```js
const webpack = require("webpack");
const configuration = {
/* Webpack configuration */
};
const compiler = webpack(configuration);
middleware(compiler, {
// Note - if you send the `Range` header you will have `ReadStream`
// Also `data` can be `string` or `Buffer`
modifyResponseData: (req, res, data, byteLength) =>
// Your logic
// Don't use `res.end()` or `res.send()` here
({ data, byteLength }),
});
```
## API
`webpack-dev-middleware` also provides convenience methods that can be use to
interact with the middleware at runtime:
### `close(callback)`
Instructs `webpack-dev-middleware` instance to stop watching for file changes.
#### Parameters
##### `callback`
Type: `Function`
Required: `No`
A function executed once the middleware has stopped watching.
```js
const express = require("express");
const webpack = require("webpack");
const compiler = webpack({
/* Webpack configuration */
});
const middleware = require("webpack-dev-middleware");
const instance = middleware(compiler);
// eslint-disable-next-line new-cap
const app = new express();
app.use(instance);
setTimeout(() => {
// Says `webpack` to stop watch changes
instance.close();
}, 1000);
```
### `invalidate(callback)`
Instructs `webpack-dev-middleware` instance to recompile the bundle, e.g. after a change to the configuration.
#### Parameters
##### `callback`
Type: `Function`
Required: `No`
A function executed once the middleware has invalidated.
```js
const express = require("express");
const webpack = require("webpack");
const compiler = webpack({
/* Webpack configuration */
});
const middleware = require("webpack-dev-middleware");
const instance = middleware(compiler);
// eslint-disable-next-line new-cap
const app = new express();
app.use(instance);
setTimeout(() => {
// After a short delay the configuration is changed and a banner plugin is added to the config
new webpack.BannerPlugin("A new banner").apply(compiler);
// Recompile the bundle with the banner plugin:
instance.invalidate();
}, 1000);
```
### `waitUntilValid(callback)`
Executes a callback function when the compiler bundle is valid, typically after
compilation.
#### Parameters
##### `callback`
Type: `Function`
Required: `No`
A function executed when the bundle becomes valid.
If the bundle is valid at the time of calling, the callback is executed immediately.
```js
const express = require("express");
const webpack = require("webpack");
const compiler = webpack({
/* Webpack configuration */
});
const middleware = require("webpack-dev-middleware");
const instance = middleware(compiler);
// eslint-disable-next-line new-cap
const app = new express();
app.use(instance);
instance.waitUntilValid(() => {
console.log("Package is in a valid state");
});
```
### `getFilenameFromUrl(url)`
Get filename from URL.
#### Parameters
##### `url`
Type: `String`
Required: `Yes`
URL for the requested file.
```js
const express = require("express");
const webpack = require("webpack");
const compiler = webpack({
/* Webpack configuration */
});
const middleware = require("webpack-dev-middleware");
const instance = middleware(compiler);
// eslint-disable-next-line new-cap
const app = new express();
app.use(instance);
instance.waitUntilValid(() => {
const filename = instance.getFilenameFromUrl("/bundle.js");
console.log(`Filename is ${filename}`);
});
```
## FAQ
### Avoid blocking requests to non-webpack resources.
Since `output.publicPath` and `output.filename`/`output.chunkFilename` can be dynamic, it's not possible to know which files are webpack bundles (and they public paths) and which are not, so we can't avoid blocking requests.
But there is a solution to avoid it - mount the middleware to a non-root route, for example:
```js
const express = require("express");
const webpack = require("webpack");
const middleware = require("webpack-dev-middleware");
const compiler = webpack({
// webpack options
});
const app = express();
// Mounting the middleware to the non-root route allows avoids this.
// Note - check your public path, if you want to handle `/dist/`, you need to setup `output.publicPath` to `/` value.
app.use(
"/dist/",
middleware(compiler, {
// webpack-dev-middleware options
}),
);
app.listen(3000, () => console.log("Example app listening on port 3000!"));
```
## Server-Side Rendering
_Note: this feature is experimental and may be removed or changed completely in the future._
In order to develop an app using server-side rendering, we need access to the
[`stats`](https://github.com/webpack/docs/wiki/node.js-api#stats), which is
generated with each build.
With server-side rendering enabled, `webpack-dev-middleware` sets the `stats` to `res.locals.webpack.devMiddleware.stats`
and the filesystem to `res.locals.webpack.devMiddleware.outputFileSystem` before invoking the next middleware,
allowing a developer to render the page body and manage the response to clients.
_Note: Requests for bundle files will still be handled by
`webpack-dev-middleware` and all requests will be pending until the build
process is finished with server-side rendering enabled._
Example Implementation:
```js
const express = require("express");
const isObject = require("is-object");
const webpack = require("webpack");
const middleware = require("webpack-dev-middleware");
const compiler = webpack({
/* Webpack configuration */
});
// eslint-disable-next-line new-cap
const app = new express();
// This function makes server rendering of asset references consistent with different webpack chunk/entry configurations
function normalizeAssets(assets) {
if (isObject(assets)) {
return Object.values(assets);
}
return Array.isArray(assets) ? assets : [assets];
}
app.use(middleware(compiler, { serverSideRender: true }));
// The following middleware would not be invoked until the latest build is finished.
app.use((req, res) => {
const { devMiddleware } = res.locals.webpack;
const { outputFileSystem } = devMiddleware;
const jsonWebpackStats = devMiddleware.stats.toJson();
const { assetsByChunkName, outputPath } = jsonWebpackStats;
// Then use `assetsByChunkName` for server-side rendering
// For example, if you have only one main chunk:
res.send(`
<html>
<head>
<title>My App</title>
<style>
${normalizeAssets(assetsByChunkName.main)
.filter((path) => path.endsWith(".css"))
.map((path) => outputFileSystem.readFileSync(path.join(outputPath, path)))
.join("\n")}
</style>
</head>
<body>
<div id="root"></div>
${normalizeAssets(assetsByChunkName.main)
.filter((path) => path.endsWith(".js"))
.map((path) => `<script src="${path}"></script>`)
.join("\n")}
</body>
</html>
`);
});
```
## Support
We do our best to keep Issues in the repository focused on bugs, features, and
needed modifications to the code for the module. Because of that, we ask users
with general support, "how-to", or "why isn't this working" questions to try one
of the other support channels that are available.
Your first-stop-shop for support for webpack-dev-server should by the excellent
[documentation][docs-url] for the module. If you see an opportunity for improvement
of those docs, please head over to the [webpack.js.org repo][wjo-url] and open a
pull request.
From there, we encourage users to visit the [webpack discussions][chat-url] and
talk to the fine folks there. If your quest for answers comes up dry in chat,
head over to [StackOverflow][stack-url] and do a quick search or open a new
question. Remember; It's always much easier to answer questions that include your
`webpack.config.js` and relevant files!
If you're twitter-savvy you can tweet [#webpack][hash-url] with your question
and someone should be able to reach out and lend a hand.
If you have discovered a :bug:, have a feature suggestion, or would like to see
a modification, please feel free to create an issue on Github. _Note: The issue
template isn't optional, so please be sure not to remove it, and please fill it
out completely._
## Other servers
Examples of use with other servers will follow here.
### Connect
```js
const http = require("node:http");
const connect = require("connect");
const webpack = require("webpack");
const devMiddleware = require("webpack-dev-middleware");
const webpackConfig = require("./webpack.config.js");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
const app = connect();
app.use(devMiddleware(compiler, devMiddlewareOptions));
http.createServer(app).listen(3000);
```
### Router
```js
const http = require("node:http");
const finalhandler = require("finalhandler");
const Router = require("router");
const webpack = require("webpack");
const devMiddleware = require("webpack-dev-middleware");
const webpackConfig = require("./webpack.config.js");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
// eslint-disable-next-line new-cap
const router = Router();
router.use(devMiddleware(compiler, devMiddlewareOptions));
const server = http.createServer((req, res) => {
router(req, res, finalhandler(req, res));
});
server.listen(3000);
```
### Express
```js
const express = require("express");
const webpack = require("webpack");
const devMiddleware = require("webpack-dev-middleware");
const webpackConfig = require("./webpack.config.js");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
const app = express();
app.use(devMiddleware(compiler, devMiddlewareOptions));
app.listen(3000, () => console.log("Example app listening on port 3000!"));
```
### Koa
```js
const Koa = require("koa");
const webpack = require("webpack");
const middleware = require("webpack-dev-middleware");
const webpackConfig = require("./webpack.simple.config");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
const app = new Koa();
app.use(middleware.koaWrapper(compiler, devMiddlewareOptions));
app.listen(3000);
```
### Hapi
```js
const Hapi = require("@hapi/hapi");
const webpack = require("webpack");
const devMiddleware = require("webpack-dev-middleware");
const webpackConfig = require("./webpack.config.js");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {};
const server = Hapi.server({ port: 3000, host: "localhost" });
await server.register({
plugin: devMiddleware.hapiPlugin(),
options: {
// The `compiler` option is required
compiler,
...devMiddlewareOptions,
},
});
await server.start();
console.log("Server running on %s", server.info.uri);
process.on("unhandledRejection", (err) => {
console.log(err);
process.exit(1);
});
```
### Fastify
Fastify interop will require the use of `fastify-express` instead of `middie` for providing middleware support. As the authors of `fastify-express` recommend, this should only be used as a stopgap while full Fastify support is worked on.
```js
const fastify = require("fastify")();
const webpack = require("webpack");
const devMiddleware = require("webpack-dev-middleware");
const webpackConfig = require("./webpack.config.js");
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
await fastify.register(require("@fastify/express"));
await fastify.use(devMiddleware(compiler, devMiddlewareOptions));
await fastify.listen(3000);
```
### Hono
```js
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import webpack from "webpack";
import devMiddleware from "webpack-dev-middleware";
import webpackConfig from "./webpack.config.js";
const compiler = webpack(webpackConfig);
const devMiddlewareOptions = {
/** Your webpack-dev-middleware-options */
};
const app = new Hono();
app.use(devMiddleware.honoWrapper(compiler, devMiddlewareOptions));
serve(app);
```
## Contributing
Please take a moment to read our contributing guidelines if you haven't yet done so.
[CONTRIBUTING](./CONTRIBUTING.md)
## License
[MIT](./LICENSE)
[npm]: https://img.shields.io/npm/v/webpack-dev-middleware.svg
[npm-url]: https://npmjs.com/package/webpack-dev-middleware
[node]: https://img.shields.io/node/v/webpack-dev-middleware.svg
[node-url]: https://nodejs.org
[tests]: https://github.com/webpack/webpack-dev-middleware/workflows/webpack-dev-middleware/badge.svg
[tests-url]: https://github.com/webpack/webpack-dev-middleware/actions
[cover]: https://codecov.io/gh/webpack/webpack-dev-middleware/branch/main/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack/webpack-dev-middleware
[discussion]: https://img.shields.io/github/discussions/webpack/webpack
[discussion-url]: https://github.com/webpack/webpack/discussions
[size]: https://packagephobia.com/badge?p=webpack-dev-middleware
[size-url]: https://packagephobia.com/result?p=webpack-dev-middleware
[docs-url]: https://webpack.js.org/guides/development/#using-webpack-dev-middleware
[hash-url]: https://twitter.com/search?q=webpack
[middleware-url]: https://github.com/webpack/webpack-dev-middleware
[stack-url]: https://stackoverflow.com/questions/tagged/webpack-dev-middleware
[chat-url]: https://github.com/webpack/webpack/discussions
[wjo-url]: https://github.com/webpack/webpack.js.org
+630
View File
@@ -0,0 +1,630 @@
"use strict";
const mime = require("mime-types");
const {
validate
} = require("schema-utils");
const middleware = require("./middleware");
const schema = require("./options.json");
const getFilenameFromUrl = require("./utils/getFilenameFromUrl");
const ready = require("./utils/ready");
const setupHooks = require("./utils/setupHooks");
const setupOutputFileSystem = require("./utils/setupOutputFileSystem");
const setupWriteToDisk = require("./utils/setupWriteToDisk");
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 locals
*/
/** @typedef {import("http").IncomingMessage} IncomingMessage */
/** @typedef {import("http").ServerResponse & ExtendedServerResponse} ServerResponse */
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @callback NextFunction
* @param {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 | undefined} 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> | 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 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
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback Middleware
* @param {RequestInternal} req
* @param {ResponseInternal} res
* @param {NextFunction} next
* @returns {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 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
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {API<RequestInternal, ResponseInternal>} webpack dev middleware
*/
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-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")
};
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 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 compilers = /** @type {MultiCompiler} */context.compiler;
const watchOptions = compilers.compilers.map(childCompiler => childCompiler.options.watchOptions || {});
context.watching = compiler.watch(watchOptions, errorHandler);
} else {
const oneCompiler = /** @type {Compiler} */context.compiler;
const watchOptions = oneCompiler.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 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>} hapi wrapper
*/
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-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;
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {(ctx: any, next: Function) => Promise<void> | void} kow wrapper
*/
function koaWrapper(compiler, options) {
const devMiddleware = wdm(compiler, options);
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @param {{req: RequestInternal, res: ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse, status: number, body: string | Buffer | import("fs").ReadStream | {message: string}, state: object}} ctx context
* @param {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 => {
ctx.body = stream;
isFinished = true;
resolve();
};
/**
* @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) {
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;
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {(ctx: any, next: Function) => Promise<void> | void} hono wrapper
*/
function honoWrapper(compiler, options) {
const devMiddleware = wdm(compiler, options);
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @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 } }} context context
* @param {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);
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @param {string} name header name
* @param {string | number | Readonly<string[]>} value value
* @returns {ResponseInternal & import("./utils/compatibleAPI").ExpectedServerResponse & { headers: any, status: 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 => {
body = stream;
isFinished = true;
resolve();
};
/**
* @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) {
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;
+683
View File
@@ -0,0 +1,683 @@
"use strict";
const path = require("node:path");
const mime = require("mime-types");
const onFinishedStream = require("on-finished");
const {
createReadStreamOrReadFileSync,
finish,
getHeadersSent,
getOutgoing,
getRequestHeader,
getRequestMethod,
getRequestURL,
getResponseHeader,
getResponseHeaders,
getStatusCode,
initState,
pipe,
removeResponseHeader,
send,
setResponseHeader,
setState,
setStatusCode
} = require("./utils/compatibleAPI");
const getFilenameFromUrl = require("./utils/getFilenameFromUrl");
const memorize = require("./utils/memorize");
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 */
/** @typedef {import("fs").ReadStream} ReadStream */
const BYTES_RANGE_REGEXP = /^ *bytes/i;
/**
* @param {"bytes"} type type
* @param {number} size size
* @param {import("range-parser").Range=} range range
* @returns {string} value of content range header
*/
function getValueContentRangeHeader(type, size, range) {
return `${type} ${range ? `${range.start}-${range.end}` : "*"}/${size}`;
}
/**
* Parse an HTTP Date into a number.
* @param {string} date date
* @returns {number} timestamp
*/
function parseHttpDate(date) {
const timestamp = date && Date.parse(date);
// istanbul ignore next: guard against date.js Date.parse patching
return typeof timestamp === "number" ? timestamp : Number.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-expect-error
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 value
* @returns {import("range-parser").Result | import("range-parser").Ranges} ranges
*/
value => {
const [len, rangeHeader] = value.split("|");
return require("range-parser")(Number(len), rangeHeader, {
combine: true
});
});
const getETag = memorize(() => require("./utils/etag"));
const getEscapeHtml = memorize(() => require("./utils/escapeHtml"));
const getParseTokenList = memorize(() => require("./utils/parseTokenList"));
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 context
* @returns {import("./index.js").Middleware<Request, Response>} wrapper
*/
function wrapper(context) {
return async function middleware(req, res, next) {
/**
* @param {NodeJS.ErrnoException=} err an error
* @returns {Promise<void>}
*/
async function goNext(err) {
if (!context.options.serverSideRender) {
return next(err);
}
return new Promise(resolve => {
ready(context, () => {
setState(res, "webpack", {
devMiddleware: context
});
resolve(next(err));
}, req);
});
}
const acceptedMethods = context.options.methods || ["GET", "HEAD"];
// TODO do we need an option here?
const forwardError = false;
initState(res);
const method = getRequestMethod(req);
if (method && !acceptedMethods.includes(method)) {
await goNext();
return;
}
/**
* @param {string} message an error message
* @param {number} status status
* @param {Partial<SendErrorOptions<Request, Response>>=} options options
* @returns {Promise<void>}
*/
async function sendError(message, status, options) {
if (forwardError) {
const error = /** @type {Error & { statusCode: number }} */
new Error(message);
error.statusCode = status;
await goNext(error);
}
const escapeHtml = getEscapeHtml();
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>`, "utf8");
// 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<ArrayBuffer>, byteLength: number }} */
options.modifyResponseData(req, res, document, byteLength));
}
setResponseHeader(res, "Content-Length", byteLength);
finish(res, document);
}
/**
* @param {NodeJS.ErrnoException} error error
* @returns {Promise<void>}
*/
async function errorHandler(error) {
switch (error.code) {
case "ENAMETOOLONG":
case "ENOENT":
case "ENOTDIR":
await sendError(error.message, 404, {
modifyResponseData: context.options.modifyResponseData
});
break;
default:
await sendError(error.message, 500, {
modifyResponseData: context.options.modifyResponseData
});
break;
}
}
/**
* @returns {string | string[] | undefined} something when conditional get exist
*/
function isConditionalGET() {
return getRequestHeader(req, "if-match") || getRequestHeader(req, "if-unmodified-since") || getRequestHeader(req, "if-none-match") || getRequestHeader(req, "if-modified-since");
}
/**
* @returns {boolean} true when precondition failure, otherwise false
*/
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 !== "*" && getParseTokenList()(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 (!Number.isNaN(unmodifiedSince)) {
const lastModified = parseHttpDate(/** @type {string} */getResponseHeader(res, "Last-Modified"));
return Number.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 res header
* @returns {boolean} true when fresh, otherwise false
*/
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 = getParseTokenList()(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;
}
/**
* @returns {boolean} true when range is fresh, otherwise false
*/
function isRangeFresh() {
const ifRange = /** @type {string | undefined} */
getRequestHeader(req, "if-range");
if (!ifRange) {
return true;
}
// if-range as etag
if (ifRange.includes('"')) {
const etag = /** @type {string | undefined} */
getResponseHeader(res, "ETag");
if (!etag) {
return true;
}
return Boolean(etag && ifRange.includes(etag));
}
// 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} range header
*/
function getRangeHeader() {
const range = /** @type {string} */getRequestHeader(req, "range");
if (range && BYTES_RANGE_REGEXP.test(range)) {
return range;
}
return undefined;
}
/**
* @param {import("range-parser").Range} range range
* @returns {[number, number]} offset and length
*/
function getOffsetAndLenFromRange(range) {
const offset = range.start;
const len = range.end - range.start + 1;
return [offset, len];
}
/**
* @param {number} offset offset
* @param {number} len len
* @returns {[number, number]} start and end
*/
function calcStartAndEnd(offset, len) {
const start = offset;
const end = Math.max(offset, offset + len - 1);
return [start, end];
}
/**
* @returns {Promise<void>}
*/
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}".`);
}
await sendError(extra.errorCode === 400 ? "Bad Request" : "Forbidden", extra.errorCode, {
modifyResponseData: context.options.modifyResponseData
});
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)) {
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")) {
const isStrongETag = context.options.etag === "strong";
// TODO cache strong etag generation?
if (isStrongETag) {
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);
({
bufferOrStream,
byteLength
} = result);
} catch (error) {
await errorHandler(/** @type {NodeJS.ErrnoException} */error);
return;
}
}
const result = await getETag()(isStrongETag ? (/** @type {Buffer | ReadStream} */bufferOrStream) : (/** @type {import("fs").Stats} */extra.stats));
// 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()) {
await sendError("Precondition Failed", 412, {
modifyResponseData: context.options.modifyResponseData
});
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);
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));
await sendError("Range Not Satisfiable", 416, {
headers: {
"Content-Range": getResponseHeader(res, "Content-Range")
},
modifyResponseData: context.options.modifyResponseData
});
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) {
await errorHandler(/** @type {NodeJS.ErrnoException} */error);
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);
return;
}
if (!isPartialContent) {
setStatusCode(res, 200);
}
const isPipeSupports = typeof (/** @type {import("fs").ReadStream} */bufferOrStream.pipe) === "function";
if (!isPipeSupports) {
send(res, /** @type {Buffer} */bufferOrStream);
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);
});
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
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
}
+308
View File
@@ -0,0 +1,308 @@
"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 get header extra method
* @property {(() => string | undefined)=} getMethod get method extra method
* @property {(() => string | undefined)=} getURL get URL extra method
*/
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @typedef {object} ExpectedServerResponse
* @property {((status: number) => void)=} setStatusCode set status code
* @property {(() => number)=} getStatusCode get status code
* @property {((name: string) => string | string[] | undefined | number)} getHeader get header
* @property {((name: string, value: number | string | Readonly<string[]>) => ExpectedServerResponse)=} setHeader set header
* @property {((name: string) => void)=} removeHeader remove header
* @property {((data: string | Buffer) => void)=} send send
* @property {((data?: string | Buffer) => void)=} finish finish
* @property {(() => string[])=} getResponseHeaders get response header
* @property {(() => boolean)=} getHeadersSent get headers sent
* @property {((data: any) => void)=} stream stream
* @property {(() => any)=} getOutgoing get outgoing
* @property {((name: string, value: any) => void)=} setState set state
*/
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req req
* @param {string} name name
* @returns {string | string[] | undefined} request header
*/
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 req
* @returns {string | undefined} request method
*/
function getRequestMethod(req) {
// Pseudo API
if (typeof req.getMethod === "function") {
return req.getMethod();
}
return req.method;
}
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req req
* @returns {string | undefined} request URL
*/
function getRequestURL(req) {
// Pseudo API
if (typeof req.getURL === "function") {
return req.getURL();
}
return req.url;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {number} code code
* @returns {void}
*/
function setStatusCode(res, code) {
// Pseudo API
if (typeof res.setStatusCode === "function") {
res.setStatusCode(code);
return;
}
// Node.js API
res.statusCode = code;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @returns {number} status code
*/
function getStatusCode(res) {
// Pseudo API
if (typeof res.getStatusCode === "function") {
return res.getStatusCode();
}
return res.statusCode;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @returns {string | string[] | undefined | number} header
*/
function getResponseHeader(res, name) {
// Real and Pseudo API
return res.getHeader(name);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @param {number | string | Readonly<string[]>} value value
* @returns {Response} response
*/
function setResponseHeader(res, name, value) {
// Real and Pseudo API
return res.setHeader(name, value);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @returns {void}
*/
function removeResponseHeader(res, name) {
// Real and Pseudo API
res.removeHeader(name);
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @returns {string[]} header names
*/
function getResponseHeaders(res) {
// Pseudo API
if (typeof res.getResponseHeaders === "function") {
return res.getResponseHeaders();
}
return res.getHeaderNames();
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @returns {boolean} true when headers were sent, otherwise false
*/
function getHeadersSent(res) {
// Pseudo API
if (typeof res.getHeadersSent === "function") {
return res.getHeadersSent();
}
return res.headersSent;
}
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {import("fs").ReadStream} bufferOrStream buffer or stream
*/
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 res
* @param {string | Buffer} bufferOrString buffer or string
* @returns {void}
*/
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 res
* @param {(string | Buffer)=} data 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 filename
* @param {OutputFileSystem} outputFileSystem output file system
* @param {number} start start
* @param {number} end end
* @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }} result with buffer or stream and byte length
*/
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 res
* @returns {Response} res 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 res
*/
function initState(res) {
if (typeof res.setState === "function") {
return;
}
// fixes #282. credit @cexoso. in certain edge situations res.locals is undefined.
res.locals || (res.locals = {});
}
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @param {any} value state
* @returns {void}
*/
function setState(res, name, value) {
if (typeof res.setState === "function") {
res.setState(name, value);
return;
}
// eslint-disable-next-line jsdoc/no-restricted-syntax
/** @type {any} */
res.locals[name] = value;
}
module.exports = {
createReadStreamOrReadFileSync,
finish,
getHeadersSent,
getOutgoing,
getRequestHeader,
getRequestMethod,
getRequestURL,
getResponseHeader,
getResponseHeaders,
getStatusCode,
initState,
pipe,
removeResponseHeader,
send,
setResponseHeader,
setState,
setStatusCode
};
+57
View File
@@ -0,0 +1,57 @@
"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:
continue;
}
if (lastIndex !== index) {
// eslint-disable-next-line unicorn/prefer-string-slice
html += str.substring(lastIndex, index);
}
lastIndex = index + 1;
html += escape;
}
// eslint-disable-next-line unicorn/prefer-string-slice
return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
}
module.exports = escapeHtml;
+75
View File
@@ -0,0 +1,75 @@
"use strict";
const crypto = require("node:crypto");
/** @typedef {import("fs").Stats} Stats */
/** @typedef {import("fs").ReadStream} ReadStream */
/**
* Generate a tag for a stat.
* @param {Stats} stats stats
* @returns {{ hash: string, buffer?: Buffer }} etag
*/
function statTag(stats) {
const mtime = stats.mtime.getTime().toString(16);
const size = stats.size.toString(16);
return {
hash: `W/"${size}-${mtime}"`
};
}
/**
* Generate an entity tag.
* @param {Buffer | ReadStream} entity entity
* @returns {Promise<{ hash: string, buffer?: Buffer }>} etag
*/
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").slice(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").slice(0, 27);
// Compute length of entity
const {
byteLength
} = entity;
return {
hash: `"${byteLength.toString(16)}-${hash}"`
};
}
/**
* Create a simple ETag.
* @param {Buffer | ReadStream | Stats} entity entity
* @returns {Promise<{ hash: string, buffer?: Buffer }>} etag
*/
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;
+140
View File
@@ -0,0 +1,140 @@
"use strict";
const path = require("node:path");
const querystring = require("node:querystring");
// eslint-disable-next-line n/no-deprecated-api
const {
parse
} = require("node:url");
const getPaths = require("./getPaths");
const memorize = require("./memorize");
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/**
* @param {string} input input
* @returns {string} unescape input
*/
function decode(input) {
return querystring.unescape(input);
}
const memoizedParse = memorize(parse, undefined, value => {
if (value.pathname) {
value.pathname = decode(value.pathname);
}
return value;
});
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
/**
* @typedef {object} Extra
* @property {import("fs").Stats=} stats stats
* @property {number=} errorCode error code
* @property {boolean=} immutable true when immutable, otherwise false
*/
/**
* decodeURIComponent.
*
* Allows V8 to only deoptimize this fn instead of all of send().
* @param {string} input
* @returns {string}
*/
// 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 context
* @param {string} url url
* @param {Extra=} extra extra
* @returns {string | undefined} filename
*/
function getFilenameFromUrl(context, url, extra = {}) {
const {
options
} = context;
const paths = getPaths(context);
/** @type {string | undefined} */
let foundFilename;
/** @type {import("node:url").Url} */
let urlObject;
try {
// The `url` property of the `request` is contains only `pathname`, `search` and `hash`
urlObject = memoizedParse(url, false, true);
} catch {
return;
}
for (const {
publicPath,
outputPath,
assetsInfo
} of paths) {
/** @type {string | undefined} */
let filename;
/** @type {import("node:url").Url} */
let publicPathObject;
try {
publicPathObject = memoizedParse(publicPath !== "auto" && publicPath ? publicPath : "/", false, true);
} catch {
continue;
}
const {
pathname
} = urlObject;
const {
pathname: publicPathPathname
} = publicPathObject;
if (pathname && publicPathPathname && pathname.startsWith(publicPathPathname)) {
// Null byte(s)
if (pathname.includes("\0")) {
extra.errorCode = 400;
return;
}
// ".." is malicious
if (UP_PATH_REGEXP.test(path.normalize(`./${pathname}`))) {
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 {
extra.stats = context.outputFileSystem.statSync(filename);
} catch {
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(publicPathPathname.length));
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 {
extra.stats = context.outputFileSystem.statSync(filename);
} catch {
continue;
}
if (extra.stats.isFile()) {
foundFilename = filename;
break;
}
}
}
}
return foundFilename;
}
module.exports = getFilenameFromUrl;
+45
View File
@@ -0,0 +1,45 @@
"use strict";
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").Stats} Stats */
/** @typedef {import("webpack").MultiStats} MultiStats */
/** @typedef {import("webpack").Asset} Asset */
/** @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 context
* @returns {{ outputPath: string, publicPath: string, assetsInfo: Asset["info"] }[]} paths
*/
function getPaths(context) {
const {
stats,
options
} = context;
/* eslint-disable unicorn/prefer-logical-operator-over-ternary */
/** @type {Stats[]} */
const childStats = /** @type {MultiStats} */
stats.stats ? /** @type {MultiStats} */stats.stats : [(/** @type {Stats} */stats)];
/** @type {{ outputPath: string, publicPath: string, assetsInfo: Asset["info"] }[]} */
const publicPaths = [];
for (const {
compilation
} of childStats) {
if (compilation.options.devServer === false) {
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;
+46
View File
@@ -0,0 +1,46 @@
"use strict";
const cacheStore = new WeakMap();
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @template T
* @typedef {(...args: any) => T} FunctionReturning
*/
/**
* @template T
* @param {FunctionReturning<T>} fn memorized function
* @param {({ cache?: Map<string, { data: T }> } | undefined)=} cache cache
* @param {((value: T) => T)=} callback callback
* @returns {FunctionReturning<T>} new function
*/
function memorize(fn, {
cache = new Map()
} = {}, callback = undefined) {
// eslint-disable-next-line jsdoc/no-restricted-syntax
/**
* @param {any} arguments_ args
* @returns {any} result
*/
const memoized = (...arguments_) => {
const [key] = arguments_;
const cacheItem = cache.get(key);
if (cacheItem) {
return cacheItem.data;
}
// @ts-expect-error
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;
+41
View File
@@ -0,0 +1,41 @@
"use strict";
/**
* Parse a HTTP token list.
* @param {string} str 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.slice(start, end));
}
end = i + 1;
start = end;
break;
default:
end = i + 1;
break;
}
}
// final token
if (start !== end) {
list.push(str.slice(start, end));
}
return list;
}
module.exports = parseTokenList;
+24
View File
@@ -0,0 +1,24 @@
"use strict";
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/** @typedef {import("../index.js").Callback} Callback */
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").FilledContext<Request, Response>} context context
* @param {Callback} callback callback
* @param {Request=} req 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;
+153
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 context
*/
function setupHooks(context) {
/**
* @returns {void}
*/
function invalid() {
if (context.state) {
context.logger.log("Compilation starting...");
}
// We are now in invalid state
context.state = false;
context.stats = undefined;
}
/**
* @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;
}
/**
* @param {Stats | MultiStats} stats stats
*/
function 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 {
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 child stats options
* @returns {StatsObjectOptions} object child stats options
*/
childStatsOptions => {
childStatsOptions = normalizeStatsOptions(childStatsOptions);
if (typeof childStatsOptions.colors === "undefined") {
const [firstCompiler] = /** @type {MultiCompiler} */
compiler.compilers;
// TODO remove `colorette` and set minimum supported webpack version is `5.101.0`
childStatsOptions.colors = typeof firstCompiler.webpack !== "undefined" && typeof firstCompiler.webpack.cli !== "undefined" && typeof firstCompiler.webpack.cli.isColorSupported === "function" ? firstCompiler.webpack.cli.isColorSupported() : require("colorette").isColorSupported;
}
return childStatsOptions;
});
} else {
statsOptions = normalizeStatsOptions(/** @type {StatsOptions} */statsOptions);
if (typeof statsOptions.colors === "undefined") {
const {
compiler
} = /** @type {{ compiler: Compiler }} */context;
// TODO remove `colorette` and set minimum supported webpack version is `5.101.0`
statsOptions.colors = typeof compiler.webpack !== "undefined" && typeof compiler.webpack.cli !== "undefined" && typeof compiler.webpack.cli.isColorSupported === "function" ? compiler.webpack.cli.isColorSupported() : 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);
}
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;
@@ -0,0 +1,57 @@
"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 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.find(item => Object.hasOwn(item.options, "devServer") && item.options.devServer !== false);
({
outputFileSystem
} = compiler || /** @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) {
continue;
}
// @ts-expect-error
compiler.outputFileSystem = outputFileSystem;
}
// @ts-expect-error
context.outputFileSystem = outputFileSystem;
}
module.exports = setupOutputFileSystem;
+69
View File
@@ -0,0 +1,69 @@
"use strict";
const fs = require("node:fs");
const path = require("node: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 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) {
continue;
}
compiler.hooks.emit.tap("DevMiddleware", () => {
// @ts-expect-error
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-expect-error
compiler.hasWebpackDevMiddlewareAssetEmittedCallback = true;
});
}
}
module.exports = setupWriteToDisk;
+541
View File
@@ -0,0 +1,541 @@
1.54.0 / 2025-03-17
===================
* Update mime type for DCM format (#362)
* mark application/octet-stream as compressible (#163)
* Fix typo in application/x-zip-compressed mimetype (#359)
* Add mime-type for Jupyter notebooks (#282)
* Add Google Drive MIME types (#311)
* Add .blend file type (#338)
* Add support for the FBX file extension (#342)
* Add Adobe DNG file (#340)
* Add Procreate Brush and Brush Set file Types (#339)
* Add support for Procreate Dreams (#341)
* replace got with undici (#352)
* Added extensions list for model/step (#293)
* Add m4b as a type of audio/mp4 (#357)
* windows 11 application/x-zip-compressed (#346)
* add dotLottie mime type (#351)
* Add some MS-related extensions and types (#336)
1.53.0 / 2024-07-12
===================
* Add extension `.sql` to `application/sql`
* Add extensions `.aac` and `.adts` to `audio/aac`
* Add extensions `.js` and `.mjs` to `text/javascript`
* Add extensions for `application/mp4` from IANA
* Add extensions from IANA for more MIME types
* Add Microsoft app installer types and extensions
* Add new upstream MIME types
* Fix extensions for `text/markdown` to match IANA
* Remove extension `.mjs` from `application/javascript`
* Remove obsolete MIME types from IANA data
1.52.0 / 2022-02-21
===================
* Add extensions from IANA for more `image/*` types
* Add extension `.asc` to `application/pgp-keys`
* Add extensions to various XML types
* Add new upstream MIME types
1.51.0 / 2021-11-08
===================
* Add new upstream MIME types
* Mark `image/vnd.microsoft.icon` as compressible
* Mark `image/vnd.ms-dds` as compressible
1.50.0 / 2021-09-15
===================
* Add deprecated iWorks mime types and extensions
* Add new upstream MIME types
1.49.0 / 2021-07-26
===================
* Add extension `.trig` to `application/trig`
* Add new upstream MIME types
1.48.0 / 2021-05-30
===================
* Add extension `.mvt` to `application/vnd.mapbox-vector-tile`
* Add new upstream MIME types
* Mark `text/yaml` as compressible
1.47.0 / 2021-04-01
===================
* Add new upstream MIME types
* Remove ambiguous extensions from IANA for `application/*+xml` types
* Update primary extension to `.es` for `application/ecmascript`
1.46.0 / 2021-02-13
===================
* Add extension `.amr` to `audio/amr`
* Add extension `.m4s` to `video/iso.segment`
* Add extension `.opus` to `audio/ogg`
* Add new upstream MIME types
1.45.0 / 2020-09-22
===================
* Add `application/ubjson` with extension `.ubj`
* Add `image/avif` with extension `.avif`
* Add `image/ktx2` with extension `.ktx2`
* Add extension `.dbf` to `application/vnd.dbf`
* Add extension `.rar` to `application/vnd.rar`
* Add extension `.td` to `application/urc-targetdesc+xml`
* Add new upstream MIME types
* Fix extension of `application/vnd.apple.keynote` to be `.key`
1.44.0 / 2020-04-22
===================
* Add charsets from IANA
* Add extension `.cjs` to `application/node`
* Add new upstream MIME types
1.43.0 / 2020-01-05
===================
* Add `application/x-keepass2` with extension `.kdbx`
* Add extension `.mxmf` to `audio/mobile-xmf`
* Add extensions from IANA for `application/*+xml` types
* Add new upstream MIME types
1.42.0 / 2019-09-25
===================
* Add `image/vnd.ms-dds` with extension `.dds`
* Add new upstream MIME types
* Remove compressible from `multipart/mixed`
1.41.0 / 2019-08-30
===================
* Add new upstream MIME types
* Add `application/toml` with extension `.toml`
* Mark `font/ttf` as compressible
1.40.0 / 2019-04-20
===================
* Add extensions from IANA for `model/*` types
* Add `text/mdx` with extension `.mdx`
1.39.0 / 2019-04-04
===================
* Add extensions `.siv` and `.sieve` to `application/sieve`
* Add new upstream MIME types
1.38.0 / 2019-02-04
===================
* Add extension `.nq` to `application/n-quads`
* Add extension `.nt` to `application/n-triples`
* Add new upstream MIME types
* Mark `text/less` as compressible
1.37.0 / 2018-10-19
===================
* Add extensions to HEIC image types
* Add new upstream MIME types
1.36.0 / 2018-08-20
===================
* Add Apple file extensions from IANA
* Add extensions from IANA for `image/*` types
* Add new upstream MIME types
1.35.0 / 2018-07-15
===================
* Add extension `.owl` to `application/rdf+xml`
* Add new upstream MIME types
- Removes extension `.woff` from `application/font-woff`
1.34.0 / 2018-06-03
===================
* Add extension `.csl` to `application/vnd.citationstyles.style+xml`
* Add extension `.es` to `application/ecmascript`
* Add new upstream MIME types
* Add `UTF-8` as default charset for `text/turtle`
* Mark all XML-derived types as compressible
1.33.0 / 2018-02-15
===================
* Add extensions from IANA for `message/*` types
* Add new upstream MIME types
* Fix some incorrect OOXML types
* Remove `application/font-woff2`
1.32.0 / 2017-11-29
===================
* Add new upstream MIME types
* Update `text/hjson` to registered `application/hjson`
* Add `text/shex` with extension `.shex`
1.31.0 / 2017-10-25
===================
* Add `application/raml+yaml` with extension `.raml`
* Add `application/wasm` with extension `.wasm`
* Add new `font` type from IANA
* Add new upstream font extensions
* Add new upstream MIME types
* Add extensions for JPEG-2000 images
1.30.0 / 2017-08-27
===================
* Add `application/vnd.ms-outlook`
* Add `application/x-arj`
* Add extension `.mjs` to `application/javascript`
* Add glTF types and extensions
* Add new upstream MIME types
* Add `text/x-org`
* Add VirtualBox MIME types
* Fix `source` records for `video/*` types that are IANA
* Update `font/opentype` to registered `font/otf`
1.29.0 / 2017-07-10
===================
* Add `application/fido.trusted-apps+json`
* Add extension `.wadl` to `application/vnd.sun.wadl+xml`
* Add new upstream MIME types
* Add `UTF-8` as default charset for `text/css`
1.28.0 / 2017-05-14
===================
* Add new upstream MIME types
* Add extension `.gz` to `application/gzip`
* Update extensions `.md` and `.markdown` to be `text/markdown`
1.27.0 / 2017-03-16
===================
* Add new upstream MIME types
* Add `image/apng` with extension `.apng`
1.26.0 / 2017-01-14
===================
* Add new upstream MIME types
* Add extension `.geojson` to `application/geo+json`
1.25.0 / 2016-11-11
===================
* Add new upstream MIME types
1.24.0 / 2016-09-18
===================
* Add `audio/mp3`
* Add new upstream MIME types
1.23.0 / 2016-05-01
===================
* Add new upstream MIME types
* Add extension `.3gpp` to `audio/3gpp`
1.22.0 / 2016-02-15
===================
* Add `text/slim`
* Add extension `.rng` to `application/xml`
* Add new upstream MIME types
* Fix extension of `application/dash+xml` to be `.mpd`
* Update primary extension to `.m4a` for `audio/mp4`
1.21.0 / 2016-01-06
===================
* Add Google document types
* Add new upstream MIME types
1.20.0 / 2015-11-10
===================
* Add `text/x-suse-ymp`
* Add new upstream MIME types
1.19.0 / 2015-09-17
===================
* Add `application/vnd.apple.pkpass`
* Add new upstream MIME types
1.18.0 / 2015-09-03
===================
* Add new upstream MIME types
1.17.0 / 2015-08-13
===================
* Add `application/x-msdos-program`
* Add `audio/g711-0`
* Add `image/vnd.mozilla.apng`
* Add extension `.exe` to `application/x-msdos-program`
1.16.0 / 2015-07-29
===================
* Add `application/vnd.uri-map`
1.15.0 / 2015-07-13
===================
* Add `application/x-httpd-php`
1.14.0 / 2015-06-25
===================
* Add `application/scim+json`
* Add `application/vnd.3gpp.ussd+xml`
* Add `application/vnd.biopax.rdf+xml`
* Add `text/x-processing`
1.13.0 / 2015-06-07
===================
* Add nginx as a source
* Add `application/x-cocoa`
* Add `application/x-java-archive-diff`
* Add `application/x-makeself`
* Add `application/x-perl`
* Add `application/x-pilot`
* Add `application/x-redhat-package-manager`
* Add `application/x-sea`
* Add `audio/x-m4a`
* Add `audio/x-realaudio`
* Add `image/x-jng`
* Add `text/mathml`
1.12.0 / 2015-06-05
===================
* Add `application/bdoc`
* Add `application/vnd.hyperdrive+json`
* Add `application/x-bdoc`
* Add extension `.rtf` to `text/rtf`
1.11.0 / 2015-05-31
===================
* Add `audio/wav`
* Add `audio/wave`
* Add extension `.litcoffee` to `text/coffeescript`
* Add extension `.sfd-hdstx` to `application/vnd.hydrostatix.sof-data`
* Add extension `.n-gage` to `application/vnd.nokia.n-gage.symbian.install`
1.10.0 / 2015-05-19
===================
* Add `application/vnd.balsamiq.bmpr`
* Add `application/vnd.microsoft.portable-executable`
* Add `application/x-ns-proxy-autoconfig`
1.9.1 / 2015-04-19
==================
* Remove `.json` extension from `application/manifest+json`
- This is causing bugs downstream
1.9.0 / 2015-04-19
==================
* Add `application/manifest+json`
* Add `application/vnd.micro+json`
* Add `image/vnd.zbrush.pcx`
* Add `image/x-ms-bmp`
1.8.0 / 2015-03-13
==================
* Add `application/vnd.citationstyles.style+xml`
* Add `application/vnd.fastcopy-disk-image`
* Add `application/vnd.gov.sk.xmldatacontainer+xml`
* Add extension `.jsonld` to `application/ld+json`
1.7.0 / 2015-02-08
==================
* Add `application/vnd.gerber`
* Add `application/vnd.msa-disk-image`
1.6.1 / 2015-02-05
==================
* Community extensions ownership transferred from `node-mime`
1.6.0 / 2015-01-29
==================
* Add `application/jose`
* Add `application/jose+json`
* Add `application/json-seq`
* Add `application/jwk+json`
* Add `application/jwk-set+json`
* Add `application/jwt`
* Add `application/rdap+json`
* Add `application/vnd.gov.sk.e-form+xml`
* Add `application/vnd.ims.imsccv1p3`
1.5.0 / 2014-12-30
==================
* Add `application/vnd.oracle.resource+json`
* Fix various invalid MIME type entries
- `application/mbox+xml`
- `application/oscp-response`
- `application/vwg-multiplexed`
- `audio/g721`
1.4.0 / 2014-12-21
==================
* Add `application/vnd.ims.imsccv1p2`
* Fix various invalid MIME type entries
- `application/vnd-acucobol`
- `application/vnd-curl`
- `application/vnd-dart`
- `application/vnd-dxr`
- `application/vnd-fdf`
- `application/vnd-mif`
- `application/vnd-sema`
- `application/vnd-wap-wmlc`
- `application/vnd.adobe.flash-movie`
- `application/vnd.dece-zip`
- `application/vnd.dvb_service`
- `application/vnd.micrografx-igx`
- `application/vnd.sealed-doc`
- `application/vnd.sealed-eml`
- `application/vnd.sealed-mht`
- `application/vnd.sealed-ppt`
- `application/vnd.sealed-tiff`
- `application/vnd.sealed-xls`
- `application/vnd.sealedmedia.softseal-html`
- `application/vnd.sealedmedia.softseal-pdf`
- `application/vnd.wap-slc`
- `application/vnd.wap-wbxml`
- `audio/vnd.sealedmedia.softseal-mpeg`
- `image/vnd-djvu`
- `image/vnd-svf`
- `image/vnd-wap-wbmp`
- `image/vnd.sealed-png`
- `image/vnd.sealedmedia.softseal-gif`
- `image/vnd.sealedmedia.softseal-jpg`
- `model/vnd-dwf`
- `model/vnd.parasolid.transmit-binary`
- `model/vnd.parasolid.transmit-text`
- `text/vnd-a`
- `text/vnd-curl`
- `text/vnd.wap-wml`
* Remove example template MIME types
- `application/example`
- `audio/example`
- `image/example`
- `message/example`
- `model/example`
- `multipart/example`
- `text/example`
- `video/example`
1.3.1 / 2014-12-16
==================
* Fix missing extensions
- `application/json5`
- `text/hjson`
1.3.0 / 2014-12-07
==================
* Add `application/a2l`
* Add `application/aml`
* Add `application/atfx`
* Add `application/atxml`
* Add `application/cdfx+xml`
* Add `application/dii`
* Add `application/json5`
* Add `application/lxf`
* Add `application/mf4`
* Add `application/vnd.apache.thrift.compact`
* Add `application/vnd.apache.thrift.json`
* Add `application/vnd.coffeescript`
* Add `application/vnd.enphase.envoy`
* Add `application/vnd.ims.imsccv1p1`
* Add `text/csv-schema`
* Add `text/hjson`
* Add `text/markdown`
* Add `text/yaml`
1.2.0 / 2014-11-09
==================
* Add `application/cea`
* Add `application/dit`
* Add `application/vnd.gov.sk.e-form+zip`
* Add `application/vnd.tmd.mediaflex.api+xml`
* Type `application/epub+zip` is now IANA-registered
1.1.2 / 2014-10-23
==================
* Rebuild database for `application/x-www-form-urlencoded` change
1.1.1 / 2014-10-20
==================
* Mark `application/x-www-form-urlencoded` as compressible.
1.1.0 / 2014-09-28
==================
* Add `application/font-woff2`
1.0.3 / 2014-09-25
==================
* Fix engine requirement in package
1.0.2 / 2014-09-25
==================
* Add `application/coap-group+json`
* Add `application/dcd`
* Add `application/vnd.apache.thrift.binary`
* Add `image/vnd.tencent.tap`
* Mark all JSON-derived types as compressible
* Update `text/vtt` data
1.0.1 / 2014-08-30
==================
* Fix extension ordering
1.0.0 / 2014-08-30
==================
* Add `application/atf`
* Add `application/merge-patch+json`
* Add `multipart/x-mixed-replace`
* Add `source: 'apache'` metadata
* Add `source: 'iana'` metadata
* Remove badly-assumed charset data
+23
View File
@@ -0,0 +1,23 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2015-2022 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+109
View File
@@ -0,0 +1,109 @@
# mime-db
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-image]][node-url]
[![Build Status][ci-image]][ci-url]
[![Coverage Status][coveralls-image]][coveralls-url]
This is a large database of mime types and information about them.
It consists of a single, public JSON file and does not include any logic,
allowing it to remain as un-opinionated as possible with an API.
It aggregates data from the following sources:
- https://www.iana.org/assignments/media-types/media-types.xhtml
- https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
- https://hg.nginx.org/nginx/raw-file/default/conf/mime.types
## Installation
```bash
npm install mime-db
```
### Database Download
If you intend to use this in a web browser, you can conveniently access the JSON file via [jsDelivr](https://www.jsdelivr.com/), a popular CDN (Content Delivery Network). To ensure stability and compatibility, it is advisable to specify [a release tag](https://github.com/jshttp/mime-db/tags) instead of using the 'master' branch. This is because the JSON file's format might change in future updates, and relying on a specific release tag will prevent potential issues arising from these changes.
```
https://cdn.jsdelivr.net/gh/jshttp/mime-db@master/db.json
```
## Usage
```js
var db = require('mime-db')
// grab data on .js files
var data = db['application/javascript']
```
## Data Structure
The JSON file is a map lookup for lowercased mime types.
Each mime type has the following properties:
- `.source` - where the mime type is defined.
If not set, it's probably a custom media type.
- `apache` - [Apache common media types](https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)
- `iana` - [IANA-defined media types](https://www.iana.org/assignments/media-types/media-types.xhtml)
- `nginx` - [nginx media types](https://hg.nginx.org/nginx/raw-file/default/conf/mime.types)
- `.extensions[]` - known extensions associated with this mime type.
- `.compressible` - whether a file of this type can be gzipped.
- `.charset` - the default charset associated with this type, if any.
If unknown, every property could be `undefined`.
## Note on MIME Type Data and Semver
This package considers the programmatic api as the semver compatibility. This means the MIME type resolution is *not* considered
in the semver bumps. This means that if you want to pin your `mime-db` data you will need to do it in your application. While
this expectation was not set in docs until now, it is how the pacakge operated, so we do not feel this is a breaking change.
## Contributing
The primary way to contribute to this database is by updating the data in
one of the upstream sources. The database is updated from the upstreams
periodically and will pull in any changes.
### Registering Media Types
The best way to get new media types included in this library is to register
them with the IANA. The community registration procedure is outlined in
[RFC 6838 section 5](https://tools.ietf.org/html/rfc6838#section-5). Types
registered with the IANA are automatically pulled into this library.
### Direct Inclusion
If that is not possible / feasible, they can be added directly here as a
"custom" type. To do this, it is required to have a primary source that
definitively lists the media type. If an extension is going to be listed as
associated with this media type, the source must definitively link the
media type and extension as well.
To edit the database, only make PRs against `src/custom-types.json` or
`src/custom-suffix.json`.
The `src/custom-types.json` file is a JSON object with the MIME type as the
keys and the values being an object with the following keys:
- `compressible` - leave out if you don't know, otherwise `true`/`false` to
indicate whether the data represented by the type is typically compressible.
- `extensions` - include an array of file extensions that are associated with
the type.
- `notes` - human-readable notes about the type, typically what the type is.
- `sources` - include an array of URLs of where the MIME type and the associated
extensions are sourced from. This needs to be a [primary source](https://en.wikipedia.org/wiki/Primary_source);
links to type aggregating sites and Wikipedia are _not acceptable_.
To update the build, run `npm run build`.
[ci-image]: https://badgen.net/github/checks/jshttp/mime-db/master?label=ci
[ci-url]: https://github.com/jshttp/mime-db/actions/workflows/ci.yml
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/mime-db/master
[coveralls-url]: https://coveralls.io/r/jshttp/mime-db?branch=master
[node-image]: https://badgen.net/npm/node/mime-db
[node-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/mime-db
[npm-url]: https://npmjs.org/package/mime-db
[npm-version-image]: https://badgen.net/npm/v/mime-db
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
/*!
* mime-db
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2015-2022 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module exports.
*/
module.exports = require('./db.json')
+56
View File
@@ -0,0 +1,56 @@
{
"name": "mime-db",
"description": "Media Type Database",
"version": "1.54.0",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)",
"Robert Kieffer <robert@broofa.com> (http://github.com/broofa)"
],
"license": "MIT",
"keywords": [
"mime",
"db",
"type",
"types",
"database",
"charset",
"charsets"
],
"repository": "jshttp/mime-db",
"devDependencies": {
"csv-parse": "4.16.3",
"eslint": "8.32.0",
"eslint-config-standard": "15.0.1",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-markdown": "3.0.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-standard": "4.1.0",
"media-typer": "1.1.0",
"mocha": "10.2.0",
"nyc": "15.1.0",
"stream-to-array": "2.3.0",
"undici": "7.1.0"
},
"files": [
"HISTORY.md",
"LICENSE",
"README.md",
"db.json",
"index.js"
],
"engines": {
"node": ">= 0.6"
},
"scripts": {
"build": "node scripts/build",
"fetch": "node scripts/fetch-apache && node scripts/fetch-iana && node scripts/fetch-nginx",
"lint": "eslint .",
"test": "mocha --reporter spec --check-leaks test/",
"test-ci": "nyc --reporter=lcovonly --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test",
"update": "npm run fetch && npm run build",
"version": "node scripts/version-history.js && git add HISTORY.md"
}
}
+428
View File
@@ -0,0 +1,428 @@
3.0.2 / 2025-11-20
===================
* Fix: update JSDoc to reflect that functions return only `false` or `string`, not `boolean|string`.
* Fix: refined mime-score logic so `.mp4` resolves correctly
* Fix:reflect the current Node.js version supported to ≥ 18 (See 3.0.0 for more details).
3.0.1 / 2025-03-26
===================
* deps: mime-db@1.54.0
3.0.0 / 2024-08-31
===================
* Drop support for node <18
* deps: mime-db@1.53.0
* resolve extension conflicts with mime-score (#119)
* asc -> application/pgp-signature is now application/pgp-keys
* mpp -> application/vnd.ms-project is now application/dash-patch+xml
* ac -> application/vnd.nokia.n-gage.ac+xml is now application/pkix-attr-cert
* bdoc -> application/x-bdoc is now application/bdoc
* wmz -> application/x-msmetafile is now application/x-ms-wmz
* xsl -> application/xslt+xml is now application/xml
* wav -> audio/wave is now audio/wav
* rtf -> text/rtf is now application/rtf
* xml -> text/xml is now application/xml
* mp4 -> video/mp4 is now application/mp4
* mpg4 -> video/mp4 is now application/mp4
2.1.35 / 2022-03-12
===================
* deps: mime-db@1.52.0
- Add extensions from IANA for more `image/*` types
- Add extension `.asc` to `application/pgp-keys`
- Add extensions to various XML types
- Add new upstream MIME types
2.1.34 / 2021-11-08
===================
* deps: mime-db@1.51.0
- Add new upstream MIME types
2.1.33 / 2021-10-01
===================
* deps: mime-db@1.50.0
- Add deprecated iWorks mime types and extensions
- Add new upstream MIME types
2.1.32 / 2021-07-27
===================
* deps: mime-db@1.49.0
- Add extension `.trig` to `application/trig`
- Add new upstream MIME types
2.1.31 / 2021-06-01
===================
* deps: mime-db@1.48.0
- Add extension `.mvt` to `application/vnd.mapbox-vector-tile`
- Add new upstream MIME types
2.1.30 / 2021-04-02
===================
* deps: mime-db@1.47.0
- Add extension `.amr` to `audio/amr`
- Remove ambigious extensions from IANA for `application/*+xml` types
- Update primary extension to `.es` for `application/ecmascript`
2.1.29 / 2021-02-17
===================
* deps: mime-db@1.46.0
- Add extension `.amr` to `audio/amr`
- Add extension `.m4s` to `video/iso.segment`
- Add extension `.opus` to `audio/ogg`
- Add new upstream MIME types
2.1.28 / 2021-01-01
===================
* deps: mime-db@1.45.0
- Add `application/ubjson` with extension `.ubj`
- Add `image/avif` with extension `.avif`
- Add `image/ktx2` with extension `.ktx2`
- Add extension `.dbf` to `application/vnd.dbf`
- Add extension `.rar` to `application/vnd.rar`
- Add extension `.td` to `application/urc-targetdesc+xml`
- Add new upstream MIME types
- Fix extension of `application/vnd.apple.keynote` to be `.key`
2.1.27 / 2020-04-23
===================
* deps: mime-db@1.44.0
- Add charsets from IANA
- Add extension `.cjs` to `application/node`
- Add new upstream MIME types
2.1.26 / 2020-01-05
===================
* deps: mime-db@1.43.0
- Add `application/x-keepass2` with extension `.kdbx`
- Add extension `.mxmf` to `audio/mobile-xmf`
- Add extensions from IANA for `application/*+xml` types
- Add new upstream MIME types
2.1.25 / 2019-11-12
===================
* deps: mime-db@1.42.0
- Add new upstream MIME types
- Add `application/toml` with extension `.toml`
- Add `image/vnd.ms-dds` with extension `.dds`
2.1.24 / 2019-04-20
===================
* deps: mime-db@1.40.0
- Add extensions from IANA for `model/*` types
- Add `text/mdx` with extension `.mdx`
2.1.23 / 2019-04-17
===================
* deps: mime-db@~1.39.0
- Add extensions `.siv` and `.sieve` to `application/sieve`
- Add new upstream MIME types
2.1.22 / 2019-02-14
===================
* deps: mime-db@~1.38.0
- Add extension `.nq` to `application/n-quads`
- Add extension `.nt` to `application/n-triples`
- Add new upstream MIME types
2.1.21 / 2018-10-19
===================
* deps: mime-db@~1.37.0
- Add extensions to HEIC image types
- Add new upstream MIME types
2.1.20 / 2018-08-26
===================
* deps: mime-db@~1.36.0
- Add Apple file extensions from IANA
- Add extensions from IANA for `image/*` types
- Add new upstream MIME types
2.1.19 / 2018-07-17
===================
* deps: mime-db@~1.35.0
- Add extension `.csl` to `application/vnd.citationstyles.style+xml`
- Add extension `.es` to `application/ecmascript`
- Add extension `.owl` to `application/rdf+xml`
- Add new upstream MIME types
- Add UTF-8 as default charset for `text/turtle`
2.1.18 / 2018-02-16
===================
* deps: mime-db@~1.33.0
- Add `application/raml+yaml` with extension `.raml`
- Add `application/wasm` with extension `.wasm`
- Add `text/shex` with extension `.shex`
- Add extensions for JPEG-2000 images
- Add extensions from IANA for `message/*` types
- Add new upstream MIME types
- Update font MIME types
- Update `text/hjson` to registered `application/hjson`
2.1.17 / 2017-09-01
===================
* deps: mime-db@~1.30.0
- Add `application/vnd.ms-outlook`
- Add `application/x-arj`
- Add extension `.mjs` to `application/javascript`
- Add glTF types and extensions
- Add new upstream MIME types
- Add `text/x-org`
- Add VirtualBox MIME types
- Fix `source` records for `video/*` types that are IANA
- Update `font/opentype` to registered `font/otf`
2.1.16 / 2017-07-24
===================
* deps: mime-db@~1.29.0
- Add `application/fido.trusted-apps+json`
- Add extension `.wadl` to `application/vnd.sun.wadl+xml`
- Add extension `.gz` to `application/gzip`
- Add new upstream MIME types
- Update extensions `.md` and `.markdown` to be `text/markdown`
2.1.15 / 2017-03-23
===================
* deps: mime-db@~1.27.0
- Add new mime types
- Add `image/apng`
2.1.14 / 2017-01-14
===================
* deps: mime-db@~1.26.0
- Add new mime types
2.1.13 / 2016-11-18
===================
* deps: mime-db@~1.25.0
- Add new mime types
2.1.12 / 2016-09-18
===================
* deps: mime-db@~1.24.0
- Add new mime types
- Add `audio/mp3`
2.1.11 / 2016-05-01
===================
* deps: mime-db@~1.23.0
- Add new mime types
2.1.10 / 2016-02-15
===================
* deps: mime-db@~1.22.0
- Add new mime types
- Fix extension of `application/dash+xml`
- Update primary extension for `audio/mp4`
2.1.9 / 2016-01-06
==================
* deps: mime-db@~1.21.0
- Add new mime types
2.1.8 / 2015-11-30
==================
* deps: mime-db@~1.20.0
- Add new mime types
2.1.7 / 2015-09-20
==================
* deps: mime-db@~1.19.0
- Add new mime types
2.1.6 / 2015-09-03
==================
* deps: mime-db@~1.18.0
- Add new mime types
2.1.5 / 2015-08-20
==================
* deps: mime-db@~1.17.0
- Add new mime types
2.1.4 / 2015-07-30
==================
* deps: mime-db@~1.16.0
- Add new mime types
2.1.3 / 2015-07-13
==================
* deps: mime-db@~1.15.0
- Add new mime types
2.1.2 / 2015-06-25
==================
* deps: mime-db@~1.14.0
- Add new mime types
2.1.1 / 2015-06-08
==================
* perf: fix deopt during mapping
2.1.0 / 2015-06-07
==================
* Fix incorrectly treating extension-less file name as extension
- i.e. `'path/to/json'` will no longer return `application/json`
* Fix `.charset(type)` to accept parameters
* Fix `.charset(type)` to match case-insensitive
* Improve generation of extension to MIME mapping
* Refactor internals for readability and no argument reassignment
* Prefer `application/*` MIME types from the same source
* Prefer any type over `application/octet-stream`
* deps: mime-db@~1.13.0
- Add nginx as a source
- Add new mime types
2.0.14 / 2015-06-06
===================
* deps: mime-db@~1.12.0
- Add new mime types
2.0.13 / 2015-05-31
===================
* deps: mime-db@~1.11.0
- Add new mime types
2.0.12 / 2015-05-19
===================
* deps: mime-db@~1.10.0
- Add new mime types
2.0.11 / 2015-05-05
===================
* deps: mime-db@~1.9.1
- Add new mime types
2.0.10 / 2015-03-13
===================
* deps: mime-db@~1.8.0
- Add new mime types
2.0.9 / 2015-02-09
==================
* deps: mime-db@~1.7.0
- Add new mime types
- Community extensions ownership transferred from `node-mime`
2.0.8 / 2015-01-29
==================
* deps: mime-db@~1.6.0
- Add new mime types
2.0.7 / 2014-12-30
==================
* deps: mime-db@~1.5.0
- Add new mime types
- Fix various invalid MIME type entries
2.0.6 / 2014-12-30
==================
* deps: mime-db@~1.4.0
- Add new mime types
- Fix various invalid MIME type entries
- Remove example template MIME types
2.0.5 / 2014-12-29
==================
* deps: mime-db@~1.3.1
- Fix missing extensions
2.0.4 / 2014-12-10
==================
* deps: mime-db@~1.3.0
- Add new mime types
2.0.3 / 2014-11-09
==================
* deps: mime-db@~1.2.0
- Add new mime types
2.0.2 / 2014-09-28
==================
* deps: mime-db@~1.1.0
- Add new mime types
- Update charsets
2.0.1 / 2014-09-07
==================
* Support Node.js 0.6
2.0.0 / 2014-09-02
==================
* Use `mime-db`
* Remove `.define()`
1.0.2 / 2014-08-04
==================
* Set charset=utf-8 for `text/javascript`
1.0.1 / 2014-06-24
==================
* Add `text/jsx` type
1.0.0 / 2014-05-12
==================
* Return `false` for unknown types
* Set charset=utf-8 for `application/json`
0.1.0 / 2014-05-02
==================
* Initial release
+23
View File
@@ -0,0 +1,23 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+126
View File
@@ -0,0 +1,126 @@
# mime-types
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][ci-image]][ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
The ultimate javascript content-type utility.
Similar to [the `mime@1.x` module](https://www.npmjs.com/package/mime), except:
- __No fallbacks.__ Instead of naively returning the first available type,
`mime-types` simply returns `false`, so do
`var type = mime.lookup('unrecognized') || 'application/octet-stream'`.
- No `new Mime()` business, so you could do `var lookup = require('mime-types').lookup`.
- No `.define()` functionality
- Bug fixes for `.lookup(path)`
Otherwise, the API is compatible with `mime` 1.x.
## Install
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```sh
$ npm install mime-types
```
## Note on MIME Type Data and Semver
This package considers the programmatic api as the semver compatibility. Additionally, the package which provides the MIME data
for this package (`mime-db`) *also* considers it's programmatic api as the semver contract. This means the MIME type resolution is *not* considered
in the semver bumps.
In the past the version of `mime-db` was pinned to give two decision points when adopting MIME data changes. This is no longer true. We still update the
`mime-db` package here as a `minor` release when necessary, but will use a `^` range going forward. This means that if you want to pin your `mime-db` data
you will need to do it in your application. While this expectation was not set in docs until now, it is how the pacakge operated, so we do not feel this is
a breaking change.
If you wish to pin your `mime-db` version you can do that with overrides via your package manager of choice. See their documentation for how to correctly configure that.
## Adding Types
All mime types are based on [mime-db](https://www.npmjs.com/package/mime-db),
so open a PR there if you'd like to add mime types.
## API
```js
var mime = require('mime-types')
```
All functions return `false` if input is invalid or not found.
### mime.lookup(path)
Lookup the content-type associated with a file.
```js
mime.lookup('json') // 'application/json'
mime.lookup('.md') // 'text/markdown'
mime.lookup('file.html') // 'text/html'
mime.lookup('folder/file.js') // 'application/javascript'
mime.lookup('folder/.htaccess') // false
mime.lookup('cats') // false
```
### mime.contentType(type)
Create a full content-type header given a content-type or extension.
When given an extension, `mime.lookup` is used to get the matching
content-type, otherwise the given content-type is used. Then if the
content-type does not already have a `charset` parameter, `mime.charset`
is used to get the default charset and add to the returned content-type.
```js
mime.contentType('markdown') // 'text/x-markdown; charset=utf-8'
mime.contentType('file.json') // 'application/json; charset=utf-8'
mime.contentType('text/html') // 'text/html; charset=utf-8'
mime.contentType('text/html; charset=iso-8859-1') // 'text/html; charset=iso-8859-1'
// from a full path
mime.contentType(path.extname('/path/to/file.json')) // 'application/json; charset=utf-8'
```
### mime.extension(type)
Get the default extension for a content-type.
```js
mime.extension('application/octet-stream') // 'bin'
```
### mime.charset(type)
Lookup the implied default charset of a content-type.
```js
mime.charset('text/markdown') // 'UTF-8'
```
### var type = mime.types[extension]
A map of content-types by extension.
### [extensions...] = mime.extensions[type]
A map of extensions by content-type.
## License
[MIT](LICENSE)
[ci-image]: https://badgen.net/github/checks/jshttp/mime-types/master?label=ci
[ci-url]: https://github.com/jshttp/mime-types/actions/workflows/ci.yml
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/mime-types/master
[coveralls-url]: https://coveralls.io/r/jshttp/mime-types?branch=master
[node-version-image]: https://badgen.net/npm/node/mime-types
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/mime-types
[npm-url]: https://npmjs.org/package/mime-types
[npm-version-image]: https://badgen.net/npm/v/mime-types
+211
View File
@@ -0,0 +1,211 @@
/*!
* mime-types
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var db = require('mime-db')
var extname = require('path').extname
var mimeScore = require('./mimeScore')
/**
* Module variables.
* @private
*/
var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/
var TEXT_TYPE_REGEXP = /^text\//i
/**
* Module exports.
* @public
*/
exports.charset = charset
exports.charsets = { lookup: charset }
exports.contentType = contentType
exports.extension = extension
exports.extensions = Object.create(null)
exports.lookup = lookup
exports.types = Object.create(null)
exports._extensionConflicts = []
// Populate the extensions/types maps
populateMaps(exports.extensions, exports.types)
/**
* Get the default charset for a MIME type.
*
* @param {string} type
* @return {false|string}
*/
function charset (type) {
if (!type || typeof type !== 'string') {
return false
}
// TODO: use media-typer
var match = EXTRACT_TYPE_REGEXP.exec(type)
var mime = match && db[match[1].toLowerCase()]
if (mime && mime.charset) {
return mime.charset
}
// default text/* to utf-8
if (match && TEXT_TYPE_REGEXP.test(match[1])) {
return 'UTF-8'
}
return false
}
/**
* Create a full Content-Type header given a MIME type or extension.
*
* @param {string} str
* @return {false|string}
*/
function contentType (str) {
// TODO: should this even be in this module?
if (!str || typeof str !== 'string') {
return false
}
var mime = str.indexOf('/') === -1 ? exports.lookup(str) : str
if (!mime) {
return false
}
// TODO: use content-type or other module
if (mime.indexOf('charset') === -1) {
var charset = exports.charset(mime)
if (charset) mime += '; charset=' + charset.toLowerCase()
}
return mime
}
/**
* Get the default extension for a MIME type.
*
* @param {string} type
* @return {false|string}
*/
function extension (type) {
if (!type || typeof type !== 'string') {
return false
}
// TODO: use media-typer
var match = EXTRACT_TYPE_REGEXP.exec(type)
// get extensions
var exts = match && exports.extensions[match[1].toLowerCase()]
if (!exts || !exts.length) {
return false
}
return exts[0]
}
/**
* Lookup the MIME type for a file path/extension.
*
* @param {string} path
* @return {false|string}
*/
function lookup (path) {
if (!path || typeof path !== 'string') {
return false
}
// get the extension ("ext" or ".ext" or full path)
var extension = extname('x.' + path)
.toLowerCase()
.slice(1)
if (!extension) {
return false
}
return exports.types[extension] || false
}
/**
* Populate the extensions and types maps.
* @private
*/
function populateMaps (extensions, types) {
Object.keys(db).forEach(function forEachMimeType (type) {
var mime = db[type]
var exts = mime.extensions
if (!exts || !exts.length) {
return
}
// mime -> extensions
extensions[type] = exts
// extension -> mime
for (var i = 0; i < exts.length; i++) {
var extension = exts[i]
types[extension] = _preferredType(extension, types[extension], type)
// DELETE (eventually): Capture extension->type maps that change as a
// result of switching to mime-score. This is just to help make reviewing
// PR #119 easier, and can be removed once that PR is approved.
const legacyType = _preferredTypeLegacy(
extension,
types[extension],
type
)
if (legacyType !== types[extension]) {
exports._extensionConflicts.push([extension, legacyType, types[extension]])
}
}
})
}
// Resolve type conflict using mime-score
function _preferredType (ext, type0, type1) {
var score0 = type0 ? mimeScore(type0, db[type0].source) : 0
var score1 = type1 ? mimeScore(type1, db[type1].source) : 0
return score0 > score1 ? type0 : type1
}
// Resolve type conflict using pre-mime-score logic
function _preferredTypeLegacy (ext, type0, type1) {
var SOURCE_RANK = ['nginx', 'apache', undefined, 'iana']
var score0 = type0 ? SOURCE_RANK.indexOf(db[type0].source) : 0
var score1 = type1 ? SOURCE_RANK.indexOf(db[type1].source) : 0
if (
exports.types[extension] !== 'application/octet-stream' &&
(score0 > score1 ||
(score0 === score1 &&
exports.types[extension]?.slice(0, 12) === 'application/'))
) {
return type0
}
return score0 > score1 ? type0 : type1
}
@@ -0,0 +1,57 @@
// 'mime-score' back-ported to CommonJS
// Score RFC facets (see https://tools.ietf.org/html/rfc6838#section-3)
var FACET_SCORES = {
'prs.': 100,
'x-': 200,
'x.': 300,
'vnd.': 400,
default: 900
}
// Score mime source (Logic originally from `jshttp/mime-types` module)
var SOURCE_SCORES = {
nginx: 10,
apache: 20,
iana: 40,
default: 30 // definitions added by `jshttp/mime-db` project?
}
var TYPE_SCORES = {
// prefer application/xml over text/xml
// prefer application/rtf over text/rtf
application: 1,
// prefer font/woff over application/font-woff
font: 2,
// prefer video/mp4 over audio/mp4 over application/mp4
// See https://www.rfc-editor.org/rfc/rfc4337.html#section-2
audio: 2,
video: 3,
default: 0
}
/**
* Get each component of the score for a mime type. The sum of these is the
* total score. The higher the score, the more "official" the type.
*/
module.exports = function mimeScore (mimeType, source = 'default') {
if (mimeType === 'application/octet-stream') {
return 0
}
const [type, subtype] = mimeType.split('/')
const facet = subtype.replace(/(\.|x-).*/, '$1')
const facetScore = FACET_SCORES[facet] || FACET_SCORES.default
const sourceScore = SOURCE_SCORES[source] || SOURCE_SCORES.default
const typeScore = TYPE_SCORES[type] || TYPE_SCORES.default
// All else being equal prefer shorter types
const lengthScore = 1 - mimeType.length / 100
return facetScore + sourceScore + typeScore + lengthScore
}
@@ -0,0 +1,49 @@
{
"name": "mime-types",
"description": "The ultimate javascript content-type utility.",
"version": "3.0.2",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jeremiah Senkpiel <fishrock123@rocketmail.com> (https://searchbeam.jit.su)",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
],
"license": "MIT",
"keywords": [
"mime",
"types"
],
"repository": "jshttp/mime-types",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
},
"dependencies": {
"mime-db": "^1.54.0"
},
"devDependencies": {
"eslint": "8.33.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-markdown": "3.0.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-standard": "4.1.0",
"mocha": "10.8.2",
"nyc": "15.1.0"
},
"files": [
"HISTORY.md",
"LICENSE",
"index.js",
"mimeScore.js"
],
"engines": {
"node": ">=18"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --reporter spec test/test.js",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}
+120
View File
@@ -0,0 +1,120 @@
{
"name": "webpack-dev-middleware",
"version": "7.4.5",
"description": "A development middleware for webpack",
"keywords": [
"webpack",
"middleware",
"development"
],
"homepage": "https://github.com/webpack/webpack-dev-middleware",
"bugs": "https://github.com/webpack/webpack-dev-middleware/issues",
"repository": "webpack/webpack-dev-middleware",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"license": "MIT",
"author": "Tobias Koppers @sokra",
"main": "dist/index.js",
"types": "types/index.d.ts",
"files": [
"dist",
"types"
],
"scripts": {
"commitlint": "commitlint --from=main",
"security": "npm audit --production",
"lint:prettier": "prettier --cache --list-different .",
"lint:code": "eslint --cache .",
"lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all -l -p \"lint:**\"",
"fix:js": "npm run lint:code -- --fix",
"fix:prettier": "npm run lint:prettier -- --write",
"fix": "npm-run-all -l fix:js fix:prettier",
"clean": "del-cli dist types",
"prebuild": "npm run clean",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir types && prettier \"types/**/*.ts\" --write",
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build": "npm-run-all -p \"build:**\"",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
"pretest": "npm run lint",
"test": "npm run test:coverage",
"prepare": "husky && npm run build",
"release": "standard-version"
},
"dependencies": {
"colorette": "^2.0.10",
"memfs": "^4.43.1",
"mime-types": "^3.0.1",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"
},
"devDependencies": {
"@babel/cli": "^7.16.7",
"@babel/core": "^7.16.7",
"@babel/preset-env": "^7.16.7",
"@eslint/js": "^9.28.0",
"@eslint/markdown": "^7.1.0",
"@commitlint/cli": "^19.0.3",
"@commitlint/config-conventional": "^19.0.3",
"@fastify/express": "^4.0.2",
"@hapi/hapi": "^21.3.7",
"@hono/node-server": "^1.12.0",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/connect": "^3.4.35",
"@types/express": "^5.0.2",
"@types/mime-types": "^3.0.1",
"@types/node": "^22.3.0",
"@types/on-finished": "^2.3.4",
"babel-jest": "^30.1.2",
"connect": "^3.7.0",
"cross-env": "^7.0.3",
"cspell": "^8.3.2",
"deepmerge": "^4.2.2",
"del-cli": "^6.0.0",
"globals": "^16.2.0",
"eslint": "^9.28.0",
"eslint-config-webpack": "^4.5.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-jsdoc": "^56.1.2",
"eslint-plugin-n": "^17.19.0",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-unicorn": "^61.0.2",
"execa": "^5.1.1",
"express-4": "npm:express@^4",
"express": "^5.1.0",
"fastify": "^5.2.1",
"file-loader": "^6.2.0",
"finalhandler": "^2.1.0",
"hono": "^4.4.13",
"husky": "^9.1.3",
"jest": "^30.1.3",
"koa": "^3.0.0",
"lint-staged": "^15.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.6.0",
"router": "^2.2.0",
"standard-version": "^9.3.0",
"supertest": "^7.0.0",
"typescript": "^5.3.3",
"webpack": "^5.101.0"
},
"peerDependencies": {
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"webpack": {
"optional": true
}
},
"engines": {
"node": ">= 18.12.0"
}
}
+512
View File
@@ -0,0 +1,512 @@
export = wdm;
/** @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 locals
*/
/** @typedef {import("http").IncomingMessage} IncomingMessage */
/** @typedef {import("http").ServerResponse & ExtendedServerResponse} ServerResponse */
/**
* @callback NextFunction
* @param {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 | undefined} 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> | 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 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
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @callback Middleware
* @param {RequestInternal} req
* @param {ResponseInternal} res
* @param {NextFunction} next
* @returns {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 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
*/
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {API<RequestInternal, ResponseInternal>} webpack dev middleware
*/
declare function wdm<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
>(
compiler: Compiler | MultiCompiler,
options?: Options<RequestInternal, ResponseInternal> | undefined,
): API<RequestInternal, ResponseInternal>;
declare namespace wdm {
export {
hapiWrapper,
koaWrapper,
honoWrapper,
Schema,
Compiler,
MultiCompiler,
Configuration,
Stats,
MultiStats,
ReadStream,
ExtendedServerResponse,
IncomingMessage,
ServerResponse,
NextFunction,
WatchOptions,
Watching,
MultiWatching,
OutputFileSystem,
Logger,
Callback,
ResponseData,
ModifyResponseData,
Context,
FilledContext,
NormalizedHeaders,
Headers,
Options,
Middleware,
Extra,
GetFilenameFromUrl,
WaitUntilValid,
Invalidate,
Close,
AdditionalMethods,
API,
WithOptional,
WithoutUndefined,
HapiPluginBase,
HapiPlugin,
HapiOptions,
};
}
/**
* @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
* @returns {HapiPlugin<HapiServer, HapiOptionsInternal>} hapi wrapper
*/
declare function hapiWrapper<
HapiServer,
HapiOptionsInternal extends HapiOptions,
>(): HapiPlugin<HapiServer, HapiOptionsInternal>;
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {(ctx: any, next: Function) => Promise<void> | void} kow wrapper
*/
declare function koaWrapper<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
>(
compiler: Compiler | MultiCompiler,
options?: Options<RequestInternal, ResponseInternal> | undefined,
): (ctx: any, next: Function) => Promise<void> | void;
/**
* @template {IncomingMessage} [RequestInternal=IncomingMessage]
* @template {ServerResponse} [ResponseInternal=ServerResponse]
* @param {Compiler | MultiCompiler} compiler compiler
* @param {Options<RequestInternal, ResponseInternal>=} options options
* @returns {(ctx: any, next: Function) => Promise<void> | void} hono wrapper
*/
declare function honoWrapper<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
>(
compiler: Compiler | MultiCompiler,
options?: Options<RequestInternal, ResponseInternal> | undefined,
): (ctx: any, next: Function) => Promise<void> | void;
type Schema = import("schema-utils/declarations/validate").Schema;
type Compiler = import("webpack").Compiler;
type MultiCompiler = import("webpack").MultiCompiler;
type Configuration = import("webpack").Configuration;
type Stats = import("webpack").Stats;
type MultiStats = import("webpack").MultiStats;
type ReadStream = import("fs").ReadStream;
type ExtendedServerResponse = {
/**
* locals
*/
locals?:
| {
webpack?: {
devMiddleware?: Context<IncomingMessage, ServerResponse>;
};
}
| undefined;
};
type IncomingMessage = import("http").IncomingMessage;
type ServerResponse = import("http").ServerResponse & ExtendedServerResponse;
type NextFunction = (err?: any | undefined) => void;
type WatchOptions = NonNullable<Configuration["watchOptions"]>;
type Watching = Compiler["watching"];
type MultiWatching = ReturnType<MultiCompiler["watch"]>;
type OutputFileSystem = import("webpack").OutputFileSystem & {
createReadStream?: typeof import("fs").createReadStream;
statSync: import("fs").StatSyncFn;
readFileSync: typeof import("fs").readFileSync;
};
type Logger = ReturnType<Compiler["getInfrastructureLogger"]>;
type Callback = (stats?: (Stats | MultiStats) | undefined) => any;
type ResponseData = {
/**
* data
*/
data: Buffer | ReadStream;
/**
* byte length
*/
byteLength: number;
};
type ModifyResponseData<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = (
req: RequestInternal,
res: ResponseInternal,
data: Buffer | ReadStream,
byteLength: number,
) => ResponseData;
type Context<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = {
/**
* state
*/
state: boolean;
/**
* stats
*/
stats: Stats | MultiStats | undefined;
/**
* callbacks
*/
callbacks: Callback[];
/**
* options
*/
options: Options<RequestInternal, ResponseInternal>;
/**
* compiler
*/
compiler: Compiler | MultiCompiler;
/**
* watching
*/
watching: Watching | MultiWatching | undefined;
/**
* logger
*/
logger: Logger;
/**
* output file system
*/
outputFileSystem: OutputFileSystem;
};
type FilledContext<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = WithoutUndefined<Context<RequestInternal, ResponseInternal>, "watching">;
type NormalizedHeaders =
| Record<string, string | number>
| Array<{
key: string;
value: number | string;
}>;
type Headers<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> =
| NormalizedHeaders
| ((
req: RequestInternal,
res: ResponseInternal,
context: Context<RequestInternal, ResponseInternal>,
) => void | undefined | NormalizedHeaders)
| undefined;
type Options<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = {
/**
* mime types
*/
mimeTypes?:
| {
[key: string]: string;
}
| undefined;
/**
* mime type default
*/
mimeTypeDefault?: (string | undefined) | undefined;
/**
* write to disk
*/
writeToDisk?: (boolean | ((targetPath: string) => boolean)) | undefined;
/**
* methods
*/
methods?: string[] | undefined;
/**
* headers
*/
headers?: Headers<RequestInternal, ResponseInternal> | undefined;
/**
* public path
*/
publicPath?: NonNullable<Configuration["output"]>["publicPath"] | undefined;
/**
* stats
*/
stats?: Configuration["stats"] | undefined;
/**
* is server side render
*/
serverSideRender?: boolean | undefined;
/**
* output file system
*/
outputFileSystem?: OutputFileSystem | undefined;
/**
* index
*/
index?: (boolean | string) | undefined;
/**
* modify response data
*/
modifyResponseData?:
| ModifyResponseData<RequestInternal, ResponseInternal>
| undefined;
/**
* options to generate etag header
*/
etag?: ("weak" | "strong") | undefined;
/**
* options to generate last modified header
*/
lastModified?: boolean | undefined;
/**
* options to generate cache headers
*/
cacheControl?:
| (
| boolean
| number
| string
| {
maxAge?: number;
immutable?: boolean;
}
)
| undefined;
/**
* is cache immutable
*/
cacheImmutable?: boolean | undefined;
};
type Middleware<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = (
req: RequestInternal,
res: ResponseInternal,
next: NextFunction,
) => Promise<void>;
type Extra = import("./utils/getFilenameFromUrl").Extra;
type GetFilenameFromUrl = (
url: string,
extra?: Extra | undefined,
) => string | undefined;
type WaitUntilValid = (callback: Callback) => any;
type Invalidate = (callback: Callback) => any;
type Close = (callback: (err: Error | null | undefined) => void) => any;
type AdditionalMethods<
RequestInternal extends IncomingMessage,
ResponseInternal extends ServerResponse,
> = {
/**
* get filename from url
*/
getFilenameFromUrl: GetFilenameFromUrl;
/**
* wait until valid
*/
waitUntilValid: WaitUntilValid;
/**
* invalidate
*/
invalidate: Invalidate;
/**
* close
*/
close: Close;
/**
* context
*/
context: Context<RequestInternal, ResponseInternal>;
};
type API<
RequestInternal extends IncomingMessage = import("http").IncomingMessage,
ResponseInternal extends ServerResponse = ServerResponse,
> = Middleware<RequestInternal, ResponseInternal> &
AdditionalMethods<RequestInternal, ResponseInternal>;
type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
type WithoutUndefined<T, K extends keyof T> = T & {
[P in K]: NonNullable<T[P]>;
};
type HapiPluginBase<S, O> = {
/**
* register
*/
register: (server: S, options: O) => void | Promise<void>;
};
type HapiPlugin<S, O> = HapiPluginBase<S, O> & {
pkg: {
name: string;
};
multiple: boolean;
};
type HapiOptions = Options & {
compiler: Compiler | MultiCompiler;
};
+53
View File
@@ -0,0 +1,53 @@
export = wrapper;
/**
* @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 context
* @returns {import("./index.js").Middleware<Request, Response>} wrapper
*/
declare function wrapper<
Request extends IncomingMessage,
Response extends ServerResponse,
>(
context: import("./index.js").FilledContext<Request, Response>,
): import("./index.js").Middleware<Request, Response>;
declare namespace wrapper {
export {
SendErrorOptions,
NextFunction,
IncomingMessage,
ServerResponse,
NormalizedHeaders,
ReadStream,
};
}
/**
* send error options
*/
type SendErrorOptions<
Request extends IncomingMessage,
Response extends ServerResponse,
> = {
/**
* headers
*/
headers?: Record<string, number | string | string[] | undefined> | undefined;
/**
* modify response data callback
*/
modifyResponseData?:
| import("./index").ModifyResponseData<Request, Response>
| undefined;
};
type NextFunction = import("./index.js").NextFunction;
type IncomingMessage = import("./index.js").IncomingMessage;
type ServerResponse = import("./index.js").ServerResponse;
type NormalizedHeaders = import("./index.js").NormalizedHeaders;
type ReadStream = import("fs").ReadStream;
+254
View File
@@ -0,0 +1,254 @@
export type IncomingMessage = import("../index.js").IncomingMessage;
export type ServerResponse = import("../index.js").ServerResponse;
export type OutputFileSystem = import("../index").OutputFileSystem;
export type ExpectedIncomingMessage = {
/**
* get header extra method
*/
getHeader?: ((name: string) => string | string[] | undefined) | undefined;
/**
* get method extra method
*/
getMethod?: (() => string | undefined) | undefined;
/**
* get URL extra method
*/
getURL?: (() => string | undefined) | undefined;
};
export type ExpectedServerResponse = {
/**
* set status code
*/
setStatusCode?: ((status: number) => void) | undefined;
/**
* get status code
*/
getStatusCode?: (() => number) | undefined;
/**
* get header
*/
getHeader: (name: string) => string | string[] | undefined | number;
/**
* set header
*/
setHeader?:
| ((
name: string,
value: number | string | Readonly<string[]>,
) => ExpectedServerResponse)
| undefined;
/**
* remove header
*/
removeHeader?: ((name: string) => void) | undefined;
/**
* send
*/
send?: ((data: string | Buffer) => void) | undefined;
/**
* finish
*/
finish?: ((data?: string | Buffer) => void) | undefined;
/**
* get response header
*/
getResponseHeaders?: (() => string[]) | undefined;
/**
* get headers sent
*/
getHeadersSent?: (() => boolean) | undefined;
/**
* stream
*/
stream?: ((data: any) => void) | undefined;
/**
* get outgoing
*/
getOutgoing?: (() => any) | undefined;
/**
* set state
*/
setState?: ((name: string, value: any) => void) | undefined;
};
/**
* @param {string} filename filename
* @param {OutputFileSystem} outputFileSystem output file system
* @param {number} start start
* @param {number} end end
* @returns {{ bufferOrStream: (Buffer | import("fs").ReadStream), byteLength: number }} result with buffer or stream and byte length
*/
export function createReadStreamOrReadFileSync(
filename: string,
outputFileSystem: OutputFileSystem,
start: number,
end: number,
): {
bufferOrStream: Buffer | import("fs").ReadStream;
byteLength: number;
};
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {(string | Buffer)=} data data
*/
export function finish<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response, data?: (string | Buffer) | undefined): void;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @returns {boolean} true when headers were sent, otherwise false
*/
export function getHeadersSent<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response): boolean;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @returns {Response} res res
*/
export function getOutgoing<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response): Response;
/** @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 get header extra method
* @property {(() => string | undefined)=} getMethod get method extra method
* @property {(() => string | undefined)=} getURL get URL extra method
*/
/**
* @typedef {object} ExpectedServerResponse
* @property {((status: number) => void)=} setStatusCode set status code
* @property {(() => number)=} getStatusCode get status code
* @property {((name: string) => string | string[] | undefined | number)} getHeader get header
* @property {((name: string, value: number | string | Readonly<string[]>) => ExpectedServerResponse)=} setHeader set header
* @property {((name: string) => void)=} removeHeader remove header
* @property {((data: string | Buffer) => void)=} send send
* @property {((data?: string | Buffer) => void)=} finish finish
* @property {(() => string[])=} getResponseHeaders get response header
* @property {(() => boolean)=} getHeadersSent get headers sent
* @property {((data: any) => void)=} stream stream
* @property {(() => any)=} getOutgoing get outgoing
* @property {((name: string, value: any) => void)=} setState set state
*/
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req req
* @param {string} name name
* @returns {string | string[] | undefined} request header
*/
export function getRequestHeader<
Request extends IncomingMessage & ExpectedIncomingMessage,
>(req: Request, name: string): string | string[] | undefined;
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req req
* @returns {string | undefined} request method
*/
export function getRequestMethod<
Request extends IncomingMessage & ExpectedIncomingMessage,
>(req: Request): string | undefined;
/**
* @template {IncomingMessage & ExpectedIncomingMessage} Request
* @param {Request} req req
* @returns {string | undefined} request URL
*/
export function getRequestURL<
Request extends IncomingMessage & ExpectedIncomingMessage,
>(req: Request): string | undefined;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @returns {string | string[] | undefined | number} header
*/
export function getResponseHeader<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response, name: string): string | string[] | undefined | number;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @returns {string[]} header names
*/
export function getResponseHeaders<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response): string[];
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @returns {number} status code
*/
export function getStatusCode<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response): number;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
*/
export function initState<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response): void;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {import("fs").ReadStream} bufferOrStream buffer or stream
*/
export function pipe<Response extends ServerResponse & ExpectedServerResponse>(
res: Response,
bufferOrStream: import("fs").ReadStream,
): void;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @returns {void}
*/
export function removeResponseHeader<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response, name: string): void;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string | Buffer} bufferOrString buffer or string
* @returns {void}
*/
export function send<Response extends ServerResponse & ExpectedServerResponse>(
res: Response,
bufferOrString: string | Buffer,
): void;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @param {number | string | Readonly<string[]>} value value
* @returns {Response} response
*/
export function setResponseHeader<
Response extends ServerResponse & ExpectedServerResponse,
>(
res: Response,
name: string,
value: number | string | Readonly<string[]>,
): Response;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {string} name name
* @param {any} value state
* @returns {void}
*/
export function setState<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response, name: string, value: any): void;
/**
* @template {ServerResponse & ExpectedServerResponse} Response
* @param {Response} res res
* @param {number} code code
* @returns {void}
*/
export function setStatusCode<
Response extends ServerResponse & ExpectedServerResponse,
>(res: Response, code: number): void;
+6
View File
@@ -0,0 +1,6 @@
export = escapeHtml;
/**
* @param {string} string raw HTML
* @returns {string} escaped HTML
*/
declare function escapeHtml(string: string): string;
+15
View File
@@ -0,0 +1,15 @@
export = etag;
/**
* Create a simple ETag.
* @param {Buffer | ReadStream | Stats} entity entity
* @returns {Promise<{ hash: string, buffer?: Buffer }>} etag
*/
declare function etag(entity: Buffer | ReadStream | Stats): Promise<{
hash: string;
buffer?: Buffer;
}>;
declare namespace etag {
export { Stats, ReadStream };
}
type Stats = import("fs").Stats;
type ReadStream = import("fs").ReadStream;
@@ -0,0 +1,49 @@
export = getFilenameFromUrl;
/**
* @typedef {object} Extra
* @property {import("fs").Stats=} stats stats
* @property {number=} errorCode error code
* @property {boolean=} immutable true when immutable, otherwise false
*/
/**
* decodeURIComponent.
*
* Allows V8 to only deoptimize this fn instead of all of send().
* @param {string} input
* @returns {string}
*/
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").FilledContext<Request, Response>} context context
* @param {string} url url
* @param {Extra=} extra extra
* @returns {string | undefined} filename
*/
declare function getFilenameFromUrl<
Request extends IncomingMessage,
Response extends ServerResponse,
>(
context: import("../index.js").FilledContext<Request, Response>,
url: string,
extra?: Extra | undefined,
): string | undefined;
declare namespace getFilenameFromUrl {
export { IncomingMessage, ServerResponse, Extra };
}
type IncomingMessage = import("../index.js").IncomingMessage;
type ServerResponse = import("../index.js").ServerResponse;
type Extra = {
/**
* stats
*/
stats?: import("fs").Stats | undefined;
/**
* error code
*/
errorCode?: number | undefined;
/**
* true when immutable, otherwise false
*/
immutable?: boolean | undefined;
};
+39
View File
@@ -0,0 +1,39 @@
export = getPaths;
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").Stats} Stats */
/** @typedef {import("webpack").MultiStats} MultiStats */
/** @typedef {import("webpack").Asset} Asset */
/** @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 context
* @returns {{ outputPath: string, publicPath: string, assetsInfo: Asset["info"] }[]} paths
*/
declare function getPaths<
Request extends IncomingMessage,
Response extends ServerResponse,
>(
context: import("../index.js").FilledContext<Request, Response>,
): {
outputPath: string;
publicPath: string;
assetsInfo: Asset["info"];
}[];
declare namespace getPaths {
export {
Compiler,
Stats,
MultiStats,
Asset,
IncomingMessage,
ServerResponse,
};
}
type Compiler = import("webpack").Compiler;
type Stats = import("webpack").Stats;
type MultiStats = import("webpack").MultiStats;
type Asset = import("webpack").Asset;
type IncomingMessage = import("../index.js").IncomingMessage;
type ServerResponse = import("../index.js").ServerResponse;
+35
View File
@@ -0,0 +1,35 @@
export = memorize;
/**
* @template T
* @typedef {(...args: any) => T} FunctionReturning
*/
/**
* @template T
* @param {FunctionReturning<T>} fn memorized function
* @param {({ cache?: Map<string, { data: T }> } | undefined)=} cache cache
* @param {((value: T) => T)=} callback callback
* @returns {FunctionReturning<T>} new function
*/
declare function memorize<T>(
fn: FunctionReturning<T>,
{
cache,
}?:
| (
| {
cache?: Map<
string,
{
data: T;
}
>;
}
| undefined
)
| undefined,
callback?: ((value: T) => T) | undefined,
): FunctionReturning<T>;
declare namespace memorize {
export { FunctionReturning };
}
type FunctionReturning<T> = (...args: any) => T;
+7
View File
@@ -0,0 +1,7 @@
export = parseTokenList;
/**
* Parse a HTTP token list.
* @param {string} str str
* @returns {string[]} tokens
*/
declare function parseTokenList(str: string): string[];
+26
View File
@@ -0,0 +1,26 @@
export = ready;
/** @typedef {import("../index.js").IncomingMessage} IncomingMessage */
/** @typedef {import("../index.js").ServerResponse} ServerResponse */
/** @typedef {import("../index.js").Callback} Callback */
/**
* @template {IncomingMessage} Request
* @template {ServerResponse} Response
* @param {import("../index.js").FilledContext<Request, Response>} context context
* @param {Callback} callback callback
* @param {Request=} req req
* @returns {void}
*/
declare function ready<
Request extends IncomingMessage,
Response extends ServerResponse,
>(
context: import("../index.js").FilledContext<Request, Response>,
callback: Callback,
req?: Request | undefined,
): void;
declare namespace ready {
export { IncomingMessage, ServerResponse, Callback };
}
type IncomingMessage = import("../index.js").IncomingMessage;
type ServerResponse = import("../index.js").ServerResponse;
type Callback = import("../index.js").Callback;
+54
View File
@@ -0,0 +1,54 @@
export = setupHooks;
/** @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 context
*/
declare function setupHooks<
Request extends IncomingMessage,
Response extends ServerResponse,
>(
context: import("../index.js").WithOptional<
import("../index.js").Context<Request, Response>,
"watching" | "outputFileSystem"
>,
): void;
declare namespace setupHooks {
export {
Configuration,
Compiler,
MultiCompiler,
Stats,
MultiStats,
IncomingMessage,
ServerResponse,
StatsOptions,
MultiStatsOptions,
StatsObjectOptions,
};
}
type Configuration = import("webpack").Configuration;
type Compiler = import("webpack").Compiler;
type MultiCompiler = import("webpack").MultiCompiler;
type Stats = import("webpack").Stats;
type MultiStats = import("webpack").MultiStats;
type IncomingMessage = import("../index.js").IncomingMessage;
type ServerResponse = import("../index.js").ServerResponse;
type StatsOptions = Configuration["stats"];
type MultiStatsOptions = {
children: Configuration["stats"][];
};
type StatsObjectOptions = Exclude<
Configuration["stats"],
boolean | string | undefined
>;
@@ -0,0 +1,24 @@
export = setupOutputFileSystem;
/** @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 context
*/
declare function setupOutputFileSystem<
Request extends IncomingMessage,
Response extends ServerResponse,
>(
context: import("../index.js").WithOptional<
import("../index.js").Context<Request, Response>,
"watching" | "outputFileSystem"
>,
): void;
declare namespace setupOutputFileSystem {
export { MultiCompiler, IncomingMessage, ServerResponse };
}
type MultiCompiler = import("webpack").MultiCompiler;
type IncomingMessage = import("../index.js").IncomingMessage;
type ServerResponse = import("../index.js").ServerResponse;
+34
View File
@@ -0,0 +1,34 @@
export = setupWriteToDisk;
/** @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 context
*/
declare function setupWriteToDisk<
Request extends IncomingMessage,
Response extends ServerResponse,
>(
context: import("../index.js").WithOptional<
import("../index.js").Context<Request, Response>,
"watching" | "outputFileSystem"
>,
): void;
declare namespace setupWriteToDisk {
export {
Compiler,
MultiCompiler,
Compilation,
IncomingMessage,
ServerResponse,
};
}
type Compiler = import("webpack").Compiler;
type MultiCompiler = import("webpack").MultiCompiler;
type Compilation = import("webpack").Compilation;
type IncomingMessage = import("../index.js").IncomingMessage;
type ServerResponse = import("../index.js").ServerResponse;