Implement correct types

This commit is contained in:
Wendelin 2025-05-09 13:07:09 +02:00
parent ff3b65605e
commit 8f7760f88f
No known key found for this signature in database
31 changed files with 461 additions and 562 deletions

View File

@ -1,34 +1,33 @@
import path from "path";
import { dependencies } from "../package.json";
import env from "./env";
import paths from "./paths";
import path from "node:path";
import packageJson from "../package.json" assert { type: "json" };
import paths, { dirname } from "./paths.ts";
import { version } from "./env.ts";
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");
const dependencies = packageJson.dependencies;
const BABEL_PLUGINS = path.join(dirname, "babel-plugins");
// GitHub base URL to use for production source maps
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
export const sourceMapURL = () => {
const ref = env.version().endsWith("dev")
const ref = version().endsWith("dev")
? process.env.GITHUB_SHA || "dev"
: env.version();
: version();
return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}/`;
};
// Files from NPM Packages that should not be imported
export const ignorePackages = () => [];
// Files from NPM packages that we should replace with empty file
export const emptyPackages = ({ isHassioBuild }) =>
[
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
import.meta.resolve("@vaadin/vaadin-material-styles/typography.js"),
import.meta.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
// Icons in supervisor conflict with icons in HA so we don't load.
isHassioBuild &&
require.resolve(
import.meta.resolve(
path.resolve(paths.root_dir, "src/components/ha-icon.ts")
),
isHassioBuild &&
require.resolve(
import.meta.resolve(
path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts")
),
].filter(Boolean);
@ -36,7 +35,7 @@ export const emptyPackages = ({ isHassioBuild }) =>
export const definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
__VERSION__: JSON.stringify(env.version()),
__VERSION__: JSON.stringify(version()),
__DEMO__: false,
__SUPERVISOR__: false,
__BACKWARDS_COMPAT__: false,
@ -67,7 +66,7 @@ export const htmlMinifierOptions = {
export const terserOptions = ({ latestBuild, isTestBuild }) => ({
safari10: !latestBuild,
ecma: latestBuild ? 2015 : 5,
ecma: latestBuild ? (2015 as const) : (5 as const),
module: latestBuild,
format: { comments: false },
sourceMap: !isTestBuild,
@ -91,6 +90,11 @@ export const babelOptions = ({
isProdBuild,
isTestBuild,
sw,
}: {
latestBuild?: boolean;
isProdBuild?: boolean;
isTestBuild?: boolean;
sw?: boolean;
}) => ({
babelrc: false,
compact: false,
@ -137,7 +141,7 @@ export const babelOptions = ({
"@polymer/polymer/lib/utils/html-tag.js": ["html"],
},
strictCSS: true,
htmlMinifier: module.exports.htmlMinifierOptions,
htmlMinifier: htmlMinifierOptions,
failOnError: false, // we can turn this off in case of false positives
},
],
@ -222,7 +226,19 @@ const publicPath = (latestBuild, root = "") =>
*/
export const config = {
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
app({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isWDS,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
isWDS?: boolean;
}) {
return {
name: "frontend" + nameSuffix(latestBuild),
entry: {
@ -257,7 +273,7 @@ export const config = {
outputPath: outputPath(paths.demo_output_root, latestBuild),
publicPath: publicPath(latestBuild),
defineOverlay: {
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
__VERSION__: JSON.stringify(`DEMO-${version()}`),
__DEMO__: true,
},
isProdBuild,
@ -267,13 +283,12 @@ export const config = {
},
cast({ isProdBuild, latestBuild }) {
const entry = {
const entry: Record<string, string> = {
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
};
if (latestBuild) {
// @ts-ignore
entry.receiver = path.resolve(
paths.cast_dir,
"src/receiver/entrypoint.ts"

View File

@ -1,34 +1,21 @@
import fs from "fs";
import path from "path";
import paths from "./paths";
import fs from "node:fs";
import path from "node:path";
import paths from "./paths.ts";
const isTrue = (value) => value === "1" || value?.toLowerCase() === "true";
export default {
isProdBuild() {
return (
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
);
},
isStatsBuild() {
return isTrue(process.env.STATS);
},
isTestBuild() {
return isTrue(process.env.IS_TEST);
},
isNetlify() {
return isTrue(process.env.NETLIFY);
},
version() {
const version = fs
.readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
if (!version) {
throw Error("Version not found");
}
return version[1];
},
isDevContainer() {
return isTrue(process.env.DEV_CONTAINER);
},
export const isProdBuild = () =>
process.env.NODE_ENV === "production" || isStatsBuild();
export const isStatsBuild = () => isTrue(process.env.STATS);
export const isTestBuild = () => isTrue(process.env.IS_TEST);
export const isNetlify = () => isTrue(process.env.NETLIFY);
export const version = () => {
const pyProjectVersion = fs
.readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
if (!pyProjectVersion) {
throw Error("Version not found");
}
return pyProjectVersion[1];
};
export const isDevContainer = () => isTrue(process.env.DEV_CONTAINER);

View File

@ -1,8 +1,7 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import gulp from "gulp";
import { parallel, series, task } from "gulp";
import { isStatsBuild, isTestBuild } from "../env.ts";
import "./clean.ts";
import "./compress.ts";
import env from "../env";
import "./entry-html.ts";
import "./gather-static.ts";
import "./gen-icons-json.ts";
@ -11,14 +10,14 @@ import "./rspack.ts";
import "./service-worker.ts";
import "./translations.ts";
gulp.task(
task(
"develop-app",
gulp.series(
series(
async () => {
process.env.NODE_ENV = "development";
},
"clean",
gulp.parallel(
parallel(
"gen-service-worker-app-dev",
"gen-icons-json",
"gen-pages-app-dev",
@ -30,25 +29,25 @@ gulp.task(
)
);
gulp.task(
task(
"build-app",
gulp.series(
series(
async () => {
process.env.NODE_ENV = "production";
},
"clean",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-app",
"rspack-prod-app",
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod"),
parallel("gen-pages-app-prod", "gen-service-worker-app-prod"),
// Don't compress running tests
...(env.isTestBuild() || env.isStatsBuild() ? [] : ["compress-app"])
...(isTestBuild() || isStatsBuild() ? [] : ["compress-app"])
)
);
gulp.task(
task(
"analyze-app",
gulp.series(
series(
async () => {
process.env.STATS = "1";
},

View File

@ -1,5 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
import gulp from "gulp";
import { parallel, series, task } from "gulp";
import "./clean.ts";
import "./entry-html.ts";
import "./gather-static.ts";
@ -7,30 +6,30 @@ import "./rspack.ts";
import "./service-worker.ts";
import "./translations.ts";
gulp.task(
task(
"develop-cast",
gulp.series(
series(
async () => {
process.env.NODE_ENV = "development";
},
"clean-cast",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast",
"gen-pages-cast-dev",
"rspack-dev-server-cast"
)
);
gulp.task(
task(
"build-cast",
gulp.series(
series(
async () => {
process.env.NODE_ENV = "production";
},
"clean-cast",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast",
"rspack-prod-cast",
"gen-pages-cast-prod"

View File

@ -1,36 +1,36 @@
import { deleteSync } from "del";
import gulp from "gulp";
import paths from "../paths";
import "./translations.js";
import { parallel, task } from "gulp";
import paths from "../paths.ts";
import "./translations.ts";
gulp.task(
task(
"clean",
gulp.parallel("clean-translations", async () =>
parallel("clean-translations", async () =>
deleteSync([paths.app_output_root, paths.build_dir])
)
);
gulp.task(
task(
"clean-demo",
gulp.parallel("clean-translations", async () =>
parallel("clean-translations", async () =>
deleteSync([paths.demo_output_root, paths.build_dir])
)
);
gulp.task(
task(
"clean-cast",
gulp.parallel("clean-translations", async () =>
parallel("clean-translations", async () =>
deleteSync([paths.cast_output_root, paths.build_dir])
)
);
gulp.task("clean-hassio", async () =>
task("clean-hassio", async () =>
deleteSync([paths.hassio_output_root, paths.build_dir])
);
gulp.task(
task(
"clean-gallery",
gulp.parallel("clean-translations", async () =>
parallel("clean-translations", async () =>
deleteSync([
paths.gallery_output_root,
paths.gallery_build,
@ -39,9 +39,9 @@ gulp.task(
)
);
gulp.task(
task(
"clean-landing-page",
gulp.parallel("clean-translations", async () =>
parallel("clean-translations", async () =>
deleteSync([
paths.landingPage_output_root,
paths.landingPage_build,

View File

@ -1,10 +1,10 @@
// Tasks to compress
import gulp from "gulp";
import { dest, parallel, src, task } from "gulp";
import brotli from "gulp-brotli";
import zopfli from "gulp-zopfli-green";
import { constants } from "node:zlib";
import paths from "../paths";
import paths from "../paths.ts";
const filesGlob = "*.{js,json,css,svg,xml}";
const brotliOptions = {
@ -16,27 +16,25 @@ const brotliOptions = {
const zopfliOptions = { threshold: 150 };
const compressModern = (rootDir, modernDir, compress) =>
gulp
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
base: rootDir,
allowEmpty: true,
})
src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
base: rootDir,
allowEmpty: true,
})
.pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions))
.pipe(gulp.dest(rootDir));
.pipe(dest(rootDir));
const compressOther = (rootDir, modernDir, compress) =>
gulp
.src(
[
`${rootDir}/**/${filesGlob}`,
`!${modernDir}/**/${filesGlob}`,
`!${rootDir}/{sw-modern,service_worker}.js`,
`${rootDir}/{authorize,onboarding}.html`,
],
{ base: rootDir, allowEmpty: true }
)
src(
[
`${rootDir}/**/${filesGlob}`,
`!${modernDir}/**/${filesGlob}`,
`!${rootDir}/{sw-modern,service_worker}.js`,
`${rootDir}/{authorize,onboarding}.html`,
],
{ base: rootDir, allowEmpty: true }
)
.pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions))
.pipe(gulp.dest(rootDir));
.pipe(dest(rootDir));
const compressAppModernBrotli = () =>
compressModern(paths.app_output_root, paths.app_output_latest, "brotli");
@ -66,18 +64,18 @@ const compressHassioOtherBrotli = () =>
const compressHassioOtherZopfli = () =>
compressOther(paths.hassio_output_root, paths.hassio_output_latest, "zopfli");
gulp.task(
task(
"compress-app",
gulp.parallel(
parallel(
compressAppModernBrotli,
compressAppOtherBrotli,
compressAppModernZopfli,
compressAppOtherZopfli
)
);
gulp.task(
task(
"compress-hassio",
gulp.parallel(
parallel(
compressHassioModernBrotli,
compressHassioOtherBrotli,
compressHassioModernZopfli,

View File

@ -1,21 +1,21 @@
import gulp from "gulp";
import "./clean.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./service-worker.js";
import "./translations.js";
import "./rspack.js";
import { parallel, series, task } from "gulp";
import "./clean.ts";
import "./entry-html.ts";
import "./gather-static.ts";
import "./gen-icons-json.ts";
import "./rspack.ts";
import "./service-worker.ts";
import "./translations.ts";
gulp.task(
task(
"develop-demo",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "development";
},
"clean-demo",
"translations-enable-merge-backend",
gulp.parallel(
parallel(
"gen-icons-json",
"gen-pages-demo-dev",
"build-translations",
@ -26,25 +26,25 @@ gulp.task(
)
);
gulp.task(
task(
"build-demo",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "production";
},
"clean-demo",
// Cast needs to be backwards compatible and older HA has no translations
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-demo",
"rspack-prod-demo",
"gen-pages-demo-prod"
)
);
gulp.task(
task(
"analyze-demo",
gulp.series(
series(
async function setEnv() {
process.env.STATS = "1";
},

View File

@ -1,10 +1,10 @@
import { LokaliseApi } from "@lokalise/node-api";
import fs from "fs/promises";
import gulp from "gulp";
import fs from "node:fs/promises";
import { dest, series, src, task } from "gulp";
import transform from "gulp-json-transform";
import JSZip from "jszip";
import mapStream from "map-stream";
import path from "path";
import path from "node:path";
const inDir = "translations";
const inDirFrontend = `${inDir}/frontend`;
@ -64,20 +64,19 @@ function convertBackendTranslations(data, _file) {
return output;
}
gulp.task("convert-backend-translations", function () {
return gulp
.src([`${inDirBackend}/*.json`])
task("convert-backend-translations", function () {
return src([`${inDirBackend}/*.json`])
.pipe(transform((data, file) => convertBackendTranslations(data, file)))
.pipe(gulp.dest(inDirBackend));
.pipe(dest(inDirBackend));
});
gulp.task("check-translations-html", function () {
return gulp
.src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`])
.pipe(checkHtml());
task("check-translations-html", function () {
return src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`]).pipe(
checkHtml()
);
});
gulp.task("check-all-files-exist", async function () {
task("check-all-files-exist", async function () {
const file = await fs.readFile(srcMeta, { encoding });
const meta = JSON.parse(file);
const writings: Promise<void>[] = [];
@ -99,7 +98,7 @@ const lokaliseProjects = {
frontend: "3420425759f6d6d241f598.13594006",
};
gulp.task("fetch-lokalise", async function () {
task("fetch-lokalise", async function () {
let apiKey;
try {
apiKey =
@ -170,9 +169,9 @@ gulp.task("fetch-lokalise", async function () {
);
});
gulp.task(
task(
"download-translations",
gulp.series(
series(
"fetch-lokalise",
"convert-backend-translations",
"check-translations-html",

View File

@ -6,12 +6,12 @@ import {
getPreUserAgentRegexes,
} from "browserslist-useragent-regexp";
import fs from "fs-extra";
import gulp from "gulp";
import { task } from "gulp";
import { minify } from "html-minifier-terser";
import template from "lodash.template";
import { dirname, extname, resolve } from "node:path";
import { htmlMinifierOptions, terserOptions } from "../bundle";
import paths from "../paths";
import { htmlMinifierOptions, terserOptions } from "../bundle.ts";
import paths from "../paths.ts";
// macOS companion app has no way to obtain the Safari version used by WKWebView,
// and it is not in the default user agent string. So we add an additional regex
@ -34,9 +34,9 @@ const getCommonTemplateVars = () => {
mobileToDesktop: true,
throwOnMissing: true,
});
const minSafariVersion = browserRegexes.find(
(regex) => regex.family === "safari"
)?.matchedVersions[0][0] ?? 18;
const minSafariVersion =
browserRegexes.find((regex) => regex.family === "safari")
?.matchedVersions[0][0] ?? 18;
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
if (!minMacOSVersion) {
throw Error(
@ -145,8 +145,12 @@ const genPagesProdTask =
resolve(inputRoot, inputSub, `${page}.template`),
{
...commonVars,
latestEntryJS: (entries as string[]).map((entry) => latestManifest[`${entry}.js`]),
es5EntryJS: (entries as string[]).map((entry) => es5Manifest[`${entry}.js`]),
latestEntryJS: (entries as string[]).map(
(entry) => latestManifest[`${entry}.js`]
),
es5EntryJS: (entries as string[]).map(
(entry) => es5Manifest[`${entry}.js`]
),
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
}
@ -167,12 +171,12 @@ const APP_PAGE_ENTRIES = {
"index.html": ["core", "app"],
};
gulp.task(
task(
"gen-pages-app-dev",
genPagesDevTask(APP_PAGE_ENTRIES, paths.root_dir, paths.app_output_root)
);
gulp.task(
task(
"gen-pages-app-prod",
genPagesProdTask(
APP_PAGE_ENTRIES,
@ -190,12 +194,12 @@ const CAST_PAGE_ENTRIES = {
"receiver.html": ["receiver"],
};
gulp.task(
task(
"gen-pages-cast-dev",
genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root)
);
gulp.task(
task(
"gen-pages-cast-prod",
genPagesProdTask(
CAST_PAGE_ENTRIES,
@ -208,12 +212,12 @@ gulp.task(
const DEMO_PAGE_ENTRIES = { "index.html": ["main"] };
gulp.task(
task(
"gen-pages-demo-dev",
genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root)
);
gulp.task(
task(
"gen-pages-demo-prod",
genPagesProdTask(
DEMO_PAGE_ENTRIES,
@ -226,7 +230,7 @@ gulp.task(
const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
gulp.task(
task(
"gen-pages-gallery-dev",
genPagesDevTask(
GALLERY_PAGE_ENTRIES,
@ -235,7 +239,7 @@ gulp.task(
)
);
gulp.task(
task(
"gen-pages-gallery-prod",
genPagesProdTask(
GALLERY_PAGE_ENTRIES,
@ -247,7 +251,7 @@ gulp.task(
const LANDING_PAGE_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
gulp.task(
task(
"gen-pages-landing-page-dev",
genPagesDevTask(
LANDING_PAGE_PAGE_ENTRIES,
@ -256,7 +260,7 @@ gulp.task(
)
);
gulp.task(
task(
"gen-pages-landing-page-prod",
genPagesProdTask(
LANDING_PAGE_PAGE_ENTRIES,
@ -269,7 +273,7 @@ gulp.task(
const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] };
gulp.task(
task(
"gen-pages-hassio-dev",
genPagesDevTask(
HASSIO_PAGE_ENTRIES,
@ -280,7 +284,7 @@ gulp.task(
)
);
gulp.task(
task(
"gen-pages-hassio-prod",
genPagesProdTask(
HASSIO_PAGE_ENTRIES,

View File

@ -1,14 +1,14 @@
// Task to download the latest Lokalise translations from the nightly workflow artifacts
// Task to download the latest 00Lokalise translations from the nightly workflow artifacts
import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device";
import { retry } from "@octokit/plugin-retry";
import { Octokit } from "@octokit/rest";
import { deleteAsync } from "del";
import { mkdir, readFile, writeFile } from "fs/promises";
import gulp from "gulp";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { series, task } from "gulp";
import jszip from "jszip";
import path from "path";
import process from "process";
import path from "node:path";
import process from "node:process";
import { extract } from "tar";
const MAX_AGE = 24; // hours
@ -22,12 +22,12 @@ const TOKEN_FILE = path.posix.join(EXTRACT_DIR, "token.json");
const ARTIFACT_FILE = path.posix.join(EXTRACT_DIR, "artifact.json");
let allowTokenSetup = false;
gulp.task("allow-setup-fetch-nightly-translations", (done) => {
task("allow-setup-fetch-nightly-translations", (done) => {
allowTokenSetup = true;
done();
});
gulp.task("fetch-nightly-translations", async function () {
task("fetch-nightly-translations", async function () {
// Skip all when environment flag is set (assumes translations are already in place)
if (process.env?.SKIP_FETCH_NIGHTLY_TRANSLATIONS) {
console.log("Skipping fetch due to environment signal");
@ -148,7 +148,7 @@ gulp.task("fetch-nightly-translations", async function () {
artifact_id: latestArtifact.id,
archive_format: "zip",
});
// @ts-ignore
// @ts-ignore OctokitResponse<unknown, 302> doesn't allow to check for 200
if (downloadResponse.status !== 200) {
throw Error("Failure downloading translations artifact");
}
@ -163,10 +163,7 @@ gulp.task("fetch-nightly-translations", async function () {
});
});
gulp.task(
task(
"setup-and-fetch-nightly-translations",
gulp.series(
"allow-setup-fetch-nightly-translations",
"fetch-nightly-translations"
)
series("allow-setup-fetch-nightly-translations", "fetch-nightly-translations")
);

View File

@ -1,19 +1,19 @@
import fs from "fs";
import fs from "node:fs";
import { glob } from "glob";
import gulp from "gulp";
import { parallel, series, task, watch } from "gulp";
import yaml from "js-yaml";
import { marked } from "marked";
import path from "path";
import paths from "../paths";
import "./clean.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./rspack";
import "./service-worker.js";
import "./translations.js";
import path from "node:path";
import paths from "../paths.ts";
import "./clean.ts";
import "./entry-html.ts";
import "./gather-static.ts";
import "./gen-icons-json.ts";
import "./rspack.ts";
import "./service-worker.ts";
import "./translations.ts";
gulp.task("gather-gallery-pages", async function gatherPages() {
task("gather-gallery-pages", async function gatherPages() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
const files = await glob(path.resolve(pageDir, "**/*"));
@ -99,7 +99,10 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
pagesToProcess[category].add(page);
}
for (const group of Object.values(sidebar) as Array<{ category: string; pages?: string[] }>) {
for (const group of Object.values(sidebar) as {
category: string;
pages?: string[];
}[]) {
const toProcess = pagesToProcess[group.category];
delete pagesToProcess[group.category];
@ -143,15 +146,15 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
);
});
gulp.task(
task(
"develop-gallery",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "development";
},
"clean-gallery",
"translations-enable-merge-backend",
gulp.parallel(
parallel(
"gen-icons-json",
"build-translations",
"build-locale-data",
@ -159,30 +162,27 @@ gulp.task(
),
"copy-static-gallery",
"gen-pages-gallery-dev",
gulp.parallel(
"rspack-dev-server-gallery",
async function watchMarkdownFiles() {
gulp.watch(
[
path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"),
path.resolve(paths.gallery_dir, "sidebar.js"),
],
gulp.series("gather-gallery-pages")
);
}
)
parallel("rspack-dev-server-gallery", async function watchMarkdownFiles() {
watch(
[
path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"),
path.resolve(paths.gallery_dir, "sidebar.js"),
],
series("gather-gallery-pages")
);
})
)
);
gulp.task(
task(
"build-gallery",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "production";
},
"clean-gallery",
"translations-enable-merge-backend",
gulp.parallel(
parallel(
"gen-icons-json",
"build-translations",
"build-locale-data",

View File

@ -1,9 +1,9 @@
// Gulp task to gather all static files.
import fs from "fs-extra";
import gulp from "gulp";
import path from "path";
import paths from "../paths";
import { task } from "gulp";
import path from "node:path";
import paths from "../paths.ts";
const npmPath = (...parts) =>
path.resolve(paths.root_dir, "node_modules", ...parts);
@ -113,33 +113,33 @@ function copyZXingWasm(staticDir) {
);
}
gulp.task("copy-locale-data", async () => {
task("copy-locale-data", async () => {
const staticDir = paths.app_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-translations-app", async () => {
task("copy-translations-app", async () => {
const staticDir = paths.app_output_static;
copyTranslations(staticDir);
});
gulp.task("copy-translations-supervisor", async () => {
task("copy-translations-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyTranslations(staticDir);
});
gulp.task("copy-translations-landing-page", async () => {
task("copy-translations-landing-page", async () => {
const staticDir = paths.landingPage_output_static;
copyTranslations(staticDir);
});
gulp.task("copy-static-supervisor", async () => {
task("copy-static-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyLocaleData(staticDir);
copyFonts(staticDir);
});
gulp.task("copy-static-app", async () => {
task("copy-static-app", async () => {
const staticDir = paths.app_output_static;
// Basic static files
fs.copySync(polyPath("public"), paths.app_output_root);
@ -157,7 +157,7 @@ gulp.task("copy-static-app", async () => {
copyQrScannerWorker(staticDir);
});
gulp.task("copy-static-demo", async () => {
task("copy-static-demo", async () => {
// Copy app static files
fs.copySync(
polyPath("public/static"),
@ -173,7 +173,7 @@ gulp.task("copy-static-demo", async () => {
copyMdiIcons(paths.demo_output_static);
});
gulp.task("copy-static-cast", async () => {
task("copy-static-cast", async () => {
// Copy app static files
fs.copySync(polyPath("public/static"), paths.cast_output_static);
// Copy cast static files
@ -186,7 +186,7 @@ gulp.task("copy-static-cast", async () => {
copyMdiIcons(paths.cast_output_static);
});
gulp.task("copy-static-gallery", async () => {
task("copy-static-gallery", async () => {
// Copy app static files
fs.copySync(polyPath("public/static"), paths.gallery_output_static);
// Copy gallery static files
@ -202,7 +202,7 @@ gulp.task("copy-static-gallery", async () => {
copyMdiIcons(paths.gallery_output_static);
});
gulp.task("copy-static-landing-page", async () => {
task("copy-static-landing-page", async () => {
// Copy landing-page static files
fs.copySync(
path.resolve(paths.landingPage_dir, "public"),

View File

@ -1,8 +1,8 @@
import fs from "fs";
import gulp from "gulp";
import fs from "node:fs";
import { task } from "gulp";
import hash from "object-hash";
import path from "path";
import paths from "../paths";
import path from "node:path";
import paths from "../paths.ts";
const ICON_PACKAGE_PATH = path.resolve("node_modules/@mdi/svg/");
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
@ -97,7 +97,7 @@ const findDifferentiator = (curString, prevString) => {
throw new Error(`Cannot find differentiator; ${curString}; ${prevString}`);
};
gulp.task("gen-icons-json", (done) => {
task("gen-icons-json", (done) => {
const meta = getMeta();
const metaAndRemoved = addRemovedMeta(meta);
@ -155,7 +155,7 @@ gulp.task("gen-icons-json", (done) => {
done();
});
gulp.task("gen-dummy-icons-json", (done) => {
task("gen-dummy-icons-json", (done) => {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}

View File

@ -1,16 +1,16 @@
import gulp from "gulp";
import env from "../env";
import "./clean.js";
import "./compress.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./rspack.js";
import "./translations.js";
import { series, task } from "gulp";
import { isTestBuild } from "../env.ts";
import "./clean.ts";
import "./compress.ts";
import "./entry-html.ts";
import "./gather-static.ts";
import "./gen-icons-json.ts";
import "./rspack.ts";
import "./translations.ts";
gulp.task(
task(
"develop-hassio",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "development";
},
@ -25,9 +25,9 @@ gulp.task(
)
);
gulp.task(
task(
"build-hassio",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "production";
},
@ -40,6 +40,6 @@ gulp.task(
"rspack-prod-hassio",
"gen-pages-hassio-prod",
...// Don't compress running tests
(env.isTestBuild() ? [] : ["compress-hassio"])
(isTestBuild() ? [] : ["compress-hassio"])
)
);

View File

@ -1,15 +1,15 @@
import gulp from "gulp";
import "./clean.js";
import "./compress.js";
import "./entry-html.js";
import "./gather-static.js";
import "./gen-icons-json.js";
import "./rspack.js";
import "./translations.js";
import { series, task } from "gulp";
import "./clean.ts";
import "./compress.ts";
import "./entry-html.ts";
import "./gather-static.ts";
import "./gen-icons-json.ts";
import "./rspack.ts";
import "./translations.ts";
gulp.task(
task(
"develop-landing-page",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "development";
},
@ -24,9 +24,9 @@ gulp.task(
)
);
gulp.task(
task(
"build-landing-page",
gulp.series(
series(
async function setEnv() {
process.env.NODE_ENV = "production";
},

View File

@ -1,8 +1,8 @@
import { deleteSync } from "del";
import { mkdir, readFile, writeFile } from "fs/promises";
import gulp from "gulp";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { series, task } from "gulp";
import { join, resolve } from "node:path";
import paths from "../paths";
import paths from "../paths.ts";
const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs");
const outDir = join(paths.build_dir, "locale-data");
@ -54,9 +54,9 @@ const convertToJSON = async (
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
};
gulp.task("clean-locale-data", async () => deleteSync([outDir]));
task("clean-locale-data", async () => deleteSync([outDir]));
gulp.task("create-locale-data", async () => {
task("create-locale-data", async () => {
const translationMeta = JSON.parse(
await readFile(
resolve(paths.translations_src, "translationMetadata.json"),
@ -83,7 +83,4 @@ gulp.task("create-locale-data", async () => {
await Promise.all(conversions);
});
gulp.task(
"build-locale-data",
gulp.series("clean-locale-data", "create-locale-data")
);
task("build-locale-data", series("clean-locale-data", "create-locale-data"));

View File

@ -3,11 +3,10 @@
import rspack from "@rspack/core";
import { RspackDevServer } from "@rspack/dev-server";
import log from "fancy-log";
import fs from "fs";
import gulp from "gulp";
import path from "path";
import env from "../env";
import paths from "../paths";
import fs from "node:fs";
import { task, watch, series } from "gulp";
import path from "node:path";
import paths from "../paths.ts";
import {
createAppConfig,
createCastConfig,
@ -15,7 +14,8 @@ import {
createGalleryConfig,
createHassioConfig,
createLandingPageConfig,
} from "../rspack";
} from "../rspack.ts";
import { isDevContainer, isStatsBuild, isTestBuild } from "../env.ts";
const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }),
@ -34,7 +34,7 @@ interface RunDevServer {
contentBase: string;
port: number;
listenHost?: string;
proxy: any;
proxy?: any;
}
/**
@ -49,12 +49,12 @@ const runDevServer = async ({
compiler,
contentBase,
port,
listenHost = undefined,
proxy = undefined,
listenHost,
proxy,
}: RunDevServer) => {
if (listenHost === undefined) {
// For dev container, we need to listen on all hosts
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
listenHost = isDevContainer() ? "0.0.0.0" : "localhost";
}
const server = new RspackDevServer(
{
@ -105,30 +105,30 @@ const prodBuild = (conf) =>
);
});
gulp.task("rspack-watch-app", () => {
task("rspack-watch-app", () => {
// This command will run forever because we don't close compiler
rspack(
process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler());
gulp.watch(
watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-translations", "copy-translations-app")
series("build-translations", "copy-translations-app")
);
});
gulp.task("rspack-prod-app", () =>
task("rspack-prod-app", () =>
prodBuild(
bothBuilds(createAppConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
isStatsBuild: isStatsBuild(),
isTestBuild: isTestBuild(),
})
)
);
gulp.task("rspack-dev-server-demo", () =>
task("rspack-dev-server-demo", () =>
runDevServer({
compiler: rspack(
createDemoConfig({ isProdBuild: false, latestBuild: true })
@ -138,16 +138,16 @@ gulp.task("rspack-dev-server-demo", () =>
})
);
gulp.task("rspack-prod-demo", () =>
task("rspack-prod-demo", () =>
prodBuild(
bothBuilds(createDemoConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isStatsBuild: isStatsBuild(),
})
)
);
gulp.task("rspack-dev-server-cast", () =>
task("rspack-dev-server-cast", () =>
runDevServer({
compiler: rspack(
createCastConfig({ isProdBuild: false, latestBuild: true })
@ -159,7 +159,7 @@ gulp.task("rspack-dev-server-cast", () =>
})
);
gulp.task("rspack-prod-cast", () =>
task("rspack-prod-cast", () =>
prodBuild(
bothBuilds(createCastConfig, {
isProdBuild: true,
@ -167,7 +167,7 @@ gulp.task("rspack-prod-cast", () =>
)
);
gulp.task("rspack-watch-hassio", () => {
task("rspack-watch-hassio", () => {
// This command will run forever because we don't close compiler
rspack(
createHassioConfig({
@ -176,23 +176,23 @@ gulp.task("rspack-watch-hassio", () => {
})
).watch({ ignored: /build/, poll: isWsl }, doneHandler());
gulp.watch(
watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-supervisor-translations", "copy-translations-supervisor")
series("build-supervisor-translations", "copy-translations-supervisor")
);
});
gulp.task("rspack-prod-hassio", () =>
task("rspack-prod-hassio", () =>
prodBuild(
bothBuilds(createHassioConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
isStatsBuild: isStatsBuild(),
isTestBuild: isTestBuild(),
})
)
);
gulp.task("rspack-dev-server-gallery", () =>
task("rspack-dev-server-gallery", () =>
runDevServer({
compiler: rspack(
createGalleryConfig({ isProdBuild: false, latestBuild: true })
@ -203,7 +203,7 @@ gulp.task("rspack-dev-server-gallery", () =>
})
);
gulp.task("rspack-prod-gallery", () =>
task("rspack-prod-gallery", () =>
prodBuild(
createGalleryConfig({
isProdBuild: true,
@ -212,7 +212,7 @@ gulp.task("rspack-prod-gallery", () =>
)
);
gulp.task("rspack-watch-landing-page", () => {
task("rspack-watch-landing-page", () => {
// This command will run forever because we don't close compiler
rspack(
process.env.ES5
@ -220,21 +220,18 @@ gulp.task("rspack-watch-landing-page", () => {
: createLandingPageConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler());
gulp.watch(
watch(
path.join(paths.translations_src, "en.json"),
gulp.series(
"build-landing-page-translations",
"copy-translations-landing-page"
)
series("build-landing-page-translations", "copy-translations-landing-page")
);
});
gulp.task("rspack-prod-landing-page", () =>
task("rspack-prod-landing-page", () =>
prodBuild(
bothBuilds(createLandingPageConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
isStatsBuild: isStatsBuild(),
isTestBuild: isTestBuild(),
})
)
);

View File

@ -1,11 +1,11 @@
// Generate service workers
import { deleteAsync } from "del";
import gulp from "gulp";
import { task } from "gulp";
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import { basename, join, relative } from "node:path";
import { injectManifest } from "workbox-build";
import paths from "../paths";
import paths from "../paths.ts";
const SW_MAP = {
[paths.app_output_latest]: "modern",
@ -23,7 +23,7 @@ self.addEventListener('install', (event) => {
});
`.trim() + "\n";
gulp.task("gen-service-worker-app-dev", async () => {
task("gen-service-worker-app-dev", async () => {
await mkdir(paths.app_output_root, { recursive: true });
await Promise.all(
Object.values(SW_MAP).map((build) =>
@ -34,7 +34,7 @@ gulp.task("gen-service-worker-app-dev", async () => {
);
});
gulp.task("gen-service-worker-app-prod", () =>
task("gen-service-worker-app-prod", () =>
Promise.all(
Object.entries(SW_MAP).map(async ([outPath, build]) => {
const manifest = JSON.parse(

View File

@ -1,9 +1,8 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable max-classes-per-file */
import { deleteAsync } from "del";
import { glob } from "glob";
import gulp from "gulp";
import { src as glupSrc, dest as gulpDest, parallel, series, task } from "gulp";
import rename from "gulp-rename";
import merge from "lodash.merge";
import { createHash } from "node:crypto";
@ -11,8 +10,8 @@ import { mkdir, readFile } from "node:fs/promises";
import { basename, join } from "node:path";
import { PassThrough, Transform } from "node:stream";
import { finished } from "node:stream/promises";
import env from "../env";
import paths from "../paths";
import { isProdBuild } from "../env.ts";
import paths from "../paths.ts";
import "./fetch-nightly-translations.ts";
const inFrontendDir = "translations/frontend";
@ -24,9 +23,9 @@ const TEST_LOCALE = "en-x-test";
let mergeBackend = false;
gulp.task(
task(
"translations-enable-merge-backend",
gulp.parallel(async () => {
parallel(async () => {
mergeBackend = true;
}, "allow-setup-fetch-nightly-translations")
);
@ -35,7 +34,11 @@ gulp.task(
// The provided function can either return a new object, or an array of
// [object, subdirectory] pairs for fragmentizing the JSON.
class CustomJSON extends Transform {
constructor(func, reviver = null) {
_func: any;
_reviver: any;
constructor(func, reviver: any = null) {
super({ objectMode: true });
this._func = func;
this._reviver = reviver;
@ -57,9 +60,17 @@ class CustomJSON extends Transform {
// Transform stream to merge Vinyl JSON files (buffer mode only).
class MergeJSON extends Transform {
_objects = [];
_objects: any[] = [];
constructor(stem, startObj = {}, reviver = null) {
_stem: any;
_startObj: any;
_reviver: any;
_outFile: any;
constructor(stem, startObj = {}, reviver: any = null) {
super({ objectMode: true, allowHalfOpen: false });
this._stem = stem;
this._startObj = structuredClone(startObj);
@ -134,18 +145,17 @@ const lokaliseTransform = (data, path, original = data) => {
return output;
};
gulp.task("clean-translations", () => deleteAsync([workDir]));
task("clean-translations", () => deleteAsync([workDir]));
const makeWorkDir = () => mkdir(workDir, { recursive: true });
const createTestTranslation = () =>
env.isProdBuild()
isProdBuild()
? Promise.resolve()
: gulp
.src(EN_SRC)
: glupSrc(EN_SRC)
.pipe(new CustomJSON(null, testReviver))
.pipe(rename(`${TEST_LOCALE}.json`))
.pipe(gulp.dest(workDir));
.pipe(gulpDest(workDir));
/**
* This task will build a master translation file, to be used as the base for
@ -157,11 +167,10 @@ const createTestTranslation = () =>
* the Lokalise update to translations/en.json will not happen immediately.
*/
const createMasterTranslation = () =>
gulp
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
glupSrc([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
.pipe(new CustomJSON(lokaliseTransform))
.pipe(new MergeJSON("en"))
.pipe(gulp.dest(workDir));
.pipe(gulpDest(workDir));
const FRAGMENTS = ["base"];
@ -188,12 +197,12 @@ const createTranslations = async () => {
// each locale, then fragmentizes and flattens the data for final output.
const translationFiles = await glob([
`${inFrontendDir}/!(en).json`,
...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
...(isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
]);
const hashStream = new Transform({
objectMode: true,
transform: async (file, _, callback) => {
const hash = env.isProdBuild()
const hash = isProdBuild()
? createHash("md5").update(file.contents).digest("hex")
: "dev";
HASHES.set(file.stem, hash);
@ -232,7 +241,7 @@ const createTranslations = async () => {
})
)
)
.pipe(gulp.dest(outDir));
.pipe(gulpDest(outDir));
// Send the English master downstream first, then for each other locale
// generate merged JSON data to continue piping. It begins with the master
@ -242,9 +251,9 @@ const createTranslations = async () => {
// TODO: This is a naive interpretation of BCP47 that should be improved.
// Will be OK for now as long as we don't have anything more complicated
// than a base translation + region.
const masterStream = gulp
.src(`${workDir}/en.json`)
.pipe(new PassThrough({ objectMode: true }));
const masterStream = glupSrc(`${workDir}/en.json`).pipe(
new PassThrough({ objectMode: true })
);
masterStream.pipe(hashStream, { end: false });
const mergesFinished = [finished(masterStream)];
for (const translationFile of translationFiles) {
@ -262,9 +271,9 @@ const createTranslations = async () => {
}
}
}
const mergeStream = gulp
.src(mergeFiles, { allowEmpty: true })
.pipe(new MergeJSON(locale, enMaster, emptyReviver));
const mergeStream = glupSrc(mergeFiles, { allowEmpty: true }).pipe(
new MergeJSON(locale, enMaster, emptyReviver)
);
mergesFinished.push(finished(mergeStream));
mergeStream.pipe(hashStream, { end: false });
}
@ -277,12 +286,11 @@ const createTranslations = async () => {
};
const writeTranslationMetaData = () =>
gulp
.src([`${paths.translations_src}/translationMetadata.json`])
glupSrc([`${paths.translations_src}/translationMetadata.json`])
.pipe(
new CustomJSON((meta) => {
// Add the test translation in development.
if (!env.isProdBuild()) {
if (!isProdBuild()) {
meta[TEST_LOCALE] = { nativeName: "Translation Test" };
}
// Filter out locales without a native name, and add the hashes.
@ -302,14 +310,14 @@ const writeTranslationMetaData = () =>
};
})
)
.pipe(gulp.dest(workDir));
.pipe(gulpDest(workDir));
gulp.task(
task(
"build-translations",
gulp.series(
gulp.parallel(
series(
parallel(
"fetch-nightly-translations",
gulp.series("clean-translations", makeWorkDir)
series("clean-translations", makeWorkDir)
),
createTestTranslation,
createMasterTranslation,
@ -318,12 +326,12 @@ gulp.task(
)
);
gulp.task(
task(
"build-supervisor-translations",
gulp.series(setFragment("supervisor"), "build-translations")
series(setFragment("supervisor"), "build-translations")
);
gulp.task(
task(
"build-landing-page-translations",
gulp.series(setFragment("landing-page"), "build-translations")
series(setFragment("landing-page"), "build-translations")
);

View File

@ -1,17 +1,15 @@
#!/usr/bin/env node
// Script to print Babel plugins and Core JS polyfills that will be used by browserslist environments
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */
import { version as babelVersion } from "@babel/core";
import presetEnv from "@babel/preset-env";
import compilationTargets from "@babel/helper-compilation-targets";
import coreJSCompat from "core-js-compat";
// eslint-disable-next-line import/extensions
import { logPlugin } from "@babel/preset-env/lib/debug.js";
// eslint-disable-next-line import/no-relative-packages, import/extensions
// eslint-disable-next-line import/no-relative-packages
import shippedPolyfills from "../node_modules/babel-plugin-polyfill-corejs3/lib/shipped-proposals.js";
import { babelOptions } from "./bundle";
import { babelOptions } from "./bundle.ts";
const detailsOpen = (heading) =>
`<details>\n<summary><h4>${heading}</h4></summary>\n`;
@ -52,7 +50,7 @@ for (const buildType of ["Modern", "Legacy"]) {
const browserslistEnv = buildType.toLowerCase();
const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
const presetEnvOpts = babelOpts.presets[0][1];
if (typeof presetEnvOpts !== "object") {
throw new Error(
"The first preset in babelOptions is not an object. This is unexpected."

View File

@ -1,63 +1,63 @@
import path from "path";
import path, { dirname as pathDirname } from "node:path";
import { fileURLToPath } from "node:url";
export const dirname = pathDirname(fileURLToPath(import.meta.url));
export default {
root_dir: path.resolve(__dirname, ".."),
root_dir: path.resolve(dirname, ".."),
build_dir: path.resolve(__dirname, "../build"),
app_output_root: path.resolve(__dirname, "../hass_frontend"),
app_output_static: path.resolve(__dirname, "../hass_frontend/static"),
app_output_latest: path.resolve(
__dirname,
"../hass_frontend/frontend_latest"
),
app_output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
build_dir: path.resolve(dirname, "../build"),
app_output_root: path.resolve(dirname, "../hass_frontend"),
app_output_static: path.resolve(dirname, "../hass_frontend/static"),
app_output_latest: path.resolve(dirname, "../hass_frontend/frontend_latest"),
app_output_es5: path.resolve(dirname, "../hass_frontend/frontend_es5"),
demo_dir: path.resolve(__dirname, "../demo"),
demo_output_root: path.resolve(__dirname, "../demo/dist"),
demo_output_static: path.resolve(__dirname, "../demo/dist/static"),
demo_output_latest: path.resolve(__dirname, "../demo/dist/frontend_latest"),
demo_output_es5: path.resolve(__dirname, "../demo/dist/frontend_es5"),
demo_dir: path.resolve(dirname, "../demo"),
demo_output_root: path.resolve(dirname, "../demo/dist"),
demo_output_static: path.resolve(dirname, "../demo/dist/static"),
demo_output_latest: path.resolve(dirname, "../demo/dist/frontend_latest"),
demo_output_es5: path.resolve(dirname, "../demo/dist/frontend_es5"),
cast_dir: path.resolve(__dirname, "../cast"),
cast_output_root: path.resolve(__dirname, "../cast/dist"),
cast_output_static: path.resolve(__dirname, "../cast/dist/static"),
cast_output_latest: path.resolve(__dirname, "../cast/dist/frontend_latest"),
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
cast_dir: path.resolve(dirname, "../cast"),
cast_output_root: path.resolve(dirname, "../cast/dist"),
cast_output_static: path.resolve(dirname, "../cast/dist/static"),
cast_output_latest: path.resolve(dirname, "../cast/dist/frontend_latest"),
cast_output_es5: path.resolve(dirname, "../cast/dist/frontend_es5"),
gallery_dir: path.resolve(__dirname, "../gallery"),
gallery_build: path.resolve(__dirname, "../gallery/build"),
gallery_output_root: path.resolve(__dirname, "../gallery/dist"),
gallery_dir: path.resolve(dirname, "../gallery"),
gallery_build: path.resolve(dirname, "../gallery/build"),
gallery_output_root: path.resolve(dirname, "../gallery/dist"),
gallery_output_latest: path.resolve(
__dirname,
dirname,
"../gallery/dist/frontend_latest"
),
gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"),
gallery_output_static: path.resolve(dirname, "../gallery/dist/static"),
landingPage_dir: path.resolve(__dirname, "../landing-page"),
landingPage_build: path.resolve(__dirname, "../landing-page/build"),
landingPage_output_root: path.resolve(__dirname, "../landing-page/dist"),
landingPage_dir: path.resolve(dirname, "../landing-page"),
landingPage_build: path.resolve(dirname, "../landing-page/build"),
landingPage_output_root: path.resolve(dirname, "../landing-page/dist"),
landingPage_output_latest: path.resolve(
__dirname,
dirname,
"../landing-page/dist/frontend_latest"
),
landingPage_output_es5: path.resolve(
__dirname,
dirname,
"../landing-page/dist/frontend_es5"
),
landingPage_output_static: path.resolve(
__dirname,
dirname,
"../landing-page/dist/static"
),
hassio_dir: path.resolve(__dirname, "../hassio"),
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
hassio_output_static: path.resolve(__dirname, "../hassio/build/static"),
hassio_dir: path.resolve(dirname, "../hassio"),
hassio_output_root: path.resolve(dirname, "../hassio/build"),
hassio_output_static: path.resolve(dirname, "../hassio/build/static"),
hassio_output_latest: path.resolve(
__dirname,
dirname,
"../hassio/build/frontend_latest"
),
hassio_output_es5: path.resolve(__dirname, "../hassio/build/frontend_es5"),
hassio_output_es5: path.resolve(dirname, "../hassio/build/frontend_es5"),
hassio_publicPath: "/api/hassio/app",
translations_src: path.resolve(__dirname, "../src/translations"),
translations_src: path.resolve(dirname, "../src/translations"),
};

View File

@ -1,17 +1,25 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-import-module-exports */
import filterStats from "@bundle-stats/plugin-webpack-filter";
import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
import * as rspack from "@rspack/core";
import { DefinePlugin, NormalModuleReplacementPlugin } from "@rspack/core";
import { defineConfig } from "@rspack/cli";
import log from "fancy-log";
import { existsSync } from "fs";
import path from "path";
import { existsSync } from "node:fs";
import path from "node:path";
import { WebpackManifestPlugin } from "rspack-manifest-plugin";
import TerserPlugin from "terser-webpack-plugin";
import { StatsWriterPlugin } from "webpack-stats-plugin";
// @ts-ignore
import WebpackBar from "webpackbar/rspack";
import * as bundle from "./bundle";
import * as paths from "./paths";
import {
babelOptions,
config,
definedVars,
emptyPackages,
sourceMapURL,
swcOptions,
terserOptions,
} from "./bundle.ts";
import paths from "./paths.ts";
class LogStartCompilePlugin {
ignoredFirst = false;
@ -39,12 +47,23 @@ export const createRspackConfig = ({
isTestBuild,
isHassioBuild,
dontHash,
}: {
name: string;
entry: any;
outputPath: string;
publicPath: string;
defineOverlay?: Record<string, any>;
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
isHassioBuild?: boolean;
dontHash?: Set<string>;
}) => {
if (!dontHash) {
dontHash = new Set();
}
const ignorePackages = bundle.ignorePackages({ latestBuild });
return {
return defineConfig({
name,
mode: isProdBuild ? "production" : "development",
target: `browserslist:${latestBuild ? "modern" : "legacy"}`,
@ -67,7 +86,7 @@ export const createRspackConfig = ({
{
loader: "babel-loader",
options: {
...bundle.babelOptions({
...babelOptions({
latestBuild,
isProdBuild,
isTestBuild,
@ -79,7 +98,7 @@ export const createRspackConfig = ({
},
{
loader: "builtin:swc-loader",
options: bundle.swcOptions(),
options: swcOptions(),
},
],
resolve: {
@ -100,7 +119,7 @@ export const createRspackConfig = ({
new TerserPlugin({
parallel: true,
extractComments: true,
terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }),
terserOptions: terserOptions({ latestBuild, isTestBuild }),
}),
],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
@ -119,7 +138,7 @@ export const createRspackConfig = ({
!chunk.canBeInitial() &&
!new RegExp(
`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`
).test(chunk.name),
).test(chunk?.name || ""),
},
},
plugins: [
@ -128,44 +147,11 @@ export const createRspackConfig = ({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
}),
new rspack.DefinePlugin(
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
new DefinePlugin(
definedVars({ isProdBuild, latestBuild, defineOverlay })
),
new rspack.IgnorePlugin({
checkResource(resource, context) {
// Only use ignore to intercept imports that we don't control
// inside node_module dependencies.
if (
!context.includes("/node_modules/") ||
// calling define.amd will call require("!!webpack amd options")
resource.startsWith("!!webpack") ||
// loaded by webpack dev server but doesn't exist.
resource === "webpack/hot" ||
resource.startsWith("@swc/helpers")
) {
return false;
}
let fullPath;
try {
fullPath = resource.startsWith(".")
? path.resolve(context, resource)
: require.resolve(resource);
} catch (err) {
console.error(
"Error in Home Assistant ignore plugin",
resource,
context
);
throw err;
}
return ignorePackages.some((toIgnorePath) =>
fullPath.startsWith(toIgnorePath)
);
},
}),
new rspack.NormalModuleReplacementPlugin(
new RegExp(bundle.emptyPackages({ isHassioBuild }).join("|")),
new NormalModuleReplacementPlugin(
new RegExp(emptyPackages({ isHassioBuild }).join("|")),
path.resolve(paths.root_dir, "src/util/empty.js")
),
!isProdBuild && new LogStartCompilePlugin(),
@ -181,7 +167,9 @@ export const createRspackConfig = ({
isProdBuild &&
isStatsBuild &&
new RsdoctorRspackPlugin({
reportDir: path.join(paths.build_dir, "rsdoctor"),
output: {
reportDir: path.join(paths.build_dir, "rsdoctor"),
},
features: ["plugins", "bundle"],
supports: {
generateTileGraph: true,
@ -216,7 +204,9 @@ export const createRspackConfig = ({
output: {
module: latestBuild,
filename: ({ chunk }) =>
!isProdBuild || isStatsBuild || dontHash.has(chunk.name)
!isProdBuild ||
isStatsBuild ||
(chunk?.name && dontHash.has(chunk.name))
? "[name].js"
: "[name].[contenthash].js",
chunkFilename:
@ -247,7 +237,7 @@ export const createRspackConfig = ({
// dev tools, and they stay happy getting 404s with valid requests.
return `/unknown${path.resolve("/", info.resourcePath)}`;
}
return new URL(info.resourcePath, bundle.sourceMapURL()).href;
return new URL(info.resourcePath, sourceMapURL()).href;
}
: undefined,
])
@ -257,7 +247,7 @@ export const createRspackConfig = ({
layers: true,
outputModule: true,
},
};
});
};
export const createAppConfig = ({
@ -265,27 +255,43 @@ export const createAppConfig = ({
latestBuild,
isStatsBuild,
isTestBuild,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
}) =>
createRspackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
);
export const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
createRspackConfig(
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
);
export const createDemoConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
}) =>
createRspackConfig(config.demo({ isProdBuild, latestBuild, isStatsBuild }));
export const createCastConfig = ({ isProdBuild, latestBuild }) =>
createRspackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
createRspackConfig(config.cast({ isProdBuild, latestBuild }));
export const createHassioConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
}: {
isProdBuild?: boolean;
latestBuild?: boolean;
isStatsBuild?: boolean;
isTestBuild?: boolean;
}) =>
createRspackConfig(
bundle.config.hassio({
config.hassio({
isProdBuild,
latestBuild,
isStatsBuild,
@ -294,7 +300,7 @@ export const createHassioConfig = ({
);
export const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
createRspackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
createRspackConfig(config.gallery({ isProdBuild, latestBuild }));
export const createLandingPageConfig = ({ isProdBuild, latestBuild }) =>
createRspackConfig(bundle.config.landingPage({ isProdBuild, latestBuild }));
createRspackConfig(config.landingPage({ isProdBuild, latestBuild }));

View File

@ -20,8 +20,7 @@
"prepack": "pinst --disable",
"postpack": "pinst --enable",
"test": "vitest run --config test/vitest.config.ts",
"test:coverage": "vitest run --config test/vitest.config.ts --coverage",
"gulp": "node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js"
"test:coverage": "vitest run --config test/vitest.config.ts --coverage"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.."
./node_modules/.bin/gulp build-app
node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js build-app

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.."
./node_modules/.bin/gulp develop-app
node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js develop-app

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.."
./node_modules/.bin/gulp setup-and-fetch-nightly-translations
node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js setup-and-fetch-nightly-translations

View File

@ -6,4 +6,4 @@ set -e
cd "$(dirname "$0")/.."
./node_modules/.bin/gulp analyze-app
node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js analyze-app

View File

@ -8,4 +8,4 @@ set -eu -o pipefail
cd "$(dirname "$0")/.."
./node_modules/.bin/gulp download-translations
node --import ./ts-node-register.js node_modules/gulp/bin/gulp.js download-translations

View File

@ -26,6 +26,7 @@
// Interop with CommonJS and other tools
"esModuleInterop": true,
"isolatedModules": true,
"allowImportingTsExtensions": true,
"plugins": [
{
"name": "ts-lit-plugin",

117
yarn.lock
View File

@ -4719,12 +4719,12 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:*, @types/node@npm:>=10.0.0":
version: 22.14.1
resolution: "@types/node@npm:22.14.1"
"@types/node@npm:*, @types/node@npm:22.15.16, @types/node@npm:>=10.0.0":
version: 22.15.16
resolution: "@types/node@npm:22.15.16"
dependencies:
undici-types: "npm:~6.21.0"
checksum: 10/561b1ad98ef5176d6da856ffbbe494f16655149f6a7d561de0423c8784910c81267d7d6459f59d68a97b3cbae9b5996b3b5dfe64f4de3de2239d295dcf4a4dcc
checksum: 10/d8055a0ab033ed16368109183f7e11d5364e5d8d5bd9a12df7fa1673a624823aaaaa54c0afef1648d0bfa7e12ef20b600f9d006accebecdb9931d2b72d05c7be
languageName: node
linkType: hard
@ -4735,15 +4735,6 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:22.15.16":
version: 22.15.16
resolution: "@types/node@npm:22.15.16"
dependencies:
undici-types: "npm:~6.21.0"
checksum: 10/d8055a0ab033ed16368109183f7e11d5364e5d8d5bd9a12df7fa1673a624823aaaaa54c0afef1648d0bfa7e12ef20b600f9d006accebecdb9931d2b72d05c7be
languageName: node
linkType: hard
"@types/node@npm:^18.15.3":
version: 18.19.86
resolution: "@types/node@npm:18.19.86"
@ -7641,17 +7632,6 @@ __metadata:
languageName: node
linkType: hard
"enhanced-resolve@npm:^0.9.1":
version: 0.9.1
resolution: "enhanced-resolve@npm:0.9.1"
dependencies:
graceful-fs: "npm:^4.1.2"
memory-fs: "npm:^0.2.0"
tapable: "npm:^0.1.8"
checksum: 10/0044dad5e27adb608ba6c87939aef39ac3131d7f189470e5e3643ce78ec6ed06ccaed9e887103c64a2d36718d38e4568c0ffbdce5eed2bc6921cafc38d250ddc
languageName: node
linkType: hard
"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0":
version: 4.5.0
resolution: "entities@npm:4.5.0"
@ -7977,27 +7957,6 @@ __metadata:
languageName: node
linkType: hard
"eslint-import-resolver-webpack@npm:0.13.10":
version: 0.13.10
resolution: "eslint-import-resolver-webpack@npm:0.13.10"
dependencies:
debug: "npm:^3.2.7"
enhanced-resolve: "npm:^0.9.1"
find-root: "npm:^1.1.0"
hasown: "npm:^2.0.2"
interpret: "npm:^1.4.0"
is-core-module: "npm:^2.15.1"
is-regex: "npm:^1.2.0"
lodash: "npm:^4.17.21"
resolve: "npm:^2.0.0-next.5"
semver: "npm:^5.7.2"
peerDependencies:
eslint-plugin-import: ">=1.4.0"
webpack: ">=1.11.0"
checksum: 10/7d317bc96b2590ba83f0f2af2e64f67693452756bc95ffd77381e8177365ac10c2fb288776fc6620376d7fc811902a7efe4932010bc250a1ac5fd4171e2402fb
languageName: node
linkType: hard
"eslint-module-utils@npm:^2.12.0":
version: 2.12.0
resolution: "eslint-module-utils@npm:2.12.0"
@ -8692,13 +8651,6 @@ __metadata:
languageName: node
linkType: hard
"find-root@npm:^1.1.0":
version: 1.1.0
resolution: "find-root@npm:1.1.0"
checksum: 10/caa799c976a14925ba7f31ca1a226fe73d3aa270f4f1b623fcfeb1c6e263111db4beb807d8acd31bd4d48d44c343b93688a9288dfbccca27463c36a0301b0bb9
languageName: node
linkType: hard
"find-up@npm:^4.1.0":
version: 4.1.0
resolution: "find-up@npm:4.1.0"
@ -9222,7 +9174,7 @@ __metadata:
languageName: node
linkType: hard
"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.8":
"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.8":
version: 4.2.11
resolution: "graceful-fs@npm:4.2.11"
checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2
@ -9554,7 +9506,6 @@ __metadata:
eslint: "npm:9.26.0"
eslint-config-airbnb-base: "npm:15.0.0"
eslint-config-prettier: "npm:10.1.2"
eslint-import-resolver-webpack: "npm:0.13.10"
eslint-plugin-import: "npm:2.31.0"
eslint-plugin-lit: "npm:2.1.1"
eslint-plugin-lit-a11y: "npm:4.1.4"
@ -9978,13 +9929,6 @@ __metadata:
languageName: node
linkType: hard
"interpret@npm:^1.4.0":
version: 1.4.0
resolution: "interpret@npm:1.4.0"
checksum: 10/5beec568d3f60543d0f61f2c5969d44dffcb1a372fe5abcdb8013968114d4e4aaac06bc971a4c9f5bd52d150881d8ebad72a8c60686b1361f5f0522f39c0e1a3
languageName: node
linkType: hard
"interpret@npm:^3.1.1":
version: 3.1.1
resolution: "interpret@npm:3.1.1"
@ -10344,7 +10288,7 @@ __metadata:
languageName: node
linkType: hard
"is-regex@npm:^1.2.0, is-regex@npm:^1.2.1":
"is-regex@npm:^1.2.1":
version: 1.2.1
resolution: "is-regex@npm:1.2.1"
dependencies:
@ -11336,13 +11280,6 @@ __metadata:
languageName: node
linkType: hard
"memory-fs@npm:^0.2.0":
version: 0.2.0
resolution: "memory-fs@npm:0.2.0"
checksum: 10/67ff4642b7767bf00159c248dbaa1203369866e9224579f8a7c7c0c3b0ed0a6e5eaa38b4753a9aa59e0f063bd09e2c9a2a97a8e593ec395b06ce216f75fc573d
languageName: node
linkType: hard
"merge-descriptors@npm:1.0.3":
version: 1.0.3
resolution: "merge-descriptors@npm:1.0.3"
@ -13004,19 +12941,6 @@ __metadata:
languageName: node
linkType: hard
"resolve@npm:^2.0.0-next.5":
version: 2.0.0-next.5
resolution: "resolve@npm:2.0.0-next.5"
dependencies:
is-core-module: "npm:^2.13.0"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
checksum: 10/2d6fd28699f901744368e6f2032b4268b4c7b9185fd8beb64f68c93ac6b22e52ae13560ceefc96241a665b985edf9ffd393ae26d2946a7d3a07b7007b7d51e79
languageName: node
linkType: hard
"resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>":
version: 1.22.10
resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d"
@ -13030,19 +12954,6 @@ __metadata:
languageName: node
linkType: hard
"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin<compat/resolve>":
version: 2.0.0-next.5
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin<compat/resolve>::version=2.0.0-next.5&hash=c3c19d"
dependencies:
is-core-module: "npm:^2.13.0"
path-parse: "npm:^1.0.7"
supports-preserve-symlinks-flag: "npm:^1.0.0"
bin:
resolve: bin/resolve
checksum: 10/05fa778de9d0347c8b889eb7a18f1f06bf0f801b0eb4610b4871a4b2f22e220900cf0ad525e94f990bb8d8921c07754ab2122c0c225ab4cdcea98f36e64fa4c2
languageName: node
linkType: hard
"restore-cursor@npm:^5.0.0":
version: 5.1.0
resolution: "restore-cursor@npm:5.1.0"
@ -13345,15 +13256,6 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^5.7.2":
version: 5.7.2
resolution: "semver@npm:5.7.2"
bin:
semver: bin/semver
checksum: 10/fca14418a174d4b4ef1fecb32c5941e3412d52a4d3d85165924ce3a47fbc7073372c26faf7484ceb4bbc2bde25880c6b97e492473dc7e9708fdfb1c6a02d546e
languageName: node
linkType: hard
"semver@npm:^6.3.0, semver@npm:^6.3.1":
version: 6.3.1
resolution: "semver@npm:6.3.1"
@ -14279,13 +14181,6 @@ __metadata:
languageName: node
linkType: hard
"tapable@npm:^0.1.8":
version: 0.1.10
resolution: "tapable@npm:0.1.10"
checksum: 10/c1059b232ff4626dd032bc5620348983071c90edee5ebf779fb11bab59b31663d9d474da0d7c43c2004145f6dac260634a60b8dbd20691f514fc5d84fe5eda1f
languageName: node
linkType: hard
"tar@npm:7.4.3, tar@npm:^7.4.3":
version: 7.4.3
resolution: "tar@npm:7.4.3"