@schalkneethling/miyagi-core 4.8.1 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/index.js +0 -35
- package/dist/css/iframe.css +1 -1
- package/dist/css/main.css +1 -1
- package/frontend/assets/css/iframe/perf.css +35 -0
- package/frontend/assets/css/iframe.css +1 -1
- package/frontend/assets/css/main/perf.css +63 -0
- package/frontend/assets/css/main.css +1 -0
- package/frontend/views/iframe_component.twig.miyagi +17 -0
- package/frontend/views/main.twig.miyagi +12 -0
- package/frontend/views/menu/nav.twig.miyagi +1 -1
- package/lib/build/index.js +0 -73
- package/lib/cli/index.js +2 -2
- package/lib/cli/perf.js +129 -0
- package/lib/cli/run.js +4 -4
- package/lib/default-config.js +0 -33
- package/lib/init/args.js +10 -23
- package/lib/init/router.js +9 -27
- package/lib/performance/classify.js +33 -0
- package/lib/performance/component.js +122 -0
- package/lib/performance/config.js +65 -0
- package/lib/performance/html-size.js +55 -0
- package/lib/performance/index.js +130 -374
- package/lib/performance/page.js +105 -0
- package/lib/performance/render-page.js +34 -0
- package/lib/performance/routes.js +72 -0
- package/lib/performance/schema.json +79 -0
- package/lib/performance/view-data.js +86 -0
- package/lib/render/index.js +0 -4
- package/lib/render/views/iframe/component.js +14 -0
- package/lib/render/views/main/component.js +23 -1
- package/lib/state/menu/index.js +1 -35
- package/package.json +2 -1
- package/frontend/assets/css/iframe/performance.css +0 -64
- package/frontend/views/performance.twig.miyagi +0 -72
- package/lib/cli/budget.js +0 -157
- package/lib/performance/report.js +0 -102
- package/lib/render/views/iframe/performance.js +0 -74
- package/lib/render/views/main/performance.js +0 -51
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { CONFIG_FILE_NAME } from "./config.js";
|
|
6
|
+
import { runPerformance } from "./index.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Attach performance API routes to the supplied Express app. Routes are
|
|
10
|
+
* always registered; each handler returns 404 if miyagi.performance.json
|
|
11
|
+
* is absent at request time, so users can drop the file in or out without
|
|
12
|
+
* restarting the server. The runPerformance() file-size + html compression
|
|
13
|
+
* caches make repeat hits cheap.
|
|
14
|
+
*
|
|
15
|
+
* Routes:
|
|
16
|
+
* GET /api/performance/components
|
|
17
|
+
* GET /api/performance/pages
|
|
18
|
+
* GET /api/performance/pages/:templatePath/:variation
|
|
19
|
+
* @param {object} app - Express app
|
|
20
|
+
* @param {{
|
|
21
|
+
* cwd: string,
|
|
22
|
+
* render?: (templatePath: string, variation: string) => Promise<string>,
|
|
23
|
+
* }} options
|
|
24
|
+
* @returns {boolean} true when the config file exists at registration time
|
|
25
|
+
*/
|
|
26
|
+
export function attachPerformanceRoutes(app, options) {
|
|
27
|
+
const handle = async (req, res, transform) => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await runPerformance({
|
|
30
|
+
cwd: options.cwd,
|
|
31
|
+
render: options.render,
|
|
32
|
+
});
|
|
33
|
+
if (!result.enabled) {
|
|
34
|
+
res.status(404).json({ error: "Performance feature not configured." });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const transformed = transform(result, res);
|
|
38
|
+
if (!res.headersSent) {
|
|
39
|
+
res.json(transformed);
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
res.status(500).json({ error: error.message });
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
app.get("/api/performance/components", (req, res) =>
|
|
47
|
+
handle(req, res, (result) => result.components),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
app.get("/api/performance/pages", (req, res) =>
|
|
51
|
+
handle(req, res, (result) => result.pages),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
app.get("/api/performance/pages/:templatePath/:variation", (req, res) =>
|
|
55
|
+
handle(req, res, (result) => {
|
|
56
|
+
// Express has already percent-decoded req.params; calling
|
|
57
|
+
// decodeURIComponent again would over-decode and throw URIError on
|
|
58
|
+
// legitimate values containing "%".
|
|
59
|
+
const { templatePath, variation } = req.params;
|
|
60
|
+
const match = result.pages.find(
|
|
61
|
+
(p) => p.templatePath === templatePath && p.variation === variation,
|
|
62
|
+
);
|
|
63
|
+
if (!match) {
|
|
64
|
+
res.status(404).json({ error: "Page not found." });
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return match;
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return existsSync(path.join(options.cwd, CONFIG_FILE_NAME));
|
|
72
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "miyagi.performance.json",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"compression": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"enum": ["raw", "gzip", "brotli"],
|
|
10
|
+
"default": "gzip"
|
|
11
|
+
},
|
|
12
|
+
"warnRatio": {
|
|
13
|
+
"type": "number",
|
|
14
|
+
"exclusiveMinimum": 0,
|
|
15
|
+
"exclusiveMaximum": 1,
|
|
16
|
+
"default": 0.8
|
|
17
|
+
},
|
|
18
|
+
"components": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"minProperties": 0,
|
|
21
|
+
"propertyNames": { "minLength": 1 },
|
|
22
|
+
"default": {},
|
|
23
|
+
"additionalProperties": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"properties": {
|
|
27
|
+
"css": { "$ref": "#/definitions/assetEntry" },
|
|
28
|
+
"js": { "$ref": "#/definitions/assetEntry" }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"pages": {
|
|
33
|
+
"default": {},
|
|
34
|
+
"type": "object",
|
|
35
|
+
"propertyNames": { "minLength": 1 },
|
|
36
|
+
"additionalProperties": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"required": ["variations"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"variations": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"propertyNames": { "minLength": 1 },
|
|
44
|
+
"additionalProperties": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"required": ["components"],
|
|
48
|
+
"properties": {
|
|
49
|
+
"components": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": { "type": "string", "minLength": 1 }
|
|
52
|
+
},
|
|
53
|
+
"budget": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"additionalProperties": false,
|
|
56
|
+
"properties": {
|
|
57
|
+
"css": { "type": "string" },
|
|
58
|
+
"js": { "type": "string" },
|
|
59
|
+
"html": { "type": "string" },
|
|
60
|
+
"total": { "type": "string" }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"definitions": {
|
|
71
|
+
"assetEntry": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
74
|
+
"properties": {
|
|
75
|
+
"budget": { "type": "string" }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { formatSize } from "./parse-size.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} metric - the metric object from runPerformance
|
|
7
|
+
* @returns {object} compact view-data shape for the Twig templates
|
|
8
|
+
*/
|
|
9
|
+
function metricView(metric) {
|
|
10
|
+
return {
|
|
11
|
+
bytes: metric.bytes,
|
|
12
|
+
bytesLabel: formatSize(metric.bytes),
|
|
13
|
+
budget: metric.budget,
|
|
14
|
+
budgetLabel: metric.budget == null ? null : formatSize(metric.budget),
|
|
15
|
+
status: metric.status,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Build the component-overview Performance section view-data for a given
|
|
21
|
+
* component path. Returns null when the feature is disabled or the component
|
|
22
|
+
* isn't listed in miyagi.performance.json.
|
|
23
|
+
* @param {object|null} runResult - return value of runPerformance() or null
|
|
24
|
+
* @param {string} componentPath
|
|
25
|
+
* @returns {object|null}
|
|
26
|
+
*/
|
|
27
|
+
export function buildComponentPerfSection(runResult, componentPath) {
|
|
28
|
+
if (!runResult || !runResult.enabled) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const match = runResult.components.find(
|
|
32
|
+
(c) => c.componentPath === componentPath,
|
|
33
|
+
);
|
|
34
|
+
if (!match) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
componentPath,
|
|
39
|
+
css: { ...metricView(match.css), path: match.css.path },
|
|
40
|
+
js: { ...metricView(match.js), path: match.js.path },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build the page-banner view-data for a given template + variation. Returns
|
|
46
|
+
* null when the feature is disabled or the page isn't listed in
|
|
47
|
+
* miyagi.performance.json.
|
|
48
|
+
* @param {object|null} runResult - return value of runPerformance() or null
|
|
49
|
+
* @param {string} templatePath
|
|
50
|
+
* @param {string} variation
|
|
51
|
+
* @returns {object|null}
|
|
52
|
+
*/
|
|
53
|
+
export function buildPagePerfBanner(runResult, templatePath, variation) {
|
|
54
|
+
if (!runResult || !runResult.enabled) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const match = runResult.pages.find(
|
|
58
|
+
(p) => p.templatePath === templatePath && p.variation === variation,
|
|
59
|
+
);
|
|
60
|
+
if (!match) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const t = match.totals;
|
|
64
|
+
return {
|
|
65
|
+
templatePath,
|
|
66
|
+
variation,
|
|
67
|
+
css: metricView(t.css),
|
|
68
|
+
js: metricView(t.js),
|
|
69
|
+
html: metricView(t.html),
|
|
70
|
+
total: metricView(t.total),
|
|
71
|
+
components: variationComponents(match.totals),
|
|
72
|
+
errors: t.errors,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Surfaces undeclared / missing component paths from the totals' errors
|
|
78
|
+
* array — used as a tooltip aid in the banner so users can see which
|
|
79
|
+
* components weren't measured. Components that *were* measured aren't
|
|
80
|
+
* listed here; the banner already shows their summed bytes.
|
|
81
|
+
* @param {object} totals - the page totals object from runPerformance
|
|
82
|
+
* @returns {string[]} component paths flagged in totals.errors
|
|
83
|
+
*/
|
|
84
|
+
function variationComponents(totals) {
|
|
85
|
+
return (totals.errors || []).map((e) => e.componentPath);
|
|
86
|
+
}
|
package/lib/render/index.js
CHANGED
|
@@ -15,8 +15,6 @@ import renderMainDocs from "./views/main/docs.js";
|
|
|
15
15
|
import renderMainIndex from "./views/main/index.js";
|
|
16
16
|
import iframeDesignTokens from "./views/iframe/design-tokens/index.js";
|
|
17
17
|
import renderMainDesignTokens from "./views/main/design-tokens.js";
|
|
18
|
-
import renderMainPerformance from "./views/main/performance.js";
|
|
19
|
-
import renderIframePerformance from "./views/iframe/performance.js";
|
|
20
18
|
|
|
21
19
|
export default {
|
|
22
20
|
renderMainIndex,
|
|
@@ -30,13 +28,11 @@ export default {
|
|
|
30
28
|
renderIframeDocs,
|
|
31
29
|
renderIframeIndex,
|
|
32
30
|
renderMainDesignTokens,
|
|
33
|
-
renderMainPerformance,
|
|
34
31
|
iframe: {
|
|
35
32
|
designTokens: {
|
|
36
33
|
colors: iframeDesignTokens.colors,
|
|
37
34
|
sizes: iframeDesignTokens.sizes,
|
|
38
35
|
typography: iframeDesignTokens.typography,
|
|
39
36
|
},
|
|
40
|
-
performance: renderIframePerformance,
|
|
41
37
|
},
|
|
42
38
|
};
|
|
@@ -8,6 +8,8 @@ import { getComponentData } from "../../../mocks/index.js";
|
|
|
8
8
|
import { getUserUiConfig, getThemeMode } from "../../helpers.js";
|
|
9
9
|
import resolveAssets from "../../helpers/resolve-assets.js";
|
|
10
10
|
import log from "../../../logger.js";
|
|
11
|
+
import { runPerformance } from "../../../performance/index.js";
|
|
12
|
+
import { buildComponentPerfSection } from "../../../performance/view-data.js";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* @param {object} object - parameter object
|
|
@@ -246,9 +248,21 @@ async function renderVariations({
|
|
|
246
248
|
componentDeclaredAssets,
|
|
247
249
|
);
|
|
248
250
|
|
|
251
|
+
let perfSection;
|
|
252
|
+
try {
|
|
253
|
+
const perfResult = await runPerformance({ cwd: process.cwd() });
|
|
254
|
+
perfSection = buildComponentPerfSection(
|
|
255
|
+
perfResult,
|
|
256
|
+
component.paths.dir.short,
|
|
257
|
+
);
|
|
258
|
+
} catch {
|
|
259
|
+
perfSection = null;
|
|
260
|
+
}
|
|
261
|
+
|
|
249
262
|
await res.render(
|
|
250
263
|
"iframe_component.twig.miyagi",
|
|
251
264
|
{
|
|
265
|
+
perfSection,
|
|
252
266
|
lang: global.config.ui.lang,
|
|
253
267
|
variations,
|
|
254
268
|
cssFiles,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import config from "../../../default-config.js";
|
|
2
2
|
import * as helpers from "../../../helpers.js";
|
|
3
3
|
import { getUserUiConfig, getThemeMode } from "../../helpers.js";
|
|
4
|
+
import { runPerformance } from "../../../performance/index.js";
|
|
5
|
+
import { buildPagePerfBanner } from "../../../performance/view-data.js";
|
|
6
|
+
import { renderPageHtml } from "../../../performance/render-page.js";
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* @param {object} object - parameter object
|
|
@@ -27,7 +30,7 @@ export default async function renderMainComponent({
|
|
|
27
30
|
`-variation-${helpers.normalizeString(variation)}.html`,
|
|
28
31
|
);
|
|
29
32
|
} else {
|
|
30
|
-
iframeSrc += `&variation=${variation}`;
|
|
33
|
+
iframeSrc += `&variation=${encodeURIComponent(variation)}`;
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -37,9 +40,28 @@ export default async function renderMainComponent({
|
|
|
37
40
|
iframeSrc += "&embedded=true";
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
let perfBanner = null;
|
|
44
|
+
if (!global.config.isBuild && variation) {
|
|
45
|
+
try {
|
|
46
|
+
const perfResult = await runPerformance({
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
render: renderPageHtml,
|
|
49
|
+
});
|
|
50
|
+
perfBanner = buildPagePerfBanner(
|
|
51
|
+
perfResult,
|
|
52
|
+
component.paths.dir.short,
|
|
53
|
+
variation,
|
|
54
|
+
);
|
|
55
|
+
} catch {
|
|
56
|
+
// Performance is opt-in; never let measurement errors block rendering.
|
|
57
|
+
perfBanner = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
40
61
|
await res.render(
|
|
41
62
|
"main.twig.miyagi",
|
|
42
63
|
{
|
|
64
|
+
perfBanner,
|
|
43
65
|
lang: global.config.ui.lang,
|
|
44
66
|
folders: global.state.menu,
|
|
45
67
|
components: global.state.components,
|
package/lib/state/menu/index.js
CHANGED
|
@@ -221,9 +221,8 @@ export const getMenu = function (sourceTree) {
|
|
|
221
221
|
|
|
222
222
|
const docsMenu = getDocsMenu(sourceTree.docs);
|
|
223
223
|
const designTokensMenu = getDesignTokensMenu();
|
|
224
|
-
const performanceMenu = getPerformanceMenu();
|
|
225
224
|
|
|
226
|
-
if (!docsMenu && !designTokensMenu &&
|
|
225
|
+
if (!docsMenu && !designTokensMenu && componentsMenu) {
|
|
227
226
|
return componentsMenu.children;
|
|
228
227
|
}
|
|
229
228
|
|
|
@@ -237,43 +236,10 @@ export const getMenu = function (sourceTree) {
|
|
|
237
236
|
if (docsMenu) {
|
|
238
237
|
menus.push(docsMenu);
|
|
239
238
|
}
|
|
240
|
-
if (performanceMenu) {
|
|
241
|
-
menus.push(performanceMenu);
|
|
242
|
-
}
|
|
243
239
|
|
|
244
240
|
return menus;
|
|
245
241
|
};
|
|
246
242
|
|
|
247
|
-
/**
|
|
248
|
-
* @returns {object|null}
|
|
249
|
-
*/
|
|
250
|
-
function getPerformanceMenu() {
|
|
251
|
-
// Dev-mode only. In build output there is no live dev-server to compute
|
|
252
|
-
// perf against, so the entry would dead-link.
|
|
253
|
-
if (global.config.isBuild) {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
if (!global.config.performance?.enabled) {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
topLevel: true,
|
|
262
|
-
name: "Performance",
|
|
263
|
-
id: "performance",
|
|
264
|
-
type: "directory",
|
|
265
|
-
shortPath: "performance",
|
|
266
|
-
children: [
|
|
267
|
-
{
|
|
268
|
-
section: "performance",
|
|
269
|
-
type: "file",
|
|
270
|
-
name: "budget",
|
|
271
|
-
url: "/iframe/performance",
|
|
272
|
-
},
|
|
273
|
-
],
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
243
|
/**
|
|
278
244
|
* @returns {object}
|
|
279
245
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schalkneethling/miyagi-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "miyagi is a component development tool for JavaScript template engines.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Schalk Neethling <schalkneethling@duck.com>, Michael Großklaus <mail@mgrossklaus.de> (https://www.mgrossklaus.de)",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"chokidar": "^5.0.0",
|
|
42
42
|
"cookie-parser": "^1.4.7",
|
|
43
43
|
"deepmerge": "^4.3.1",
|
|
44
|
+
"dependency-tree": "^11.4.3",
|
|
44
45
|
"directory-tree": "^3.5.2",
|
|
45
46
|
"express": "^5.1.0",
|
|
46
47
|
"html-validate": "^10.11.2",
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
.PerformanceTable {
|
|
2
|
-
border-collapse: collapse;
|
|
3
|
-
margin-block: 1em;
|
|
4
|
-
width: 100%;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.PerformanceTable :is(th, td) {
|
|
8
|
-
padding: 0.35em 0.75em;
|
|
9
|
-
text-align: start;
|
|
10
|
-
vertical-align: top;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.PerformanceTable thead th {
|
|
14
|
-
border-block-end: 0.0625rem solid currentcolor;
|
|
15
|
-
font-weight: bold;
|
|
16
|
-
text-transform: uppercase;
|
|
17
|
-
font-size: 0.875em;
|
|
18
|
-
letter-spacing: 0.0375em;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.PerformanceTable tbody tr:not(:last-child) :is(td, th) {
|
|
22
|
-
border-block-end: 0.0625rem solid var(--color-Outline);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.PerformanceTable-totals :is(th, td) {
|
|
26
|
-
font-weight: bold;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.PerformanceTable tr[data-missing="true"] td {
|
|
30
|
-
color: var(--color-Iframe-text-secondary);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.PerformanceStatus {
|
|
34
|
-
display: inline-block;
|
|
35
|
-
padding: 0.15em 0.6em;
|
|
36
|
-
border-radius: 0.25em;
|
|
37
|
-
font-size: 0.875em;
|
|
38
|
-
font-weight: bold;
|
|
39
|
-
letter-spacing: 0.0375em;
|
|
40
|
-
text-transform: uppercase;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.PerformanceStatus--ok {
|
|
44
|
-
background: color-mix(in srgb, var(--color-Positive) 20%, transparent);
|
|
45
|
-
color: var(--color-Positive);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.PerformanceStatus--warn {
|
|
49
|
-
background: color-mix(
|
|
50
|
-
in srgb,
|
|
51
|
-
var(--color-Iframe-text-secondary) 25%,
|
|
52
|
-
transparent
|
|
53
|
-
);
|
|
54
|
-
color: var(--color-Iframe-text);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.PerformanceStatus--exceed {
|
|
58
|
-
background: color-mix(in srgb, var(--color-Negative) 20%, transparent);
|
|
59
|
-
color: var(--color-Negative);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.PerformanceStatus--unbudgeted {
|
|
63
|
-
color: var(--color-Iframe-text-secondary);
|
|
64
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
{% extends "@miyagi/layouts/iframe_default.twig.miyagi" %}
|
|
2
|
-
{% block body %}
|
|
3
|
-
<div class="Wrapper">
|
|
4
|
-
<div class="Documentation">
|
|
5
|
-
<h1>Performance budget</h1>
|
|
6
|
-
<p>Measured at <strong>{{ compression }}</strong>. OK: {{ summary.ok }} · Warn: {{ summary.warn }} · Exceed: {{ summary.exceed }} · Unbudgeted: {{ summary.unbudgeted }}.</p>
|
|
7
|
-
|
|
8
|
-
<h2>Evaluations</h2>
|
|
9
|
-
<table class="PerformanceTable">
|
|
10
|
-
<thead>
|
|
11
|
-
<tr>
|
|
12
|
-
<th scope="col">Category</th>
|
|
13
|
-
<th scope="col">Item</th>
|
|
14
|
-
<th scope="col">Actual</th>
|
|
15
|
-
<th scope="col">Budget</th>
|
|
16
|
-
<th scope="col">Status</th>
|
|
17
|
-
</tr>
|
|
18
|
-
</thead>
|
|
19
|
-
<tbody>
|
|
20
|
-
{% for row in evaluations %}
|
|
21
|
-
<tr data-status="{{ row.status }}">
|
|
22
|
-
<td>{{ row.category }}</td>
|
|
23
|
-
<td>{% if row.key == "total" %}Total{% else %}{{ row.label }}{% endif %}</td>
|
|
24
|
-
<td>{{ row.actualFormatted }}</td>
|
|
25
|
-
<td>{{ row.budgetFormatted }}</td>
|
|
26
|
-
<td>
|
|
27
|
-
<span class="PerformanceStatus PerformanceStatus--{{ row.status }}">
|
|
28
|
-
{% if row.status == "ok" %}OK{% elseif row.status == "warn" %}WARN{% elseif row.status == "exceed" %}EXCEED{% else %}—{% endif %}
|
|
29
|
-
</span>
|
|
30
|
-
{% if row.ratioPercent is not null %}
|
|
31
|
-
<small>({{ row.ratioPercent }}%)</small>
|
|
32
|
-
{% endif %}
|
|
33
|
-
</td>
|
|
34
|
-
</tr>
|
|
35
|
-
{% endfor %}
|
|
36
|
-
</tbody>
|
|
37
|
-
</table>
|
|
38
|
-
|
|
39
|
-
{% for cat in categories %}
|
|
40
|
-
{% if cat.files|length > 0 %}
|
|
41
|
-
<h2>{{ cat.category }}</h2>
|
|
42
|
-
<table class="PerformanceTable">
|
|
43
|
-
<thead>
|
|
44
|
-
<tr>
|
|
45
|
-
<th scope="col">File</th>
|
|
46
|
-
<th scope="col">Raw</th>
|
|
47
|
-
<th scope="col">Gzip</th>
|
|
48
|
-
<th scope="col">Brotli</th>
|
|
49
|
-
</tr>
|
|
50
|
-
</thead>
|
|
51
|
-
<tbody>
|
|
52
|
-
{% for file in cat.files %}
|
|
53
|
-
<tr{% if file.missing %} data-missing="true"{% endif %}>
|
|
54
|
-
<td>{{ file.path }}{% if file.missing %} <em>(missing)</em>{% endif %}</td>
|
|
55
|
-
<td>{{ file.raw }}</td>
|
|
56
|
-
<td>{{ file.gzip }}</td>
|
|
57
|
-
<td>{{ file.brotli }}</td>
|
|
58
|
-
</tr>
|
|
59
|
-
{% endfor %}
|
|
60
|
-
<tr class="PerformanceTable-totals">
|
|
61
|
-
<th scope="row">Total</th>
|
|
62
|
-
<td>{{ cat.totals.raw }}</td>
|
|
63
|
-
<td>{{ cat.totals.gzip }}</td>
|
|
64
|
-
<td>{{ cat.totals.brotli }}</td>
|
|
65
|
-
</tr>
|
|
66
|
-
</tbody>
|
|
67
|
-
</table>
|
|
68
|
-
{% endif %}
|
|
69
|
-
{% endfor %}
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
{% endblock %}
|