Frontend: Foundations for multi tenant frontend (#78815)

* Frontend: Foundations for multi tenant frontend

* improve manifest parsing for multi-tenant frontend (#78876)

* add test

* add test

* ??

* Updates

* Added cache

* test cleanup

* lint

* fix test

* fix error templates

* cleanup

* remove copy

* revert changes to list testdata

* comment cleanup

* prepare integration tests

* Remove integrety

---------

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Torkel Ödegaard 2023-12-05 08:34:22 +01:00 committed by GitHub
parent 7b78061235
commit ed128ea964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1861 additions and 124 deletions

2
.gitignore vendored
View File

@ -7,8 +7,6 @@ awsconfig
/.awcache
/dist
/public/build
/public/views/index.html
/public/views/error.html
/emails/dist
/reports
/e2e/tmp

View File

@ -151,6 +151,7 @@
"@types/testing-library__jest-dom": "5.14.8",
"@types/tinycolor2": "1.4.3",
"@types/uuid": "9.0.2",
"@types/webpack-assets-manifest": "^5",
"@types/yargs": "17.0.24",
"@typescript-eslint/eslint-plugin": "5.42.0",
"@typescript-eslint/parser": "5.42.0",
@ -417,6 +418,7 @@
"uuid": "9.0.0",
"vendor": "link:./public/vendor",
"visjs-network": "4.25.0",
"webpack-assets-manifest": "^5.1.0",
"whatwg-fetch": "3.6.2",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz"
},

View File

@ -34,4 +34,16 @@ type IndexViewData struct {
// Nonce is a cryptographic identifier for use with Content Security Policy.
Nonce string
NewsFeedEnabled bool
Assets *EntryPointAssets
}
type EntryPointAssets struct {
JSFiles []EntryPointAsset
CSSDark string
CSSLight string
}
type EntryPointAsset struct {
FilePath string
Integrity string
}

View File

@ -11,6 +11,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/webassets"
"github.com/grafana/grafana/pkg/middleware"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
@ -81,6 +82,10 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
}
theme := hs.getThemeForIndexData(prefs.Theme, c.Query("theme"))
assets, err := webassets.GetWebAssets(hs.Cfg)
if err != nil {
return nil, err
}
userOrgCount := 1
userOrgs, err := hs.orgService.GetUserOrgList(c.Req.Context(), &org.GetUserOrgListQuery{UserID: userID})
@ -142,6 +147,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
ContentDeliveryURL: hs.Cfg.GetContentDeliveryURL(hs.License.ContentDeliveryPrefix()),
LoadingLogo: "public/img/grafana_icon.svg",
IsDevelopmentEnv: hs.Cfg.Env == setting.Dev,
Assets: assets,
}
if hs.Cfg.CSPEnabled {

View File

@ -52,6 +52,11 @@ func fakeSetIndexViewData(t *testing.T) {
User: &dtos.CurrentUser{},
Settings: &dtos.FrontendSettingsDTO{},
NavTree: &navtree.NavTreeRoot{},
Assets: &dtos.EntryPointAssets{
JSFiles: []dtos.EntryPointAsset{},
CSSDark: "dark.css",
CSSLight: "light.css",
},
}
return data, nil
}
@ -63,7 +68,7 @@ func fakeViewIndex(t *testing.T) {
getViewIndex = origGetViewIndex
})
getViewIndex = func() string {
return "index-template"
return "index"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,91 @@
package webassets
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/setting"
)
type ManifestInfo struct {
FilePath string `json:"src,omitempty"`
Integrity string `json:"integrity,omitempty"`
// The known entrypoints
App *EntryPointInfo `json:"app,omitempty"`
Dark *EntryPointInfo `json:"dark,omitempty"`
Light *EntryPointInfo `json:"light,omitempty"`
}
type EntryPointInfo struct {
Assets struct {
JS []string `json:"js,omitempty"`
CSS []string `json:"css,omitempty"`
} `json:"assets,omitempty"`
}
var entryPointAssetsCache *dtos.EntryPointAssets = nil
func GetWebAssets(cfg *setting.Cfg) (*dtos.EntryPointAssets, error) {
if cfg.Env != setting.Dev && entryPointAssetsCache != nil {
return entryPointAssetsCache, nil
}
result, err := readWebAssets(filepath.Join(cfg.StaticRootPath, "build", "assets-manifest.json"))
entryPointAssetsCache = result
return entryPointAssetsCache, err
}
func readWebAssets(manifestpath string) (*dtos.EntryPointAssets, error) {
//nolint:gosec
bytes, err := os.ReadFile(manifestpath)
if err != nil {
return nil, fmt.Errorf("failed to load assets-manifest.json %w", err)
}
manifest := map[string]ManifestInfo{}
err = json.Unmarshal(bytes, &manifest)
if err != nil {
return nil, fmt.Errorf("failed to read assets-manifest.json %w", err)
}
integrity := make(map[string]string, 100)
for _, v := range manifest {
if v.Integrity != "" && v.FilePath != "" {
integrity[v.FilePath] = v.Integrity
}
}
entryPoints, ok := manifest["entrypoints"]
if !ok {
return nil, fmt.Errorf("could not find entrypoints in asssets-manifest")
}
if entryPoints.App == nil || len(entryPoints.App.Assets.JS) == 0 {
return nil, fmt.Errorf("missing app entry")
}
if entryPoints.Dark == nil || len(entryPoints.Dark.Assets.CSS) == 0 {
return nil, fmt.Errorf("missing dark entry")
}
if entryPoints.Light == nil || len(entryPoints.Light.Assets.CSS) == 0 {
return nil, fmt.Errorf("missing light entry")
}
entryPointJSAssets := make([]dtos.EntryPointAsset, 0)
for _, entry := range entryPoints.App.Assets.JS {
entryPointJSAssets = append(entryPointJSAssets, dtos.EntryPointAsset{
FilePath: entry,
Integrity: integrity[entry],
})
}
return &dtos.EntryPointAssets{
JSFiles: entryPointJSAssets,
CSSDark: entryPoints.Dark.Assets.CSS[0],
CSSLight: entryPoints.Light.Assets.CSS[0],
}, nil
}

