Index: frontend/node_modules/html-webpack-plugin/LICENSE
===================================================================
--- frontend/node_modules/html-webpack-plugin/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -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.
Index: frontend/node_modules/html-webpack-plugin/README.md
===================================================================
--- frontend/node_modules/html-webpack-plugin/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,667 @@
+[![npm][npm]][npm-url]
+[![node][node]][node-url]
+![npm](https://img.shields.io/npm/dw/html-webpack-plugin.svg)
+[![tests][tests]][tests-url]
+[![Backers on Open Collective](https://opencollective.com/html-webpack-plugin/backers/badge.svg)](#backers)
+[![Sponsors on Open Collective](https://opencollective.com/html-webpack-plugin/sponsors/badge.svg)](#sponsors)
+
+<div align="center">
+  <img width="200" height="200" src="https://www.w3.org/html/logo/downloads/HTML5_Badge.svg">
+  <a href="https://github.com/webpack/webpack">
+    <img width="200" height="200"
+      src="https://webpack.js.org/assets/icon-square-big.svg">
+  </a>
+  <div>
+    <img width="100" height="100" title="Webpack Plugin" src="http://michael-ciniawsky.github.io/postcss-load-plugins/logo.svg">
+  </div>
+  <h1>HTML Webpack Plugin</h1>
+  <p>Plugin that simplifies creation of HTML files to serve your bundles</p>
+</div>
+
+<h2 align="center">Install</h2>
+
+<h3>Webpack 5</h3>
+
+```bash
+  npm i --save-dev html-webpack-plugin
+```
+
+```bash
+  yarn add --dev html-webpack-plugin
+```
+
+<h3>Webpack 4</h3>
+
+```bash
+  npm i --save-dev html-webpack-plugin@4
+```
+
+```bash
+  yarn add --dev html-webpack-plugin@4
+```
+
+This is a [webpack](http://webpack.js.org/) plugin that simplifies creation of HTML files to serve your `webpack` bundles. This is especially useful for `webpack` bundles that include a hash in the filename which changes every compilation. You can either let the plugin generate an HTML file for you, supply
+your own template using `lodash` templates or use your own loader.
+
+<h2 align="center">Sponsors</h2>
+
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/0/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/0/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/1/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/1/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/2/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/2/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/3/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/3/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/4/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/4/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/5/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/5/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/6/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/6/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/7/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/7/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/8/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/8/avatar.svg"></a>
+<a href="https://opencollective.com/html-webpack-plugin/sponsor/9/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/9/avatar.svg"></a>
+
+Thanks for supporting the ongoing improvements to the html-webpack-plugin!
+
+<h2 align="center">Zero Config</h2>
+
+The `html-webpack-plugin` works without configuration.  
+It's a great addition to the [⚙️ webpack-config-plugins](https://github.com/namics/webpack-config-plugins/blob/master/README.md#zero-config-webpack-dev-server-example).
+
+<h2 align="center">Plugins</h2>
+
+The `html-webpack-plugin` provides [hooks](https://github.com/jantimon/html-webpack-plugin#events) to extend it to your needs. There are already some really powerful plugins which can be integrated with zero configuration
+
+- [webpack-subresource-integrity](https://www.npmjs.com/package/webpack-subresource-integrity) for enhanced asset security
+- [appcache-webpack-plugin](https://github.com/lettertwo/appcache-webpack-plugin) for iOS and Android offline usage
+- [favicons-webpack-plugin](https://github.com/jantimon/favicons-webpack-plugin) which generates favicons and icons for iOS, Android and desktop browsers
+- [html-webpack-harddisk-plugin](https://github.com/jantimon/html-webpack-harddisk-plugin) can be used to always write to disk the html file, useful when webpack-dev-server / HMR are being used
+- [html-webpack-inline-svg-plugin](https://github.com/thegc/html-webpack-inline-svg-plugin) to inline SVGs in the resulting HTML file.
+- [html-webpack-exclude-assets-plugin](https://github.com/jamesjieye/html-webpack-exclude-assets-plugin) for excluding assets using regular expressions
+- [html-webpack-include-assets-plugin](https://github.com/jharris4/html-webpack-include-assets-plugin) for including lists of js or css file paths (such as those copied by the copy-webpack-plugin).
+- [html-webpack-injector](https://github.com/thearchitgarg/html-webpack-injector) to inject chunks in `head` or `body` (different locations ) of same html document.
+- [resource-hints-webpack-plugin](https://github.com/jantimon/resource-hints-webpack-plugin) to add resource hints for faster initial page loads using `<link rel='preload'>` and `<link rel='prefetch'>`
+- [link-media-html-webpack-plugin](https://github.com/yaycmyk/link-media-html-webpack-plugin) allows for injected stylesheet `<link />` tags to have their media attribute set automatically; useful for providing specific desktop/mobile/print etc. stylesheets that the browser will conditionally download
+- [html-webpack-inline-style-plugin](https://github.com/djaax/html-webpack-inline-style-plugin) for inlining styles to HTML elements using [juice](https://github.com/Automattic/juice). Useful for email generation automatisation.
+- [html-webpack-exclude-empty-assets-plugin](https://github.com/KnisterPeter/html-webpack-exclude-empty-assets-plugin) removes empty assets from being added to the html. This fixes some problems with extract-text-plugin with webpack 4.
+- [webpack-concat-plugin](https://github.com/hxlniada/webpack-concat-plugin) for concat and uglify files that needn't to be webpack bundles(for legacy files) and inject to html-webpack-plugin.
+- [html-webpack-link-type-plugin](https://github.com/steadyapp/html-webpack-link-type-plugin) adds a configurable mimetype to resources injected as links (such as adding type="text/css" to external stylesheets) for compatibility with "strict mode".
+- [csp-html-webpack-plugin](https://github.com/slackhq/csp-html-webpack-plugin) to add [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) meta tags to the HTML output
+- [strict-csp-html-webpack-plugin](https://github.com/google/strict-csp/tree/main/strict-csp-html-webpack-plugin) to add a [**strict** Content-Security-Policy (CSP)](https://web.dev) as a `meta` tag to the HTML output. A strict CSP is specifically efficient against XSS attacks.
+- [webpack-nomodule-plugin](https://github.com/swimmadude66/webpack-nomodule-plugin) allows you to add a `nomodule` attribute to specific injected scripts, which prevents the scripts from being loaded by newer browsers. Good for limiting loads of polyfills.
+- [html-webpack-skip-assets-plugin](https://github.com/swimmadude66/html-webpack-skip-assets-plugin) Skip adding certain output files to the html file. Built as a drop-in replacement for [html-webpack-exclude-assets-plugin](https://www.npmjs.com/package/html-webpack-exclude-assets-plugin) and works with newer [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) versions
+- [html-webpack-inject-preload](https://github.com/principalstudio/html-webpack-inject-preload) allows to add preload links &lt;link rel='preload'> anywhere you want.
+- [inject-body-webpack-plugin](https://github.com/Jaid/inject-body-webpack-plugin) is a simple method of injecting a custom HTML string into the body.
+- [html-webpack-plugin-django](https://github.com/TommasoAmici/html-webpack-plugin-django) a Webpack plugin to inject Django static tags.
+- [html-webpack-inject-attributes-plugin](https://github.com/dyw934854565/html-webpack-inject-attributes-plugin) add extra attributes to inject assetTags.
+- [js-entry-webpack-plugin](https://github.com/liam61/html-webpack-plugin) creates webpack bundles into your js entry
+
+<h2 align="center">Usage</h2>
+
+The plugin will generate an HTML5 file for you that includes all your `webpack`
+bundles in the head using `script` tags. Just add the plugin to your `webpack`
+config as follows:
+
+**webpack.config.js**
+
+```js
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+
+module.exports = {
+  entry: "index.js",
+  output: {
+    path: __dirname + "/dist",
+    filename: "index_bundle.js",
+  },
+  plugins: [new HtmlWebpackPlugin()],
+};
+```
+
+This will generate a file `dist/index.html` containing the following
+
+```html
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <title>Webpack App</title>
+    <script defer src="index_bundle.js"></script>
+  </head>
+  <body></body>
+</html>
+```
+
+If you have multiple `webpack` entry points, they will all be included with `script` tags in the generated HTML.
+
+If you have any CSS assets in webpack's output (for example, CSS extracted with the [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin))
+then these will be included with `<link>` tags in the HTML head.
+
+If you have plugins that make use of it, `html-webpack-plugin` should be ordered first before any of the integrated Plugins.
+
+<h2 align="center">Options</h2>
+
+You can pass a hash of configuration options to `html-webpack-plugin`.
+Allowed values are as follows:
+
+|           Name           |                         Type                         |                        Default                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
+| :----------------------: | :--------------------------------------------------: | :---------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+|       **`title`**        |                      `{String}`                      |                     `Webpack App`                     | The title to use for the generated HTML document                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
+|      **`filename`**      |                 `{String\|Function}`                 |                    `'index.html'`                     | The file to write the HTML to. Defaults to `index.html`. You can specify a subdirectory here too (eg: `assets/admin.html`). The `[name]` placeholder will be replaced with the entry name. Can also be a function e.g. `(entryName) => entryName + '.html'`.                                                                                                                                                                                                                                                                               |
+|      **`template`**      |                      `{String}`                      |                          ``                           | `webpack` relative or absolute path to the template. By default it will use `src/index.ejs` if it exists. Please see the [docs](https://github.com/jantimon/html-webpack-plugin/blob/master/docs/template-option.md) for details                                                                                                                                                                                                                                                                                                           |
+|  **`templateContent`**   |             `{string\|Function\|false}`              |                         false                         | Can be used instead of `template` to provide an inline template - please read the [Writing Your Own Templates](https://github.com/jantimon/html-webpack-plugin#writing-your-own-templates) section                                                                                                                                                                                                                                                                                                                                         |
+| **`templateParameters`** |            `{Boolean\|Object\|Function}`             |                        `false`                        | Allows to overwrite the parameters used in the template - see [example](https://github.com/jantimon/html-webpack-plugin/tree/master/examples/template-parameters)                                                                                                                                                                                                                                                                                                                                                                          |
+|       **`inject`**       |                 `{Boolean\|String}`                  |                        `true`                         | `true \|\| 'head' \|\| 'body' \|\| false` Inject all assets into the given `template` or `templateContent`. When passing `'body'` all javascript resources will be placed at the bottom of the body element. `'head'` will place the scripts in the head element. Passing `true` will add it to the head/body depending on the `scriptLoading` option. Passing `false` will disable automatic injections. - see the [inject:false example](https://github.com/jantimon/html-webpack-plugin/tree/master/examples/custom-insertion-position) |
+|     **`publicPath`**     |                  `{String\|'auto'}`                  |                       `'auto'`                        | The publicPath used for script and link tags                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
+|   **`scriptLoading`**    | `{'blocking'\|'defer'\|'module'\|'systemjs-module'}` |                       `'defer'`                       | Modern browsers support non blocking javascript loading (`'defer'`) to improve the page startup performance. Setting to `'module'` adds attribute [`type="module"`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#applying_the_module_to_your_html). This also implies "defer", since modules are automatically deferred.                                                                                                                                                                                          |
+|      **`favicon`**       |                      `{String}`                      |                          ``                           | Adds the given favicon path to the output HTML                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
+|        **`meta`**        |                      `{Object}`                      |                         `{}`                          | Allows to inject `meta`-tags. E.g. `meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}`                                                                                                                                                                                                                                                                                                                                                                                                                             |
+|        **`base`**        |              `{Object\|String\|false}`               |                        `false`                        | Inject a [`base`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag. E.g. `base: "https://example.com/path/page.html`                                                                                                                                                                                                                                                                                                                                                                                                    |
+|       **`minify`**       |                 `{Boolean\|Object}`                  | `true` if `mode` is `'production'`, otherwise `false` | Controls if and in what ways the output should be minified. See [minification](#minification) below for more details.                                                                                                                                                                                                                                                                                                                                                                                                                      |
+|        **`hash`**        |                     `{Boolean}`                      |                        `false`                        | If `true` then append a unique `webpack` compilation hash to all included scripts and CSS files (i.e. `main.js?hash=compilation_hash`). This is useful for cache busting                                                                                                                                                                                                                                                                                                                                                                   |
+|       **`cache`**        |                     `{Boolean}`                      |                        `true`                         | Emit the file only if it was changed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
+|     **`showErrors`**     |                     `{Boolean}`                      |                        `true`                         | Errors details will be written into the HTML page                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
+|       **`chunks`**       |                        `{?}`                         |                          `?`                          | Allows you to add only some chunks (e.g only the unit-test chunk)                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
+|   **`chunksSortMode`**   |                 `{String\|Function}`                 |                        `auto`                         | Allows to control how chunks should be sorted before they are included to the HTML. Allowed values are `'none' \| 'auto' \| 'manual' \| {Function}`                                                                                                                                                                                                                                                                                                                                                                                        |
+|   **`excludeChunks`**    |                  `{Array.<string>}`                  |                          ``                           | Allows you to skip some chunks (e.g don't add the unit-test chunk)                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+|       **`xhtml`**        |                     `{Boolean}`                      |                        `false`                        | If `true` render the `link` tags as self-closing (XHTML compliant)                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+
+Here's an example webpack config illustrating how to use these options
+
+**webpack.config.js**
+
+```js
+{
+  entry: 'index.js',
+  output: {
+    path: __dirname + '/dist',
+    filename: 'index_bundle.js'
+  },
+  plugins: [
+    new HtmlWebpackPlugin({
+      title: 'My App',
+      filename: 'assets/admin.html'
+    })
+  ]
+}
+```
+
+### Generating Multiple HTML Files
+
+To generate more than one HTML file, declare the plugin more than
+once in your plugins array
+
+**webpack.config.js**
+
+```js
+{
+  entry: 'index.js',
+  output: {
+    path: __dirname + '/dist',
+    filename: 'index_bundle.js'
+  },
+  plugins: [
+    new HtmlWebpackPlugin(), // Generates default index.html
+    new HtmlWebpackPlugin({  // Also generate a test.html
+      filename: 'test.html',
+      template: 'src/assets/test.html'
+    })
+  ]
+}
+```
+
+### Writing Your Own Templates
+
+If the default generated HTML doesn't meet your needs you can supply
+your own template. The easiest way is to use the `template` option and pass a custom HTML file.
+The html-webpack-plugin will automatically inject all necessary CSS, JS, manifest
+and favicon files into the markup.
+
+Details of other template loaders are [documented here](https://github.com/jantimon/html-webpack-plugin/blob/master/docs/template-option.md).
+
+```js
+plugins: [
+  new HtmlWebpackPlugin({
+    title: "Custom template",
+    // Load a custom template (lodash by default)
+    template: "index.html",
+  }),
+];
+```
+
+**index.html**
+
+```html
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body></body>
+</html>
+```
+
+If you already have a template loader, you can use it to parse the template.
+Please note that this will also happen if you specify the html-loader and use `.html` file as template.
+
+**webpack.config.js**
+
+```js
+module: {
+  loaders: [
+    { test: /\.hbs$/, loader: "handlebars-loader" }
+  ]
+},
+plugins: [
+  new HtmlWebpackPlugin({
+    title: 'Custom template using Handlebars',
+    template: 'index.hbs'
+  })
+]
+```
+
+You can use the `lodash` syntax out of the box. If the `inject` feature doesn't fit your needs and you want full control over the asset placement use the [default template](https://github.com/jaketrent/html-webpack-template/blob/86f285d5c790a6c15263f5cc50fd666d51f974fd/index.html) of the [html-webpack-template project](https://github.com/jaketrent/html-webpack-template) as a starting point for writing your own.
+
+The following variables are available in the template by default (you can extend them using the `templateParameters` option):
+
+- `htmlWebpackPlugin`: data specific to this plugin
+
+  - `htmlWebpackPlugin.options`: the options hash that was passed to
+    the plugin. In addition to the options actually used by this plugin,
+    you can use this hash to pass arbitrary data through to your template.
+
+  - `htmlWebpackPlugin.tags`: the prepared `headTags` and `bodyTags` Array to render the `<base>`, `<meta>`, `<script>` and `<link>` tags.
+    Can be used directly in templates and literals. For example:
+    ```html
+    <html>
+      <head>
+        <%= htmlWebpackPlugin.tags.headTags %>
+      </head>
+      <body>
+        <%= htmlWebpackPlugin.tags.bodyTags %>
+      </body>
+    </html>
+    ```
+  - `htmlWebpackPlugin.files`: direct access to the files used during the compilation.
+
+    ```typescript
+    publicPath: string;
+    js: string[];
+    css: string[];
+    manifest?: string;
+    favicon?: string;
+    ```
+
+- `webpackConfig`: the webpack configuration that was used for this compilation. This
+  can be used, for example, to get the `publicPath` (`webpackConfig.output.publicPath`).
+
+- `compilation`: the webpack [compilation object](https://webpack.js.org/api/compilation-object/).
+  This can be used, for example, to get the contents of processed assets and inline them
+  directly in the page, through `compilation.assets[...].source()`
+  (see [the inline template example](examples/inline/template.pug)).
+
+The template can also be directly inlined directly into the options object.  
+⚠️ **`templateContent` does not allow to use webpack loaders for your template and will not watch for template file changes**
+
+**webpack.config.js**
+
+```js
+new HtmlWebpackPlugin({
+  templateContent: `
+    <html>
+      <body>
+        <h1>Hello World</h1>
+      </body>
+    </html>
+  `,
+});
+```
+
+The `templateContent` can also access all `templateParameters` values.  
+⚠️ **`templateContent` does not allow to use webpack loaders for your template and will not watch for template file changes**
+
+**webpack.config.js**
+
+```js
+new HtmlWebpackPlugin({
+  inject: false,
+  templateContent: ({ htmlWebpackPlugin }) => `
+    <html>
+      <head>
+        ${htmlWebpackPlugin.tags.headTags}
+      </head>
+      <body>
+        <h1>Hello World</h1>
+        ${htmlWebpackPlugin.tags.bodyTags}
+      </body>
+    </html>
+  `,
+});
+```
+
+### Filtering Chunks
+
+To include only certain chunks you can limit the chunks being used
+
+**webpack.config.js**
+
+```js
+plugins: [
+  new HtmlWebpackPlugin({
+    chunks: ["app"],
+  }),
+];
+```
+
+It is also possible to exclude certain chunks by setting the `excludeChunks` option
+
+**webpack.config.js**
+
+```js
+plugins: [
+  new HtmlWebpackPlugin({
+    excludeChunks: ["dev-helper"],
+  }),
+];
+```
+
+### Minification
+
+If the `minify` option is set to `true` (the default when webpack's `mode` is `'production'`),
+the generated HTML will be minified using [html-minifier-terser](https://github.com/DanielRuf/html-minifier-terser)
+and the following options:
+
+```js
+{
+  collapseWhitespace: true,
+  keepClosingSlash: true,
+  removeComments: true,
+  removeRedundantAttributes: true,
+  removeScriptTypeAttributes: true,
+  removeStyleLinkTypeAttributes: true,
+  useShortDoctype: true
+}
+```
+
+To use custom [html-minifier options](https://github.com/DanielRuf/html-minifier-terser#options-quick-reference)
+pass an object to `minify` instead. This object will not be merged with the defaults above.
+
+To disable minification during production mode set the `minify` option to `false`.
+
+### Meta Tags
+
+If the `meta` option is set the html-webpack-plugin will inject meta tags.  
+For the default template the html-webpack-plugin will already provide a default for the `viewport` meta tag.
+
+Please take a look at this well maintained list of almost all [possible meta tags](https://github.com/joshbuchea/HEAD#meta).
+
+#### name/content meta tags
+
+Most meta tags are configured by setting a `name` and a `content` attribute.  
+To add those use a key/value pair:
+
+**webpack.config.js**
+
+```js
+plugins: [
+  new HtmlWebpackPlugin({
+    meta: {
+      viewport: "width=device-width, initial-scale=1, shrink-to-fit=no",
+      // Will generate: <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+      "theme-color": "#4285f4",
+      // Will generate: <meta name="theme-color" content="#4285f4">
+    },
+  }),
+];
+```
+
+#### Simulate http response headers
+
+The **http-equiv** attribute is essentially used to simulate a HTTP response header.  
+This format is supported using an object notation which allows you to add any attribute:
+
+**webpack.config.js**
+
+```js
+plugins: [
+  new HtmlWebpackPlugin({
+    meta: {
+      "Content-Security-Policy": {
+        "http-equiv": "Content-Security-Policy",
+        content: "default-src https:",
+      },
+      // Will generate: <meta http-equiv="Content-Security-Policy" content="default-src https:">
+      // Which equals to the following http header: `Content-Security-Policy: default-src https:`
+      "set-cookie": {
+        "http-equiv": "set-cookie",
+        content: "name=value; expires=date; path=url",
+      },
+      // Will generate: <meta http-equiv="set-cookie" content="value; expires=date; path=url">
+      // Which equals to the following http header: `set-cookie: value; expires=date; path=url`
+    },
+  }),
+];
+```
+
+### Base Tag
+
+When the `base` option is used,
+html-webpack-plugin will inject a [base tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base).
+By default, a base tag will not be injected.
+
+The following two are identical and will both insert `<base href="http://example.com/some/page.html">`:
+
+```js
+new HtmlWebpackPlugin({
+  base: "http://example.com/some/page.html",
+});
+```
+
+```js
+new HtmlWebpackPlugin({
+  base: { href: "http://example.com/some/page.html" },
+});
+```
+
+The `target` can be specified with the corresponding key:
+
+```js
+new HtmlWebpackPlugin({
+  base: {
+    href: "http://example.com/some/page.html",
+    target: "_blank",
+  },
+});
+```
+
+which will inject the element `<base href="http://example.com/some/page.html" target="_blank">`.
+
+### Long Term Caching
+
+For long term caching add `contenthash` to the filename.
+
+**Example:**
+
+```js
+plugins: [
+  new HtmlWebpackPlugin({
+    filename: "index.[contenthash].html",
+  }),
+];
+```
+
+`contenthash` is the hash of the content of the output file.
+
+Refer webpack's [Template Strings](https://webpack.js.org/configuration/output/#template-strings) for more details
+
+### Events
+
+To allow other [plugins](https://github.com/webpack/docs/wiki/plugins) to alter the HTML this plugin executes
+[tapable](https://github.com/webpack/tapable/tree/master) hooks.
+
+The [lib/hooks.js](https://github.com/jantimon/html-webpack-plugin/blob/master/lib/hooks.js) contains all information
+about which values are passed.
+
+[![Concept flow uml](https://raw.githubusercontent.com/jantimon/html-webpack-plugin/master/flow.png)](https://github.com/jantimon/html-webpack-plugin/blob/master/flow.puml)
+
+#### `beforeAssetTagGeneration` hook
+
+```
+    AsyncSeriesWaterfallHook<{
+      assets: {
+        publicPath: string,
+        js: Array<{string}>,
+        css: Array<{string}>,
+        favicon?: string | undefined,
+        manifest?: string | undefined
+      },
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>
+```
+
+#### `alterAssetTags` hook
+
+```
+    AsyncSeriesWaterfallHook<{
+      assetTags: {
+        scripts: Array<HtmlTagObject>,
+        styles: Array<HtmlTagObject>,
+        meta: Array<HtmlTagObject>,
+      },
+      publicPath: string,
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>
+```
+
+#### `alterAssetTagGroups` hook
+
+```
+    AsyncSeriesWaterfallHook<{
+      headTags: Array<HtmlTagObject | HtmlTagObject>,
+      bodyTags: Array<HtmlTagObject | HtmlTagObject>,
+      publicPath: string,
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>
+```
+
+#### `afterTemplateExecution` hook
+
+```
+    AsyncSeriesWaterfallHook<{
+      html: string,
+      headTags: Array<HtmlTagObject | HtmlTagObject>,
+      bodyTags: Array<HtmlTagObject | HtmlTagObject>,
+      outputName: string,
+      plugin: HtmlWebpackPlugin,
+    }>
+```
+
+#### `beforeEmit` hook
+
+```
+    AsyncSeriesWaterfallHook<{
+      html: string,
+      outputName: string,
+      plugin: HtmlWebpackPlugin,
+    }>
+```
+
+#### `afterEmit` hook
+
+```
+    AsyncSeriesWaterfallHook<{
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>
+```
+
+Example implementation: [webpack-subresource-integrity](https://www.npmjs.com/package/webpack-subresource-integrity)
+
+**plugin.js**
+
+```js
+// If your plugin is direct dependent to the html webpack plugin:
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+// If your plugin is using html-webpack-plugin as an optional dependency
+// you can use https://github.com/tallesl/node-safe-require instead:
+const HtmlWebpackPlugin = require("safe-require")("html-webpack-plugin");
+
+class MyPlugin {
+  apply(compiler) {
+    compiler.hooks.compilation.tap("MyPlugin", (compilation) => {
+      console.log("The compiler is starting a new compilation...");
+
+      // Static Plugin interface |compilation |HOOK NAME | register listener
+      HtmlWebpackPlugin.getCompilationHooks(compilation).beforeEmit.tapAsync(
+        "MyPlugin", // <-- Set a meaningful name here for stacktraces
+        (data, cb) => {
+          // Manipulate the content
+          data.html += "The Magic Footer";
+          // Tell webpack to move on
+          cb(null, data);
+        },
+      );
+    });
+  }
+}
+
+module.exports = MyPlugin;
+```
+
+**webpack.config.js**
+
+```js
+plugins: [new MyPlugin({ options: "" })];
+```
+
+Note that the callback must be passed the HtmlWebpackPluginData in order to pass this onto any other plugins listening on the same `beforeEmit` event
+
+<h2 align="center">Maintainers</h2>
+
+<table>
+  <tbody>
+    <tr>
+      <td align="center">
+        <img width="150" height="150"
+        src="https://avatars3.githubusercontent.com/u/4113649?v=3&s=150">
+        </br>
+        <a href="https://github.com/jantimon">Jan Nicklas</a>
+      </td>
+      <td align="center">
+        <img width="150" height="150"
+        src="https://avatars2.githubusercontent.com/u/4112409?v=3&s=150">
+        </br>
+        <a href="https://github.com/mastilver">Thomas Sileghem</a>
+      </td>
+    </tr>
+  <tbody>
+</table>
+
+## Backers
+
+Thank you to all our backers!  
+If you want to support the project as well [become a sponsor](https://opencollective.com/html-webpack-plugin#sponsor) or a [a backer](https://opencollective.com/html-webpack-plugin#backer).
+
+<a href="https://opencollective.com/html-webpack-plugin/backer/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/0/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/1/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/2/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/3/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/4/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/5/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/6/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/7/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/8/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/html-webpack-plugin/backer/9/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/9/avatar.svg?requireActive=false"></a>
+
+## Contributors
+
+This project exists thanks to all the people who contribute.
+
+You're free to contribute to this project by submitting [issues](https://github.com/jantimon/html-webpack-plugin/issues) and/or [pull requests](https://github.com/jantimon/html-webpack-plugin/pulls). This project is test-driven, so keep in mind that every change and new feature should be covered by tests.
+
+<a href="https://github.com/jantimon/html-webpack-plugin/graphs/contributors"><img src="https://opencollective.com/html-webpack-plugin/contributors.svg?width=890&button=false" /></a>
+
+[npm]: https://img.shields.io/npm/v/html-webpack-plugin.svg
+[npm-url]: https://npmjs.com/package/html-webpack-plugin
+[node]: https://img.shields.io/node/v/html-webpack-plugin.svg
+[node-url]: https://nodejs.org
+[tests]: https://github.com/jantimon/html-webpack-plugin/workflows/CI/badge.svg
+[tests-url]: https://github.com/jantimon/html-webpack-plugin/actions?query=workflow%3ACI
Index: frontend/node_modules/html-webpack-plugin/default_index.ejs
===================================================================
--- frontend/node_modules/html-webpack-plugin/default_index.ejs	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/default_index.ejs	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+  </body>
+</html>
Index: frontend/node_modules/html-webpack-plugin/index.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1568 @@
+// @ts-check
+"use strict";
+
+const promisify = require("util").promisify;
+
+const vm = require("vm");
+const fs = require("fs");
+const path = require("path");
+const { CachedChildCompilation } = require("./lib/cached-child-compiler");
+
+const {
+  createHtmlTagObject,
+  htmlTagObjectToString,
+  HtmlTagArray,
+} = require("./lib/html-tags");
+const prettyError = require("./lib/errors.js");
+const chunkSorter = require("./lib/chunksorter.js");
+const { AsyncSeriesWaterfallHook } = require("tapable");
+
+/** @typedef {import("./typings").HtmlTagObject} HtmlTagObject */
+/** @typedef {import("./typings").Options} HtmlWebpackOptions */
+/** @typedef {import("./typings").ProcessedOptions} ProcessedHtmlWebpackOptions */
+/** @typedef {import("./typings").TemplateParameter} TemplateParameter */
+/** @typedef {import("webpack").Compiler} Compiler */
+/** @typedef {import("webpack").Compilation} Compilation */
+/** @typedef {Required<Compilation["outputOptions"]["publicPath"]>} PublicPath */
+/** @typedef {ReturnType<Compiler["getInfrastructureLogger"]>} Logger */
+/** @typedef {Compilation["entrypoints"] extends Map<string, infer I> ? I : never} Entrypoint */
+/** @typedef {Array<{ name: string, source: import('webpack').sources.Source, info?: import('webpack').AssetInfo }>} PreviousEmittedAssets */
+/** @typedef {{ publicPath: string, js: Array<string>, css: Array<string>, manifest?: string, favicon?: string }} AssetsInformationByGroups */
+/** @typedef {import("./typings").Hooks} HtmlWebpackPluginHooks */
+/**
+ * @type {WeakMap<Compilation, HtmlWebpackPluginHooks>}}
+ */
+const compilationHooksMap = new WeakMap();
+
+class HtmlWebpackPlugin {
+  // The following is the API definition for all available hooks
+  // For the TypeScript definition, see the Hooks type in typings.d.ts
+  /**
+   beforeAssetTagGeneration:
+   AsyncSeriesWaterfallHook<{
+      assets: {
+        publicPath: string,
+        js: Array<string>,
+        css: Array<string>,
+        favicon?: string | undefined,
+        manifest?: string | undefined
+      },
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>,
+   alterAssetTags:
+   AsyncSeriesWaterfallHook<{
+      assetTags: {
+        scripts: Array<HtmlTagObject>,
+        styles: Array<HtmlTagObject>,
+        meta: Array<HtmlTagObject>,
+      },
+      publicPath: string,
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>,
+   alterAssetTagGroups:
+   AsyncSeriesWaterfallHook<{
+      headTags: Array<HtmlTagObject | HtmlTagObject>,
+      bodyTags: Array<HtmlTagObject | HtmlTagObject>,
+      publicPath: string,
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>,
+   afterTemplateExecution:
+   AsyncSeriesWaterfallHook<{
+      html: string,
+      headTags: Array<HtmlTagObject | HtmlTagObject>,
+      bodyTags: Array<HtmlTagObject | HtmlTagObject>,
+      outputName: string,
+      plugin: HtmlWebpackPlugin,
+    }>,
+   beforeEmit:
+   AsyncSeriesWaterfallHook<{
+      html: string,
+      outputName: string,
+      plugin: HtmlWebpackPlugin,
+    }>,
+   afterEmit:
+   AsyncSeriesWaterfallHook<{
+      outputName: string,
+      plugin: HtmlWebpackPlugin
+    }>
+   */
+
+  /**
+   * Returns all public hooks of the html webpack plugin for the given compilation
+   *
+   * @param {Compilation} compilation
+   * @returns {HtmlWebpackPluginHooks}
+   */
+  static getCompilationHooks(compilation) {
+    let hooks = compilationHooksMap.get(compilation);
+
+    if (!hooks) {
+      hooks = {
+        beforeAssetTagGeneration: new AsyncSeriesWaterfallHook(["pluginArgs"]),
+        alterAssetTags: new AsyncSeriesWaterfallHook(["pluginArgs"]),
+        alterAssetTagGroups: new AsyncSeriesWaterfallHook(["pluginArgs"]),
+        afterTemplateExecution: new AsyncSeriesWaterfallHook(["pluginArgs"]),
+        beforeEmit: new AsyncSeriesWaterfallHook(["pluginArgs"]),
+        afterEmit: new AsyncSeriesWaterfallHook(["pluginArgs"]),
+      };
+      compilationHooksMap.set(compilation, hooks);
+    }
+
+    return hooks;
+  }
+
+  /**
+   * @param {HtmlWebpackOptions} [options]
+   */
+  constructor(options) {
+    /** @type {HtmlWebpackOptions} */
+    // TODO remove me in the next major release
+    this.userOptions = options || {};
+    this.version = HtmlWebpackPlugin.version;
+
+    // Default options
+    /** @type {ProcessedHtmlWebpackOptions} */
+    const defaultOptions = {
+      template: "auto",
+      templateContent: false,
+      templateParameters: templateParametersGenerator,
+      filename: "index.html",
+      publicPath:
+        this.userOptions.publicPath === undefined
+          ? "auto"
+          : this.userOptions.publicPath,
+      hash: false,
+      inject: this.userOptions.scriptLoading === "blocking" ? "body" : "head",
+      scriptLoading: "defer",
+      compile: true,
+      favicon: false,
+      minify: "auto",
+      cache: true,
+      showErrors: true,
+      chunks: "all",
+      excludeChunks: [],
+      chunksSortMode: "auto",
+      meta: {},
+      base: false,
+      title: "Webpack App",
+      xhtml: false,
+    };
+
+    /** @type {ProcessedHtmlWebpackOptions} */
+    this.options = Object.assign(defaultOptions, this.userOptions);
+  }
+
+  /**
+   *
+   * @param {Compiler} compiler
+   * @returns {void}
+   */
+  apply(compiler) {
+    this.logger = compiler.getInfrastructureLogger("HtmlWebpackPlugin");
+
+    const options = this.options;
+
+    options.template = this.getTemplatePath(
+      this.options.template,
+      compiler.context,
+    );
+
+    // Assert correct option spelling
+    if (
+      options.scriptLoading !== "defer" &&
+      options.scriptLoading !== "blocking" &&
+      options.scriptLoading !== "module" &&
+      options.scriptLoading !== "systemjs-module"
+    ) {
+      /** @type {Logger} */
+      (this.logger).error(
+        'The "scriptLoading" option need to be set to "defer", "blocking" or "module" or "systemjs-module"',
+      );
+    }
+
+    if (
+      options.inject !== true &&
+      options.inject !== false &&
+      options.inject !== "head" &&
+      options.inject !== "body"
+    ) {
+      /** @type {Logger} */
+      (this.logger).error(
+        'The `inject` option needs to be set to true, false, "head" or "body',
+      );
+    }
+
+    if (
+      this.options.templateParameters !== false &&
+      typeof this.options.templateParameters !== "function" &&
+      typeof this.options.templateParameters !== "object"
+    ) {
+      /** @type {Logger} */
+      (this.logger).error(
+        "The `templateParameters` has to be either a function or an object or false",
+      );
+    }
+
+    // Default metaOptions if no template is provided
+    if (
+      !this.userOptions.template &&
+      options.templateContent === false &&
+      options.meta
+    ) {
+      options.meta = Object.assign(
+        {},
+        options.meta,
+        {
+          // TODO remove in the next major release
+          // From https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag
+          viewport: "width=device-width, initial-scale=1",
+        },
+        this.userOptions.meta,
+      );
+    }
+
+    // entryName to fileName conversion function
+    const userOptionFilename =
+      this.userOptions.filename || this.options.filename;
+    const filenameFunction =
+      typeof userOptionFilename === "function"
+        ? userOptionFilename
+        : // Replace '[name]' with entry name
+          (entryName) => userOptionFilename.replace(/\[name\]/g, entryName);
+
+    /** output filenames for the given entry names */
+    const entryNames = Object.keys(compiler.options.entry);
+    const outputFileNames = new Set(
+      (entryNames.length ? entryNames : ["main"]).map(filenameFunction),
+    );
+
+    // Hook all options into the webpack compiler
+    outputFileNames.forEach((outputFileName) => {
+      // Instance variables to keep caching information for multiple builds
+      const assetJson = { value: undefined };
+      /**
+       * store the previous generated asset to emit them even if the content did not change
+       * to support watch mode for third party plugins like the clean-webpack-plugin or the compression plugin
+       * @type {PreviousEmittedAssets}
+       */
+      const previousEmittedAssets = [];
+
+      // Inject child compiler plugin
+      const childCompilerPlugin = new CachedChildCompilation(compiler);
+
+      if (!this.options.templateContent) {
+        childCompilerPlugin.addEntry(this.options.template);
+      }
+
+      // convert absolute filename into relative so that webpack can
+      // generate it at correct location
+      let filename = outputFileName;
+
+      if (path.resolve(filename) === path.normalize(filename)) {
+        const outputPath =
+          /** @type {string} - Once initialized the path is always a string */ (
+            compiler.options.output.path
+          );
+
+        filename = path.relative(outputPath, filename);
+      }
+
+      compiler.hooks.thisCompilation.tap(
+        "HtmlWebpackPlugin",
+        /**
+         * Hook into the webpack compilation
+         * @param {Compilation} compilation
+         */
+        (compilation) => {
+          compilation.hooks.processAssets.tapAsync(
+            {
+              name: "HtmlWebpackPlugin",
+              stage:
+                /**
+                 * Generate the html after minification and dev tooling is done
+                 */
+                compiler.webpack.Compilation
+                  .PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
+            },
+            /**
+             * Hook into the process assets hook
+             * @param {any} _
+             * @param {(err?: Error) => void} callback
+             */
+            (_, callback) => {
+              this.generateHTML(
+                compiler,
+                compilation,
+                filename,
+                childCompilerPlugin,
+                previousEmittedAssets,
+                assetJson,
+                callback,
+              );
+            },
+          );
+        },
+      );
+    });
+  }
+
+  /**
+   * Helper to return the absolute template path with a fallback loader
+   *
+   * @private
+   * @param {string} template The path to the template e.g. './index.html'
+   * @param {string} context The webpack base resolution path for relative paths e.g. process.cwd()
+   */
+  getTemplatePath(template, context) {
+    if (template === "auto") {
+      template = path.resolve(context, "src/index.ejs");
+      if (!fs.existsSync(template)) {
+        template = path.join(__dirname, "default_index.ejs");
+      }
+    }
+
+    // If the template doesn't use a loader use the lodash template loader
+    if (template.indexOf("!") === -1) {
+      template =
+        require.resolve("./lib/loader.js") +
+        "!" +
+        path.resolve(context, template);
+    }
+
+    // Resolve template path
+    return template.replace(
+      /([!])([^/\\][^!?]+|[^/\\!?])($|\?[^!?\n]+$)/,
+      (match, prefix, filepath, postfix) =>
+        prefix + path.resolve(filepath) + postfix,
+    );
+  }
+
+  /**
+   * Return all chunks from the compilation result which match the exclude and include filters
+   *
+   * @private
+   * @param {any} chunks
+   * @param {string[]|'all'} includedChunks
+   * @param {string[]} excludedChunks
+   */
+  filterEntryChunks(chunks, includedChunks, excludedChunks) {
+    return chunks.filter((chunkName) => {
+      // Skip if the chunks should be filtered and the given chunk was not added explicity
+      if (
+        Array.isArray(includedChunks) &&
+        includedChunks.indexOf(chunkName) === -1
+      ) {
+        return false;
+      }
+
+      // Skip if the chunks should be filtered and the given chunk was excluded explicity
+      if (
+        Array.isArray(excludedChunks) &&
+        excludedChunks.indexOf(chunkName) !== -1
+      ) {
+        return false;
+      }
+
+      // Add otherwise
+      return true;
+    });
+  }
+
+  /**
+   * Helper to sort chunks
+   *
+   * @private
+   * @param {string[]} entryNames
+   * @param {string|((entryNameA: string, entryNameB: string) => number)} sortMode
+   * @param {Compilation} compilation
+   */
+  sortEntryChunks(entryNames, sortMode, compilation) {
+    // Custom function
+    if (typeof sortMode === "function") {
+      return entryNames.sort(sortMode);
+    }
+    // Check if the given sort mode is a valid chunkSorter sort mode
+    if (typeof chunkSorter[sortMode] !== "undefined") {
+      return chunkSorter[sortMode](entryNames, compilation, this.options);
+    }
+    throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
+  }
+
+  /**
+   * Encode each path component using `encodeURIComponent` as files can contain characters
+   * which needs special encoding in URLs like `+ `.
+   *
+   * Valid filesystem characters which need to be encoded for urls:
+   *
+   * # pound, % percent, & ampersand, { left curly bracket, } right curly bracket,
+   * \ back slash, < left angle bracket, > right angle bracket, * asterisk, ? question mark,
+   * blank spaces, $ dollar sign, ! exclamation point, ' single quotes, " double quotes,
+   * : colon, @ at sign, + plus sign, ` backtick, | pipe, = equal sign
+   *
+   * However the query string must not be encoded:
+   *
+   *  fo:demonstration-path/very fancy+name.js?path=/home?value=abc&value=def#zzz
+   *    ^             ^    ^    ^     ^    ^  ^    ^^    ^     ^   ^     ^   ^
+   *    |             |    |    |     |    |  |    ||    |     |   |     |   |
+   *    encoded       |    |    encoded    |  |    ||    |     |   |     |   |
+   *                 ignored              ignored  ignored     ignored   ignored
+   *
+   * @private
+   * @param {string} filePath
+   */
+  urlencodePath(filePath) {
+    // People use the filepath in quite unexpected ways.
+    // Try to extract the first querystring of the url:
+    //
+    // some+path/demo.html?value=abc?def
+    //
+    const queryStringStart = filePath.indexOf("?");
+    const urlPath =
+      queryStringStart === -1 ? filePath : filePath.substr(0, queryStringStart);
+    const queryString = filePath.substr(urlPath.length);
+    // Encode all parts except '/' which are not part of the querystring:
+    const encodedUrlPath = urlPath.split("/").map(encodeURIComponent).join("/");
+    return encodedUrlPath + queryString;
+  }
+
+  /**
+   * Appends a cache busting hash to the query string of the url
+   * E.g. http://localhost:8080/ -> http://localhost:8080/?50c9096ba6183fd728eeb065a26ec175
+   *
+   * @private
+   * @param {string | undefined} url
+   * @param {string} hash
+   */
+  appendHash(url, hash) {
+    if (!url) {
+      return url;
+    }
+
+    return url + (url.indexOf("?") === -1 ? "?" : "&") + hash;
+  }
+
+  /**
+   * Generate the relative or absolute base url to reference images, css, and javascript files
+   * from within the html file - the publicPath
+   *
+   * @private
+   * @param {Compilation} compilation
+   * @param {string} filename
+   * @param {string | 'auto'} customPublicPath
+   * @returns {string}
+   */
+  getPublicPath(compilation, filename, customPublicPath) {
+    /**
+     * @type {string} the configured public path to the asset root
+     * if a path publicPath is set in the current webpack config use it otherwise
+     * fallback to a relative path
+     */
+    const webpackPublicPath = compilation.getAssetPath(
+      /** @type {NonNullable<Compilation["outputOptions"]["publicPath"]>} */ (
+        compilation.outputOptions.publicPath
+      ),
+      { hash: compilation.hash },
+    );
+    // Webpack 5 introduced "auto" as default value
+    const isPublicPathDefined = webpackPublicPath !== "auto";
+
+    let publicPath =
+      // If the html-webpack-plugin options contain a custom public path unset it
+      customPublicPath !== "auto"
+        ? customPublicPath
+        : isPublicPathDefined
+          ? // If a hard coded public path exists use it
+            webpackPublicPath
+          : // If no public path was set get a relative url path
+            path
+              .relative(
+                path.resolve(
+                  /** @type {string} */ (compilation.options.output.path),
+                  path.dirname(filename),
+                ),
+                /** @type {string} */ (compilation.options.output.path),
+              )
+              .split(path.sep)
+              .join("/");
+
+    if (publicPath.length && publicPath.substr(-1, 1) !== "/") {
+      publicPath += "/";
+    }
+
+    return publicPath;
+  }
+
+  /**
+   * The getAssetsForHTML extracts the asset information of a webpack compilation for all given entry names.
+   *
+   * @private
+   * @param {Compilation} compilation
+   * @param {string} outputName
+   * @param {string[]} entryNames
+   * @returns {AssetsInformationByGroups}
+   */
+  getAssetsInformationByGroups(compilation, outputName, entryNames) {
+    /** The public path used inside the html file */
+    const publicPath = this.getPublicPath(
+      compilation,
+      outputName,
+      this.options.publicPath,
+    );
+    /**
+     * @type {AssetsInformationByGroups}
+     */
+    const assets = {
+      // The public path
+      publicPath,
+      // Will contain all js and mjs files
+      js: [],
+      // Will contain all css files
+      css: [],
+      // Will contain the html5 appcache manifest files if it exists
+      manifest: Object.keys(compilation.assets).find(
+        (assetFile) => path.extname(assetFile) === ".appcache",
+      ),
+      // Favicon
+      favicon: undefined,
+    };
+
+    // Append a hash for cache busting
+    if (this.options.hash && assets.manifest) {
+      assets.manifest = this.appendHash(
+        assets.manifest,
+        /** @type {string} */ (compilation.hash),
+      );
+    }
+
+    // Extract paths to .js, .mjs and .css files from the current compilation
+    const entryPointPublicPathMap = {};
+    const extensionRegexp = /\.(css|js|mjs)(\?|$)/;
+
+    for (let i = 0; i < entryNames.length; i++) {
+      const entryName = entryNames[i];
+      /** entryPointUnfilteredFiles - also includes hot module update files */
+      const entryPointUnfilteredFiles = /** @type {Entrypoint} */ (
+        compilation.entrypoints.get(entryName)
+      ).getFiles();
+      const entryPointFiles = entryPointUnfilteredFiles.filter((chunkFile) => {
+        const asset = compilation.getAsset(chunkFile);
+
+        if (!asset) {
+          return true;
+        }
+
+        // Prevent hot-module files from being included:
+        const assetMetaInformation = asset.info || {};
+
+        return !(
+          assetMetaInformation.hotModuleReplacement ||
+          assetMetaInformation.development
+        );
+      });
+      // Prepend the publicPath and append the hash depending on the
+      // webpack.output.publicPath and hashOptions
+      // E.g. bundle.js -> /bundle.js?hash
+      const entryPointPublicPaths = entryPointFiles.map((chunkFile) => {
+        const entryPointPublicPath = publicPath + this.urlencodePath(chunkFile);
+        return this.options.hash
+          ? this.appendHash(
+              entryPointPublicPath,
+              /** @type {string} */ (compilation.hash),
+            )
+          : entryPointPublicPath;
+      });
+
+      entryPointPublicPaths.forEach((entryPointPublicPath) => {
+        const extMatch = extensionRegexp.exec(
+          /** @type {string} */ (entryPointPublicPath),
+        );
+
+        // Skip if the public path is not a .css, .mjs or .js file
+        if (!extMatch) {
+          return;
+        }
+
+        // Skip if this file is already known
+        // (e.g. because of common chunk optimizations)
+        if (entryPointPublicPathMap[entryPointPublicPath]) {
+          return;
+        }
+
+        entryPointPublicPathMap[entryPointPublicPath] = true;
+
+        // ext will contain .js or .css, because .mjs recognizes as .js
+        const ext = extMatch[1] === "mjs" ? "js" : extMatch[1];
+
+        assets[ext].push(entryPointPublicPath);
+      });
+    }
+
+    return assets;
+  }
+
+  /**
+   * Once webpack is done with compiling the template into a NodeJS code this function
+   * evaluates it to generate the html result
+   *
+   * The evaluateCompilationResult is only a class function to allow spying during testing.
+   * Please change that in a further refactoring
+   *
+   * @param {string} source
+   * @param {string} publicPath
+   * @param {string} templateFilename
+   * @returns {Promise<string | (() => string | Promise<string>)>}
+   */
+  evaluateCompilationResult(source, publicPath, templateFilename) {
+    if (!source) {
+      return Promise.reject(
+        new Error("The child compilation didn't provide a result"),
+      );
+    }
+
+    // The LibraryTemplatePlugin stores the template result in a local variable.
+    // By adding it to the end the value gets extracted during evaluation
+    if (source.indexOf("HTML_WEBPACK_PLUGIN_RESULT") >= 0) {
+      source += ";\nHTML_WEBPACK_PLUGIN_RESULT";
+    }
+
+    const templateWithoutLoaders = templateFilename
+      .replace(/^.+!/, "")
+      .replace(/\?.+$/, "");
+    const globalClone = Object.create(
+      Object.getPrototypeOf(global),
+      Object.getOwnPropertyDescriptors(global),
+    );
+    // Presence of `eval` and `Function` breaks template's explicit `eval` call
+    // Ref: https://github.com/nodejs/help/issues/2880
+    delete globalClone.eval;
+    delete globalClone.Function;
+    // Not using `...global` as it throws when localStorage is not explicitly enabled in Node 25+
+    // Provide a CommonJS-style `module`/`exports` pair so templates compiled as CommonJS
+    // (e.g. Rspack's child compilation output, which wraps the result in `module.exports = ...`)
+    // can assign to them instead of failing with `module is not defined`.
+    const sandboxModule = { exports: {} };
+    const vmContext = vm.createContext(
+      Object.assign(globalClone, {
+        HTML_WEBPACK_PLUGIN: true,
+        // Copying nonstandard globals like `require` explicitly as they may be absent from `global`
+        require: require,
+        module: sandboxModule,
+        exports: sandboxModule.exports,
+        htmlWebpackPluginPublicPath: publicPath,
+        __filename: templateWithoutLoaders,
+        __dirname: path.dirname(templateWithoutLoaders),
+      }),
+    );
+
+    const vmScript = new vm.Script(source, {
+      filename: templateWithoutLoaders,
+    });
+
+    // Evaluate code and cast to string
+    let newSource;
+
+    try {
+      newSource = vmScript.runInContext(vmContext);
+    } catch (e) {
+      return Promise.reject(e);
+    }
+
+    if (
+      typeof newSource === "object" &&
+      newSource.__esModule &&
+      newSource.default !== undefined
+    ) {
+      newSource = newSource.default;
+    }
+
+    return typeof newSource === "string" || typeof newSource === "function"
+      ? Promise.resolve(newSource)
+      : Promise.reject(
+          new Error(
+            'The loader "' + templateWithoutLoaders + "\" didn't return html.",
+          ),
+        );
+  }
+
+  /**
+   * Add toString methods for easier rendering inside the template
+   *
+   * @private
+   * @param {Array<HtmlTagObject>} assetTagGroup
+   * @returns {Array<HtmlTagObject>}
+   */
+  prepareAssetTagGroupForRendering(assetTagGroup) {
+    const xhtml = this.options.xhtml;
+    return HtmlTagArray.from(
+      assetTagGroup.map((assetTag) => {
+        const copiedAssetTag = Object.assign({}, assetTag);
+        copiedAssetTag.toString = function () {
+          return htmlTagObjectToString(this, xhtml);
+        };
+        return copiedAssetTag;
+      }),
+    );
+  }
+
+  /**
+   * Generate the template parameters for the template function
+   *
+   * @private
+   * @param {Compilation} compilation
+   * @param {AssetsInformationByGroups} assetsInformationByGroups
+   * @param {{
+       headTags: HtmlTagObject[],
+       bodyTags: HtmlTagObject[]
+     }} assetTags
+   * @returns {Promise<{[key: any]: any}>}
+   */
+  getTemplateParameters(compilation, assetsInformationByGroups, assetTags) {
+    const templateParameters = this.options.templateParameters;
+
+    if (templateParameters === false) {
+      return Promise.resolve({});
+    }
+
+    if (
+      typeof templateParameters !== "function" &&
+      typeof templateParameters !== "object"
+    ) {
+      throw new Error(
+        "templateParameters has to be either a function or an object",
+      );
+    }
+
+    const templateParameterFunction =
+      typeof templateParameters === "function"
+        ? // A custom function can overwrite the entire template parameter preparation
+          templateParameters
+        : // If the template parameters is an object merge it with the default values
+          (compilation, assetsInformationByGroups, assetTags, options) =>
+            Object.assign(
+              {},
+              templateParametersGenerator(
+                compilation,
+                assetsInformationByGroups,
+                assetTags,
+                options,
+              ),
+              templateParameters,
+            );
+    const preparedAssetTags = {
+      headTags: this.prepareAssetTagGroupForRendering(assetTags.headTags),
+      bodyTags: this.prepareAssetTagGroupForRendering(assetTags.bodyTags),
+    };
+    return Promise.resolve().then(() =>
+      templateParameterFunction(
+        compilation,
+        assetsInformationByGroups,
+        preparedAssetTags,
+        this.options,
+      ),
+    );
+  }
+
+  /**
+   * This function renders the actual html by executing the template function
+   *
+   * @private
+   * @param {(templateParameters) => string | Promise<string>} templateFunction
+   * @param {AssetsInformationByGroups} assetsInformationByGroups
+   * @param {{
+       headTags: HtmlTagObject[],
+       bodyTags: HtmlTagObject[]
+     }} assetTags
+   * @param {Compilation} compilation
+   * @returns Promise<string>
+   */
+  executeTemplate(
+    templateFunction,
+    assetsInformationByGroups,
+    assetTags,
+    compilation,
+  ) {
+    // Template processing
+    const templateParamsPromise = this.getTemplateParameters(
+      compilation,
+      assetsInformationByGroups,
+      assetTags,
+    );
+
+    return templateParamsPromise.then((templateParams) => {
+      try {
+        // If html is a promise return the promise
+        // If html is a string turn it into a promise
+        return templateFunction(templateParams);
+      } catch (e) {
+        // @ts-ignore
+        compilation.errors.push(new Error("Template execution failed: " + e));
+        return Promise.reject(e);
+      }
+    });
+  }
+
+  /**
+   * Html Post processing
+   *
+   * @private
+   * @param {Compiler} compiler The compiler instance
+   * @param {any} originalHtml The input html
+   * @param {AssetsInformationByGroups} assetsInformationByGroups
+   * @param {{headTags: HtmlTagObject[], bodyTags: HtmlTagObject[]}} assetTags The asset tags to inject
+   * @returns {Promise<string>}
+   */
+  postProcessHtml(
+    compiler,
+    originalHtml,
+    assetsInformationByGroups,
+    assetTags,
+  ) {
+    let html = originalHtml;
+
+    if (typeof html !== "string") {
+      return Promise.reject(
+        new Error(
+          "Expected html to be a string but got " + JSON.stringify(html),
+        ),
+      );
+    }
+
+    if (this.options.inject) {
+      const htmlRegExp = /(<html[^>]*>)/i;
+      const headRegExp = /(<\/head\s*>)/i;
+      const bodyRegExp = /(<\/body\s*>)/i;
+      const metaViewportRegExp = /<meta[^>]+name=["']viewport["'][^>]*>/i;
+      const body = assetTags.bodyTags.map((assetTagObject) =>
+        htmlTagObjectToString(assetTagObject, this.options.xhtml),
+      );
+      const head = assetTags.headTags
+        .filter((item) => {
+          if (
+            item.tagName === "meta" &&
+            item.attributes &&
+            item.attributes.name === "viewport" &&
+            metaViewportRegExp.test(html)
+          ) {
+            return false;
+          }
+
+          return true;
+        })
+        .map((assetTagObject) =>
+          htmlTagObjectToString(assetTagObject, this.options.xhtml),
+        );
+
+      if (body.length) {
+        if (bodyRegExp.test(html)) {
+          // Append assets to body element
+          html = html.replace(bodyRegExp, (match) => body.join("") + match);
+        } else {
+          // Append scripts to the end of the file if no <body> element exists:
+          html += body.join("");
+        }
+      }
+
+      if (head.length) {
+        // Create a head tag if none exists
+        if (!headRegExp.test(html)) {
+          if (!htmlRegExp.test(html)) {
+            html = "<head></head>" + html;
+          } else {
+            html = html.replace(htmlRegExp, (match) => match + "<head></head>");
+          }
+        }
+
+        // Append assets to head element
+        html = html.replace(headRegExp, (match) => head.join("") + match);
+      }
+
+      // Inject manifest into the opening html tag
+      if (assetsInformationByGroups.manifest) {
+        html = html.replace(/(<html[^>]*)(>)/i, (match, start, end) => {
+          // Append the manifest only if no manifest was specified
+          if (/\smanifest\s*=/.test(match)) {
+            return match;
+          }
+          return (
+            start +
+            ' manifest="' +
+            assetsInformationByGroups.manifest +
+            '"' +
+            end
+          );
+        });
+      }
+    }
+
+    // TODO avoid this logic and use https://github.com/webpack-contrib/html-minimizer-webpack-plugin under the hood in the next major version
+    // Check if webpack is running in production mode
+    // @see https://github.com/webpack/webpack/blob/3366421f1784c449f415cda5930a8e445086f688/lib/WebpackOptionsDefaulter.js#L12-L14
+    const isProductionLikeMode =
+      compiler.options.mode === "production" || !compiler.options.mode;
+    const needMinify =
+      this.options.minify === true ||
+      typeof this.options.minify === "object" ||
+      (this.options.minify === "auto" && isProductionLikeMode);
+
+    if (!needMinify) {
+      return Promise.resolve(html);
+    }
+
+    const minifyOptions =
+      typeof this.options.minify === "object"
+        ? this.options.minify
+        : {
+            // https://www.npmjs.com/package/html-minifier-terser#options-quick-reference
+            collapseWhitespace: true,
+            keepClosingSlash: true,
+            removeComments: true,
+            removeRedundantAttributes: true,
+            removeScriptTypeAttributes: true,
+            removeStyleLinkTypeAttributes: true,
+            useShortDoctype: true,
+          };
+
+    try {
+      html = require("html-minifier-terser").minify(html, minifyOptions);
+    } catch (e) {
+      const isParseError = String(e.message).indexOf("Parse Error") === 0;
+
+      if (isParseError) {
+        e.message =
+          "html-webpack-plugin could not minify the generated output.\n" +
+          "In production mode the html minification is enabled by default.\n" +
+          "If you are not generating a valid html output please disable it manually.\n" +
+          "You can do so by adding the following setting to your HtmlWebpackPlugin config:\n|\n|" +
+          "    minify: false\n|\n" +
+          "See https://github.com/jantimon/html-webpack-plugin#options for details.\n\n" +
+          "For parser dedicated bugs please create an issue here:\n" +
+          "https://danielruf.github.io/html-minifier-terser/" +
+          "\n" +
+          e.message;
+      }
+
+      return Promise.reject(e);
+    }
+
+    return Promise.resolve(html);
+  }
+
+  /**
+   * Helper to return a sorted unique array of all asset files out of the asset object
+   * @private
+   */
+  getAssetFiles(assets) {
+    const files = [
+      ...new Set(
+        Object.keys(assets)
+          .filter((assetType) => assetType !== "chunks" && assets[assetType])
+          .reduce((files, assetType) => files.concat(assets[assetType]), []),
+      ),
+    ];
+    files.sort();
+    return files;
+  }
+
+  /**
+   * Converts a favicon file from disk to a webpack resource and returns the url to the resource
+   *
+   * @private
+   * @param {Compiler} compiler
+   * @param {string|false} favicon
+   * @param {Compilation} compilation
+   * @param {string} publicPath
+   * @param {PreviousEmittedAssets} previousEmittedAssets
+   * @returns {Promise<string|undefined>}
+   */
+  generateFavicon(
+    compiler,
+    favicon,
+    compilation,
+    publicPath,
+    previousEmittedAssets,
+  ) {
+    if (!favicon) {
+      return Promise.resolve(undefined);
+    }
+
+    const filename = path.resolve(compilation.compiler.context, favicon);
+
+    return promisify(compilation.inputFileSystem.readFile)(filename)
+      .then((buf) => {
+        const source = new compiler.webpack.sources.RawSource(
+          /** @type {string | Buffer} */ (buf),
+          false,
+        );
+        const name = path.basename(filename);
+
+        compilation.fileDependencies.add(filename);
+        compilation.emitAsset(name, source);
+        previousEmittedAssets.push({ name, source });
+
+        const faviconPath = publicPath + name;
+
+        if (this.options.hash) {
+          return this.appendHash(
+            faviconPath,
+            /** @type {string} */ (compilation.hash),
+          );
+        }
+
+        return faviconPath;
+      })
+      .catch(() =>
+        Promise.reject(
+          new Error("HtmlWebpackPlugin: could not load file " + filename),
+        ),
+      );
+  }
+
+  /**
+   * Generate all tags script for the given file paths
+   *
+   * @private
+   * @param {Array<string>} jsAssets
+   * @returns {Array<HtmlTagObject>}
+   */
+  generatedScriptTags(jsAssets) {
+    // @ts-ignore
+    return jsAssets.map((src) => {
+      const attributes = {};
+
+      if (this.options.scriptLoading === "defer") {
+        attributes.defer = true;
+      } else if (this.options.scriptLoading === "module") {
+        attributes.type = "module";
+      } else if (this.options.scriptLoading === "systemjs-module") {
+        attributes.type = "systemjs-module";
+      }
+
+      attributes.src = src;
+
+      return {
+        tagName: "script",
+        voidTag: false,
+        meta: { plugin: "html-webpack-plugin" },
+        attributes,
+      };
+    });
+  }
+
+  /**
+   * Generate all style tags for the given file paths
+   *
+   * @private
+   * @param {Array<string>} cssAssets
+   * @returns {Array<HtmlTagObject>}
+   */
+  generateStyleTags(cssAssets) {
+    return cssAssets.map((styleAsset) => ({
+      tagName: "link",
+      voidTag: true,
+      meta: { plugin: "html-webpack-plugin" },
+      attributes: {
+        href: styleAsset,
+        rel: "stylesheet",
+      },
+    }));
+  }
+
+  /**
+   * Generate an optional base tag
+   *
+   * @param {string | {[attributeName: string]: string}} base
+   * @returns {Array<HtmlTagObject>}
+   */
+  generateBaseTag(base) {
+    return [
+      {
+        tagName: "base",
+        voidTag: true,
+        meta: { plugin: "html-webpack-plugin" },
+        // attributes e.g. { href:"http://example.com/page.html" target:"_blank" }
+        attributes:
+          typeof base === "string"
+            ? {
+                href: base,
+              }
+            : base,
+      },
+    ];
+  }
+
+  /**
+   * Generate all meta tags for the given meta configuration
+   *
+   * @private
+   * @param {false | {[name: string]:  false | string | {[attributeName: string]: string|boolean}}} metaOptions
+   * @returns {Array<HtmlTagObject>}
+   */
+  generatedMetaTags(metaOptions) {
+    if (metaOptions === false) {
+      return [];
+    }
+
+    // Make tags self-closing in case of xhtml
+    // Turn { "viewport" : "width=500, initial-scale=1" } into
+    // [{ name:"viewport" content:"width=500, initial-scale=1" }]
+    const metaTagAttributeObjects = Object.keys(metaOptions)
+      .map((metaName) => {
+        const metaTagContent = metaOptions[metaName];
+        return typeof metaTagContent === "string"
+          ? {
+              name: metaName,
+              content: metaTagContent,
+            }
+          : metaTagContent;
+      })
+      .filter((attribute) => attribute !== false);
+
+    // Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into
+    // the html-webpack-plugin tag structure
+    return metaTagAttributeObjects.map((metaTagAttributes) => {
+      if (metaTagAttributes === false) {
+        throw new Error("Invalid meta tag");
+      }
+      return {
+        tagName: "meta",
+        voidTag: true,
+        meta: { plugin: "html-webpack-plugin" },
+        attributes: metaTagAttributes,
+      };
+    });
+  }
+
+  /**
+   * Generate a favicon tag for the given file path
+   *
+   * @private
+   * @param {string} favicon
+   * @returns {Array<HtmlTagObject>}
+   */
+  generateFaviconTag(favicon) {
+    return [
+      {
+        tagName: "link",
+        voidTag: true,
+        meta: { plugin: "html-webpack-plugin" },
+        attributes: {
+          rel: "icon",
+          href: favicon,
+        },
+      },
+    ];
+  }
+
+  /**
+   * Group assets to head and body tags
+   *
+   * @param {{
+      scripts: Array<HtmlTagObject>;
+      styles: Array<HtmlTagObject>;
+      meta: Array<HtmlTagObject>;
+    }} assetTags
+   * @param {"body" | "head"} scriptTarget
+   * @returns {{
+      headTags: Array<HtmlTagObject>;
+      bodyTags: Array<HtmlTagObject>;
+    }}
+   */
+  groupAssetsByElements(assetTags, scriptTarget) {
+    /** @type {{ headTags: Array<HtmlTagObject>; bodyTags: Array<HtmlTagObject>; }} */
+    const result = {
+      headTags: [...assetTags.meta, ...assetTags.styles],
+      bodyTags: [],
+    };
+
+    // Add script tags to head or body depending on
+    // the htmlPluginOptions
+    if (scriptTarget === "body") {
+      result.bodyTags.push(...assetTags.scripts);
+    } else {
+      // If script loading is blocking add the scripts to the end of the head
+      // If script loading is non-blocking add the scripts in front of the css files
+      const insertPosition =
+        this.options.scriptLoading === "blocking"
+          ? result.headTags.length
+          : assetTags.meta.length;
+
+      result.headTags.splice(insertPosition, 0, ...assetTags.scripts);
+    }
+
+    return result;
+  }
+
+  /**
+   * Replace [contenthash] in filename
+   *
+   * @see https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/
+   *
+   * @private
+   * @param {Compiler} compiler
+   * @param {string} filename
+   * @param {string|Buffer} fileContent
+   * @param {Compilation} compilation
+   * @returns {{ path: string, info: {} }}
+   */
+  replacePlaceholdersInFilename(compiler, filename, fileContent, compilation) {
+    if (/\[\\*([\w:]+)\\*\]/i.test(filename) === false) {
+      return { path: filename, info: {} };
+    }
+
+    const hash = compiler.webpack.util.createHash(
+      compilation.outputOptions.hashFunction,
+    );
+
+    hash.update(fileContent);
+
+    if (compilation.outputOptions.hashSalt) {
+      hash.update(compilation.outputOptions.hashSalt);
+    }
+
+    const contentHash = /** @type {string} */ (
+      hash
+        .digest(compilation.outputOptions.hashDigest)
+        .slice(0, compilation.outputOptions.hashDigestLength)
+    );
+
+    return compilation.getPathWithInfo(filename, {
+      contentHash,
+      chunk: {
+        hash: contentHash,
+        // @ts-ignore
+        contentHash,
+      },
+    });
+  }
+
+  /**
+   * Function to generate HTML file.
+   *
+   * @private
+   * @param {Compiler} compiler
+   * @param {Compilation} compilation
+   * @param {string} outputName
+   * @param {CachedChildCompilation} childCompilerPlugin
+   * @param {PreviousEmittedAssets} previousEmittedAssets
+   * @param {{ value: string | undefined }} assetJson
+   * @param {(err?: Error) => void} callback
+   */
+  generateHTML(
+    compiler,
+    compilation,
+    outputName,
+    childCompilerPlugin,
+    previousEmittedAssets,
+    assetJson,
+    callback,
+  ) {
+    // Get all entry point names for this html file
+    const entryNames = Array.from(compilation.entrypoints.keys());
+    const filteredEntryNames = this.filterEntryChunks(
+      entryNames,
+      this.options.chunks,
+      this.options.excludeChunks,
+    );
+    const sortedEntryNames = this.sortEntryChunks(
+      filteredEntryNames,
+      this.options.chunksSortMode,
+      compilation,
+    );
+    const templateResult = this.options.templateContent
+      ? { mainCompilationHash: compilation.hash }
+      : childCompilerPlugin.getCompilationEntryResult(this.options.template);
+
+    if ("error" in templateResult) {
+      compilation.errors.push(
+        new Error(
+          prettyError(templateResult.error, compiler.context).toString(),
+        ),
+      );
+    }
+
+    // If the child compilation was not executed during a previous main compile run
+    // it is a cached result
+    const isCompilationCached =
+      templateResult.mainCompilationHash !== compilation.hash;
+    /** Generated file paths from the entry point names */
+    const assetsInformationByGroups = this.getAssetsInformationByGroups(
+      compilation,
+      outputName,
+      sortedEntryNames,
+    );
+    // If the template and the assets did not change we don't have to emit the html
+    const newAssetJson = JSON.stringify(
+      this.getAssetFiles(assetsInformationByGroups),
+    );
+
+    if (
+      isCompilationCached &&
+      this.options.cache &&
+      assetJson.value === newAssetJson
+    ) {
+      previousEmittedAssets.forEach(({ name, source, info }) => {
+        compilation.emitAsset(name, source, info);
+      });
+      return callback();
+    } else {
+      previousEmittedAssets.length = 0;
+      assetJson.value = newAssetJson;
+    }
+
+    // The html-webpack plugin uses a object representation for the html-tags which will be injected
+    // to allow altering them more easily
+    // Just before they are converted a third-party-plugin author might change the order and content
+    const assetsPromise = this.generateFavicon(
+      compiler,
+      this.options.favicon,
+      compilation,
+      assetsInformationByGroups.publicPath,
+      previousEmittedAssets,
+    ).then((faviconPath) => {
+      assetsInformationByGroups.favicon = faviconPath;
+      return HtmlWebpackPlugin.getCompilationHooks(
+        compilation,
+      ).beforeAssetTagGeneration.promise({
+        assets: assetsInformationByGroups,
+        outputName,
+        plugin: this,
+      });
+    });
+
+    // Turn the js and css paths into grouped HtmlTagObjects
+    const assetTagGroupsPromise = assetsPromise
+      // And allow third-party-plugin authors to reorder and change the assetTags before they are grouped
+      .then(({ assets }) =>
+        HtmlWebpackPlugin.getCompilationHooks(
+          compilation,
+        ).alterAssetTags.promise({
+          assetTags: {
+            scripts: this.generatedScriptTags(assets.js),
+            styles: this.generateStyleTags(assets.css),
+            meta: [
+              ...(this.options.base !== false
+                ? this.generateBaseTag(this.options.base)
+                : []),
+              ...this.generatedMetaTags(this.options.meta),
+              ...(assets.favicon
+                ? this.generateFaviconTag(assets.favicon)
+                : []),
+            ],
+          },
+          outputName,
+          publicPath: assetsInformationByGroups.publicPath,
+          plugin: this,
+        }),
+      )
+      .then(({ assetTags }) => {
+        // Inject scripts to body unless it set explicitly to head
+        const scriptTarget =
+          this.options.inject === "head" ||
+          (this.options.inject !== "body" &&
+            this.options.scriptLoading !== "blocking")
+            ? "head"
+            : "body";
+        // Group assets to `head` and `body` tag arrays
+        const assetGroups = this.groupAssetsByElements(assetTags, scriptTarget);
+        // Allow third-party-plugin authors to reorder and change the assetTags once they are grouped
+        return HtmlWebpackPlugin.getCompilationHooks(
+          compilation,
+        ).alterAssetTagGroups.promise({
+          headTags: assetGroups.headTags,
+          bodyTags: assetGroups.bodyTags,
+          outputName,
+          publicPath: assetsInformationByGroups.publicPath,
+          plugin: this,
+        });
+      });
+
+    // Turn the compiled template into a nodejs function or into a nodejs string
+    const templateEvaluationPromise = Promise.resolve().then(() => {
+      if ("error" in templateResult) {
+        return this.options.showErrors
+          ? prettyError(templateResult.error, compiler.context).toHtml()
+          : "ERROR";
+      }
+
+      // Allow to use a custom function / string instead
+      if (this.options.templateContent !== false) {
+        return this.options.templateContent;
+      }
+
+      // Once everything is compiled evaluate the html factory and replace it with its content
+      if ("compiledEntry" in templateResult) {
+        const compiledEntry = templateResult.compiledEntry;
+        const assets = compiledEntry.assets;
+
+        // Store assets from child compiler to re-emit them later
+        for (const name in assets) {
+          previousEmittedAssets.push({
+            name,
+            source: assets[name].source,
+            info: assets[name].info,
+          });
+        }
+
+        return this.evaluateCompilationResult(
+          compiledEntry.content,
+          assetsInformationByGroups.publicPath,
+          this.options.template,
+        );
+      }
+
+      return Promise.reject(
+        new Error("Child compilation contained no compiledEntry"),
+      );
+    });
+    const templateExecutionPromise = Promise.all([
+      assetsPromise,
+      assetTagGroupsPromise,
+      templateEvaluationPromise,
+    ])
+      // Execute the template
+      .then(([assetsHookResult, assetTags, compilationResult]) =>
+        typeof compilationResult !== "function"
+          ? compilationResult
+          : this.executeTemplate(
+              compilationResult,
+              assetsHookResult.assets,
+              { headTags: assetTags.headTags, bodyTags: assetTags.bodyTags },
+              compilation,
+            ),
+      );
+
+    const injectedHtmlPromise = Promise.all([
+      assetTagGroupsPromise,
+      templateExecutionPromise,
+    ])
+      // Allow plugins to change the html before assets are injected
+      .then(([assetTags, html]) => {
+        const pluginArgs = {
+          html,
+          headTags: assetTags.headTags,
+          bodyTags: assetTags.bodyTags,
+          plugin: this,
+          outputName,
+        };
+        return HtmlWebpackPlugin.getCompilationHooks(
+          compilation,
+        ).afterTemplateExecution.promise(pluginArgs);
+      })
+      .then(({ html, headTags, bodyTags }) => {
+        return this.postProcessHtml(compiler, html, assetsInformationByGroups, {
+          headTags,
+          bodyTags,
+        });
+      });
+
+    const emitHtmlPromise = injectedHtmlPromise
+      // Allow plugins to change the html after assets are injected
+      .then((html) => {
+        const pluginArgs = { html, plugin: this, outputName };
+        return HtmlWebpackPlugin.getCompilationHooks(compilation)
+          .beforeEmit.promise(pluginArgs)
+          .then((result) => result.html);
+      })
+      .catch((err) => {
+        // In case anything went wrong the promise is resolved
+        // with the error message and an error is logged
+        compilation.errors.push(
+          new Error(prettyError(err, compiler.context).toString()),
+        );
+        return this.options.showErrors
+          ? prettyError(err, compiler.context).toHtml()
+          : "ERROR";
+      })
+      .then((html) => {
+        const filename = outputName.replace(
+          /\[templatehash([^\]]*)\]/g,
+          require("util").deprecate(
+            (match, options) => `[contenthash${options}]`,
+            "[templatehash] is now [contenthash]",
+          ),
+        );
+        const replacedFilename = this.replacePlaceholdersInFilename(
+          compiler,
+          filename,
+          html,
+          compilation,
+        );
+        const source = new compiler.webpack.sources.RawSource(html, false);
+
+        // Add the evaluated html code to the webpack assets
+        compilation.emitAsset(
+          replacedFilename.path,
+          source,
+          replacedFilename.info,
+        );
+        previousEmittedAssets.push({ name: replacedFilename.path, source });
+
+        return replacedFilename.path;
+      })
+      .then((finalOutputName) =>
+        HtmlWebpackPlugin.getCompilationHooks(compilation)
+          .afterEmit.promise({
+            outputName: finalOutputName,
+            plugin: this,
+          })
+          .catch((err) => {
+            /** @type {Logger} */
+            (this.logger).error(err);
+            return null;
+          })
+          .then(() => null),
+      );
+
+    // Once all files are added to the webpack compilation
+    // let the webpack compiler continue
+    emitHtmlPromise.then(() => {
+      callback();
+    });
+  }
+}
+
+/**
+ * The default for options.templateParameter
+ * Generate the template parameters
+ *
+ * Generate the template parameters for the template function
+ * @param {Compilation} compilation
+ * @param {AssetsInformationByGroups} assets
+ * @param {{
+     headTags: HtmlTagObject[],
+     bodyTags: HtmlTagObject[]
+   }} assetTags
+ * @param {ProcessedHtmlWebpackOptions} options
+ * @returns {TemplateParameter}
+ */
+function templateParametersGenerator(compilation, assets, assetTags, options) {
+  return {
+    compilation: compilation,
+    webpackConfig: compilation.options,
+    htmlWebpackPlugin: {
+      tags: assetTags,
+      files: assets,
+      options: options,
+    },
+  };
+}
+
+// Statics:
+/**
+ * The major version number of this plugin
+ */
+HtmlWebpackPlugin.version = 5;
+
+/**
+ * A static helper to get the hooks for this plugin
+ *
+ * Usage: HtmlWebpackPlugin.getHooks(compilation).HOOK_NAME.tapAsync('YourPluginName', () => { ... });
+ */
+// TODO remove me in the next major release in favor getCompilationHooks
+HtmlWebpackPlugin.getHooks = HtmlWebpackPlugin.getCompilationHooks;
+HtmlWebpackPlugin.createHtmlTagObject = createHtmlTagObject;
+
+module.exports = HtmlWebpackPlugin;
Index: frontend/node_modules/html-webpack-plugin/lib/cached-child-compiler.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/lib/cached-child-compiler.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/lib/cached-child-compiler.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,482 @@
+// @ts-check
+/**
+ * @file
+ * Helper plugin manages the cached state of the child compilation
+ *
+ * To optimize performance the child compilation is running asynchronously.
+ * Therefore it needs to be started in the compiler.make phase and ends after
+ * the compilation.afterCompile phase.
+ *
+ * To prevent bugs from blocked hooks there is no promise or event based api
+ * for this plugin.
+ *
+ * Example usage:
+ *
+ * ```js
+    const childCompilerPlugin = new PersistentChildCompilerPlugin();
+    childCompilerPlugin.addEntry('./src/index.js');
+    compiler.hooks.afterCompile.tapAsync('MyPlugin', (compilation, callback) => {
+      console.log(childCompilerPlugin.getCompilationResult()['./src/index.js']));
+      return true;
+    });
+ * ```
+ */
+"use strict";
+
+// Import types
+/** @typedef {import("webpack").Compiler} Compiler */
+/** @typedef {import("webpack").Compilation} Compilation */
+/** @typedef {import("webpack/lib/FileSystemInfo").Snapshot} Snapshot */
+/** @typedef {import("./child-compiler").ChildCompilationTemplateResult} ChildCompilationTemplateResult */
+/** @typedef {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} FileDependencies */
+/** @typedef {{
+  dependencies: FileDependencies,
+  compiledEntries: {[entryName: string]: ChildCompilationTemplateResult}
+} | {
+  dependencies: FileDependencies,
+  error: Error
+}} ChildCompilationResult */
+
+const { HtmlWebpackChildCompiler } = require("./child-compiler");
+
+/**
+ * This plugin is a singleton for performance reasons.
+ * To keep track if a plugin does already exist for the compiler they are cached
+ * in this map
+ * @type {WeakMap<Compiler, PersistentChildCompilerSingletonPlugin>}}
+ */
+const compilerMap = new WeakMap();
+
+class CachedChildCompilation {
+  /**
+   * @param {Compiler} compiler
+   */
+  constructor(compiler) {
+    /**
+     * @private
+     * @type {Compiler}
+     */
+    this.compiler = compiler;
+    // Create a singleton instance for the compiler
+    // if there is none
+    if (compilerMap.has(compiler)) {
+      return;
+    }
+    const persistentChildCompilerSingletonPlugin =
+      new PersistentChildCompilerSingletonPlugin();
+    compilerMap.set(compiler, persistentChildCompilerSingletonPlugin);
+    persistentChildCompilerSingletonPlugin.apply(compiler);
+  }
+
+  /**
+   * apply is called by the webpack main compiler during the start phase
+   * @param {string} entry
+   */
+  addEntry(entry) {
+    const persistentChildCompilerSingletonPlugin = compilerMap.get(
+      this.compiler,
+    );
+    if (!persistentChildCompilerSingletonPlugin) {
+      throw new Error(
+        "PersistentChildCompilerSingletonPlugin instance not found.",
+      );
+    }
+    persistentChildCompilerSingletonPlugin.addEntry(entry);
+  }
+
+  getCompilationResult() {
+    const persistentChildCompilerSingletonPlugin = compilerMap.get(
+      this.compiler,
+    );
+    if (!persistentChildCompilerSingletonPlugin) {
+      throw new Error(
+        "PersistentChildCompilerSingletonPlugin instance not found.",
+      );
+    }
+    return persistentChildCompilerSingletonPlugin.getLatestResult();
+  }
+
+  /**
+   * Returns the result for the given entry
+   * @param {string} entry
+   * @returns {
+      | { mainCompilationHash: string, error: Error }
+      | { mainCompilationHash: string, compiledEntry: ChildCompilationTemplateResult }
+    }
+   */
+  getCompilationEntryResult(entry) {
+    const latestResult = this.getCompilationResult();
+    const compilationResult = latestResult.compilationResult;
+    return "error" in compilationResult
+      ? {
+          mainCompilationHash: latestResult.mainCompilationHash,
+          error: compilationResult.error,
+        }
+      : {
+          mainCompilationHash: latestResult.mainCompilationHash,
+          compiledEntry: compilationResult.compiledEntries[entry],
+        };
+  }
+}
+
+class PersistentChildCompilerSingletonPlugin {
+  /**
+   *
+   * @param {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}} fileDependencies
+   * @param {Compilation} mainCompilation
+   * @param {number} startTime
+   */
+  static createSnapshot(fileDependencies, mainCompilation, startTime) {
+    return new Promise((resolve, reject) => {
+      mainCompilation.fileSystemInfo.createSnapshot(
+        startTime,
+        fileDependencies.fileDependencies,
+        fileDependencies.contextDependencies,
+        fileDependencies.missingDependencies,
+        // @ts-ignore
+        null,
+        (err, snapshot) => {
+          if (err) {
+            return reject(err);
+          }
+          resolve(snapshot);
+        },
+      );
+    });
+  }
+
+  /**
+   * Returns true if the files inside this snapshot
+   * have not been changed
+   *
+   * @param {Snapshot} snapshot
+   * @param {Compilation} mainCompilation
+   * @returns {Promise<boolean | undefined>}
+   */
+  static isSnapshotValid(snapshot, mainCompilation) {
+    return new Promise((resolve, reject) => {
+      mainCompilation.fileSystemInfo.checkSnapshotValid(
+        snapshot,
+        (err, isValid) => {
+          if (err) {
+            reject(err);
+          }
+          resolve(isValid);
+        },
+      );
+    });
+  }
+
+  static watchFiles(mainCompilation, fileDependencies) {
+    Object.keys(fileDependencies).forEach((dependencyType) => {
+      fileDependencies[dependencyType].forEach((fileDependency) => {
+        mainCompilation[dependencyType].add(fileDependency);
+      });
+    });
+  }
+
+  constructor() {
+    /**
+     * @private
+     * @type {
+      | {
+        isCompiling: false,
+        isVerifyingCache: false,
+        entries: string[],
+        compiledEntries: string[],
+        mainCompilationHash: string,
+        compilationResult: ChildCompilationResult
+      }
+    | Readonly<{
+      isCompiling: false,
+      isVerifyingCache: true,
+      entries: string[],
+      previousEntries: string[],
+      previousResult: ChildCompilationResult
+    }>
+    | Readonly <{
+      isVerifyingCache: false,
+      isCompiling: true,
+      entries: string[],
+    }>
+  } the internal compilation state */
+    this.compilationState = {
+      isCompiling: false,
+      isVerifyingCache: false,
+      entries: [],
+      compiledEntries: [],
+      mainCompilationHash: "initial",
+      compilationResult: {
+        dependencies: {
+          fileDependencies: [],
+          contextDependencies: [],
+          missingDependencies: [],
+        },
+        compiledEntries: {},
+      },
+    };
+  }
+
+  /**
+   * apply is called by the webpack main compiler during the start phase
+   * @param {Compiler} compiler
+   */
+  apply(compiler) {
+    /** @type Promise<ChildCompilationResult> */
+    let childCompilationResultPromise = Promise.resolve({
+      dependencies: {
+        fileDependencies: [],
+        contextDependencies: [],
+        missingDependencies: [],
+      },
+      compiledEntries: {},
+    });
+    /**
+     * The main compilation hash which will only be updated
+     * if the childCompiler changes
+     */
+    /** @type {string} */
+    let mainCompilationHashOfLastChildRecompile = "";
+    /** @type {Snapshot | undefined} */
+    let previousFileSystemSnapshot;
+    let compilationStartTime = new Date().getTime();
+
+    compiler.hooks.make.tapAsync(
+      "PersistentChildCompilerSingletonPlugin",
+      (mainCompilation, callback) => {
+        if (
+          this.compilationState.isCompiling ||
+          this.compilationState.isVerifyingCache
+        ) {
+          return callback(new Error("Child compilation has already started"));
+        }
+
+        // Update the time to the current compile start time
+        compilationStartTime = new Date().getTime();
+
+        // The compilation starts - adding new templates is now not possible anymore
+        this.compilationState = {
+          isCompiling: false,
+          isVerifyingCache: true,
+          previousEntries: this.compilationState.compiledEntries,
+          previousResult: this.compilationState.compilationResult,
+          entries: this.compilationState.entries,
+        };
+
+        // Validate cache:
+        const isCacheValidPromise = this.isCacheValid(
+          previousFileSystemSnapshot,
+          mainCompilation,
+        );
+
+        let cachedResult = childCompilationResultPromise;
+        childCompilationResultPromise = isCacheValidPromise.then(
+          (isCacheValid) => {
+            // Reuse cache
+            if (isCacheValid) {
+              return cachedResult;
+            }
+            // Start the compilation
+            const compiledEntriesPromise = this.compileEntries(
+              mainCompilation,
+              this.compilationState.entries,
+            );
+            // Update snapshot as soon as we know the fileDependencies
+            // this might possibly cause bugs if files were changed between
+            // compilation start and snapshot creation
+            compiledEntriesPromise
+              .then((childCompilationResult) => {
+                return PersistentChildCompilerSingletonPlugin.createSnapshot(
+                  childCompilationResult.dependencies,
+                  mainCompilation,
+                  compilationStartTime,
+                );
+              })
+              .then((snapshot) => {
+                previousFileSystemSnapshot = snapshot;
+              });
+            return compiledEntriesPromise;
+          },
+        );
+
+        // Add files to compilation which needs to be watched:
+        mainCompilation.hooks.optimizeTree.tapAsync(
+          "PersistentChildCompilerSingletonPlugin",
+          (chunks, modules, callback) => {
+            const handleCompilationDonePromise =
+              childCompilationResultPromise.then((childCompilationResult) => {
+                this.watchFiles(
+                  mainCompilation,
+                  childCompilationResult.dependencies,
+                );
+              });
+            handleCompilationDonePromise.then(
+              // @ts-ignore
+              () => callback(null, chunks, modules),
+              callback,
+            );
+          },
+        );
+
+        // Store the final compilation once the main compilation hash is known
+        mainCompilation.hooks.additionalAssets.tapAsync(
+          "PersistentChildCompilerSingletonPlugin",
+          (callback) => {
+            const didRecompilePromise = Promise.all([
+              childCompilationResultPromise,
+              cachedResult,
+            ]).then(([childCompilationResult, cachedResult]) => {
+              // Update if childCompilation changed
+              return cachedResult !== childCompilationResult;
+            });
+
+            const handleCompilationDonePromise = Promise.all([
+              childCompilationResultPromise,
+              didRecompilePromise,
+            ]).then(([childCompilationResult, didRecompile]) => {
+              // Update hash and snapshot if childCompilation changed
+              if (didRecompile) {
+                mainCompilationHashOfLastChildRecompile =
+                  /** @type {string} */ (mainCompilation.hash);
+              }
+              this.compilationState = {
+                isCompiling: false,
+                isVerifyingCache: false,
+                entries: this.compilationState.entries,
+                compiledEntries: this.compilationState.entries,
+                compilationResult: childCompilationResult,
+                mainCompilationHash: mainCompilationHashOfLastChildRecompile,
+              };
+            });
+            handleCompilationDonePromise.then(() => callback(null), callback);
+          },
+        );
+
+        // Continue compilation:
+        callback(null);
+      },
+    );
+  }
+
+  /**
+   * Add a new entry to the next compile run
+   * @param {string} entry
+   */
+  addEntry(entry) {
+    if (
+      this.compilationState.isCompiling ||
+      this.compilationState.isVerifyingCache
+    ) {
+      throw new Error(
+        "The child compiler has already started to compile. " +
+          "Please add entries before the main compiler 'make' phase has started or " +
+          "after the compilation is done.",
+      );
+    }
+    if (this.compilationState.entries.indexOf(entry) === -1) {
+      this.compilationState.entries = [...this.compilationState.entries, entry];
+    }
+  }
+
+  getLatestResult() {
+    if (
+      this.compilationState.isCompiling ||
+      this.compilationState.isVerifyingCache
+    ) {
+      throw new Error(
+        "The child compiler is not done compiling. " +
+          "Please access the result after the compiler 'make' phase has started or " +
+          "after the compilation is done.",
+      );
+    }
+    return {
+      mainCompilationHash: this.compilationState.mainCompilationHash,
+      compilationResult: this.compilationState.compilationResult,
+    };
+  }
+
+  /**
+   * Verify that the cache is still valid
+   * @private
+   * @param {Snapshot | undefined} snapshot
+   * @param {Compilation} mainCompilation
+   * @returns {Promise<boolean | undefined>}
+   */
+  isCacheValid(snapshot, mainCompilation) {
+    if (!this.compilationState.isVerifyingCache) {
+      return Promise.reject(
+        new Error(
+          "Cache validation can only be done right before the compilation starts",
+        ),
+      );
+    }
+    // If there are no entries we don't need a new child compilation
+    if (this.compilationState.entries.length === 0) {
+      return Promise.resolve(true);
+    }
+    // If there are new entries the cache is invalid
+    if (
+      this.compilationState.entries !== this.compilationState.previousEntries
+    ) {
+      return Promise.resolve(false);
+    }
+    // Mark the cache as invalid if there is no snapshot
+    if (!snapshot) {
+      return Promise.resolve(false);
+    }
+
+    return PersistentChildCompilerSingletonPlugin.isSnapshotValid(
+      snapshot,
+      mainCompilation,
+    );
+  }
+
+  /**
+   * Start to compile all templates
+   *
+   * @private
+   * @param {Compilation} mainCompilation
+   * @param {string[]} entries
+   * @returns {Promise<ChildCompilationResult>}
+   */
+  compileEntries(mainCompilation, entries) {
+    const compiler = new HtmlWebpackChildCompiler(entries);
+    return compiler.compileTemplates(mainCompilation).then(
+      (result) => {
+        return {
+          // The compiled sources to render the content
+          compiledEntries: result,
+          // The file dependencies to find out if a
+          // recompilation is required
+          dependencies: compiler.fileDependencies,
+          // The main compilation hash can be used to find out
+          // if this compilation was done during the current compilation
+          mainCompilationHash: mainCompilation.hash,
+        };
+      },
+      (error) => ({
+        // The compiled sources to render the content
+        error,
+        // The file dependencies to find out if a
+        // recompilation is required
+        dependencies: compiler.fileDependencies,
+        // The main compilation hash can be used to find out
+        // if this compilation was done during the current compilation
+        mainCompilationHash: mainCompilation.hash,
+      }),
+    );
+  }
+
+  /**
+   * @private
+   * @param {Compilation} mainCompilation
+   * @param {FileDependencies} files
+   */
+  watchFiles(mainCompilation, files) {
+    PersistentChildCompilerSingletonPlugin.watchFiles(mainCompilation, files);
+  }
+}
+
+module.exports = {
+  CachedChildCompilation,
+};
Index: frontend/node_modules/html-webpack-plugin/lib/child-compiler.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/lib/child-compiler.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/lib/child-compiler.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,264 @@
+// @ts-check
+"use strict";
+
+/**
+ * @file
+ * This file uses webpack to compile a template with a child compiler.
+ *
+ * [TEMPLATE] -> [JAVASCRIPT]
+ *
+ */
+
+/** @typedef {import("webpack").Chunk} Chunk */
+/** @typedef {import("webpack").sources.Source} Source */
+/** @typedef {{hash: string, entry: Chunk, content: string, assets: {[name: string]: { source: Source, info: import("webpack").AssetInfo }}}} ChildCompilationTemplateResult */
+
+/**
+ * The HtmlWebpackChildCompiler is a helper to allow reusing one childCompiler
+ * for multiple HtmlWebpackPlugin instances to improve the compilation performance.
+ */
+class HtmlWebpackChildCompiler {
+  /**
+   *
+   * @param {string[]} templates
+   */
+  constructor(templates) {
+    /**
+     * @type {string[]} templateIds
+     * The template array will allow us to keep track which input generated which output
+     */
+    this.templates = templates;
+    /** @type {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>} */
+    this.compilationPromise; // eslint-disable-line
+    /** @type {number | undefined} */
+    this.compilationStartedTimestamp; // eslint-disable-line
+    /** @type {number | undefined} */
+    this.compilationEndedTimestamp; // eslint-disable-line
+    /**
+     * All file dependencies of the child compiler
+     * @type {{fileDependencies: string[], contextDependencies: string[], missingDependencies: string[]}}
+     */
+    this.fileDependencies = {
+      fileDependencies: [],
+      contextDependencies: [],
+      missingDependencies: [],
+    };
+  }
+
+  /**
+   * Returns true if the childCompiler is currently compiling
+   *
+   * @returns {boolean}
+   */
+  isCompiling() {
+    return !this.didCompile() && this.compilationStartedTimestamp !== undefined;
+  }
+
+  /**
+   * Returns true if the childCompiler is done compiling
+   *
+   * @returns {boolean}
+   */
+  didCompile() {
+    return this.compilationEndedTimestamp !== undefined;
+  }
+
+  /**
+   * This function will start the template compilation
+   * once it is started no more templates can be added
+   *
+   * @param {import('webpack').Compilation} mainCompilation
+   * @returns {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>}
+   */
+  compileTemplates(mainCompilation) {
+    const webpack = mainCompilation.compiler.webpack;
+    const Compilation = webpack.Compilation;
+
+    const NodeTemplatePlugin = webpack.node.NodeTemplatePlugin;
+    const NodeTargetPlugin = webpack.node.NodeTargetPlugin;
+    const LoaderTargetPlugin = webpack.LoaderTargetPlugin;
+    const EntryPlugin = webpack.EntryPlugin;
+
+    // To prevent multiple compilations for the same template
+    // the compilation is cached in a promise.
+    // If it already exists return
+    if (this.compilationPromise) {
+      return this.compilationPromise;
+    }
+
+    const outputOptions = {
+      filename: "__child-[name]",
+      publicPath: "",
+      library: {
+        type: "var",
+        name: "HTML_WEBPACK_PLUGIN_RESULT",
+      },
+      scriptType: /** @type {'text/javascript'} */ ("text/javascript"),
+      iife: true,
+    };
+    const compilerName = "HtmlWebpackCompiler";
+    // Create an additional child compiler which takes the template
+    // and turns it into an Node.JS html factory.
+    // This allows us to use loaders during the compilation
+    const childCompiler = mainCompilation.createChildCompiler(
+      compilerName,
+      outputOptions,
+      [
+        // Compile the template to nodejs javascript
+        new NodeTargetPlugin(),
+        new NodeTemplatePlugin(),
+        new LoaderTargetPlugin("node"),
+        new webpack.library.EnableLibraryPlugin("var"),
+      ],
+    );
+    // The file path context which webpack uses to resolve all relative files to
+    childCompiler.context = mainCompilation.compiler.context;
+
+    // Generate output file names
+    const temporaryTemplateNames = this.templates.map(
+      (template, index) => `__child-HtmlWebpackPlugin_${index}-${template}`,
+    );
+
+    // Add all templates
+    this.templates.forEach((template, index) => {
+      new EntryPlugin(
+        childCompiler.context,
+        "data:text/javascript,__webpack_public_path__ = __webpack_base_uri__ = htmlWebpackPluginPublicPath;",
+        `HtmlWebpackPlugin_${index}-${template}`,
+      ).apply(childCompiler);
+      new EntryPlugin(
+        childCompiler.context,
+        template,
+        `HtmlWebpackPlugin_${index}-${template}`,
+      ).apply(childCompiler);
+    });
+
+    // The templates are compiled and executed by NodeJS - similar to server side rendering
+    // Unfortunately this causes issues as some loaders require an absolute URL to support ES Modules
+    // The following config enables relative URL support for the child compiler
+    childCompiler.options.module = { ...childCompiler.options.module };
+    childCompiler.options.module.parser = {
+      ...childCompiler.options.module.parser,
+    };
+    childCompiler.options.module.parser.javascript = {
+      ...childCompiler.options.module.parser.javascript,
+      url: "relative",
+    };
+
+    this.compilationStartedTimestamp = new Date().getTime();
+    /** @type {Promise<{[templatePath: string]: ChildCompilationTemplateResult}>} */
+    this.compilationPromise = new Promise((resolve, reject) => {
+      /** @type {Source[]} */
+      const extractedAssets = [];
+
+      childCompiler.hooks.thisCompilation.tap(
+        "HtmlWebpackPlugin",
+        (compilation) => {
+          compilation.hooks.processAssets.tap(
+            {
+              name: "HtmlWebpackPlugin",
+              stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
+            },
+            (assets) => {
+              temporaryTemplateNames.forEach((temporaryTemplateName) => {
+                if (assets[temporaryTemplateName]) {
+                  extractedAssets.push(assets[temporaryTemplateName]);
+
+                  compilation.deleteAsset(temporaryTemplateName);
+                }
+              });
+            },
+          );
+        },
+      );
+
+      childCompiler.runAsChild((err, entries, childCompilation) => {
+        // Extract templates
+        // TODO fine a better way to store entries and results, to avoid duplicate chunks and assets
+        const compiledTemplates = entries
+          ? extractedAssets.map((asset) => asset.source())
+          : [];
+
+        // Extract file dependencies
+        if (entries && childCompilation) {
+          this.fileDependencies = {
+            fileDependencies: Array.from(childCompilation.fileDependencies),
+            contextDependencies: Array.from(
+              childCompilation.contextDependencies,
+            ),
+            missingDependencies: Array.from(
+              childCompilation.missingDependencies,
+            ),
+          };
+        }
+
+        // Reject the promise if the childCompilation contains error
+        if (
+          childCompilation &&
+          childCompilation.errors &&
+          childCompilation.errors.length
+        ) {
+          const errorDetailsArray = [];
+          for (const error of childCompilation.errors) {
+            let message = error.message;
+            if (error.stack) {
+              message += "\n" + error.stack;
+            }
+            errorDetailsArray.push(message);
+          }
+          const errorDetails = errorDetailsArray.join("\n");
+
+          reject(new Error("Child compilation failed:\n" + errorDetails));
+
+          return;
+        }
+
+        // Reject if the error object contains errors
+        if (err) {
+          reject(err);
+          return;
+        }
+
+        if (!childCompilation || !entries) {
+          reject(new Error("Empty child compilation"));
+          return;
+        }
+
+        /**
+         * @type {{[templatePath: string]: ChildCompilationTemplateResult}}
+         */
+        const result = {};
+
+        /** @type {{[name: string]: { source: Source, info: import("webpack").AssetInfo }}} */
+        const assets = {};
+
+        for (const asset of childCompilation.getAssets()) {
+          assets[asset.name] = { source: asset.source, info: asset.info };
+        }
+
+        compiledTemplates.forEach((templateSource, entryIndex) => {
+          // The compiledTemplates are generated from the entries added in
+          // the addTemplate function.
+          // Therefore, the array index of this.templates should be the as entryIndex.
+          result[this.templates[entryIndex]] = {
+            // TODO, can we have Buffer here?
+            content: /** @type {string} */ (templateSource),
+            hash: childCompilation.hash || "XXXX",
+            entry: entries[entryIndex],
+            assets,
+          };
+        });
+
+        this.compilationEndedTimestamp = new Date().getTime();
+
+        resolve(result);
+      });
+    });
+
+    return this.compilationPromise;
+  }
+}
+
+module.exports = {
+  HtmlWebpackChildCompiler,
+};
Index: frontend/node_modules/html-webpack-plugin/lib/chunksorter.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/lib/chunksorter.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/lib/chunksorter.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,45 @@
+// @ts-check
+"use strict";
+
+/** @typedef {import("webpack").Compilation} Compilation */
+
+/**
+ * @type {{[sortmode: string] : (entryPointNames: Array<string>, compilation: Compilation, htmlWebpackPluginOptions: any) => Array<string> }}
+ * This file contains different sort methods for the entry chunks names
+ */
+module.exports = {};
+
+/**
+ * Performs identity mapping (no-sort).
+ * @param  {Array<string>} chunks the chunks to sort
+ * @return {Array<string>} The sorted chunks
+ */
+module.exports.none = (chunks) => chunks;
+
+/**
+ * Sort manually by the chunks
+ * @param {string[]} entryPointNames the chunks to sort
+ * @param {Compilation} compilation the webpack compilation
+ * @param {any} htmlWebpackPluginOptions the plugin options
+ * @return {string[]} The sorted chunks
+ */
+module.exports.manual = (
+  entryPointNames,
+  compilation,
+  htmlWebpackPluginOptions,
+) => {
+  const chunks = htmlWebpackPluginOptions.chunks;
+  if (!Array.isArray(chunks)) {
+    return entryPointNames;
+  }
+  // Remove none existing entries from
+  // htmlWebpackPluginOptions.chunks
+  return chunks.filter((entryPointName) => {
+    return compilation.entrypoints.has(entryPointName);
+  });
+};
+
+/**
+ * Defines the default sorter.
+ */
+module.exports.auto = module.exports.none;
Index: frontend/node_modules/html-webpack-plugin/lib/errors.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/lib/errors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/lib/errors.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,42 @@
+// @ts-nocheck
+"use strict";
+
+let prettyError;
+
+function getPrettyError() {
+  if (!prettyError) {
+    // lazily require to improve startup time since pretty-error is rather heavy package
+    const PrettyError = require("pretty-error");
+    prettyError = new PrettyError();
+    prettyError.withoutColors();
+    prettyError.skipPackage("html-plugin-evaluation");
+    prettyError.skipNodeFiles();
+    prettyError.skip(function (traceLine) {
+      return traceLine.path === "html-plugin-evaluation";
+    });
+  }
+  return prettyError;
+}
+
+module.exports = function (err, context) {
+  return {
+    toHtml: function () {
+      return "Html Webpack Plugin:\n<pre>\n" + this.toString() + "</pre>";
+    },
+    toJsonHtml: function () {
+      return JSON.stringify(this.toHtml());
+    },
+    toString: function () {
+      try {
+        return getPrettyError()
+          .render(err)
+          .replace(/webpack:\/\/\/\./g, context);
+      } catch (e) {
+        // This can sometimes fail. We don't know why, but returning the
+        // original error is better than returning the error thrown by
+        // pretty-error.
+        return err;
+      }
+    },
+  };
+};
Index: frontend/node_modules/html-webpack-plugin/lib/hooks.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/lib/hooks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/lib/hooks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,7 @@
+// TODO remove in the next major release
+const plugin = require("../index");
+
+module.exports = {
+  getHtmlWebpackPluginHooks: (compilation) =>
+    plugin.getCompilationHooks(compilation),
+};
Index: frontend/node_modules/html-webpack-plugin/lib/html-tags.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/lib/html-tags.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/lib/html-tags.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,127 @@
+// @ts-check
+/** @typedef {import("../typings").HtmlTagObject} HtmlTagObject */
+/**
+ * @file
+ * This file provides to helper to create html as a object representation as
+ * those objects are easier to modify than pure string representations
+ *
+ * Usage:
+ * ```
+ * const element = createHtmlTagObject('h1', {class: 'demo'}, 'Hello World');
+ * const html = htmlTagObjectToString(element);
+ * console.log(html) // -> <h1 class="demo">Hello World</h1>
+ * ```
+ */
+
+/**
+ * All html tag elements which must not contain innerHTML
+ * @see https://www.w3.org/TR/html5/syntax.html#void-elements
+ */
+const voidTags = [
+  "area",
+  "base",
+  "br",
+  "col",
+  "embed",
+  "hr",
+  "img",
+  "input",
+  "keygen",
+  "link",
+  "meta",
+  "param",
+  "source",
+  "track",
+  "wbr",
+];
+
+/**
+ * Turn a tag definition into a html string
+ * @param {HtmlTagObject} tagDefinition
+ *  A tag element according to the htmlWebpackPlugin object notation
+ *
+ * @param xhtml {boolean}
+ *   Whether the generated html should add closing slashes to be xhtml compliant
+ */
+function htmlTagObjectToString(tagDefinition, xhtml) {
+  const attributes = Object.keys(tagDefinition.attributes || {})
+    .filter(function (attributeName) {
+      return (
+        tagDefinition.attributes[attributeName] === "" ||
+        tagDefinition.attributes[attributeName]
+      );
+    })
+    .map(function (attributeName) {
+      if (tagDefinition.attributes[attributeName] === true) {
+        return xhtml
+          ? attributeName + '="' + attributeName + '"'
+          : attributeName;
+      }
+      return (
+        attributeName + '="' + tagDefinition.attributes[attributeName] + '"'
+      );
+    });
+  return (
+    "<" +
+    [tagDefinition.tagName].concat(attributes).join(" ") +
+    (tagDefinition.voidTag && xhtml ? "/" : "") +
+    ">" +
+    (tagDefinition.innerHTML || "") +
+    (tagDefinition.voidTag ? "" : "</" + tagDefinition.tagName + ">")
+  );
+}
+
+/**
+ * Static helper to create a tag object to be get injected into the dom
+ *
+ * @param {string} tagName
+ * the name of the tag e.g. 'div'
+ *
+ * @param {{[attributeName: string]: string|boolean|null|undefined}} [attributes]
+ * tag attributes e.g. `{ 'class': 'example', disabled: true }`
+ *
+ * @param {string} [innerHTML]
+ *
+ * @param {{[attributeName: string]: string|boolean|null|undefined}} [meta]
+ * meta information about the tag e.g. `{ 'plugin': 'html-webpack-plugin' }`
+ *
+ * @returns {HtmlTagObject}
+ */
+function createHtmlTagObject(tagName, attributes, innerHTML, meta) {
+  return {
+    tagName: tagName,
+    voidTag: voidTags.indexOf(tagName) !== -1,
+    attributes: attributes || {},
+    meta: meta || {},
+    innerHTML: innerHTML,
+  };
+}
+
+/**
+ * The `HtmlTagArray Array with a custom `.toString()` method.
+ *
+ * This allows the following:
+ * ```
+ *   const tags = HtmlTagArray.from([tag1, tag2]);
+ *   const scriptTags = tags.filter((tag) => tag.tagName === 'script');
+ *   const html = scriptTags.toString();
+ * ```
+ *
+ * Or inside a string literal:
+ * ```
+ *   const tags = HtmlTagArray.from([tag1, tag2]);
+ *   const html = `<html><body>${tags.filter((tag) => tag.tagName === 'script')}</body></html>`;
+ * ```
+ *
+ */
+class HtmlTagArray extends Array {
+  toString() {
+    return this.join("");
+  }
+}
+
+module.exports = {
+  HtmlTagArray: HtmlTagArray,
+  createHtmlTagObject: createHtmlTagObject,
+  htmlTagObjectToString: htmlTagObjectToString,
+};
Index: frontend/node_modules/html-webpack-plugin/lib/loader.js
===================================================================
--- frontend/node_modules/html-webpack-plugin/lib/loader.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/lib/loader.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,55 @@
+/* This loader renders the template with underscore if no other loader was found */
+// @ts-nocheck
+"use strict";
+const _template = require("lodash/template");
+
+module.exports = function (source) {
+  // Get templating options
+  const options = this.getOptions();
+  const force = options.force || false;
+
+  const allLoadersButThisOne = this.loaders.filter(
+    (loader) => loader.normal !== module.exports,
+  );
+
+  // This loader shouldn't kick in if there is any other loader (unless it's explicitly enforced)
+  if (allLoadersButThisOne.length > 0 && !force) {
+    return source;
+  }
+
+  // Allow only one html-webpack-plugin loader to allow loader options in the webpack config
+  const htmlWebpackPluginLoaders = this.loaders.filter(
+    (loader) => loader.normal === module.exports,
+  );
+  const lastHtmlWebpackPluginLoader =
+    htmlWebpackPluginLoaders[htmlWebpackPluginLoaders.length - 1];
+  if (this.loaders[this.loaderIndex] !== lastHtmlWebpackPluginLoader) {
+    return source;
+  }
+
+  // Skip .js files (unless it's explicitly enforced)
+  if (/\.(c|m)?js$/.test(this.resourcePath) && !force) {
+    return source;
+  }
+
+  // The following part renders the template with lodash as a minimalistic loader
+  //
+  const template = _template(source, {
+    interpolate: /<%=([\s\S]+?)%>/g,
+    variable: "data",
+    ...options,
+  });
+  // Use `eval("require")("lodash")` to enforce using the native nodejs require
+  // during template execution
+  return (
+    'var _ = eval("require")(' +
+    JSON.stringify(require.resolve("lodash")) +
+    ");" +
+    "module.exports = function (templateParams) { with(templateParams) {" +
+    // Execute the lodash template
+    "return (" +
+    template.source +
+    ")();" +
+    "}}"
+  );
+};
Index: frontend/node_modules/html-webpack-plugin/package.json
===================================================================
--- frontend/node_modules/html-webpack-plugin/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,107 @@
+{
+  "name": "html-webpack-plugin",
+  "version": "5.6.7",
+  "license": "MIT",
+  "description": "Simplifies creation of HTML files to serve your webpack bundles",
+  "author": "Jan Nicklas <j.nicklas@me.com> (https://github.com/jantimon)",
+  "main": "index.js",
+  "types": "typings.d.ts",
+  "files": [
+    "lib/",
+    "index.js",
+    "default_index.ejs",
+    "typings.d.ts"
+  ],
+  "scripts": {
+    "posttest": "tsc",
+    "puml": "npx puml generate flow.puml -o flow.png",
+    "build-examples": "node examples/build-examples.js",
+    "commitlint": "commitlint --from=master",
+    "security": "npm audit --omit=dev",
+    "lint:prettier": "prettier --cache --list-different .",
+    "lint:js": "eslint --cache .",
+    "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"",
+    "lint": "npm-run-all -l -p \"lint:**\"",
+    "fix:js": "npm run lint:js -- --fix",
+    "fix:prettier": "npm run lint:prettier -- --write",
+    "fix": "npm-run-all -l fix:js fix:prettier",
+    "test:only": "cross-env NODE_ENV=test jest",
+    "test:watch": "npm run test:only -- --watch",
+    "test:manual": "npm run build && webpack-dev-server test/manual/src/index.js --open --config test/manual/webpack.config.js",
+    "test:coverage": "npm run test:only -- --coverage",
+    "pretest": "npm run lint",
+    "test": "npm run test:coverage",
+    "prepare": "husky",
+    "release": "standard-version"
+  },
+  "devDependencies": {
+    "@commitlint/cli": "^18.4.4",
+    "@commitlint/config-conventional": "^18.4.4",
+    "@types/node": "^20.2.5",
+    "cross-env": "^7.0.3",
+    "cspell": "^8.3.2",
+    "css-loader": "5.0.1",
+    "cz-conventional-changelog": "2.1.0",
+    "dir-compare": "^3.3.0",
+    "eslint": "^8.56.0",
+    "handlebars-loader": "^1.7.3",
+    "html-loader": "2.1.1",
+    "husky": "^9.0.10",
+    "jest": "^27.2.5",
+    "lint-staged": "^15.2.2",
+    "mini-css-extract-plugin": "^1.6.0",
+    "npm-run-all": "^4.1.5",
+    "prettier": "^3.2.5",
+    "pug": "3.0.2",
+    "pug-loader": "2.4.0",
+    "raw-loader": "4.0.2",
+    "rimraf": "2.6.3",
+    "standard-version": "^9.3.0",
+    "style-loader": "2.0.0",
+    "typescript": "4.9.4",
+    "webpack": "^5.104.1",
+    "webpack-cli": "4.5.0",
+    "webpack-recompilation-simulator": "3.2.0"
+  },
+  "dependencies": {
+    "@types/html-minifier-terser": "^6.0.0",
+    "html-minifier-terser": "^6.0.2",
+    "lodash": "^4.17.21",
+    "pretty-error": "^4.0.0",
+    "tapable": "^2.0.0"
+  },
+  "peerDependencies": {
+    "@rspack/core": "0.x || 1.x",
+    "webpack": "^5.20.0"
+  },
+  "peerDependenciesMeta": {
+    "@rspack/core": {
+      "optional": true
+    },
+    "webpack": {
+      "optional": true
+    }
+  },
+  "keywords": [
+    "webpack",
+    "plugin",
+    "html",
+    "html-webpack-plugin"
+  ],
+  "bugs": "https://github.com/jantimon/html-webpack-plugin/issues",
+  "homepage": "https://github.com/jantimon/html-webpack-plugin",
+  "repository": "https://github.com/jantimon/html-webpack-plugin.git",
+  "engines": {
+    "node": ">=10.13.0"
+  },
+  "jest": {
+    "watchPathIgnorePatterns": [
+      "<rootDir>/dist"
+    ],
+    "testEnvironment": "node"
+  },
+  "funding": {
+    "type": "opencollective",
+    "url": "https://opencollective.com/html-webpack-plugin"
+  }
+}
Index: frontend/node_modules/html-webpack-plugin/typings.d.ts
===================================================================
--- frontend/node_modules/html-webpack-plugin/typings.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/html-webpack-plugin/typings.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,293 @@
+import { AsyncSeriesWaterfallHook } from "tapable";
+import { Compiler, Compilation } from "webpack";
+import { Options as HtmlMinifierOptions } from "html-minifier-terser";
+
+export = HtmlWebpackPlugin;
+
+declare class HtmlWebpackPlugin {
+  constructor(options?: HtmlWebpackPlugin.Options);
+
+  userOptions: HtmlWebpackPlugin.Options;
+
+  /** Current HtmlWebpackPlugin Major */
+  version: number;
+
+  /**
+   * Options after html-webpack-plugin has been initialized with defaults
+   */
+  options?: HtmlWebpackPlugin.ProcessedOptions;
+
+  apply(compiler: Compiler): void;
+
+  static getHooks(compilation: Compilation): HtmlWebpackPlugin.Hooks;
+  static getCompilationHooks(compilation: Compilation): HtmlWebpackPlugin.Hooks;
+
+  /**
+   * Static helper to create a tag object to be get injected into the dom
+   */
+  static createHtmlTagObject(
+    tagName: string,
+    attributes?: { [attributeName: string]: string | boolean },
+    innerHTML?: string,
+  ): HtmlWebpackPlugin.HtmlTagObject;
+
+  static readonly version: number;
+}
+
+declare namespace HtmlWebpackPlugin {
+  type MinifyOptions = HtmlMinifierOptions;
+
+  interface Options {
+    /**
+     * Emit the file only if it was changed.
+     * @default true
+     */
+    cache?: boolean;
+    /**
+     * List all entries which should be injected
+     */
+    chunks?: "all" | string[];
+    /**
+     * Allows to control how chunks should be sorted before they are included to the html.
+     * @default 'auto'
+     */
+    chunksSortMode?:
+      | "auto"
+      // `none` is deprecated and an alias for `auto` now.
+      | "none"
+      | "manual"
+      | ((entryNameA: string, entryNameB: string) => number);
+    /**
+     * List all entries which should not be injected
+     */
+    excludeChunks?: string[];
+    /**
+     * Path to the favicon icon
+     */
+    favicon?: false | string;
+    /**
+     * The file to write the HTML to.
+     * Supports subdirectories eg: `assets/admin.html`
+     * [name] will be replaced by the entry name
+     * Supports a function to generate the name
+     *
+     * @default 'index.html'
+     */
+    filename?: string | ((entryName: string) => string);
+    /**
+     * By default the public path is set to `auto` - that way the html-webpack-plugin will try
+     * to set the publicPath according to the current filename and the webpack publicPath setting
+     */
+    publicPath?: string | "auto";
+    /**
+     * If `true` then append a unique `webpack` compilation hash to all included scripts and CSS files.
+     * This is useful for cache busting
+     */
+    hash?: boolean;
+    /**
+     * Inject all assets into the given `template` or `templateContent`.
+     */
+    inject?:
+      | false // Don't inject scripts
+      | true // Inject scripts into body
+      | "body" // Inject scripts into body
+      | "head"; // Inject scripts into head
+    /**
+     * Set up script loading
+     * blocking will result in <script src="..."></script>
+     * defer will result in <script defer src="..."></script>
+     *
+     * @default 'defer'
+     */
+    scriptLoading?: "blocking" | "defer" | "module" | "systemjs-module";
+    /**
+     * Inject meta tags
+     */
+    meta?:
+      | false // Disable injection
+      | {
+          [name: string]:
+            | string
+            | false // name content pair e.g. {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}`
+            | { [attributeName: string]: string | boolean }; // custom properties e.g. { name:"viewport" content:"width=500, initial-scale=1" }
+        };
+    /**
+     * HTML Minification options accepts the following values:
+     * - Set to `false` to disable minification
+     * - Set to `'auto'` to enable minification only for production mode
+     * - Set to custom minification according to
+     * {@link https://github.com/kangax/html-minifier#options-quick-reference}
+     */
+    minify?: "auto" | boolean | MinifyOptions;
+    /**
+     * Render errors into the HTML page
+     */
+    showErrors?: boolean;
+    /**
+     * The `webpack` require path to the template.
+     * @see https://github.com/jantimon/html-webpack-plugin/blob/master/docs/template-option.md
+     */
+    template?: string;
+    /**
+     * Allow to use a html string instead of reading from a file
+     */
+    templateContent?:
+      | false // Use the template option instead to load a file
+      | string
+      | ((templateParameters: {
+          [option: string]: any;
+        }) => string | Promise<string>)
+      | Promise<string>;
+    /**
+     * Allows to overwrite the parameters used in the template
+     */
+    templateParameters?:
+      | false // Pass an empty object to the template function
+      | ((
+          compilation: Compilation,
+          assets: {
+            publicPath: string;
+            js: Array<string>;
+            css: Array<string>;
+            manifest?: string;
+            favicon?: string;
+          },
+          assetTags: {
+            headTags: HtmlTagObject[];
+            bodyTags: HtmlTagObject[];
+          },
+          options: ProcessedOptions,
+        ) => { [option: string]: any } | Promise<{ [option: string]: any }>)
+      | { [option: string]: any };
+    /**
+     * The title to use for the generated HTML document
+     */
+    title?: string;
+    /**
+     * Enforce self closing tags e.g. <link />
+     */
+    xhtml?: boolean;
+    /**
+     * In addition to the options actually used by this plugin, you can use this hash to pass arbitrary data through
+     * to your template.
+     */
+    [option: string]: any;
+  }
+
+  /**
+   * The plugin options after adding default values
+   */
+  interface ProcessedOptions extends Required<Options> {
+    filename: string;
+  }
+
+  /**
+   * The values which are available during template execution
+   *
+   * Please keep in mind that the `templateParameter` options allows to change them
+   */
+  interface TemplateParameter {
+    compilation: Compilation;
+    htmlWebpackPlugin: {
+      tags: {
+        headTags: HtmlTagObject[];
+        bodyTags: HtmlTagObject[];
+      };
+      files: {
+        publicPath: string;
+        js: Array<string>;
+        css: Array<string>;
+        manifest?: string;
+        favicon?: string;
+      };
+      options: Options;
+    };
+    webpackConfig: any;
+  }
+
+  interface Hooks {
+    alterAssetTags: AsyncSeriesWaterfallHook<{
+      assetTags: {
+        scripts: HtmlTagObject[];
+        styles: HtmlTagObject[];
+        meta: HtmlTagObject[];
+      };
+      publicPath: string;
+      outputName: string;
+      plugin: HtmlWebpackPlugin;
+    }>;
+
+    alterAssetTagGroups: AsyncSeriesWaterfallHook<{
+      headTags: HtmlTagObject[];
+      bodyTags: HtmlTagObject[];
+      outputName: string;
+      publicPath: string;
+      plugin: HtmlWebpackPlugin;
+    }>;
+
+    afterTemplateExecution: AsyncSeriesWaterfallHook<{
+      html: string;
+      headTags: HtmlTagObject[];
+      bodyTags: HtmlTagObject[];
+      outputName: string;
+      plugin: HtmlWebpackPlugin;
+    }>;
+
+    beforeAssetTagGeneration: AsyncSeriesWaterfallHook<{
+      assets: {
+        publicPath: string;
+        js: Array<string>;
+        css: Array<string>;
+        favicon?: string;
+        manifest?: string;
+      };
+      outputName: string;
+      plugin: HtmlWebpackPlugin;
+    }>;
+
+    beforeEmit: AsyncSeriesWaterfallHook<{
+      html: string;
+      outputName: string;
+      plugin: HtmlWebpackPlugin;
+    }>;
+
+    afterEmit: AsyncSeriesWaterfallHook<{
+      outputName: string;
+      plugin: HtmlWebpackPlugin;
+    }>;
+  }
+
+  /**
+   * A tag element according to the htmlWebpackPlugin object notation
+   */
+  interface HtmlTagObject {
+    /**
+     * Attributes of the html tag
+     * E.g. `{'disabled': true, 'value': 'demo'}`
+     */
+    attributes: {
+      [attributeName: string]: string | boolean | null | undefined;
+    };
+    /**
+     * The tag name e.g. `'div'`
+     */
+    tagName: string;
+    /**
+     * The inner HTML
+     */
+    innerHTML?: string;
+    /**
+     * Whether this html must not contain innerHTML
+     * @see https://www.w3.org/TR/html5/syntax.html#void-elements
+     */
+    voidTag: boolean;
+    /**
+     * Meta information about the tag
+     * E.g. `{'plugin': 'html-webpack-plugin'}`
+     */
+    meta: {
+      plugin?: string;
+      [metaAttributeName: string]: any;
+    };
+  }
+}
