Repository: webpack/webpack-dev-middleware (JavaScript)

Issue:
Allow setting default mime-type from webpack config.
<!-- Please don't delete this template otherwise your issue will be closed immediately -->
<!-- Before creating an issue please make sure you are using the latest version of webpack. -->
### Feature Proposal

Configuration option to set default mime-type

```js
// webpack.dev.js
module.exports = {
  devServer: {
    devMiddleWare: {
      // provide default when mime-type evals to false.
      mimeTypeDefault: 'text/html'
    }
  }
}
```
<!-- Please ask questions on discussions, StackOverflow or the webpack Gitter. -->
Stack overflow issue detailing the context in which this use case arose: https://stackoverflow.com/questions/70011373/how-to-configure-webpack-dev-server-to-serve-extensionless-files-as-text-html

<!-- https://github.com/webpack/webpack/discussions -->
<!-- https://stackoverflow.com/questions/ask?tags=webpack -->
<!-- https://gitter.im/webpack/webpack -->
<!-- Issues which contain questions or support requests will be closed. -->

### Feature Use Case
For developing applications with pages that don't have a file extension.  E.g. localhost:8080/example.  It would be convenient to be able to specify a default mime-type for files that don't have a file extension; specifically, 'text/html'.

#### Current State
The express server will emit this content type as `application/octet-stream` even though middleware goes out of it's way to avoid inappropriately setting a mime-type at [middleware.js#L104-L113](https://github.com/webpack/webpack-dev-middleware/blob/5a6a3f0f8e6b0f8fef33629f0f6fa5bed545a88c/src/middleware.js#L104-L113).  Express uses mime 1.6 which eventually fills in a default type at [mine.js#L69-L73](https://github.com/broofa/mime/blob/ad2c94c807ebecf365b229919755cb51de5e2e6f/mime.js#L69-L73) The result being there is a default content type, and it's determined further down in the stack.  

In production deployment this is not a problem. Configuring production web servers to emit the desired mime type, re-writing the requests, or specifying the mime-type in metadata makes this not a production issue. 

### Please paste the results of `npx webpack-cli info` here, and mention other relevant information
```sh
∫ wp5-extensionless/webpack-boilerplate (main)⮞ npx webpack-cli info

  System:
    OS: Linux 5.11 Ubuntu 20.04.3 LTS (Focal Fossa)
    CPU: (4) x64 Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz
    Memory: 11.16 GB / 31.31 GB
  Binaries:
    Node: 16.13.0 - /usr/bin/node
    npm: 8.1.0 - /usr/bin/npm
  Browsers:
    Firefox: 94.0
  Packages:
    babel-loader: ^8.2.2 => 8.2.2 
    clean-webpack-plugin: ^4.0.0 => 4.0.0 
    copy-webpack-plugin: ^9.0.1 => 9.0.1 
    css-loader: ^6.4.0 => 6.4.0 
    css-minimizer-webpack-plugin: ^3.1.1 => 3.1.1 
    eslint-import-resolver-webpack: ^0.13.1 => 0.13.1 
    html-webpack-plugin: ^5.3.2 => 5.3.2 
    postcss-loader: ^6.2.0 => 6.2.0 
    sass-loader: ^12.2.0 => 12.2.0 
    style-loader: ^3.3.0 => 3.3.0 
    webpack: ^5.58.2 => 5.58.2 
    webpack-cli: ^4.9.0 => 4.9.0 
    webpack-dev-server: ^4.3.1 => 4.3.1 
    webpack-merge: ^5.8.0 => 5.8.0 
```


Discussion:
PR welcome



