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

View File

@@ -31,4 +31,7 @@ module.exports = {
isDevContainer() {
return isTrue(process.env.DEV_CONTAINER);
},
jsMinifier() {
return (process.env.JS_MINIFIER || "swc").toLowerCase();
},
};

View File

@@ -80,7 +80,13 @@ const doneHandler = (done) => (err, stats) => {
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) {
done();

View File

@@ -13,6 +13,7 @@ const { WebpackManifestPlugin } = require("rspack-manifest-plugin");
const log = require("fancy-log");
// eslint-disable-next-line @typescript-eslint/naming-convention
const WebpackBar = require("webpackbar/rspack");
const env = require("./env.cjs");
const paths = require("./paths.cjs");
const bundle = require("./bundle.cjs");
@@ -100,11 +101,20 @@ const createRspackConfig = ({
},
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
extractComments: true,
terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }),
}),
env.jsMinifier() === "terser"
? new TerserPlugin({
parallel: true,
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",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",

43
script/benchmark_minifiers Executable file
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"

View File

@@ -635,8 +635,12 @@ class HUIRoot extends LitElement {
`;
}
private _handleWindowScroll = () => {
this.toggleAttribute("scrolled", window.scrollY !== 0);
private _handleContainerScroll = () => {
const viewRoot = this._viewRoot;
this.toggleAttribute(
"scrolled",
viewRoot ? viewRoot.scrollTop !== 0 : false
);
};
private _locationChanged = () => {
@@ -667,7 +671,7 @@ class HUIRoot extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
window.addEventListener("scroll", this._handleWindowScroll, {
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true,
});
this._handleUrlChanged();
@@ -678,7 +682,7 @@ class HUIRoot extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
window.addEventListener("scroll", this._handleWindowScroll, {
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true,
});
window.addEventListener("popstate", this._handlePopState);
@@ -689,10 +693,14 @@ class HUIRoot extends LitElement {
public disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("scroll", this._handleWindowScroll);
this._viewRoot?.removeEventListener("scroll", this._handleContainerScroll);
window.removeEventListener("popstate", this._handlePopState);
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
window.history.scrollRestoration = "auto";
}
@@ -825,9 +833,12 @@ class HUIRoot extends LitElement {
(this._restoreScroll && this._viewScrollPositions[newSelectView]) ||
0;
this._restoreScroll = false;
requestAnimationFrame(() =>
scrollTo({ behavior: "auto", top: position })
);
requestAnimationFrame(() => {
const viewRoot = this._viewRoot;
if (viewRoot) {
viewRoot.scrollTo({ behavior: "auto", top: position });
}
});
}
this._selectView(newSelectView, force);
});
@@ -1152,7 +1163,7 @@ class HUIRoot extends LitElement {
const path = this.config.views[viewIndex].path || viewIndex;
this._navigateToView(path);
} 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
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;
@@ -1469,9 +1481,14 @@ class HUIRoot extends LitElement {
hui-view-container {
position: relative;
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;
padding-top: calc(
margin-top: calc(
var(--header-height) + var(--safe-area-inset-top) +
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 *
*/
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) +
calc(var(--tab-bar-height, 56px) - 2px) +
var(--safe-area-inset-top, 0px)

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() {
const scrollContainer = this._getScrollContainer();
const scrollTop = scrollContainer?.scrollTop ?? 0;
// Save current scroll position
if (this._sidebarTabActive) {
this._sidebarScrollTop = window.scrollY;
this._sidebarScrollTop = scrollTop;
} else {
this._contentScrollTop = window.scrollY;
this._contentScrollTop = scrollTop;
}
this._sidebarTabActive = !this._sidebarTabActive;
@@ -428,7 +436,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const scrollY = this._sidebarTabActive
? this._sidebarScrollTop
: this._contentScrollTop;
window.scrollTo(0, scrollY);
scrollContainer?.scrollTo(0, scrollY);
});
}

View File

@@ -4,6 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { listenMediaQuery } from "../../../common/dom/media_query";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { haStyleScrollbar } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
type BackgroundConfig = LovelaceViewConfig["background"];
@@ -22,6 +23,7 @@ class HuiViewContainer extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
this.classList.add("ha-scrollbar");
this._setUpMediaQuery();
this._applyTheme();
}
@@ -74,11 +76,16 @@ class HuiViewContainer extends LitElement {
}
}
static styles = css`
:host {
display: relative;
}
`;
static styles = [
haStyleScrollbar,
css`
:host {
display: block;
height: 100%;
-webkit-overflow-scrolling: touch;
}
`,
];
}
declare global {

View File

@@ -224,17 +224,20 @@ export const haStyleDialogFixedTop = css`
`;
export const haStyleScrollbar = css`
.ha-scrollbar::-webkit-scrollbar {
.ha-scrollbar::-webkit-scrollbar,
:host(.ha-scrollbar)::-webkit-scrollbar {
width: 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);
background: var(--scrollbar-thumb-color);
}
.ha-scrollbar {
.ha-scrollbar,
:host(.ha-scrollbar) {
overflow-y: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;