mirror of
https://github.com/grafana/grafana.git
synced 2024-11-21 16:38:03 -06:00
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:
parent
7b78061235
commit
ed128ea964
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,8 +7,6 @@ awsconfig
|
||||
/.awcache
|
||||
/dist
|
||||
/public/build
|
||||
/public/views/index.html
|
||||
/public/views/error.html
|
||||
/emails/dist
|
||||
/reports
|
||||
/e2e/tmp
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
1554
pkg/api/webassets/testdata/sample-assets-manifest.json
vendored
Normal file
1554
pkg/api/webassets/testdata/sample-assets-manifest.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
91
pkg/api/webassets/webassets.go
Normal file
91
pkg/api/webassets/webassets.go
Normal 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
|
||||
}
|
48
pkg/api/webassets/webassets_test.go
Normal file
48
pkg/api/webassets/webassets_test.go
Normal 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))
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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" />
|
@ -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');
|
@ -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;
|
@ -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,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
@ -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': {
|
||||
|
@ -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'),
|
||||
|
68
yarn.lock
68
yarn.lock
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user