View File

@ -0,0 +1,48 @@
package webassets
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestReadWebassets(t *testing.T) {
assets, err := readWebAssets("testdata/sample-assets-manifest.json")
require.NoError(t, err)
dto, err := json.MarshalIndent(assets, "", " ")
require.NoError(t, err)
//fmt.Printf("%s\n", string(dto))
require.JSONEq(t, `{
"JSFiles": [
{
"FilePath": "public/build/runtime.20ed8c01880b812ed29f.js",
"Integrity": "sha256-rcdxIHk6cWgu4jiFa1a+pWlileYD/R72GaS8ZACBUdw= sha384-I/VJZQkt+TuJTvu61ihdWPds7EHfLrW5CxeQ0x9gtSqoPg9Z17Uawz1yoYaTdxqQ sha512-4CPAbh4KdTmGxHoQw4pgpYmgAquupVfwfo6UBV2cGU3vGFnEwkhq320037ETwWs+n9xB/bAMOvrdabp1SA1+8g=="
},
{
"FilePath": "public/build/3951.4e474348841d792ab1ba.js",
"Integrity": "sha256-dHqXXTRA3osYhHr9rol8hOV0nC4VP0pr5tbMp5VD95Q= sha384-4QJaSTibnxdYeYsLnmXtd1+If6IkAmXlLR0uYHN5+N+fS0FegHRH7MIFaRGjiO1B sha512-vRLEeEGbxBCx0z+l/m14fSK49reqWGA9zQzsCrD+TQQBmP07YIoRPwopMMyxtKljbbRFV0bW2bUZ7ZvzOZYoIQ=="
},
{
"FilePath": "public/build/3651.4e8f7603e9778e1e9b59.js",
"Integrity": "sha256-+N7caL91pVANd7C/aquAneRTjBQenCwaEKqj+3qkjxc= sha384-GQR7GyHPEwwEVph9gGYWEWvMYxkITwcOjieehbPidXZrybuQyw9cpDkjnWo1tj/w sha512-zyPM+8AxyLuECEXjb9w6Z2Sy8zmJdkfTWQphcvAb8AU4ZdkCqLmyjmOs/QQlpfKDe0wdOLyR3V9QgTDDlxtVlQ=="
},
{
"FilePath": "public/build/1272.8c79fc44bf7cd993c953.js",
"Integrity": "sha256-d7MRVimV83v4YQ5rdURfTaaFtiedXP3EMLT06gvvBuQ= sha384-8tRpYHQ+sEkZ8ptiIbKAbKPpHTJVnmaWDN56vJoWWUCzV1Q2w034wcJNKDJDJdAs sha512-cIZWoJHusF8qODBOj2j4b18ewcLLMo/92YQSwYQjln2G5e3o1bSO476ox2I2iecJ/tnhQK5j01h9BzTt3dNTrA=="
},
{
"FilePath": "public/build/6902.070074e8f5a989b8f4c3.js",
"Integrity": "sha256-TMo/uTZueyEHtkBzlLZzhwYKWF0epE4qbouo5xcwZkU= sha384-xylZJMtJ7+EsUBBdQZvPh+BeHJ3BnfclqI2vx/8QC9jvfYe/lhRsWW9OMJsxE/Aq sha512-EOmf+KZQMFPoTWAROL8bBLFfHhgvDH8ONycq37JaV7lz+sQOTaWBN2ZD0F/mMdOD5zueTg/Y1RAUP6apoEcHNQ=="
},
{
"FilePath": "public/build/app.0439db6f56ee4aa501b2.js",
"Integrity": "sha256-q6muaKY7BuN2Ff+00aw69628MXatcFnLNzWRnAD98DI= sha384-gv6lAbkngOHR05bvyOR8dm/J3wIjQQWSjyxK7W8vt2rG9uxcjvvDQV7aI6YbUhfX sha512-o/0mSlJ/OoqrpGdOIWCE3ZCe8n+qqLbgNCERtx9G8FIzsv++CvIWSGbbILjOTGfnEfEQWcKMH0macVpVBSe1Og=="
}
],
"CSSDark": "public/build/grafana.dark.a28b24b45b2bbcc628cc.css",
"CSSLight": "public/build/grafana.light.3572f6d5f8b7daa8d8d0.css"
}`, string(dto))
}

