Compare commits

...

4 Commits

Author SHA1 Message Date
Aidan Timson 3bf5833262 Benchmark script 2026-02-11 02:14:56 +00:00
Aidan Timson 8c9da19935 SWC 2026-02-11 02:12:54 +00:00
Aidan Timson 4b1eeb0eb1 Use scrollbar styles on host 2026-02-11 02:12:54 +00:00
Aidan Timson f7ffdabe5d Move scrolling for dashboards inside view container 2026-02-11 02:12:54 +00:00
8 changed files with 133 additions and 31 deletions
+3
View File
@@ -31,4 +31,7 @@ module.exports = {
isDevContainer() { isDevContainer() {
return isTrue(process.env.DEV_CONTAINER); return isTrue(process.env.DEV_CONTAINER);
}, },
jsMinifier() {
return (process.env.JS_MINIFIER || "swc").toLowerCase();
},
}; };
+7 -1
View File
@@ -80,7 +80,13 @@ const doneHandler = (done) => (err, stats) => {
console.log(stats.toString("minimal")); console.log(stats.toString("minimal"));
} }
log(`Build done @ ${new Date().toLocaleTimeString()}`); const durationMs =
stats?.startTime && stats?.endTime ? stats.endTime - stats.startTime : 0;
const durationLabel = durationMs
? ` (${(durationMs / 1000).toFixed(1)}s, minifier: ${env.jsMinifier()})`
: ` (minifier: ${env.jsMinifier()})`;
log(`Build done @ ${new Date().toLocaleTimeString()}${durationLabel}`);
if (done) { if (done) {
done(); done();
+15 -5
View File
@@ -13,6 +13,7 @@ const { WebpackManifestPlugin } = require("rspack-manifest-plugin");
const log = require("fancy-log"); const log = require("fancy-log");
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
const WebpackBar = require("webpackbar/rspack"); const WebpackBar = require("webpackbar/rspack");
const env = require("./env.cjs");
const paths = require("./paths.cjs"); const paths = require("./paths.cjs");
const bundle = require("./bundle.cjs"); const bundle = require("./bundle.cjs");
@@ -100,11 +101,20 @@ const createRspackConfig = ({
}, },
optimization: { optimization: {
minimizer: [ minimizer: [
new TerserPlugin({ env.jsMinifier() === "terser"
parallel: true, ? new TerserPlugin({
extractComments: true, parallel: true,
terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }), extractComments: true,
}), terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }),
})
: new rspack.SwcJsMinimizerRspackPlugin({
extractComments: true,
minimizerOptions: {
ecma: latestBuild ? 2015 : 5,
module: latestBuild,
format: { comments: false },
},
}),
], ],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
+43
View File
@@ -0,0 +1,43 @@
#!/bin/sh
set -e
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
OUT_ROOT="$ROOT_DIR/hass_frontend"
OUT_LATEST="$OUT_ROOT/frontend_latest"
OUT_ES5="$OUT_ROOT/frontend_es5"
bytes_dir() {
if [ -d "$1" ]; then
du -sb "$1" | cut -f1
else
echo 0
fi
}
run_build() {
minifier="$1"
printf "\n==> Building with %s\n" "$minifier"
start_time=$(date +%s)
JS_MINIFIER="$minifier" "$ROOT_DIR/script/build_frontend"
end_time=$(date +%s)
duration=$((end_time - start_time))
latest_size=$(bytes_dir "$OUT_LATEST")
es5_size=$(bytes_dir "$OUT_ES5")
total_size=$(bytes_dir "$OUT_ROOT")
printf "%s|%s|%s|%s\n" "$minifier" "$duration" "$latest_size" "$es5_size" >> "$ROOT_DIR/temp/minifier_benchmark.tsv"
printf " duration: %ss\n" "$duration"
printf " frontend_latest: %s bytes\n" "$latest_size"
printf " frontend_es5: %s bytes\n" "$es5_size"
printf " hass_frontend: %s bytes\n" "$total_size"
}
mkdir -p "$ROOT_DIR/temp"
rm -f "$ROOT_DIR/temp/minifier_benchmark.tsv"
run_build swc
run_build terser
printf "\n==> Summary (minifier | seconds | latest bytes | es5 bytes)\n"
cat "$ROOT_DIR/temp/minifier_benchmark.tsv"
+36 -14
View File
@@ -635,8 +635,12 @@ class HUIRoot extends LitElement {
`; `;
} }
private _handleWindowScroll = () => { private _handleContainerScroll = () => {
this.toggleAttribute("scrolled", window.scrollY !== 0); const viewRoot = this._viewRoot;
this.toggleAttribute(
"scrolled",
viewRoot ? viewRoot.scrollTop !== 0 : false
);
}; };
private _locationChanged = () => { private _locationChanged = () => {
@@ -667,7 +671,7 @@ class HUIRoot extends LitElement {
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
window.addEventListener("scroll", this._handleWindowScroll, { this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true, passive: true,
}); });
this._handleUrlChanged(); this._handleUrlChanged();
@@ -678,7 +682,7 @@ class HUIRoot extends LitElement {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
window.addEventListener("scroll", this._handleWindowScroll, { this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true, passive: true,
}); });
window.addEventListener("popstate", this._handlePopState); window.addEventListener("popstate", this._handlePopState);
@@ -689,10 +693,14 @@ class HUIRoot extends LitElement {
public disconnectedCallback(): void { public disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
window.removeEventListener("scroll", this._handleWindowScroll); this._viewRoot?.removeEventListener("scroll", this._handleContainerScroll);
window.removeEventListener("popstate", this._handlePopState); window.removeEventListener("popstate", this._handlePopState);
window.removeEventListener("location-changed", this._locationChanged); window.removeEventListener("location-changed", this._locationChanged);
this.toggleAttribute("scrolled", window.scrollY !== 0); const viewRoot = this._viewRoot;
this.toggleAttribute(
"scrolled",
viewRoot ? viewRoot.scrollTop !== 0 : false
);
// Re-enable history scroll restoration when leaving the page // Re-enable history scroll restoration when leaving the page
window.history.scrollRestoration = "auto"; window.history.scrollRestoration = "auto";
} }
@@ -825,9 +833,12 @@ class HUIRoot extends LitElement {
(this._restoreScroll && this._viewScrollPositions[newSelectView]) || (this._restoreScroll && this._viewScrollPositions[newSelectView]) ||
0; 0;
this._restoreScroll = false; this._restoreScroll = false;
requestAnimationFrame(() => requestAnimationFrame(() => {
scrollTo({ behavior: "auto", top: position }) const viewRoot = this._viewRoot;
); if (viewRoot) {
viewRoot.scrollTo({ behavior: "auto", top: position });
}
});
} }
this._selectView(newSelectView, force); this._selectView(newSelectView, force);
}); });
@@ -1152,7 +1163,7 @@ class HUIRoot extends LitElement {
const path = this.config.views[viewIndex].path || viewIndex; const path = this.config.views[viewIndex].path || viewIndex;
this._navigateToView(path); this._navigateToView(path);
} else if (!this._editMode) { } else if (!this._editMode) {
scrollTo({ behavior: "smooth", top: 0 }); this._viewRoot?.scrollTo({ behavior: "smooth", top: 0 });
} }
} }
@@ -1163,7 +1174,8 @@ class HUIRoot extends LitElement {
// Save scroll position of current view // Save scroll position of current view
if (this._curView != null) { if (this._curView != null) {
this._viewScrollPositions[this._curView] = window.scrollY; const viewRoot = this._viewRoot;
this._viewScrollPositions[this._curView] = viewRoot?.scrollTop ?? 0;
} }
viewIndex = viewIndex === undefined ? 0 : viewIndex; viewIndex = viewIndex === undefined ? 0 : viewIndex;
@@ -1469,9 +1481,14 @@ class HUIRoot extends LitElement {
hui-view-container { hui-view-container {
position: relative; position: relative;
display: flex; display: flex;
min-height: 100vh; height: calc(
100vh - var(--header-height) - var(--safe-area-inset-top) - var(
--view-container-padding-top,
0px
)
);
box-sizing: border-box; box-sizing: border-box;
padding-top: calc( margin-top: calc(
var(--header-height) + var(--safe-area-inset-top) + var(--header-height) + var(--safe-area-inset-top) +
var(--view-container-padding-top, 0px) var(--view-container-padding-top, 0px)
); );
@@ -1494,7 +1511,12 @@ class HUIRoot extends LitElement {
* In edit mode we have the tab bar on a new line * * In edit mode we have the tab bar on a new line *
*/ */
hui-view-container.has-tab-bar { hui-view-container.has-tab-bar {
padding-top: calc( height: calc(
100vh - var(--header-height, 56px) - calc(
var(--tab-bar-height, 56px) - 2px
) - var(--safe-area-inset-top, 0px)
);
margin-top: calc(
var(--header-height, 56px) + var(--header-height, 56px) +
calc(var(--tab-bar-height, 56px) - 2px) + calc(var(--tab-bar-height, 56px) - 2px) +
var(--safe-area-inset-top, 0px) var(--safe-area-inset-top, 0px)
+11 -3
View File
@@ -407,12 +407,20 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
} }
} }
private _getScrollContainer(): Element | null {
// The scroll container is the hui-view-container parent
return this.closest("hui-view-container");
}
private _toggleView() { private _toggleView() {
const scrollContainer = this._getScrollContainer();
const scrollTop = scrollContainer?.scrollTop ?? 0;
// Save current scroll position // Save current scroll position
if (this._sidebarTabActive) { if (this._sidebarTabActive) {
this._sidebarScrollTop = window.scrollY; this._sidebarScrollTop = scrollTop;
} else { } else {
this._contentScrollTop = window.scrollY; this._contentScrollTop = scrollTop;
} }
this._sidebarTabActive = !this._sidebarTabActive; this._sidebarTabActive = !this._sidebarTabActive;
@@ -428,7 +436,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const scrollY = this._sidebarTabActive const scrollY = this._sidebarTabActive
? this._sidebarScrollTop ? this._sidebarScrollTop
: this._contentScrollTop; : this._contentScrollTop;
window.scrollTo(0, scrollY); scrollContainer?.scrollTo(0, scrollY);
}); });
} }
@@ -4,6 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { listenMediaQuery } from "../../../common/dom/media_query"; import { listenMediaQuery } from "../../../common/dom/media_query";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { haStyleScrollbar } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
type BackgroundConfig = LovelaceViewConfig["background"]; type BackgroundConfig = LovelaceViewConfig["background"];
@@ -22,6 +23,7 @@ class HuiViewContainer extends LitElement {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.classList.add("ha-scrollbar");
this._setUpMediaQuery(); this._setUpMediaQuery();
this._applyTheme(); this._applyTheme();
} }
@@ -74,11 +76,16 @@ class HuiViewContainer extends LitElement {
} }
} }
static styles = css` static styles = [
:host { haStyleScrollbar,
display: relative; css`
} :host {
`; display: block;
height: 100%;
-webkit-overflow-scrolling: touch;
}
`,
];
} }
declare global { declare global {
+6 -3
View File
@@ -224,17 +224,20 @@ export const haStyleDialogFixedTop = css`
`; `;
export const haStyleScrollbar = css` export const haStyleScrollbar = css`
.ha-scrollbar::-webkit-scrollbar { .ha-scrollbar::-webkit-scrollbar,
:host(.ha-scrollbar)::-webkit-scrollbar {
width: 0.4rem; width: 0.4rem;
height: 0.4rem; height: 0.4rem;
} }
.ha-scrollbar::-webkit-scrollbar-thumb { .ha-scrollbar::-webkit-scrollbar-thumb,
:host(.ha-scrollbar)::-webkit-scrollbar-thumb {
border-radius: var(--ha-border-radius-sm); border-radius: var(--ha-border-radius-sm);
background: var(--scrollbar-thumb-color); background: var(--scrollbar-thumb-color);
} }
.ha-scrollbar { .ha-scrollbar,
:host(.ha-scrollbar) {
overflow-y: auto; overflow-y: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent; scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin; scrollbar-width: thin;