Correct serving modern build to macOS companion app (#21724)

pull/21729/head
Steve Repsher 2024-08-21 11:51:07 -04:00 committed by GitHub
parent f730761b3f
commit 5e486d9cf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 66 additions and 21 deletions

View File

@ -1,35 +1,76 @@
// Tasks to generate entry HTML // Tasks to generate entry HTML
import { getUserAgentRegex } from "browserslist-useragent-regexp"; import {
applyVersionsToRegexes,
compileRegex,
getPreUserAgentRegexes,
} from "browserslist-useragent-regexp";
import fs from "fs-extra"; import fs from "fs-extra";
import gulp from "gulp"; import gulp from "gulp";
import { minify } from "html-minifier-terser"; import { minify } from "html-minifier-terser";
import template from "lodash.template"; import template from "lodash.template";
import path from "path"; import { dirname, extname, resolve } from "node:path";
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
import env from "../env.cjs"; import env from "../env.cjs";
import paths from "../paths.cjs"; import paths from "../paths.cjs";
// 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
// to serve modern based on a minimum macOS version. We take the minimum Safari
// major version from browserslist and manually map that to a supported macOS
// version. Note this assumes the user has kept Safari updated.
const HA_MACOS_REGEX =
/Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/;
const SAFARI_TO_MACOS = {
15: [10, 15, 0],
16: [11, 0, 0],
17: [12, 0, 0],
18: [13, 0, 0],
};
const getCommonTemplateVars = () => {
const browserRegexes = getPreUserAgentRegexes({
env: "modern",
allowHigherVersions: true,
mobileToDesktop: true,
throwOnMissing: true,
});
const minSafariVersion = browserRegexes.find(
(regex) => regex.family === "safari"
)?.matchedVersions[0][0];
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
if (!minMacOSVersion) {
throw Error(
`Could not find minimum MacOS version for Safari ${minSafariVersion}.`
);
}
const haMacOSRegex = applyVersionsToRegexes(
[
{
family: "ha_macos",
regex: HA_MACOS_REGEX,
matchedVersions: [minMacOSVersion],
requestVersions: [minMacOSVersion],
},
],
{ ignorePatch: true, allowHigherVersions: true }
);
return {
useRollup: env.useRollup(),
useWDS: env.useWDS(),
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
};
};
const renderTemplate = (templateFile, data = {}) => { const renderTemplate = (templateFile, data = {}) => {
const compiled = template( const compiled = template(
fs.readFileSync(templateFile, { encoding: "utf-8" }) fs.readFileSync(templateFile, { encoding: "utf-8" })
); );
return compiled({ return compiled({
...data, ...data,
useRollup: env.useRollup(),
useWDS: env.useWDS(),
modernRegex: getUserAgentRegex({
env: "modern",
allowHigherVersions: true,
mobileToDesktop: true,
throwOnMissing: true,
}).toString(),
// Resolve any child/nested templates relative to the parent and pass the same data // Resolve any child/nested templates relative to the parent and pass the same data
renderTemplate: (childTemplate) => renderTemplate: (childTemplate) =>
renderTemplate( renderTemplate(resolve(dirname(templateFile), childTemplate), data),
path.resolve(path.dirname(templateFile), childTemplate),
data
),
}); });
}; };
@ -63,10 +104,12 @@ const genPagesDevTask =
publicRoot = "" publicRoot = ""
) => ) =>
async () => { async () => {
const commonVars = getCommonTemplateVars();
for (const [page, entries] of Object.entries(pageEntries)) { for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate( const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`), resolve(inputRoot, inputSub, `${page}.template`),
{ {
...commonVars,
latestEntryJS: entries.map((entry) => latestEntryJS: entries.map((entry) =>
useWDS useWDS
? `http://localhost:8000/src/entrypoints/${entry}.ts` ? `http://localhost:8000/src/entrypoints/${entry}.ts`
@ -81,7 +124,7 @@ const genPagesDevTask =
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
} }
); );
fs.outputFileSync(path.resolve(outputRoot, page), content); fs.outputFileSync(resolve(outputRoot, page), content);
} }
}; };
@ -98,16 +141,18 @@ const genPagesProdTask =
) => ) =>
async () => { async () => {
const latestManifest = fs.readJsonSync( const latestManifest = fs.readJsonSync(
path.resolve(outputLatest, "manifest.json") resolve(outputLatest, "manifest.json")
); );
const es5Manifest = outputES5 const es5Manifest = outputES5
? fs.readJsonSync(path.resolve(outputES5, "manifest.json")) ? fs.readJsonSync(resolve(outputES5, "manifest.json"))
: {}; : {};
const commonVars = getCommonTemplateVars();
const minifiedHTML = []; const minifiedHTML = [];
for (const [page, entries] of Object.entries(pageEntries)) { for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate( const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`), resolve(inputRoot, inputSub, `${page}.template`),
{ {
...commonVars,
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
latestCustomPanelJS: latestManifest["custom-panel.js"], latestCustomPanelJS: latestManifest["custom-panel.js"],
@ -115,8 +160,8 @@ const genPagesProdTask =
} }
); );
minifiedHTML.push( minifiedHTML.push(
minifyHtml(content, path.extname(page)).then((minified) => minifyHtml(content, extname(page)).then((minified) =>
fs.outputFileSync(path.resolve(outputRoot, page), minified) fs.outputFileSync(resolve(outputRoot, page), minified)
) )
); );
} }