View File

@ -149,9 +149,14 @@ func TestMiddlewareContext(t *testing.T) {
User: &dtos.CurrentUser{},
Settings: &dtos.FrontendSettingsDTO{},
NavTree: &navtree.NavTreeRoot{},
Assets: &dtos.EntryPointAssets{
JSFiles: []dtos.EntryPointAsset{},
CSSDark: "dark.css",
CSSLight: "light.css",
},
}
t.Log("Calling HTML", "data", data)
c.HTML(http.StatusOK, "index-template", data)
c.HTML(http.StatusOK, "index", data)
t.Log("Returned HTML with code 200")
}
sc.fakeReq("GET", "/").exec()
@ -199,7 +204,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
cfg.LoginCookieName = "grafana_session"
cfg.LoginMaxLifetime = loginMaxLifetime
// Required when rendering errors
cfg.ErrTemplateName = "error-template"
cfg.ErrTemplateName = "error"
for _, cb := range cbs {
cb(cfg)
}

View File

@ -23,6 +23,8 @@ import (
"os"
"runtime"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/webassets"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/setting"
@ -135,13 +137,19 @@ func Recovery(cfg *setting.Cfg) web.Middleware {
return
}
assets, _ := webassets.GetWebAssets(cfg)
if assets == nil {
assets = &dtos.EntryPointAssets{JSFiles: []dtos.EntryPointAsset{}}
}
data := struct {
Title string
AppTitle string
AppSubUrl string
Theme string
ErrorMsg string
}{"Server Error", "Grafana", cfg.AppSubURL, cfg.DefaultTheme, ""}
Assets *dtos.EntryPointAssets
}{"Server Error", "Grafana", cfg.AppSubURL, cfg.DefaultTheme, "", assets}
if setting.Env == setting.Dev {
if err, ok := r.(error); ok {

View File

@ -50,7 +50,7 @@ func panicHandler(c *contextmodel.ReqContext) {
func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
t.Run(desc, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.ErrTemplateName = "error-template"
cfg.ErrTemplateName = "error"
cfg.UserFacingDefaultError = "test error"
sc := &scenarioContext{
t: t,

View File

@ -154,17 +154,41 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
publicDir := filepath.Join(tmpDir, "public")
err = os.MkdirAll(publicDir, 0750)
require.NoError(t, err)
viewsDir := filepath.Join(publicDir, "views")
err = fs.CopyRecursive(filepath.Join(rootDir, "public", "views"), viewsDir)
require.NoError(t, err)
// Copy index template to index.html, since Grafana will try to use the latter
err = fs.CopyFile(filepath.Join(rootDir, "public", "views", "index-template.html"),
filepath.Join(viewsDir, "index.html"))
// add a stub manifest to the build directory
buildDir := filepath.Join(publicDir, "build")
err = os.MkdirAll(buildDir, 0750)
require.NoError(t, err)
// Copy error template to error.html, since Grafana will try to use the latter
err = fs.CopyFile(filepath.Join(rootDir, "public", "views", "error-template.html"),
filepath.Join(viewsDir, "error.html"))
err = os.WriteFile(filepath.Join(buildDir, "assets-manifest.json"), []byte(`{
"entrypoints": {
"app": {
"assets": {
"js": ["public/build/runtime.XYZ.js"]
}
},
"dark": {
"assets": {
"css": ["public/build/dark.css"]
}
},
"light": {
"assets": {
"css": ["public/build/light.css"]
}
}
},
"runtime.50398398ecdeaf58968c.js": {
"src": "public/build/runtime.XYZ.js",
"integrity": "sha256-k1g7TksMHFQhhQGE"
}
}
`), 0750)
require.NoError(t, err)
emailsDir := filepath.Join(publicDir, "emails")
err = fs.CopyRecursive(filepath.Join(rootDir, "public", "emails"), emailsDir)
require.NoError(t, err)

View File

@ -269,7 +269,7 @@ function initEchoSrv() {
window.addEventListener('load', (e) => {
const loadMetricName = 'frontend_boot_load_time_seconds';
// Metrics below are marked in public/views/index-template.html
// Metrics below are marked in public/views/index.html
const jsLoadMetricName = 'frontend_boot_js_done_time_seconds';
const cssLoadMetricName = 'frontend_boot_css_time_seconds';

View File

@ -1,6 +1,6 @@
/**
* Check to see if browser is not supported by Grafana
* This function is copied to index-template.html but is here so we can write tests
* This function is copied to index.html but is here so we can write tests
* */
export function checkBrowserCompatibility() {
const isIE = navigator.userAgent.indexOf('MSIE') > -1;

View File

@ -11,9 +11,9 @@
<base href="[[.AppSubUrl]]/" />
[[ if eq .Theme "light" ]]
<link rel="stylesheet" href="public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>" />
[[ else ]]
<link rel="stylesheet" href="public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>" />
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/[[.Assets.CSSLight]]" />
[[ else if eq .Theme "dark" ]]
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/[[.Assets.CSSDark]]" />
[[ end ]]
<link rel="icon" type="image/png" href="public/img/fav32.png" />

View File

@ -20,9 +20,9 @@
<!-- If theme is "system", we inject the stylesheets with javascript further down the page -->
[[ if eq .ThemeType "light" ]]
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>" />
<link rel="stylesheet" href="[[.ContentDeliveryURL]][[.Assets.CSSLight]]" />
[[ else if eq .ThemeType "dark" ]]
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>" />
<link rel="stylesheet" href="[[.ContentDeliveryURL]][[.Assets.CSSDark]]" />
[[ end ]]
<script nonce="[[.Nonce]]">
@ -245,8 +245,8 @@
settings: [[.Settings]],
navTree: [[.NavTree]],
themePaths: {
light: '[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>',
dark: '[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>'
light: '[[.ContentDeliveryURL]][[.Assets.CSSLight]]',
dark: '[[.ContentDeliveryURL]][[.Assets.CSSDark]]'
}
};
@ -328,21 +328,15 @@
})(window, document, 'script', 'dataLayer', '[[.GoogleTagManagerId]]');
</script>
<!-- End Google Tag Manager -->
[[end]] <% for (index in htmlWebpackPlugin.files.js) { %> <% if (htmlWebpackPlugin.files.jsIntegrity) { %>
<script
nonce="[[.Nonce]]"
src="[[.ContentDeliveryURL]]<%= htmlWebpackPlugin.files.js[index] %>"
type="text/javascript"
integrity="<%= htmlWebpackPlugin.files.jsIntegrity[index] %>"
crossorigin="<%= webpackConfig.output.crossOriginLoading %>"
></script>
<% } else { %>
<script
nonce="[[.Nonce]]"
src="[[.ContentDeliveryURL]]<%= htmlWebpackPlugin.files.js[index] %>"
[[end]]
[[range $asset := .Assets.JSFiles]]
<script
nonce="[[$.Nonce]]"
src="[[$.ContentDeliveryURL]][[$asset.FilePath]]"
type="text/javascript"
></script>
<% } %> <% } %>
[[end]]
<script nonce="[[.Nonce]]">
performance.mark('frontend_boot_js_done_time_seconds');

View File

@ -1,42 +0,0 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
/*
* This plugin returns the css associated with entrypoints. Those chunks can be found
* in `htmlWebpackPlugin.files.cssChunks`.
* The HTML Webpack plugin removed the chunks object in v5 in favour of an array however if we want
* to do anything smart with hashing (e.g. [contenthash]) we need a map of { themeName: chunkNameWithHash }.
*/
class HTMLWebpackCSSChunks {
/**
* @param {import('webpack').Compiler} compiler
*/
apply(compiler) {
compiler.hooks.compilation.tap(
'HTMLWebpackCSSChunks',
/**
* @param {import('webpack').Compilation} compilation
*/
(compilation) => {
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(
'HTMLWebpackCSSChunks',
(data, cb) => {
data.assets.cssChunks = {};
for (const entryPoint of compilation.entrypoints.values()) {
for (const chunk of entryPoint.chunks) {
const cssFile = [...chunk.files].find((file) => file.endsWith('.css'));
if (cssFile !== undefined) {
data.assets.cssChunks[chunk.name] = cssFile;
}
}
}
cb(null, data);
}
);
}
);
}
}
module.exports = HTMLWebpackCSSChunks;

View File

@ -4,13 +4,12 @@ const browserslist = require('browserslist');
const { resolveToEsbuildTarget } = require('esbuild-plugin-browserslist');
const ESLintPlugin = require('eslint-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const { DefinePlugin } = require('webpack');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const { merge } = require('webpack-merge');
const HTMLWebpackCSSChunks = require('./plugins/HTMLWebpackCSSChunks');
const common = require('./webpack.common.js');
const esbuildTargets = resolveToEsbuildTarget(browserslist(), { printUnknownTargets: false });
// esbuild-loader 3.0.0+ requires format to be set to prevent it
@ -101,26 +100,16 @@ module.exports = (env = {}) => {
new MiniCssExtractPlugin({
filename: 'grafana.[name].[contenthash].css',
}),
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/error.html'),
template: path.resolve(__dirname, '../../public/views/error-template.html'),
inject: false,
chunksSortMode: 'none',
excludeChunks: ['dark', 'light'],
}),
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/index.html'),
template: path.resolve(__dirname, '../../public/views/index-template.html'),
inject: false,
chunksSortMode: 'none',
excludeChunks: ['dark', 'light'],
}),
new HTMLWebpackCSSChunks(),
new DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development'),
},
}),
new WebpackAssetsManifest({
entrypoints: true,
integrity: true,
publicPath: true,
}),
],
});
};