Patch:
diff --git a/README.md b/README.md
index e1d0df64a..246716c31 100644
--- a/README.md
+++ b/README.md
@@ -60,17 +60,18 @@ 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.                                                          |
-|       **[`publicPath`](#publicpath)**       |         `String`          |  `output.publicPath` (from a configuration)   | 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.                 |
+|                    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.                     |
+|       **[`publicPath`](#publicpath)**       |  `String`  |  `output.publicPath` (from a configuration)   | 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. |
 
 The middleware accepts an `options` Object. The following is a property reference for the Object.
 
@@ -164,6 +165,13 @@ 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.
+
 ### publicPath
 
 Type: `String`
diff --git a/src/index.js b/src/index.js
index 0d6d6dec0..7252768c0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -80,6 +80,7 @@ const noop = () => {};
  * @template {ServerResponse} ResponseInternal
  * @typedef {Object} Options
  * @property {{[key: string]: string}} [mimeTypes]
+ * @property {string | undefined} [mimeTypeDefault]
  * @property {boolean | ((targetPath: string) => boolean)} [writeToDisk]
  * @property {string} [methods]
  * @property {Headers<RequestInternal, ResponseInternal>} [headers]
diff --git a/src/middleware.js b/src/middleware.js
index 0d314c43d..76c2e4eab 100644
--- a/src/middleware.js
+++ b/src/middleware.js
@@ -146,6 +146,12 @@ function wrapper(context) {
         // https://tools.ietf.org/html/rfc7231#section-3.1.1.5
         if (contentType) {
           setHeaderForResponse(res, "Content-Type", contentType);
+        } else if (context.options.mimeTypeDefault) {
+          setHeaderForResponse(
+            res,
+            "Content-Type",
+            context.options.mimeTypeDefault
+          );
         }
       }
 
diff --git a/src/options.json b/src/options.json
index 0fa043a38..7a56c491c 100644
--- a/src/options.json
+++ b/src/options.json
@@ -6,6 +6,11 @@
       "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",
diff --git a/types/index.d.ts b/types/index.d.ts
index 152459637..e4e96841c 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -58,6 +58,7 @@ export = wdm;
  * @template {ServerResponse} ResponseInternal
  * @typedef {Object} Options
  * @property {{[key: string]: string}} [mimeTypes]
+ * @property {string | undefined} [mimeTypeDefault]
  * @property {boolean | ((targetPath: string) => boolean)} [writeToDisk]
  * @property {string} [methods]
  * @property {Headers<RequestInternal, ResponseInternal>} [headers]
@@ -164,6 +165,7 @@ type Options<
         [key: string]: string;
       }
     | undefined;
+  mimeTypeDefault?: string | undefined;
   writeToDisk?: boolean | ((targetPath: string) => boolean) | undefined;
   methods?: string | undefined;
   headers?: Headers<RequestInternal, ResponseInternal>;


Test patch:
diff --git a/test/middleware.test.js b/test/middleware.test.js
index 4135765dd..da1bdf17a 100644
--- a/test/middleware.test.js
+++ b/test/middleware.test.js
@@ -2163,6 +2163,52 @@ describe.each([
       });
     });
 
+    describe("mimeTypeDefault option", () => {
+      describe('should set the correct value for "Content-Type" header to unknown MIME type', () => {
+        beforeAll((done) => {
+          const outputPath = path.resolve(__dirname, "./outputs/basic");
+          const compiler = getCompiler({
+            ...webpackConfig,
+            output: {
+              filename: "bundle.js",
+              path: outputPath,
+            },
+          });
+
+          instance = middleware(compiler, {
+            mimeTypeDefault: "text/plain",
+          });
+
+          app = framework();
+          app.use(instance);
+
+          listen = listenShorthand(done);
+
+          req = request(app);
+
+          instance.context.outputFileSystem.mkdirSync(outputPath, {
+            recursive: true,
+          });
+          instance.context.outputFileSystem.writeFileSync(
+            path.resolve(outputPath, "file.unknown"),
+            "welcome"
+          );
+        });
+
+        afterAll(close);
+
+        it('should return the "200" code for the "GET" request to "file.html"', async () => {
+          const response = await req.get("/file.unknown");
+
+          expect(response.statusCode).toEqual(200);
+          expect(
+            response.headers["content-type"].startsWith("text/plain")
+          ).toBe(true);
+          expect(response.text).toEqual("welcome");
+        });
+      });
+    });
+
     describe("watchOptions option", () => {
       describe("should work without value", () => {
         let compiler;