@schalkneethling/miyagi-core 4.7.0 → 4.8.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 +35 -0
- package/dist/css/iframe.css +1 -1
- package/frontend/assets/css/iframe/performance.css +64 -0
- package/frontend/assets/css/iframe.css +1 -0
- package/frontend/views/performance.twig.miyagi +72 -0
- package/lib/build/index.js +73 -0
- package/lib/cli/budget.js +157 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/run.js +11 -0
- package/lib/default-config.js +33 -0
- package/lib/init/args.js +39 -0
- package/lib/init/router.js +27 -0
- package/lib/performance/index.js +400 -0
- package/lib/performance/measure.js +124 -0
- package/lib/performance/parse-size.js +86 -0
- package/lib/performance/report.js +102 -0
- package/lib/render/index.js +4 -0
- package/lib/render/views/iframe/performance.js +74 -0
- package/lib/render/views/main/performance.js +51 -0
- package/lib/state/menu/index.js +45 -4
- package/package.json +1 -1
package/api/index.js
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
validateComponentHtml as validateComponentHtmlImpl,
|
|
18
18
|
} from "../lib/validator/html.js";
|
|
19
19
|
import { generateMarkdownReport } from "../lib/validator/html-report.js";
|
|
20
|
+
import { runPerformance } from "../lib/performance/index.js";
|
|
21
|
+
import { generatePerformanceReport } from "../lib/performance/report.js";
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* @param {object} obj
|
|
@@ -327,6 +329,39 @@ export const validateHtml = async (options = {}) => {
|
|
|
327
329
|
};
|
|
328
330
|
};
|
|
329
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Run the performance-budget check programmatically.
|
|
334
|
+
* @param {object} [options]
|
|
335
|
+
* @param {boolean} [options.html] - include post-build HTML pages
|
|
336
|
+
* @param {string} [options.buildFolder] - required when html is true
|
|
337
|
+
* @param {"raw"|"gzip"|"brotli"} [options.compression] - override config compression
|
|
338
|
+
* @returns {Promise<object>}
|
|
339
|
+
*/
|
|
340
|
+
export const getPerformance = async (options = {}) => {
|
|
341
|
+
global.app = await init("api");
|
|
342
|
+
|
|
343
|
+
if (options.compression) {
|
|
344
|
+
global.config.performance = {
|
|
345
|
+
...(global.config.performance || {}),
|
|
346
|
+
compression: options.compression,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const result = runPerformance({
|
|
351
|
+
config: global.config,
|
|
352
|
+
html: Boolean(options.html),
|
|
353
|
+
buildFolder: options.buildFolder,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
success: result.summary.exceed === 0,
|
|
358
|
+
data: {
|
|
359
|
+
result,
|
|
360
|
+
report: generatePerformanceReport(result),
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
|
|
330
365
|
/**
|
|
331
366
|
* @param {object} obj
|
|
332
367
|
* @param {string|null} obj.component
|
package/dist/css/iframe.css
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
--json-tree-js-container-border-color
|
|
5
5
|
);--json-tree-js-button-text-color:var(--json-tree-js-color-white);--json-tree-js-button-background-color-hover:var(
|
|
6
6
|
--json-tree-js-container-border-color
|
|
7
|
-
);--json-tree-js-button-text-color-hover:var(--json-tree-js-color-snow-white);--json-tree-js-button-background-color-active:#616b79;--json-tree-js-button-text-color-active:var(--json-tree-js-color-snow-white);--json-tree-js-border-radius:0.5rem;--json-tree-js-border-style-scrollbar:inset 0 0 6px var(--json-tree-js-color-dark-gray);--json-tree-js-border-size:0.5px;--json-tree-js-spacing:10px;--json-tree-js-spacing-font-size:1em;--json-tree-js-transition:all 0.3s}div.json-tree-js{background-color:var(--color-Code-background);border:.0625rem solid var(--color-Outline);box-sizing:border-box;color:var(--json-tree-js-color-white);display:inline-block;font-size:var(--json-tree-js-spacing-font-size);font-weight:var(--json-tree-js-text-bold-weight);line-height:1.5;margin:0!important;padding:var(--json-tree-js-spacing);width:100%}div.json-tree-js div.no-click{pointer-events:none!important}div.json-tree-js *,div.json-tree-js :after,div.json-tree-js :before{box-sizing:border-box}}@layer miyagi{div.json-tree-js :is(.down-arrow,.right-arrow,.no-arrow){appearance:none;background:none;border:none;display:inline-flex;font:inherit;height:1.5em;padding:0;width:1.5em}div.json-tree-js .no-arrow{pointer-events:none;visibility:hidden}div.json-tree-js :is(.down-arrow,.right-arrow){align-items:center;cursor:pointer;justify-content:center;transform:translateY(-.125em)}div.json-tree-js :is(.down-arrow,.right-arrow):after{border:var(--toggle-border);border-color:currentcolor;border-inline-end-width:.25em;border-top-width:.25em;content:"";display:inline-block;flex:0 0 var(--toggle-width);font-size:.4em;height:var(--toggle-height)}div.json-tree-js :is(.down-arrow,.right-arrow):hover{opacity:.7}div.json-tree-js .down-arrow:after{transform:translateY(-25%) rotate(135deg)}div.json-tree-js .right-arrow:after{transform:translateX(-25%) rotate(45deg)}div.json-tree-js div.title-bar{display:flex;margin-bottom:var(--json-tree-js-spacing)}div.json-tree-js div.title-bar div.controls,div.json-tree-js div.title-bar div.title{display:none}div.json-tree-js div.object-type-title{font-weight:var(--json-tree-js-header-bold-weight);text-align:left!important}div.json-tree-js div.object-type-title span.array{color:var(--json-tree-js-color-array)}div.json-tree-js div.object-type-title span.object{color:var(--json-tree-js-color-object)}div.json-tree-js div.object-type-title span.count{font-weight:var(--json-tree-js-text-bold-weight);margin-left:calc(var(--json-tree-js-spacing)/2)}div.json-tree-js div.object-type-contents{margin-left:calc(var(--json-tree-js-spacing)*2);margin-top:calc(var(--json-tree-js-spacing)/2);text-align:left!important}div.json-tree-js div.object-type-contents div.object-type-value{margin-bottom:calc(var(--json-tree-js-spacing)/2);margin-top:calc(var(--json-tree-js-spacing)/2)}div.json-tree-js div.object-type-contents div.object-type-value span.split{margin-left:calc(var(--json-tree-js-spacing)/2);margin-right:calc(var(--json-tree-js-spacing)/2)}div.json-tree-js div.object-type-contents div.object-type-value span.boolean,div.json-tree-js div.object-type-contents div.object-type-value span.date,div.json-tree-js div.object-type-contents div.object-type-value span.decimal,div.json-tree-js div.object-type-contents div.object-type-value span.function,div.json-tree-js div.object-type-contents div.object-type-value span.null,div.json-tree-js div.object-type-contents div.object-type-value span.number,div.json-tree-js div.object-type-contents div.object-type-value span.string,div.json-tree-js div.object-type-contents div.object-type-value span.unknown{transition:var(--json-tree-js-transition);transition-property:opacity}div.json-tree-js div.object-type-contents div.object-type-value span.boolean:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.date:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.decimal:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.function:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.null:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.number:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.string:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.unknown:not(.no-hover):hover{cursor:pointer;opacity:.7}div.json-tree-js div.object-type-contents div.object-type-value span.comma{color:var(--json-tree-js-color-white);font-weight:var(--json-tree-js-text-bold-weight)}div.json-tree-js div.object-type-contents div.object-type-value span.boolean{color:var(--json-tree-js-color-boolean)}div.json-tree-js div.object-type-contents div.object-type-value span.decimal{color:var(--json-tree-js-color-decimal)}div.json-tree-js div.object-type-contents div.object-type-value span.number{color:var(--json-tree-js-color-number)}div.json-tree-js div.object-type-contents div.object-type-value span.string{color:var(--json-tree-js-color-string)}div.json-tree-js div.object-type-contents div.object-type-value span.date{color:var(--json-tree-js-color-date)}div.json-tree-js div.object-type-contents div.object-type-value span.array{color:var(--json-tree-js-color-array);font-weight:var(--json-tree-js-header-bold-weight)}div.json-tree-js div.object-type-contents div.object-type-value span.object{color:var(--json-tree-js-color-object);font-weight:var(--json-tree-js-header-bold-weight)}div.json-tree-js div.object-type-contents div.object-type-value span.null{color:var(--json-tree-js-color-null);font-style:italic}div.json-tree-js div.object-type-contents div.object-type-value span.function{color:var(--json-tree-js-color-function);font-style:italic}div.json-tree-js div.object-type-contents div.object-type-value span.unknown{color:var(--json-tree-js-color-unknown);font-style:italic}div.json-tree-js div.object-type-contents div.object-type-value span.count{font-weight:var(--json-tree-js-text-bold-weight);margin-left:calc(var(--json-tree-js-spacing)/2)}.custom-scroll-bars::-webkit-scrollbar{width:12px}.custom-scroll-bars::-webkit-scrollbar-thumb,.custom-scroll-bars::-webkit-scrollbar-track{box-shadow:var(--json-tree-js-border-style-scrollbar)}.custom-scroll-bars::-webkit-scrollbar-thumb{background:var(--json-tree-js-color-white)}.custom-scroll-bars::-webkit-scrollbar-thumb:hover{background-color:var(--json-tree-js-color-white)}.custom-scroll-bars::-webkit-scrollbar-thumb:active{background-color:var(--json-tree-js-color-lighter-gray)}}@layer miyagi{.Colors{display:grid;gap:3em;grid-template-columns:repeat(auto-fill,minmax(14em,1fr))}.Colors-button{--size:3em;display:flex;flex-wrap:wrap}.Colors-button:after,.Colors-button:before{display:block}.Colors--all .Colors-button:before{margin-inline-end:10px;width:var(--size)}.Colors--all .Colors-button:before,.Colors--decoration .Colors-button:before{background:var(--color);border:1px solid var(--backdrop,transparent);content:"";height:var(--size);order:-1}.Colors--decoration .Colors-button:before{width:100%}.Colors--all .Colors-button:after,.Colors--typo .Colors-button:after{color:var(--color);font-size:var(--size);font-weight:700;order:-1;-webkit-text-stroke:1px var(--backdrop,transparent);text-stroke:1px var(--backdrop,transparent)}.Colors--all .Colors-button:after{content:"Aa"}.Colors--typo .Colors-button:after{content:"AaBbCc"}.Colors-prop,.Colors-value{flex:1 0 100%;margin:10px 0 0}.Fonts-item:not(:first-child){margin-top:3em}.Fonts-button{color:inherit}.Fonts-button:before{content:"The quick brown fox jumps over the lazy dog";font-family:var(--font-family);font-feature-settings:var(--font-feature-settings);font-kerning:var(--font-kerning);font-size:var(--font-size);font-size-adjust:var(--font-size-adjust);font-stretch:var(--font-stretch);font-style:var(--font-style);font-variant:var(--font-variant);font-variant-caps:var(--font-variant-caps);font-weight:var(--font-weight);letter-spacing:var(--letter-spacing);line-height:var(--line-height);text-shadow:var(--text-shadow);text-transform:var(--text-transform)}.Fonts-details{display:flex;list-style:none;padding:0}.Fonts-details>li:not(:last-child){border-inline-end:.0625rem solid currentcolor;margin-inline-end:.5em;padding-inline-end:.5em}.Spacings{align-items:flex-start;display:flex;flex-direction:column}.Spacings-item:not(:first-child){margin-top:3em}.Spacings-button{color:inherit}.Spacings-button:before{background:currentcolor;content:"";display:block;height:var(--spacing);width:var(--spacing)}.CustomPropsGroup{list-style:none;margin:3em 0;padding:0}.CustomProp{border:.0625rem solid transparent}.CustomProp,.CustomProp[aria-selected=true]{position:relative}.CustomProp-prop{display:block;margin:10px 0 0;word-break:break-all}.CustomProp-button{appearance:none;background:none;border:none;font:inherit;padding:0;text-align:start;width:fit-content}.CustomProp-button:focus{outline:none}.CustomProp-button:not([aria-expanded=true]):focus .CustomProp-prop,.CustomProp-button:not([aria-expanded=true]):hover .CustomProp-prop{text-decoration:underline}.CustomProp-details{background:var(--color-Tooltip-background);bottom:calc(100% + 15px);box-shadow:0 4px 10px rgba(0,0,0,.1);box-sizing:border-box;display:none;font-size:14px;gap:.5rem 1rem;grid-template-columns:fit-content(50%) 1fr;inset-inline-start:0;line-height:1.5;margin:0;outline:.0625rem solid var(--color-Tooltip-outline);padding:15px;position:absolute;white-space:nowrap;z-index:1}}@layer miyagi{}@layer miyagi{}@layer miyagi{@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.CustomProp-button:hover+.CustomProp-details,.CustomProp-details:not([hidden]){animation:fadeIn .15s ease;display:grid}.CustomProp-details:after,.CustomProp-details:before{border:10px solid transparent;content:"";display:block;height:0;inset-inline-start:15px;position:absolute;width:0}.CustomProp-details:before{border-top-color:var(--color-Tooltip-outline);top:100%}.CustomProp-details:after{border-top-color:var(--color-Tooltip-background);top:calc(100% - 2px)}.CustomProp-detailsProperty{font-weight:600}.CustomProp-detailsValue{margin:0}}html{--iframe-spacing:clamp(0.75rem,4vi,2.5rem);height:100%}body{background:var(--color-Iframe-background);color:var(--color-Iframe-text);font-family:var(--font-family);margin:0;min-height:100%}.Wrapper{box-sizing:border-box;padding:var(--iframe-spacing);width:100%}a{color:var(--color-Iframe-link);text-decoration:underline}a:focus-visible,button:focus-visible,summary:focus-visible{border-radius:.25em;outline:3px solid currentcolor;outline-offset:2px}code[class*=language-],pre[class*=language-]{border-radius:0;box-shadow:none;text-shadow:none}.Code code,.Documentation code,:not(pre)>code[class*=language-]{font-size-adjust:.525;text-shadow:none}.Code code,.Documentation,.ErrorMessage,.Information,:not(pre)>code[class*=language-]{line-height:3.125ex}.Code code,.Documentation code,.Information code,:not(pre)>code[class*=language-]{font-family:Menlo,Monaco,monospace}.Documentation h1{font-size:2.4em;font-weight:700;line-height:1;text-transform:capitalize}.Documentation>*+*{margin-top:3.125ex}.Documentation i{font-style:italic}.Documentation table{border-spacing:0}.Documentation th{border-block-end:.0625rem solid currentcolor;text-align:left}.Documentation :is(th,td){padding:.25em .5em}.Documentation tr:not(:last-child) td{border-block-end:.0625rem solid var(--color-Outline)}.Information-val{margin-inline-start:0}.Component-variationHeader{display:flex;font-size:14px;gap:16px;inset-inline-end:40px;line-height:1;position:absolute;top:13px}.Documentation>:first-child{margin-top:0}.Documentation p{max-width:64ch}.SectionTitle{align-items:center;display:flex;font-size:1.6em;margin:2em 0 1em;scroll-margin-top:1.5em}.SectionTitle:after{background:var(--color-Outline);content:"";flex:1;height:.0625rem;margin-inline-start:20px}.Information-wrapper+.Information-wrapper{margin-top:30px}.Information-attr{color:var(--color-Iframe-text-secondary);font-family:var(--font-family);font-size:.875em;letter-spacing:.0375em;text-decoration:none;text-transform:uppercase}.Information-attr:not(:first-child){margin-top:1em}.Documentation code,.Information code{font-weight:600}.Status:before{display:inline-block;margin-inline-end:.5em}.Status--valid{color:var(--color-Positive)}.Status--valid:before{content:"✓"}.Status--invalid{color:var(--color-Negative)}.Status--invalid:before{content:"✗"}.Error{align-items:center;display:flex;height:100%;justify-content:center;margin:0}.Error,.ErrorMessage{color:var(--color-Negative)}.ErrorMessage{margin:1.5em 0}.Iframe-newTabLink{align-items:center;display:flex;font-weight:400}.Iframe-newTabLink:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' aria-hidden='true' viewBox='0 0 24 24'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6m4-3h6v6m-11 5L21 3'/%3E%3C/svg%3E");content:"";display:block;height:1em;margin-inline-end:.5em;width:1em}.Code,:not(pre)>code[class*=language-],pre[class*=language-]{background:var(--color-Code-background)}:not(pre)>code[class*=language-],pre[class*=language-]{border-color:var(--color-Outline)}.Code,:not(pre)>code[class*=language-]{border:.0625rem solid var(--color-Outline);padding:calc(var(--iframe-spacing)/2)}.Code,pre[class*=language-]{font-size:.875em}.Tabs-tab summary{cursor:default;list-style-type:none;padding-block:10px;padding-inline-start:16px;position:relative}.Tabs-tab summary:before{border:var(--toggle-border);border-color:currentcolor;border-inline-end-width:.25em;border-top-width:.25em;content:"";display:block;font-size:.4em;height:var(--toggle-height);inset-inline-start:.25rem;position:absolute;top:50%;transition:var(--toggle-transition);width:var(--toggle-width)}.Tabs-tab summary::-webkit-details-marker{display:none}.Tabs-tab:not([open]) summary:before{transform:var(--toggle-transition-closed)}.Tabs-tab[open] summary:before{transform:var(--toggle-transition-opened)}.Component:not(:last-child){margin-bottom:calc(var(--iframe-spacing)*2)}.Component-iframeWrapper{height:calc(100vh - var(--iframe-spacing)*2.5);width:100%}.Component-iframeWrapper:not(.has-fixedHeight){outline:.0625rem solid var(--color-Outline);resize:both}.Component-iframe{height:100%;width:100%}.Component-head{display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;padding:0 0 20px}.Component-headMeta{align-items:center;display:flex;gap:16px}.Component-mockValidation{appearance:none;background:none;border:none;color:var(--color-Iframe-link);cursor:pointer;font-family:var(--font-family);font-size:inherit;line-height:1;margin:0;padding:0;text-decoration:underline}.Component-mockData{background:light-dark(#f2f2f2,#1f1f1f);border:none;box-sizing:border-box;height:calc(100vb - var(--iframe-spacing)*2);max-width:70ch;overscroll-behavior:contain;padding:var(--iframe-spacing);width:calc(100vi - var(--iframe-spacing)*2)}.Component-mockData::backdrop{background:light-dark(hsla(0,0%,100%,.8),rgba(0,0,0,.8))}.Component-mockData:not([open]){display:none}.Component-mockDataHeading{margin-block:0 1em}.Component-closeMockData{appearance:none;background:none;border:0;color:var(--color-Iframe-link);cursor:pointer;inset-block-start:1rem;inset-inline-end:1rem;line-height:1;margin:0;padding:0;position:absolute;text-decoration:underline}.Component-closeMockData,.Component-file{font-family:var(--font-family);font-size:.875em}.Component-file{align-items:center;color:var(--color-Iframe-text-secondary);display:inline-flex;flex-wrap:wrap;letter-spacing:.0375em;text-decoration:none;text-transform:uppercase}.Component-file:active,.Component-file:focus,.Component-file:hover{text-decoration:underline}.Component-fileFolders{color:var(--color-Iframe-text-tertiary)}.ComponentView{border:.0625rem solid var(--color-Outline);box-sizing:border-box;height:calc(100vh - var(--iframe-spacing)*2);overflow:hidden;resize:both;width:100%}.ComponentView-iframe{height:100%;width:100%}[data-mode=presentation] .DeveloperInformation{display:none}@media screen and (prefers-reduced-motion){*,:after,:before{animation:none!important;transition:none!important}}@media (prefers-color-scheme:dark){.Iframe-newTabLink:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' aria-hidden='true' viewBox='0 0 24 24'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6m4-3h6v6m-11 5L21 3'/%3E%3C/svg%3E")}}
|
|
7
|
+
);--json-tree-js-button-text-color-hover:var(--json-tree-js-color-snow-white);--json-tree-js-button-background-color-active:#616b79;--json-tree-js-button-text-color-active:var(--json-tree-js-color-snow-white);--json-tree-js-border-radius:0.5rem;--json-tree-js-border-style-scrollbar:inset 0 0 6px var(--json-tree-js-color-dark-gray);--json-tree-js-border-size:0.5px;--json-tree-js-spacing:10px;--json-tree-js-spacing-font-size:1em;--json-tree-js-transition:all 0.3s}div.json-tree-js{background-color:var(--color-Code-background);border:.0625rem solid var(--color-Outline);box-sizing:border-box;color:var(--json-tree-js-color-white);display:inline-block;font-size:var(--json-tree-js-spacing-font-size);font-weight:var(--json-tree-js-text-bold-weight);line-height:1.5;margin:0!important;padding:var(--json-tree-js-spacing);width:100%}div.json-tree-js div.no-click{pointer-events:none!important}div.json-tree-js *,div.json-tree-js :after,div.json-tree-js :before{box-sizing:border-box}}@layer miyagi{div.json-tree-js :is(.down-arrow,.right-arrow,.no-arrow){appearance:none;background:none;border:none;display:inline-flex;font:inherit;height:1.5em;padding:0;width:1.5em}div.json-tree-js .no-arrow{pointer-events:none;visibility:hidden}div.json-tree-js :is(.down-arrow,.right-arrow){align-items:center;cursor:pointer;justify-content:center;transform:translateY(-.125em)}div.json-tree-js :is(.down-arrow,.right-arrow):after{border:var(--toggle-border);border-color:currentcolor;border-inline-end-width:.25em;border-top-width:.25em;content:"";display:inline-block;flex:0 0 var(--toggle-width);font-size:.4em;height:var(--toggle-height)}div.json-tree-js :is(.down-arrow,.right-arrow):hover{opacity:.7}div.json-tree-js .down-arrow:after{transform:translateY(-25%) rotate(135deg)}div.json-tree-js .right-arrow:after{transform:translateX(-25%) rotate(45deg)}div.json-tree-js div.title-bar{display:flex;margin-bottom:var(--json-tree-js-spacing)}div.json-tree-js div.title-bar div.controls,div.json-tree-js div.title-bar div.title{display:none}div.json-tree-js div.object-type-title{font-weight:var(--json-tree-js-header-bold-weight);text-align:left!important}div.json-tree-js div.object-type-title span.array{color:var(--json-tree-js-color-array)}div.json-tree-js div.object-type-title span.object{color:var(--json-tree-js-color-object)}div.json-tree-js div.object-type-title span.count{font-weight:var(--json-tree-js-text-bold-weight);margin-left:calc(var(--json-tree-js-spacing)/2)}div.json-tree-js div.object-type-contents{margin-left:calc(var(--json-tree-js-spacing)*2);margin-top:calc(var(--json-tree-js-spacing)/2);text-align:left!important}div.json-tree-js div.object-type-contents div.object-type-value{margin-bottom:calc(var(--json-tree-js-spacing)/2);margin-top:calc(var(--json-tree-js-spacing)/2)}div.json-tree-js div.object-type-contents div.object-type-value span.split{margin-left:calc(var(--json-tree-js-spacing)/2);margin-right:calc(var(--json-tree-js-spacing)/2)}div.json-tree-js div.object-type-contents div.object-type-value span.boolean,div.json-tree-js div.object-type-contents div.object-type-value span.date,div.json-tree-js div.object-type-contents div.object-type-value span.decimal,div.json-tree-js div.object-type-contents div.object-type-value span.function,div.json-tree-js div.object-type-contents div.object-type-value span.null,div.json-tree-js div.object-type-contents div.object-type-value span.number,div.json-tree-js div.object-type-contents div.object-type-value span.string,div.json-tree-js div.object-type-contents div.object-type-value span.unknown{transition:var(--json-tree-js-transition);transition-property:opacity}div.json-tree-js div.object-type-contents div.object-type-value span.boolean:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.date:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.decimal:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.function:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.null:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.number:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.string:not(.no-hover):hover,div.json-tree-js div.object-type-contents div.object-type-value span.unknown:not(.no-hover):hover{cursor:pointer;opacity:.7}div.json-tree-js div.object-type-contents div.object-type-value span.comma{color:var(--json-tree-js-color-white);font-weight:var(--json-tree-js-text-bold-weight)}div.json-tree-js div.object-type-contents div.object-type-value span.boolean{color:var(--json-tree-js-color-boolean)}div.json-tree-js div.object-type-contents div.object-type-value span.decimal{color:var(--json-tree-js-color-decimal)}div.json-tree-js div.object-type-contents div.object-type-value span.number{color:var(--json-tree-js-color-number)}div.json-tree-js div.object-type-contents div.object-type-value span.string{color:var(--json-tree-js-color-string)}div.json-tree-js div.object-type-contents div.object-type-value span.date{color:var(--json-tree-js-color-date)}div.json-tree-js div.object-type-contents div.object-type-value span.array{color:var(--json-tree-js-color-array);font-weight:var(--json-tree-js-header-bold-weight)}div.json-tree-js div.object-type-contents div.object-type-value span.object{color:var(--json-tree-js-color-object);font-weight:var(--json-tree-js-header-bold-weight)}div.json-tree-js div.object-type-contents div.object-type-value span.null{color:var(--json-tree-js-color-null);font-style:italic}div.json-tree-js div.object-type-contents div.object-type-value span.function{color:var(--json-tree-js-color-function);font-style:italic}div.json-tree-js div.object-type-contents div.object-type-value span.unknown{color:var(--json-tree-js-color-unknown);font-style:italic}div.json-tree-js div.object-type-contents div.object-type-value span.count{font-weight:var(--json-tree-js-text-bold-weight);margin-left:calc(var(--json-tree-js-spacing)/2)}.custom-scroll-bars::-webkit-scrollbar{width:12px}.custom-scroll-bars::-webkit-scrollbar-thumb,.custom-scroll-bars::-webkit-scrollbar-track{box-shadow:var(--json-tree-js-border-style-scrollbar)}.custom-scroll-bars::-webkit-scrollbar-thumb{background:var(--json-tree-js-color-white)}.custom-scroll-bars::-webkit-scrollbar-thumb:hover{background-color:var(--json-tree-js-color-white)}.custom-scroll-bars::-webkit-scrollbar-thumb:active{background-color:var(--json-tree-js-color-lighter-gray)}}@layer miyagi{.Colors{display:grid;gap:3em;grid-template-columns:repeat(auto-fill,minmax(14em,1fr))}.Colors-button{--size:3em;display:flex;flex-wrap:wrap}.Colors-button:after,.Colors-button:before{display:block}.Colors--all .Colors-button:before{margin-inline-end:10px;width:var(--size)}.Colors--all .Colors-button:before,.Colors--decoration .Colors-button:before{background:var(--color);border:1px solid var(--backdrop,transparent);content:"";height:var(--size);order:-1}.Colors--decoration .Colors-button:before{width:100%}.Colors--all .Colors-button:after,.Colors--typo .Colors-button:after{color:var(--color);font-size:var(--size);font-weight:700;order:-1;-webkit-text-stroke:1px var(--backdrop,transparent);text-stroke:1px var(--backdrop,transparent)}.Colors--all .Colors-button:after{content:"Aa"}.Colors--typo .Colors-button:after{content:"AaBbCc"}.Colors-prop,.Colors-value{flex:1 0 100%;margin:10px 0 0}.Fonts-item:not(:first-child){margin-top:3em}.Fonts-button{color:inherit}.Fonts-button:before{content:"The quick brown fox jumps over the lazy dog";font-family:var(--font-family);font-feature-settings:var(--font-feature-settings);font-kerning:var(--font-kerning);font-size:var(--font-size);font-size-adjust:var(--font-size-adjust);font-stretch:var(--font-stretch);font-style:var(--font-style);font-variant:var(--font-variant);font-variant-caps:var(--font-variant-caps);font-weight:var(--font-weight);letter-spacing:var(--letter-spacing);line-height:var(--line-height);text-shadow:var(--text-shadow);text-transform:var(--text-transform)}.Fonts-details{display:flex;list-style:none;padding:0}.Fonts-details>li:not(:last-child){border-inline-end:.0625rem solid currentcolor;margin-inline-end:.5em;padding-inline-end:.5em}.Spacings{align-items:flex-start;display:flex;flex-direction:column}.Spacings-item:not(:first-child){margin-top:3em}.Spacings-button{color:inherit}.Spacings-button:before{background:currentcolor;content:"";display:block;height:var(--spacing);width:var(--spacing)}.CustomPropsGroup{list-style:none;margin:3em 0;padding:0}.CustomProp{border:.0625rem solid transparent}.CustomProp,.CustomProp[aria-selected=true]{position:relative}.CustomProp-prop{display:block;margin:10px 0 0;word-break:break-all}.CustomProp-button{appearance:none;background:none;border:none;font:inherit;padding:0;text-align:start;width:fit-content}.CustomProp-button:focus{outline:none}.CustomProp-button:not([aria-expanded=true]):focus .CustomProp-prop,.CustomProp-button:not([aria-expanded=true]):hover .CustomProp-prop{text-decoration:underline}.CustomProp-details{background:var(--color-Tooltip-background);bottom:calc(100% + 15px);box-shadow:0 4px 10px rgba(0,0,0,.1);box-sizing:border-box;display:none;font-size:14px;gap:.5rem 1rem;grid-template-columns:fit-content(50%) 1fr;inset-inline-start:0;line-height:1.5;margin:0;outline:.0625rem solid var(--color-Tooltip-outline);padding:15px;position:absolute;white-space:nowrap;z-index:1}}@layer miyagi{}@layer miyagi{}@layer miyagi{@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.CustomProp-button:hover+.CustomProp-details,.CustomProp-details:not([hidden]){animation:fadeIn .15s ease;display:grid}.CustomProp-details:after,.CustomProp-details:before{border:10px solid transparent;content:"";display:block;height:0;inset-inline-start:15px;position:absolute;width:0}.CustomProp-details:before{border-top-color:var(--color-Tooltip-outline);top:100%}.CustomProp-details:after{border-top-color:var(--color-Tooltip-background);top:calc(100% - 2px)}.CustomProp-detailsProperty{font-weight:600}.CustomProp-detailsValue{margin:0}.PerformanceTable{border-collapse:collapse;margin-block:1em;width:100%}}@layer miyagi{.PerformanceTable :is(th,td){padding:.35em .75em;text-align:start;vertical-align:top}.PerformanceTable thead th{border-block-end:.0625rem solid currentcolor;font-size:.875em;font-weight:700;letter-spacing:.0375em;text-transform:uppercase}.PerformanceTable tbody tr:not(:last-child) :is(td,th){border-block-end:.0625rem solid var(--color-Outline)}.PerformanceTable-totals :is(th,td){font-weight:700}.PerformanceTable tr[data-missing=true] td{color:var(--color-Iframe-text-secondary)}.PerformanceStatus{border-radius:.25em;display:inline-block;font-size:.875em;font-weight:700;letter-spacing:.0375em;padding:.15em .6em;text-transform:uppercase}.PerformanceStatus--ok{background:color-mix(in srgb,var(--color-Positive) 20%,transparent);color:var(--color-Positive)}.PerformanceStatus--warn{background:color-mix(in srgb,var(--color-Iframe-text-secondary) 25%,transparent);color:var(--color-Iframe-text)}.PerformanceStatus--exceed{background:color-mix(in srgb,var(--color-Negative) 20%,transparent);color:var(--color-Negative)}.PerformanceStatus--unbudgeted{color:var(--color-Iframe-text-secondary)}}html{--iframe-spacing:clamp(0.75rem,4vi,2.5rem);height:100%}body{background:var(--color-Iframe-background);color:var(--color-Iframe-text);font-family:var(--font-family);margin:0;min-height:100%}.Wrapper{box-sizing:border-box;padding:var(--iframe-spacing);width:100%}a{color:var(--color-Iframe-link);text-decoration:underline}a:focus-visible,button:focus-visible,summary:focus-visible{border-radius:.25em;outline:3px solid currentcolor;outline-offset:2px}code[class*=language-],pre[class*=language-]{border-radius:0;box-shadow:none;text-shadow:none}.Code code,.Documentation code,:not(pre)>code[class*=language-]{font-size-adjust:.525;text-shadow:none}.Code code,.Documentation,.ErrorMessage,.Information,:not(pre)>code[class*=language-]{line-height:3.125ex}.Code code,.Documentation code,.Information code,:not(pre)>code[class*=language-]{font-family:Menlo,Monaco,monospace}.Documentation h1{font-size:2.4em;font-weight:700;line-height:1;text-transform:capitalize}.Documentation>*+*{margin-top:3.125ex}.Documentation i{font-style:italic}.Documentation table{border-spacing:0}.Documentation th{border-block-end:.0625rem solid currentcolor;text-align:left}.Documentation :is(th,td){padding:.25em .5em}.Documentation tr:not(:last-child) td{border-block-end:.0625rem solid var(--color-Outline)}.Information-val{margin-inline-start:0}.Component-variationHeader{display:flex;font-size:14px;gap:16px;inset-inline-end:40px;line-height:1;position:absolute;top:13px}.Documentation>:first-child{margin-top:0}.Documentation p{max-width:64ch}.SectionTitle{align-items:center;display:flex;font-size:1.6em;margin:2em 0 1em;scroll-margin-top:1.5em}.SectionTitle:after{background:var(--color-Outline);content:"";flex:1;height:.0625rem;margin-inline-start:20px}.Information-wrapper+.Information-wrapper{margin-top:30px}.Information-attr{color:var(--color-Iframe-text-secondary);font-family:var(--font-family);font-size:.875em;letter-spacing:.0375em;text-decoration:none;text-transform:uppercase}.Information-attr:not(:first-child){margin-top:1em}.Documentation code,.Information code{font-weight:600}.Status:before{display:inline-block;margin-inline-end:.5em}.Status--valid{color:var(--color-Positive)}.Status--valid:before{content:"✓"}.Status--invalid{color:var(--color-Negative)}.Status--invalid:before{content:"✗"}.Error{align-items:center;display:flex;height:100%;justify-content:center;margin:0}.Error,.ErrorMessage{color:var(--color-Negative)}.ErrorMessage{margin:1.5em 0}.Iframe-newTabLink{align-items:center;display:flex;font-weight:400}.Iframe-newTabLink:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' aria-hidden='true' viewBox='0 0 24 24'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6m4-3h6v6m-11 5L21 3'/%3E%3C/svg%3E");content:"";display:block;height:1em;margin-inline-end:.5em;width:1em}.Code,:not(pre)>code[class*=language-],pre[class*=language-]{background:var(--color-Code-background)}:not(pre)>code[class*=language-],pre[class*=language-]{border-color:var(--color-Outline)}.Code,:not(pre)>code[class*=language-]{border:.0625rem solid var(--color-Outline);padding:calc(var(--iframe-spacing)/2)}.Code,pre[class*=language-]{font-size:.875em}.Tabs-tab summary{cursor:default;list-style-type:none;padding-block:10px;padding-inline-start:16px;position:relative}.Tabs-tab summary:before{border:var(--toggle-border);border-color:currentcolor;border-inline-end-width:.25em;border-top-width:.25em;content:"";display:block;font-size:.4em;height:var(--toggle-height);inset-inline-start:.25rem;position:absolute;top:50%;transition:var(--toggle-transition);width:var(--toggle-width)}.Tabs-tab summary::-webkit-details-marker{display:none}.Tabs-tab:not([open]) summary:before{transform:var(--toggle-transition-closed)}.Tabs-tab[open] summary:before{transform:var(--toggle-transition-opened)}.Component:not(:last-child){margin-bottom:calc(var(--iframe-spacing)*2)}.Component-iframeWrapper{height:calc(100vh - var(--iframe-spacing)*2.5);width:100%}.Component-iframeWrapper:not(.has-fixedHeight){outline:.0625rem solid var(--color-Outline);resize:both}.Component-iframe{height:100%;width:100%}.Component-head{display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;padding:0 0 20px}.Component-headMeta{align-items:center;display:flex;gap:16px}.Component-mockValidation{appearance:none;background:none;border:none;color:var(--color-Iframe-link);cursor:pointer;font-family:var(--font-family);font-size:inherit;line-height:1;margin:0;padding:0;text-decoration:underline}.Component-mockData{background:light-dark(#f2f2f2,#1f1f1f);border:none;box-sizing:border-box;height:calc(100vb - var(--iframe-spacing)*2);max-width:70ch;overscroll-behavior:contain;padding:var(--iframe-spacing);width:calc(100vi - var(--iframe-spacing)*2)}.Component-mockData::backdrop{background:light-dark(hsla(0,0%,100%,.8),rgba(0,0,0,.8))}.Component-mockData:not([open]){display:none}.Component-mockDataHeading{margin-block:0 1em}.Component-closeMockData{appearance:none;background:none;border:0;color:var(--color-Iframe-link);cursor:pointer;inset-block-start:1rem;inset-inline-end:1rem;line-height:1;margin:0;padding:0;position:absolute;text-decoration:underline}.Component-closeMockData,.Component-file{font-family:var(--font-family);font-size:.875em}.Component-file{align-items:center;color:var(--color-Iframe-text-secondary);display:inline-flex;flex-wrap:wrap;letter-spacing:.0375em;text-decoration:none;text-transform:uppercase}.Component-file:active,.Component-file:focus,.Component-file:hover{text-decoration:underline}.Component-fileFolders{color:var(--color-Iframe-text-tertiary)}.ComponentView{border:.0625rem solid var(--color-Outline);box-sizing:border-box;height:calc(100vh - var(--iframe-spacing)*2);overflow:hidden;resize:both;width:100%}.ComponentView-iframe{height:100%;width:100%}[data-mode=presentation] .DeveloperInformation{display:none}@media screen and (prefers-reduced-motion){*,:after,:before{animation:none!important;transition:none!important}}@media (prefers-color-scheme:dark){.Iframe-newTabLink:before{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' aria-hidden='true' viewBox='0 0 24 24'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6m4-3h6v6m-11 5L21 3'/%3E%3C/svg%3E")}}
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
@import url("./iframe/accordion-tabs.css") layer(miyagi);
|
|
5
5
|
@import url("./iframe/jsontree.js.css") layer(miyagi);
|
|
6
6
|
@import url("./iframe/styleguide/index.css") layer(miyagi);
|
|
7
|
+
@import url("./iframe/performance.css") layer(miyagi);
|
|
7
8
|
|
|
8
9
|
html {
|
|
9
10
|
--iframe-spacing: clamp(0.75rem, 4vi, 2.5rem);
|
|
@@ -0,0 +1,72 @@
|
|
|
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 %}
|
package/lib/build/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import appConfig from "../default-config.js";
|
|
|
7
7
|
import { t } from "../i18n/index.js";
|
|
8
8
|
import log from "../logger.js";
|
|
9
9
|
import { getVariationData } from "../mocks/index.js";
|
|
10
|
+
import { runPerformance } from "../performance/index.js";
|
|
11
|
+
import { generatePerformanceReport } from "../performance/report.js";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Module for creating a static build
|
|
@@ -176,6 +178,12 @@ export default () => {
|
|
|
176
178
|
.then(async () => {
|
|
177
179
|
await createJsonOutputFile(buildFolder, paths);
|
|
178
180
|
|
|
181
|
+
const performanceError = await writePerformanceReport(buildFolder);
|
|
182
|
+
if (performanceError) {
|
|
183
|
+
reject(performanceError);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
179
187
|
resolve(
|
|
180
188
|
t("buildDone").replace(
|
|
181
189
|
"{{count}}",
|
|
@@ -190,6 +198,71 @@ export default () => {
|
|
|
190
198
|
});
|
|
191
199
|
});
|
|
192
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Runs the performance-budget check against the just-built output, logs a
|
|
203
|
+
* summary, and (when configured) writes a markdown report. Never throws —
|
|
204
|
+
* a missing budget configuration is treated as "feature disabled". The only
|
|
205
|
+
* way this returns an Error is when `performance.report.failOnExceed` is on
|
|
206
|
+
* and a budget has actually been exceeded.
|
|
207
|
+
* @param {string} buildFolder
|
|
208
|
+
* @returns {Promise<Error|null>}
|
|
209
|
+
*/
|
|
210
|
+
async function writePerformanceReport(buildFolder) {
|
|
211
|
+
const perfConfig = global.config.performance;
|
|
212
|
+
if (!perfConfig || !perfConfig.enabled) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let result;
|
|
217
|
+
try {
|
|
218
|
+
result = runPerformance({
|
|
219
|
+
config: global.config,
|
|
220
|
+
html: true,
|
|
221
|
+
buildFolder,
|
|
222
|
+
});
|
|
223
|
+
} catch (error) {
|
|
224
|
+
log("warn", `Performance budget check skipped: ${error.message}`);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const configuredOutput = perfConfig.report?.output;
|
|
229
|
+
if (configuredOutput) {
|
|
230
|
+
// A bare filename (no directory component) lands inside the build folder
|
|
231
|
+
// alongside output.json; anything with a directory is honoured as-is.
|
|
232
|
+
const outputPath =
|
|
233
|
+
path.dirname(configuredOutput) === "."
|
|
234
|
+
? path.join(buildFolder, configuredOutput)
|
|
235
|
+
: path.resolve(configuredOutput);
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await writeFile(
|
|
239
|
+
outputPath,
|
|
240
|
+
generatePerformanceReport(result),
|
|
241
|
+
"utf-8",
|
|
242
|
+
);
|
|
243
|
+
log("info", null, `Wrote ${outputPath}.`);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
log("warn", `Failed to write performance report: ${error.message}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const { exceed, warn, ok, unbudgeted } = result.summary;
|
|
250
|
+
const summaryLine = `Performance budget (${result.compression}): ${ok} ok, ${warn} warn, ${exceed} exceed, ${unbudgeted} unbudgeted.`;
|
|
251
|
+
|
|
252
|
+
if (exceed > 0) {
|
|
253
|
+
log("warn", summaryLine);
|
|
254
|
+
if (perfConfig.report?.failOnExceed) {
|
|
255
|
+
return new Error(
|
|
256
|
+
`Performance budget exceeded in ${exceed} categor${exceed === 1 ? "y" : "ies"}.`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
log("info", summaryLine);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
193
266
|
/**
|
|
194
267
|
* Creates an "output.json" file with the given array as content
|
|
195
268
|
* @param {string} buildFolder
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import getConfig from "../config.js";
|
|
3
|
+
import log from "../logger.js";
|
|
4
|
+
import { EXIT_CODES } from "../errors.js";
|
|
5
|
+
import { runPerformance } from "../performance/index.js";
|
|
6
|
+
import { generatePerformanceReport } from "../performance/report.js";
|
|
7
|
+
import { formatSize } from "../performance/parse-size.js";
|
|
8
|
+
|
|
9
|
+
const VALID_COMPRESSIONS = ["raw", "gzip", "brotli"];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `miyagi budget` — on-demand performance budget check.
|
|
13
|
+
* By default walks the configured global CSS/JS and asset folders, measures
|
|
14
|
+
* them, and compares against `config.performance.budgets`. The HTML category
|
|
15
|
+
* (post-build pages) is opt-in via `--build-folder`.
|
|
16
|
+
* @param {object} args
|
|
17
|
+
* @returns {Promise<object>}
|
|
18
|
+
*/
|
|
19
|
+
export default async function budgetCli(args) {
|
|
20
|
+
const config = await getConfig(args);
|
|
21
|
+
global.config = config;
|
|
22
|
+
|
|
23
|
+
if (args.compression && !VALID_COMPRESSIONS.includes(args.compression)) {
|
|
24
|
+
log(
|
|
25
|
+
"error",
|
|
26
|
+
`Unknown --compression "${args.compression}". Use one of: ${VALID_COMPRESSIONS.join(", ")}`,
|
|
27
|
+
);
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
code: EXIT_CODES.CLI_USAGE_ERROR,
|
|
31
|
+
shouldExit: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (args.compression) {
|
|
36
|
+
config.performance = {
|
|
37
|
+
...(config.performance || {}),
|
|
38
|
+
compression: args.compression,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = runPerformance({
|
|
43
|
+
config,
|
|
44
|
+
html: Boolean(args.buildFolder),
|
|
45
|
+
buildFolder: args.buildFolder,
|
|
46
|
+
listAllPages: args.listAllPages,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (args.json) {
|
|
50
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
51
|
+
} else {
|
|
52
|
+
printTable(result);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (args.output) {
|
|
56
|
+
const report = generatePerformanceReport(result);
|
|
57
|
+
try {
|
|
58
|
+
await writeFile(args.output, report, "utf-8");
|
|
59
|
+
log("info", `Performance report written to ${args.output}`);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
log("error", `Failed to write report: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const exceeded = result.summary.exceed > 0;
|
|
66
|
+
const shouldFail =
|
|
67
|
+
args.fail || config.performance?.report?.failOnExceed;
|
|
68
|
+
|
|
69
|
+
if (exceeded && shouldFail) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
code: EXIT_CODES.VALIDATION_ERROR,
|
|
73
|
+
shouldExit: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (exceeded) {
|
|
78
|
+
log(
|
|
79
|
+
"warn",
|
|
80
|
+
`Performance budget exceeded in ${result.summary.exceed} categor${result.summary.exceed === 1 ? "y" : "ies"}.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
code: EXIT_CODES.SUCCESS,
|
|
87
|
+
shouldExit: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {import("../performance/index.js").PerformanceResult} result
|
|
93
|
+
* @returns {void}
|
|
94
|
+
*/
|
|
95
|
+
function printTable(result) {
|
|
96
|
+
const { evaluations, compression, summary } = result;
|
|
97
|
+
|
|
98
|
+
log("info", `Performance budget — measured at ${compression}.`);
|
|
99
|
+
|
|
100
|
+
const header = ["Category", "Item", "Actual", "Budget", "Status"];
|
|
101
|
+
const rows = evaluations.map((row) => [
|
|
102
|
+
row.category,
|
|
103
|
+
row.key === "total" ? "Total" : shortLabel(row.label),
|
|
104
|
+
formatSize(row.actual),
|
|
105
|
+
formatSize(row.budget),
|
|
106
|
+
statusLabel(row.status),
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
const columnWidths = header.map((heading, index) =>
|
|
110
|
+
Math.max(heading.length, ...rows.map((row) => row[index].length)),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const padCell = (text, width) => text + " ".repeat(width - text.length);
|
|
114
|
+
const renderRow = (columns) =>
|
|
115
|
+
columns.map((cell, index) => padCell(cell, columnWidths[index])).join(" ");
|
|
116
|
+
|
|
117
|
+
process.stdout.write(`${renderRow(header)}\n`);
|
|
118
|
+
process.stdout.write(
|
|
119
|
+
`${columnWidths.map((width) => "-".repeat(width)).join(" ")}\n`,
|
|
120
|
+
);
|
|
121
|
+
for (const row of rows) {
|
|
122
|
+
process.stdout.write(`${renderRow(row)}\n`);
|
|
123
|
+
}
|
|
124
|
+
process.stdout.write(
|
|
125
|
+
`\nOK: ${summary.ok} Warn: ${summary.warn} Exceed: ${summary.exceed} Unbudgeted: ${summary.unbudgeted}\n`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const MAX_LABEL_LENGTH = 60;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {string} label
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
function shortLabel(label) {
|
|
136
|
+
if (label.length <= MAX_LABEL_LENGTH) {
|
|
137
|
+
return label;
|
|
138
|
+
}
|
|
139
|
+
return `…${label.slice(-(MAX_LABEL_LENGTH - 1))}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} status
|
|
144
|
+
* @returns {string}
|
|
145
|
+
*/
|
|
146
|
+
function statusLabel(status) {
|
|
147
|
+
switch (status) {
|
|
148
|
+
case "ok":
|
|
149
|
+
return "OK";
|
|
150
|
+
case "warn":
|
|
151
|
+
return "WARN";
|
|
152
|
+
case "exceed":
|
|
153
|
+
return "EXCEED";
|
|
154
|
+
default:
|
|
155
|
+
return "—";
|
|
156
|
+
}
|
|
157
|
+
}
|
package/lib/cli/index.js
CHANGED
|
@@ -3,9 +3,11 @@ import componentImport from "./component.js";
|
|
|
3
3
|
import drupalAssetsImport from "./drupal-assets.js";
|
|
4
4
|
import doctorImport from "./doctor.js";
|
|
5
5
|
import validateHtmlImport from "./validate-html.js";
|
|
6
|
+
import budgetImport from "./budget.js";
|
|
6
7
|
|
|
7
8
|
export const lint = lintImport;
|
|
8
9
|
export const component = componentImport;
|
|
9
10
|
export const drupalAssets = drupalAssetsImport;
|
|
10
11
|
export const doctor = doctorImport;
|
|
11
12
|
export const validateHtml = validateHtmlImport;
|
|
13
|
+
export const budgetCli = budgetImport;
|
package/lib/cli/run.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
drupalAssets,
|
|
11
11
|
doctor,
|
|
12
12
|
validateHtml as validateHtmlCli,
|
|
13
|
+
budgetCli,
|
|
13
14
|
} from "./index.js";
|
|
14
15
|
import { EXIT_CODES, MiyagiError } from "../errors.js";
|
|
15
16
|
|
|
@@ -235,6 +236,15 @@ async function runDoctorCommand(args) {
|
|
|
235
236
|
return await doctor(args);
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
/**
|
|
240
|
+
* @param {object} args
|
|
241
|
+
* @returns {Promise<object>}
|
|
242
|
+
*/
|
|
243
|
+
async function runBudgetCommand(args) {
|
|
244
|
+
applyCliEnv(args);
|
|
245
|
+
return await budgetCli(args);
|
|
246
|
+
}
|
|
247
|
+
|
|
238
248
|
/**
|
|
239
249
|
* @param {Error|MiyagiError|string} error
|
|
240
250
|
* @param {string[]} argv
|
|
@@ -281,6 +291,7 @@ export async function runCli(argv = process.argv) {
|
|
|
281
291
|
validateHtml: runValidateHtmlCommand,
|
|
282
292
|
drupalAssets: runDrupalAssetsCommand,
|
|
283
293
|
doctor: runDoctorCommand,
|
|
294
|
+
budget: runBudgetCommand,
|
|
284
295
|
},
|
|
285
296
|
argv,
|
|
286
297
|
);
|
package/lib/default-config.js
CHANGED
|
@@ -89,6 +89,39 @@ export default {
|
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
91
|
namespaces: {},
|
|
92
|
+
// Performance budget feature — tracks asset byte size against configured
|
|
93
|
+
// limits and surfaces the state via `miyagi budget`, the build-time report,
|
|
94
|
+
// and the dev-server UI. Defaults mirror the "Slow 4G / Moto G4" tier from
|
|
95
|
+
// web.dev's "Your First Performance Budget" (see docs).
|
|
96
|
+
performance: {
|
|
97
|
+
enabled: true,
|
|
98
|
+
compression: "gzip",
|
|
99
|
+
report: {
|
|
100
|
+
failOnExceed: false,
|
|
101
|
+
output: "performance-report.md",
|
|
102
|
+
},
|
|
103
|
+
budgets: {
|
|
104
|
+
global: {
|
|
105
|
+
css: "35 kB",
|
|
106
|
+
js: "200 kB",
|
|
107
|
+
total: null,
|
|
108
|
+
},
|
|
109
|
+
html: {
|
|
110
|
+
perPage: "30 kB",
|
|
111
|
+
total: null,
|
|
112
|
+
},
|
|
113
|
+
folders: {
|
|
114
|
+
fonts: { total: "30 kB" },
|
|
115
|
+
images: { total: "50 kB" },
|
|
116
|
+
total: null,
|
|
117
|
+
},
|
|
118
|
+
perComponent: {
|
|
119
|
+
css: null,
|
|
120
|
+
js: null,
|
|
121
|
+
total: null,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
92
125
|
projectName: "miyagi",
|
|
93
126
|
ui: {
|
|
94
127
|
mode: "light",
|
package/lib/init/args.js
CHANGED
|
@@ -168,6 +168,45 @@ export default function createCli(handlers, argv = process.argv) {
|
|
|
168
168
|
() => {},
|
|
169
169
|
commandHandler(handlers.doctor),
|
|
170
170
|
)
|
|
171
|
+
.command(
|
|
172
|
+
"budget",
|
|
173
|
+
"Checks asset byte sizes against your performance budget",
|
|
174
|
+
(builder) =>
|
|
175
|
+
builder
|
|
176
|
+
.option("compression", {
|
|
177
|
+
description: "Which compression to compare against the budget",
|
|
178
|
+
type: "string",
|
|
179
|
+
choices: ["raw", "gzip", "brotli"],
|
|
180
|
+
})
|
|
181
|
+
.option("fail", {
|
|
182
|
+
description: "Exit with a non-zero code if any budget is exceeded",
|
|
183
|
+
type: "boolean",
|
|
184
|
+
default: false,
|
|
185
|
+
})
|
|
186
|
+
.option("json", {
|
|
187
|
+
description:
|
|
188
|
+
"Emit the full evaluation as JSON on stdout (for CI / automation)",
|
|
189
|
+
type: "boolean",
|
|
190
|
+
default: false,
|
|
191
|
+
})
|
|
192
|
+
.option("output", {
|
|
193
|
+
alias: "o",
|
|
194
|
+
description: "Also write a markdown report to this path",
|
|
195
|
+
type: "string",
|
|
196
|
+
})
|
|
197
|
+
.option("build-folder", {
|
|
198
|
+
description:
|
|
199
|
+
"Include post-build HTML pages from this folder (reads output.json)",
|
|
200
|
+
type: "string",
|
|
201
|
+
})
|
|
202
|
+
.option("list-all-pages", {
|
|
203
|
+
description:
|
|
204
|
+
"List every HTML page in the evaluation, not just those that exceed or warn",
|
|
205
|
+
type: "boolean",
|
|
206
|
+
default: false,
|
|
207
|
+
}),
|
|
208
|
+
commandHandler(handlers.budget),
|
|
209
|
+
)
|
|
171
210
|
.help()
|
|
172
211
|
.version(pkgJson.version)
|
|
173
212
|
.alias("help", "h")
|