View File

@ -1,13 +1,11 @@
'use strict';
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const { DefinePlugin } = require('webpack');
const { merge } = require('webpack-merge');
const HTMLWebpackCSSChunks = require('./plugins/HTMLWebpackCSSChunks');
const common = require('./webpack.common.js');
module.exports = merge(common, {
@ -80,14 +78,6 @@ module.exports = merge(common, {
new MiniCssExtractPlugin({
filename: 'grafana.[name].[contenthash].css',
}),
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/index.html'),
template: path.resolve(__dirname, '../../public/views/index-template.html'),
inject: false,
chunksSortMode: 'none',
excludeChunks: ['dark', 'light'],
}),
new HTMLWebpackCSSChunks(),
new ReactRefreshWebpackPlugin(),
new DefinePlugin({
'process.env': {

View File

@ -4,13 +4,12 @@ const browserslist = require('browserslist');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { EsbuildPlugin } = require('esbuild-loader');
const { resolveToEsbuildTarget } = require('esbuild-plugin-browserslist');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const { merge } = require('webpack-merge');
const HTMLWebpackCSSChunks = require('./plugins/HTMLWebpackCSSChunks');
const common = require('./webpack.common.js');
const esbuildTargets = resolveToEsbuildTarget(browserslist(), { printUnknownTargets: false });
@ -67,21 +66,15 @@ module.exports = (env = {}) =>
new MiniCssExtractPlugin({
filename: 'grafana.[name].[contenthash].css',
}),
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/error.html'),
template: path.resolve(__dirname, '../../public/views/error-template.html'),
inject: false,
excludeChunks: ['dark', 'light'],
chunksSortMode: 'none',
/**
* I know we have two manifest plugins here.
* WebpackManifestPlugin was only used in prod before and does not support integrity hashes
*/
new WebpackAssetsManifest({
entrypoints: true,
integrity: true,
publicPath: true,
}),
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/index.html'),
template: path.resolve(__dirname, '../../public/views/index-template.html'),
inject: false,
excludeChunks: ['manifest', 'dark', 'light'],
chunksSortMode: 'none',
}),
new HTMLWebpackCSSChunks(),
new WebpackManifestPlugin({
fileName: path.join(process.cwd(), 'manifest.json'),
filter: (file) => !file.name.endsWith('.map'),

View File

@ -9439,6 +9439,17 @@ __metadata:
languageName: node
linkType: hard
"@types/webpack-assets-manifest@npm:^5":
version: 5.1.4
resolution: "@types/webpack-assets-manifest@npm:5.1.4"
dependencies:
"@types/node": "npm:*"
tapable: "npm:^2.2.0"
webpack: "npm:^5"
checksum: 02aa3a400227693d5cb5db4364caf91df66e9529139999c0686f139138b76d27d200de5ad8d77d4199538d92f31d37df5dcdc6bdd8ccddcabf95bfc96b964c3a
languageName: node
linkType: hard
"@types/webpack-env@npm:1.18.1":
version: 1.18.1
resolution: "@types/webpack-env@npm:1.18.1"
@ -12050,7 +12061,7 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2":
"chalk@npm:^4.0, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
dependencies:
@ -14050,6 +14061,13 @@ __metadata:
languageName: node
linkType: hard
"deepmerge@npm:^4.0":
version: 4.3.1
resolution: "deepmerge@npm:4.3.1"
checksum: 058d9e1b0ff1a154468bf3837aea436abcfea1ba1d165ddaaf48ca93765fdd01a30d33c36173da8fbbed951dd0a267602bc782fe288b0fc4b7e1e7091afc4529
languageName: node
linkType: hard
"deepmerge@npm:^4.2.2":
version: 4.2.2
resolution: "deepmerge@npm:4.2.2"
@ -17412,6 +17430,7 @@ __metadata:
"@types/tinycolor2": "npm:1.4.3"
"@types/trusted-types": "npm:2.0.3"
"@types/uuid": "npm:9.0.2"
"@types/webpack-assets-manifest": "npm:^5"
"@types/webpack-env": "npm:1.18.1"
"@types/yargs": "npm:17.0.24"
"@typescript-eslint/eslint-plugin": "npm:5.42.0"
@ -17617,6 +17636,7 @@ __metadata:
vendor: "link:./public/vendor"
visjs-network: "npm:4.25.0"
webpack: "npm:5.89.0"
webpack-assets-manifest: "npm:^5.1.0"
webpack-bundle-analyzer: "npm:4.9.0"
webpack-cli: "npm:5.1.4"
webpack-dev-server: "npm:4.15.1"
@ -21306,6 +21326,15 @@ __metadata:
languageName: node
linkType: hard
"lockfile@npm:^1.0":
version: 1.0.4
resolution: "lockfile@npm:1.0.4"
dependencies:
signal-exit: "npm:^3.0.2"
checksum: 2fe86f932c106fabd15f56ffde2a87c99b02e4ca173b5fb64886349d3d0d82417e22a2be2b413006a55f4efbeac7beb679e8a7ac7f767d91981059c25be4dc15
languageName: node
linkType: hard
"lodash-es@npm:^4.17.21":
version: 4.17.21
resolution: "lodash-es@npm:4.17.21"
@ -21320,6 +21349,20 @@ __metadata:
languageName: node
linkType: hard
"lodash.get@npm:^4.0":
version: 4.4.2
resolution: "lodash.get@npm:4.4.2"
checksum: 2a4925f6e89bc2c010a77a802d1ba357e17ed1ea03c2ddf6a146429f2856a216663e694a6aa3549a318cbbba3fd8b7decb392db457e6ac0b83dc745ed0a17380
languageName: node
linkType: hard
"lodash.has@npm:^4.0":
version: 4.5.2
resolution: "lodash.has@npm:4.5.2"
checksum: 35c0862e715bc22528dd3cd34f1e66d25d58f0ecef9a43aa409fb7ddebaf6495cb357ae242f141e4b2325258f4a6bafdd8928255d51f1c0a741ae9b93951c743
languageName: node
linkType: hard
"lodash.intersection@npm:^4.4.0":
version: 4.4.0
resolution: "lodash.intersection@npm:4.4.0"
@ -27170,7 +27213,7 @@ __metadata:
languageName: node
linkType: hard
"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0":
"schema-utils@npm:^3.0, schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0":
version: 3.3.0
resolution: "schema-utils@npm:3.3.0"
dependencies:
@ -28718,7 +28761,7 @@ __metadata:
languageName: node
linkType: hard
"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1":
"tapable@npm:^2.0, tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1":
version: 2.2.1
resolution: "tapable@npm:2.2.1"
checksum: 1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a
@ -30380,6 +30423,23 @@ __metadata:
languageName: node
linkType: hard
"webpack-assets-manifest@npm:^5.1.0":
version: 5.1.0
resolution: "webpack-assets-manifest@npm:5.1.0"
dependencies:
chalk: "npm:^4.0"
deepmerge: "npm:^4.0"
lockfile: "npm:^1.0"
lodash.get: "npm:^4.0"
lodash.has: "npm:^4.0"
schema-utils: "npm:^3.0"
tapable: "npm:^2.0"
peerDependencies:
webpack: ^5.2.0
checksum: 150e6e1f1dbeb04519b08d61c4251fa40a7b51fa711a3122eb655674303b46493e108025e94fdd050c4d3e8c377501e4b60d5df5632b1670f8d295f0d49e35f8
languageName: node
linkType: hard
"webpack-bundle-analyzer@npm:4.9.0":
version: 4.9.0
resolution: "webpack-bundle-analyzer@npm:4.9.0"
@ -30590,7 +30650,7 @@ __metadata:
languageName: node
linkType: hard
"webpack@npm:5, webpack@npm:5.89.0":
"webpack@npm:5, webpack@npm:5.89.0, webpack@npm:^5":
version: 5.89.0
resolution: "webpack@npm:5.89.0"
dependencies: