diff --git a/build-scripts/gulp/app.cjs b/build-scripts/gulp/app.cjs index 0b6c49354c..4e77e26326 100644 --- a/build-scripts/gulp/app.cjs +++ b/build-scripts/gulp/app.cjs @@ -24,8 +24,7 @@ gulp.task( gulp.parallel( "gen-service-worker-app-dev", "gen-icons-json", - "gen-pages-dev", - "gen-index-app-dev", + "gen-pages-app-dev", "build-translations", "build-locale-data" ), @@ -50,10 +49,6 @@ gulp.task( env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", // Don't compress running tests ...(env.isTestBuild() ? [] : ["compress-app"]), - gulp.parallel( - "gen-pages-prod", - "gen-index-app-prod", - "gen-service-worker-app-prod" - ) + gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod") ) ); diff --git a/build-scripts/gulp/cast.cjs b/build-scripts/gulp/cast.cjs index 4dbc9b1eff..ae1a961139 100644 --- a/build-scripts/gulp/cast.cjs +++ b/build-scripts/gulp/cast.cjs @@ -19,7 +19,7 @@ gulp.task( "translations-enable-merge-backend", gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), "copy-static-cast", - "gen-index-cast-dev", + "gen-pages-cast-dev", env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast" ) ); @@ -35,6 +35,6 @@ gulp.task( gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), "copy-static-cast", env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast", - "gen-index-cast-prod" + "gen-pages-cast-prod" ) ); diff --git a/build-scripts/gulp/demo.cjs b/build-scripts/gulp/demo.cjs index 65d8035c66..657dd35634 100644 --- a/build-scripts/gulp/demo.cjs +++ b/build-scripts/gulp/demo.cjs @@ -21,7 +21,7 @@ gulp.task( "translations-enable-merge-backend", gulp.parallel( "gen-icons-json", - "gen-index-demo-dev", + "gen-pages-demo-dev", "build-translations", "build-locale-data" ), @@ -42,6 +42,6 @@ gulp.task( gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), "copy-static-demo", env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo", - "gen-index-demo-prod" + "gen-pages-demo-prod" ) ); diff --git a/build-scripts/gulp/entry-html.cjs b/build-scripts/gulp/entry-html.cjs index ffb569f7ed..53d7b24f1f 100644 --- a/build-scripts/gulp/entry-html.cjs +++ b/build-scripts/gulp/entry-html.cjs @@ -8,344 +8,223 @@ const paths = require("../paths.cjs"); const env = require("../env.cjs"); const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs"); -const templatePath = (tpl) => - path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`); - -const readFile = (pth) => fs.readFileSync(pth).toString(); - -const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { - const compiled = template(readFile(pathFunc(pth))); +const renderTemplate = (templateFile, data = {}) => { + const compiled = template( + fs.readFileSync(templateFile, { encoding: "utf-8" }) + ); return compiled({ ...data, useRollup: env.useRollup(), useWDS: env.useWDS(), - renderTemplate, + // Resolve any child/nested templates relative to the parent and pass the same data + renderTemplate: (childTemplate) => + renderTemplate( + path.resolve(path.dirname(templateFile), childTemplate), + data + ), }); }; -const renderDemoTemplate = (pth, data = {}) => - renderTemplate(pth, data, (tpl) => - path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`) - ); +const WRAP_TAGS = { ".js": "script", ".css": "style" }; -const renderCastTemplate = (pth, data = {}) => - renderTemplate(pth, data, (tpl) => - path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`) - ); - -const renderGalleryTemplate = (pth, data = {}) => - renderTemplate(pth, data, (tpl) => - path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`) - ); - -const minifyHtml = (content) => - minify(content, { +const minifyHtml = (content, ext) => { + const wrapTag = WRAP_TAGS[ext] || ""; + const begTag = wrapTag && `<${wrapTag}>`; + const endTag = wrapTag && `${wrapTag}>`; + return minify(begTag + content + endTag, { ...htmlMinifierOptions, conservativeCollapse: false, minifyJS: terserOptions({ latestBuild: false, // Shared scripts should be ES5 isTestBuild: true, // Don't need source maps }), - }); + }).then((wrapped) => + wrapTag ? wrapped.slice(begTag.length, -endTag.length) : wrapped + ); +}; -const PAGES = ["onboarding", "authorize"]; +// Function to generate a dev task for each project's configuration +// Note Currently WDS paths are hard-coded to only work for app +const genPagesDevTask = + ( + pageEntries, + inputRoot, + outputRoot, + useWDS = false, + inputSub = "src/html", + publicRoot = "" + ) => + async () => { + for (const [page, entries] of Object.entries(pageEntries)) { + const content = renderTemplate( + path.resolve(inputRoot, inputSub, `${page}.template`), + { + latestEntryJS: entries.map((entry) => + useWDS + ? `http://localhost:8000/src/entrypoints/${entry}.ts` + : `${publicRoot}/frontend_latest/${entry}.js` + ), + es5EntryJS: entries.map( + (entry) => `${publicRoot}/frontend_es5/${entry}.js` + ), + latestCustomPanelJS: useWDS + ? "http://localhost:8000/src/entrypoints/custom-panel.ts" + : `${publicRoot}/frontend_latest/custom-panel.js`, + es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, + } + ); + fs.outputFileSync(path.resolve(outputRoot, page), content); + } + }; -gulp.task("gen-pages-dev", (done) => { - for (const page of PAGES) { - const content = renderTemplate(page, { - latestPageJS: `/frontend_latest/${page}.js`, - - es5PageJS: `/frontend_es5/${page}.js`, - }); - - fs.outputFileSync( - path.resolve(paths.app_output_root, `${page}.html`), - content - ); - } - done(); -}); - -gulp.task("gen-pages-prod", async () => { - const latestManifest = require(path.resolve( - paths.app_output_latest, - "manifest.json" - )); - const es5Manifest = require(path.resolve( - paths.app_output_es5, - "manifest.json" - )); - - const minifiedHTML = []; - for (const page of PAGES) { - const content = renderTemplate(page, { - latestPageJS: latestManifest[`${page}.js`], - es5PageJS: es5Manifest[`${page}.js`], - }); - - minifiedHTML.push( - minifyHtml(content).then((minified) => - fs.outputFileSync( - path.resolve(paths.app_output_root, `${page}.html`), - minified +// Same as previous but for production builds +// (includes minification and hashed file names from manifest) +const genPagesProdTask = + ( + pageEntries, + inputRoot, + outputRoot, + outputLatest, + outputES5, + inputSub = "src/html" + ) => + async () => { + const latestManifest = require(path.resolve(outputLatest, "manifest.json")); + const es5Manifest = outputES5 + ? require(path.resolve(outputES5, "manifest.json")) + : {}; + const minifiedHTML = []; + for (const [page, entries] of Object.entries(pageEntries)) { + const content = renderTemplate( + path.resolve(inputRoot, inputSub, `${page}.template`), + { + latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), + es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), + latestCustomPanelJS: latestManifest["custom-panel.js"], + es5CustomPanelJS: es5Manifest["custom-panel.js"], + } + ); + minifiedHTML.push( + minifyHtml(content, path.extname(page)).then((minified) => + fs.outputFileSync(path.resolve(outputRoot, page), minified) ) - ) - ); - } - await Promise.all(minifiedHTML); -}); + ); + } + await Promise.all(minifiedHTML); + }; -gulp.task("gen-index-app-dev", (done) => { - let latestAppJS; - let latestCoreJS; - let latestCustomPanelJS; +// Map HTML pages to their required entrypoints +const APP_PAGE_ENTRIES = { + "authorize.html": ["authorize"], + "onboarding.html": ["onboarding"], + "index.html": ["core", "app"], +}; - if (env.useWDS()) { - latestAppJS = "http://localhost:8000/src/entrypoints/app.ts"; - latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts"; - latestCustomPanelJS = - "http://localhost:8000/src/entrypoints/custom-panel.ts"; - } else { - latestAppJS = "/frontend_latest/app.js"; - latestCoreJS = "/frontend_latest/core.js"; - latestCustomPanelJS = "/frontend_latest/custom-panel.js"; - } +gulp.task( + "gen-pages-app-dev", + genPagesDevTask( + APP_PAGE_ENTRIES, + paths.polymer_dir, + paths.app_output_root, + env.useWDS() + ) +); - const content = renderTemplate("index", { - latestAppJS, - latestCoreJS, - latestCustomPanelJS, - - es5AppJS: "/frontend_es5/app.js", - es5CoreJS: "/frontend_es5/core.js", - es5CustomPanelJS: "/frontend_es5/custom-panel.js", - }).replace(/#THEMEC/g, "{{ theme_color }}"); - - fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content); - done(); -}); - -gulp.task("gen-index-app-prod", async () => { - const latestManifest = require(path.resolve( +gulp.task( + "gen-pages-app-prod", + genPagesProdTask( + APP_PAGE_ENTRIES, + paths.polymer_dir, + paths.app_output_root, paths.app_output_latest, - "manifest.json" - )); - const es5Manifest = require(path.resolve( - paths.app_output_es5, - "manifest.json" - )); - const content = renderTemplate("index", { - latestAppJS: latestManifest["app.js"], - latestCoreJS: latestManifest["core.js"], - latestCustomPanelJS: latestManifest["custom-panel.js"], + paths.app_output_es5 + ) +); - es5AppJS: es5Manifest["app.js"], - es5CoreJS: es5Manifest["core.js"], - es5CustomPanelJS: es5Manifest["custom-panel.js"], - }); - const minified = (await minifyHtml(content)).replace( - /#THEMEC/g, - "{{ theme_color }}" - ); +const CAST_PAGE_ENTRIES = { + "faq.html": ["launcher"], + "index.html": ["launcher"], + "media.html": ["media"], + "receiver.html": ["receiver"], +}; - fs.outputFileSync( - path.resolve(paths.app_output_root, "index.html"), - minified - ); -}); +gulp.task( + "gen-pages-cast-dev", + genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root) +); -gulp.task("gen-index-cast-dev", (done) => { - const contentReceiver = renderCastTemplate("receiver", { - latestReceiverJS: "/frontend_latest/receiver.js", - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "receiver.html"), - contentReceiver - ); - - const contentMedia = renderCastTemplate("media", { - latestMediaJS: "/frontend_latest/media.js", - es5MediaJS: "/frontend_es5/media.js", - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "media.html"), - contentMedia - ); - - const contentFAQ = renderCastTemplate("launcher-faq", { - latestLauncherJS: "/frontend_latest/launcher.js", - es5LauncherJS: "/frontend_es5/launcher.js", - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "faq.html"), - contentFAQ - ); - - const contentLauncher = renderCastTemplate("launcher", { - latestLauncherJS: "/frontend_latest/launcher.js", - es5LauncherJS: "/frontend_es5/launcher.js", - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "index.html"), - contentLauncher - ); - done(); -}); - -gulp.task("gen-index-cast-prod", (done) => { - const latestManifest = require(path.resolve( +gulp.task( + "gen-pages-cast-prod", + genPagesProdTask( + CAST_PAGE_ENTRIES, + paths.cast_dir, + paths.cast_output_root, paths.cast_output_latest, - "manifest.json" - )); - const es5Manifest = require(path.resolve( - paths.cast_output_es5, - "manifest.json" - )); + paths.cast_output_es5 + ) +); - const contentReceiver = renderCastTemplate("receiver", { - latestReceiverJS: latestManifest["receiver.js"], - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "receiver.html"), - contentReceiver - ); +const DEMO_PAGE_ENTRIES = { "index.html": ["main"] }; - const contentMedia = renderCastTemplate("media", { - latestMediaJS: latestManifest["media.js"], - es5MediaJS: es5Manifest["media.js"], - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "media.html"), - contentMedia - ); +gulp.task( + "gen-pages-demo-dev", + genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root) +); - const contentFAQ = renderCastTemplate("launcher-faq", { - latestLauncherJS: latestManifest["launcher.js"], - es5LauncherJS: es5Manifest["launcher.js"], - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "faq.html"), - contentFAQ - ); - - const contentLauncher = renderCastTemplate("launcher", { - latestLauncherJS: latestManifest["launcher.js"], - es5LauncherJS: es5Manifest["launcher.js"], - }); - fs.outputFileSync( - path.resolve(paths.cast_output_root, "index.html"), - contentLauncher - ); - done(); -}); - -gulp.task("gen-index-demo-dev", (done) => { - const content = renderDemoTemplate("index", { - latestDemoJS: "/frontend_latest/main.js", - - es5DemoJS: "/frontend_es5/main.js", - }); - - fs.outputFileSync( - path.resolve(paths.demo_output_root, "index.html"), - content - ); - done(); -}); - -gulp.task("gen-index-demo-prod", async () => { - const latestManifest = require(path.resolve( +gulp.task( + "gen-pages-demo-prod", + genPagesProdTask( + DEMO_PAGE_ENTRIES, + paths.demo_dir, + paths.demo_output_root, paths.demo_output_latest, - "manifest.json" - )); - const es5Manifest = require(path.resolve( - paths.demo_output_es5, - "manifest.json" - )); - const content = renderDemoTemplate("index", { - latestDemoJS: latestManifest["main.js"], + paths.demo_output_es5 + ) +); - es5DemoJS: es5Manifest["main.js"], - }); - const minified = await minifyHtml(content); +const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] }; - fs.outputFileSync( - path.resolve(paths.demo_output_root, "index.html"), - minified - ); -}); +gulp.task( + "gen-pages-gallery-dev", + genPagesDevTask( + GALLERY_PAGE_ENTRIES, + paths.gallery_dir, + paths.gallery_output_root + ) +); -gulp.task("gen-index-gallery-dev", (done) => { - const content = renderGalleryTemplate("index", { - latestGalleryJS: "./frontend_latest/entrypoint.js", - }); +gulp.task( + "gen-pages-gallery-prod", + genPagesProdTask( + GALLERY_PAGE_ENTRIES, + paths.gallery_dir, + paths.gallery_output_root, + paths.gallery_output_latest + ) +); - fs.outputFileSync( - path.resolve(paths.gallery_output_root, "index.html"), - content - ); - done(); -}); +const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] }; -gulp.task("gen-index-gallery-prod", async () => { - const latestManifest = require(path.resolve( - paths.gallery_output_latest, - "manifest.json" - )); - const content = renderGalleryTemplate("index", { - latestGalleryJS: latestManifest["entrypoint.js"], - }); - const minified = await minifyHtml(content); +gulp.task( + "gen-pages-hassio-dev", + genPagesDevTask( + HASSIO_PAGE_ENTRIES, + paths.hassio_dir, + paths.hassio_output_root, + undefined, + "src", + paths.hassio_publicPath + ) +); - fs.outputFileSync( - path.resolve(paths.gallery_output_root, "index.html"), - minified - ); -}); - -gulp.task("gen-index-hassio-dev", async () => { - writeHassioEntrypoint( - `${paths.hassio_publicPath}/frontend_latest/entrypoint.js`, - `${paths.hassio_publicPath}/frontend_es5/entrypoint.js` - ); -}); - -gulp.task("gen-index-hassio-prod", async () => { - const latestManifest = require(path.resolve( +gulp.task( + "gen-pages-hassio-prod", + genPagesProdTask( + HASSIO_PAGE_ENTRIES, + paths.hassio_dir, + paths.hassio_output_root, paths.hassio_output_latest, - "manifest.json" - )); - const es5Manifest = require(path.resolve( paths.hassio_output_es5, - "manifest.json" - )); - writeHassioEntrypoint( - latestManifest["entrypoint.js"], - es5Manifest["entrypoint.js"] - ); -}); - -function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) { - fs.mkdirSync(paths.hassio_output_root, { recursive: true }); - // Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5 - fs.writeFileSync( - path.resolve(paths.hassio_output_root, "entrypoint.js"), - ` -function loadES5() { - var el = document.createElement('script'); - el.src = '${es5Entrypoint}'; - document.body.appendChild(el); -} -if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) { - loadES5(); -} else { - try { - new Function("import('${latestEntrypoint}')")(); - } catch (err) { - loadES5(); - } -} - `, - { encoding: "utf-8" } - ); -} + "src" + ) +); diff --git a/build-scripts/gulp/gallery.cjs b/build-scripts/gulp/gallery.cjs index 74a752fb5d..3670eb18e3 100644 --- a/build-scripts/gulp/gallery.cjs +++ b/build-scripts/gulp/gallery.cjs @@ -159,7 +159,7 @@ gulp.task( "gather-gallery-pages" ), "copy-static-gallery", - "gen-index-gallery-dev", + "gen-pages-gallery-dev", gulp.parallel( env.useRollup() ? "rollup-dev-server-gallery" @@ -193,6 +193,6 @@ gulp.task( ), "copy-static-gallery", env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery", - "gen-index-gallery-prod" + "gen-pages-gallery-prod" ) ); diff --git a/build-scripts/gulp/hassio.cjs b/build-scripts/gulp/hassio.cjs index 562ed3b646..a958cb0365 100644 --- a/build-scripts/gulp/hassio.cjs +++ b/build-scripts/gulp/hassio.cjs @@ -17,7 +17,7 @@ gulp.task( }, "clean-hassio", "gen-dummy-icons-json", - "gen-index-hassio-dev", + "gen-pages-hassio-dev", "build-supervisor-translations", "copy-translations-supervisor", "build-locale-data", @@ -39,7 +39,7 @@ gulp.task( "build-locale-data", "copy-locale-data-supervisor", env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", - "gen-index-hassio-prod", + "gen-pages-hassio-prod", ...// Don't compress running tests (env.isTestBuild() ? [] : ["compress-hassio"]) ) diff --git a/cast/src/html/_social_meta.html.template b/cast/src/html/_social_meta.html.template new file mode 100644 index 0000000000..d3ee681ed3 --- /dev/null +++ b/cast/src/html/_social_meta.html.template @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/cast/src/html/launcher-faq.html.template b/cast/src/html/faq.html.template similarity index 95% rename from cast/src/html/launcher-faq.html.template rename to cast/src/html/faq.html.template index 7ba706c16c..c67a5b477a 100644 --- a/cast/src/html/launcher-faq.html.template +++ b/cast/src/html/faq.html.template @@ -3,7 +3,7 @@