From 70d6c6b902407a02c51cff7d6b16488c838d8d7d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Oct 2019 15:02:54 -0700 Subject: [PATCH] Refactor Webpack build scripts (#4093) * Refactor Webpack build scripts * Add Gallery too * Fix icons * Update travis --- .travis.yml | 7 +- build-scripts/env.js | 6 + build-scripts/gulp/app.js | 9 +- build-scripts/gulp/cast.js | 10 +- build-scripts/gulp/clean.js | 16 ++ build-scripts/gulp/compress.js | 7 + build-scripts/gulp/demo.js | 10 +- build-scripts/gulp/entry-html.js | 46 +++- build-scripts/gulp/gallery.js | 38 +++ build-scripts/gulp/gather-static.js | 12 + build-scripts/gulp/gen-icons.js | 58 ++-- build-scripts/gulp/hassio.js | 34 +++ build-scripts/gulp/webpack.js | 193 +++++++------ build-scripts/paths.js | 9 + build-scripts/webpack.js | 397 +++++++++++++-------------- cast/webpack.config.js | 11 + demo/webpack.config.js | 7 +- gallery/public/index.html | 18 -- gallery/script/build_gallery | 12 +- gallery/script/develop_gallery | 8 +- gallery/src/components/demo-cards.js | 8 +- gallery/src/html/index.html.template | 22 ++ gallery/webpack.config.js | 112 ++++---- hassio/script/build_hassio | 9 +- hassio/script/develop | 9 +- hassio/script/gen-icons.js | 20 -- hassio/webpack.config.js | 66 +---- webpack.config.js | 4 +- 28 files changed, 626 insertions(+), 532 deletions(-) create mode 100644 build-scripts/env.js create mode 100644 build-scripts/gulp/gallery.js create mode 100644 build-scripts/gulp/hassio.js create mode 100644 cast/webpack.config.js delete mode 100644 gallery/public/index.html create mode 100644 gallery/src/html/index.html.template delete mode 100755 hassio/script/gen-icons.js diff --git a/.travis.yml b/.travis.yml index 4b357440b7..24433d2e24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,19 +8,20 @@ install: yarn install script: - npm run build - hassio/script/build_hassio + # Because else eslint fails because hassio has cleaned that build + - ./node_modules/.bin/gulp gen-icons-app - npm run test # - xvfb-run wct --module-resolution=node --npm # - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi' services: - docker before_deploy: - - 'docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21' + - "docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21" deploy: provider: script script: script/travis_deploy - 'on': + "on": branch: master dist: trusty addons: sauce_connect: true - diff --git a/build-scripts/env.js b/build-scripts/env.js new file mode 100644 index 0000000000..101858a367 --- /dev/null +++ b/build-scripts/env.js @@ -0,0 +1,6 @@ +module.exports = { + isProdBuild: process.env.NODE_ENV === "production", + isStatsBuild: process.env.STATS === "1", + isTravis: process.env.TRAVIS === "true", + isNetlify: process.env.NETLIFY === "true", +}; diff --git a/build-scripts/gulp/app.js b/build-scripts/gulp/app.js index a86e68b527..985067c71f 100644 --- a/build-scripts/gulp/app.js +++ b/build-scripts/gulp/app.js @@ -1,6 +1,8 @@ // Run HA develop mode const gulp = require("gulp"); +const envVars = require("../env"); + require("./clean.js"); require("./translations.js"); require("./gen-icons.js"); @@ -19,7 +21,7 @@ gulp.task( "clean", gulp.parallel( "gen-service-worker-dev", - "gen-icons", + gulp.parallel("gen-icons-app", "gen-icons-mdi"), "gen-pages-dev", "gen-index-app-dev", gulp.series("create-test-translation", "build-translations") @@ -36,10 +38,11 @@ gulp.task( process.env.NODE_ENV = "production"; }, "clean", - gulp.parallel("gen-icons", "build-translations"), + gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"), "copy-static", "webpack-prod-app", - ...(process.env.CI === "true" ? [] : ["compress-app"]), + ...// Don't compress running tests + (envVars.isTravis ? [] : ["compress-app"]), gulp.parallel( "gen-pages-prod", "gen-index-app-prod", diff --git a/build-scripts/gulp/cast.js b/build-scripts/gulp/cast.js index 974d2c29cf..34fcbb67a4 100644 --- a/build-scripts/gulp/cast.js +++ b/build-scripts/gulp/cast.js @@ -1,4 +1,3 @@ -// Run cast develop mode const gulp = require("gulp"); require("./clean.js"); @@ -16,7 +15,12 @@ gulp.task( process.env.NODE_ENV = "development"; }, "clean-cast", - gulp.parallel("gen-icons", "gen-index-cast-dev", "build-translations"), + gulp.parallel( + "gen-icons-app", + "gen-icons-mdi", + "gen-index-cast-dev", + "build-translations" + ), "copy-static-cast", "webpack-dev-server-cast" ) @@ -29,7 +33,7 @@ gulp.task( process.env.NODE_ENV = "production"; }, "clean-cast", - gulp.parallel("gen-icons", "build-translations"), + gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"), "copy-static-cast", "webpack-prod-cast", "gen-index-cast-prod" diff --git a/build-scripts/gulp/clean.js b/build-scripts/gulp/clean.js index f81728fcb3..41fec51e69 100644 --- a/build-scripts/gulp/clean.js +++ b/build-scripts/gulp/clean.js @@ -9,15 +9,31 @@ gulp.task( return del([config.root, config.build_dir]); }) ); + gulp.task( "clean-demo", gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { return del([config.demo_root, config.build_dir]); }) ); + gulp.task( "clean-cast", gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { return del([config.cast_root, config.build_dir]); }) ); + +gulp.task( + "clean-hassio", + gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { + return del([config.hassio_root, config.build_dir]); + }) +); + +gulp.task( + "clean-gallery", + gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { + return del([config.gallery_root, config.build_dir]); + }) +); diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js index f41122c55c..de604f7fa3 100644 --- a/build-scripts/gulp/compress.js +++ b/build-scripts/gulp/compress.js @@ -29,3 +29,10 @@ gulp.task("compress-app", function compressApp() { return merge(jsLatest, jsEs5, polyfills, translations); }); + +gulp.task("compress-hassio", function compressApp() { + return gulp + .src(path.resolve(paths.hassio_root, "**/*.js")) + .pipe(zopfli()) + .pipe(gulp.dest(paths.hassio_root)); +}); diff --git a/build-scripts/gulp/demo.js b/build-scripts/gulp/demo.js index 2c8070962d..de51760281 100644 --- a/build-scripts/gulp/demo.js +++ b/build-scripts/gulp/demo.js @@ -17,7 +17,8 @@ gulp.task( }, "clean-demo", gulp.parallel( - "gen-icons", + "gen-icons-app", + "gen-icons-mdi", "gen-icons-demo", "gen-index-demo-dev", "build-translations" @@ -34,7 +35,12 @@ gulp.task( process.env.NODE_ENV = "production"; }, "clean-demo", - gulp.parallel("gen-icons", "gen-icons-demo", "build-translations"), + gulp.parallel( + "gen-icons-app", + "gen-icons-mdi", + "gen-icons-demo", + "build-translations" + ), "copy-static-demo", "webpack-prod-demo", "gen-index-demo-prod" diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.js index 33abdff712..e8ad05e8b4 100644 --- a/build-scripts/gulp/entry-html.js +++ b/build-scripts/gulp/entry-html.js @@ -11,12 +11,6 @@ const config = require("../paths.js"); const templatePath = (tpl) => path.resolve(config.polymer_dir, "src/html/", `${tpl}.html.template`); -const demoTemplatePath = (tpl) => - path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`); - -const castTemplatePath = (tpl) => - path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`); - const readFile = (pth) => fs.readFileSync(pth).toString(); const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { @@ -25,10 +19,19 @@ const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { }; const renderDemoTemplate = (pth, data = {}) => - renderTemplate(pth, data, demoTemplatePath); + renderTemplate(pth, data, (tpl) => + path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`) + ); const renderCastTemplate = (pth, data = {}) => - renderTemplate(pth, data, castTemplatePath); + renderTemplate(pth, data, (tpl) => + path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`) + ); + +const renderGalleryTemplate = (pth, data = {}) => + renderTemplate(pth, data, (tpl) => + path.resolve(config.gallery_dir, "src/html/", `${tpl}.html.template`) + ); const minifyHtml = (content) => minify(content, { @@ -209,8 +212,33 @@ gulp.task("gen-index-demo-prod", (done) => { es5Compatibility: es5Manifest["compatibility.js"], es5DemoJS: es5Manifest["main.js"], }); - const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}"); + const minified = minifyHtml(content); fs.outputFileSync(path.resolve(config.demo_root, "index.html"), minified); done(); }); + +gulp.task("gen-index-gallery-dev", (done) => { + // In dev mode we don't mangle names, so we hardcode urls. That way we can + // run webpack as last in watch mode, which blocks output. + const content = renderGalleryTemplate("index", { + latestGalleryJS: "./entrypoint.js", + }); + + fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), content); + done(); +}); + +gulp.task("gen-index-gallery-prod", (done) => { + const latestManifest = require(path.resolve( + config.gallery_output, + "manifest.json" + )); + const content = renderGalleryTemplate("index", { + latestGalleryJS: latestManifest["entrypoint.js"], + }); + const minified = minifyHtml(content); + + fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), minified); + done(); +}); diff --git a/build-scripts/gulp/gallery.js b/build-scripts/gulp/gallery.js new file mode 100644 index 0000000000..a46c6c1939 --- /dev/null +++ b/build-scripts/gulp/gallery.js @@ -0,0 +1,38 @@ +// Run demo develop mode +const gulp = require("gulp"); + +require("./clean.js"); +require("./translations.js"); +require("./gen-icons.js"); +require("./gather-static.js"); +require("./webpack.js"); +require("./service-worker.js"); +require("./entry-html.js"); + +gulp.task( + "develop-gallery", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "development"; + }, + "clean-gallery", + gulp.parallel("gen-icons-app", "gen-icons-app", "build-translations"), + "copy-static-gallery", + "gen-index-gallery-dev", + "webpack-dev-server-gallery" + ) +); + +gulp.task( + "build-gallery", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "production"; + }, + "clean-gallery", + gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"), + "copy-static-gallery", + "webpack-prod-gallery", + "gen-index-gallery-prod" + ) +); diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index 7b3b9300be..17a1a87ae8 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -111,3 +111,15 @@ gulp.task("copy-static-cast", (done) => { copyTranslations(paths.cast_static); done(); }); + +gulp.task("copy-static-gallery", (done) => { + // Copy app static files + fs.copySync(polyPath("public/static"), paths.gallery_static); + // Copy gallery static files + fs.copySync(path.resolve(paths.gallery_dir, "public"), paths.gallery_root); + + copyMapPanel(paths.gallery_static); + copyFonts(paths.gallery_static); + copyTranslations(paths.gallery_static); + done(); +}); diff --git a/build-scripts/gulp/gen-icons.js b/build-scripts/gulp/gen-icons.js index 11548794c4..b116b8abea 100644 --- a/build-scripts/gulp/gen-icons.js +++ b/build-scripts/gulp/gen-icons.js @@ -57,18 +57,6 @@ function generateIconset(iconsetName, iconNames) { return `${iconDefs}`; } -// Generate the full MDI iconset -function genMDIIcons() { - const meta = JSON.parse( - fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8") - ); - const iconNames = meta.map((iconInfo) => iconInfo.name); - if (!fs.existsSync(OUTPUT_DIR)) { - fs.mkdirSync(OUTPUT_DIR); - } - fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames)); -} - // Helper function to map recursively over files in a folder and it's subfolders function mapFiles(startPath, filter, mapFunc) { const files = fs.readdirSync(startPath); @@ -101,24 +89,27 @@ function findIcons(searchPath, iconsetName) { return icons; } -function genHassIcons() { +gulp.task("gen-icons-mdi", (done) => { + const meta = JSON.parse( + fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8") + ); + const iconNames = meta.map((iconInfo) => iconInfo.name); + if (!fs.existsSync(OUTPUT_DIR)) { + fs.mkdirSync(OUTPUT_DIR); + } + fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames)); + done(); +}); + +gulp.task("gen-icons-app", (done) => { const iconNames = findIcons("./src", "hass"); BUILT_IN_PANEL_ICONS.forEach((name) => iconNames.add(name)); if (!fs.existsSync(OUTPUT_DIR)) { fs.mkdirSync(OUTPUT_DIR); } fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames)); -} - -gulp.task("gen-icons-mdi", (done) => { - genMDIIcons(); done(); }); -gulp.task("gen-icons-hass", (done) => { - genHassIcons(); - done(); -}); -gulp.task("gen-icons", gulp.series("gen-icons-hass", "gen-icons-mdi")); gulp.task("gen-icons-demo", (done) => { const iconNames = findIcons(path.resolve(paths.demo_dir, "./src"), "hademo"); @@ -129,8 +120,21 @@ gulp.task("gen-icons-demo", (done) => { done(); }); -module.exports = { - findIcons, - generateIconset, - genMDIIcons, -}; +gulp.task("gen-icons-hassio", (done) => { + const iconNames = findIcons( + path.resolve(paths.hassio_dir, "./src"), + "hassio" + ); + // Find hassio icons inside HA main repo. + for (const item of findIcons( + path.resolve(paths.polymer_dir, "./src"), + "hassio" + )) { + iconNames.add(item); + } + fs.writeFileSync( + path.resolve(paths.hassio_dir, "hassio-icons.html"), + generateIconset("hassio", iconNames) + ); + done(); +}); diff --git a/build-scripts/gulp/hassio.js b/build-scripts/gulp/hassio.js new file mode 100644 index 0000000000..164a5b9deb --- /dev/null +++ b/build-scripts/gulp/hassio.js @@ -0,0 +1,34 @@ +const gulp = require("gulp"); + +const envVars = require("../env"); + +require("./clean.js"); +require("./gen-icons.js"); +require("./webpack.js"); +require("./compress.js"); + +gulp.task( + "develop-hassio", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "development"; + }, + "clean-hassio", + gulp.parallel("gen-icons-hassio", "gen-icons-mdi"), + "webpack-watch-hassio" + ) +); + +gulp.task( + "build-hassio", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "production"; + }, + "clean-hassio", + gulp.parallel("gen-icons-hassio", "gen-icons-mdi"), + "webpack-prod-hassio", + ...// Don't compress running tests + (envVars.isTravis ? [] : ["compress-hassio"]) + ) +); diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index 6015009d2d..9ea304658b 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -1,6 +1,5 @@ // Tasks to run webpack. const gulp = require("gulp"); -const path = require("path"); const webpack = require("webpack"); const WebpackDevServer = require("webpack-dev-server"); const log = require("fancy-log"); @@ -9,8 +8,33 @@ const { createAppConfig, createDemoConfig, createCastConfig, + createHassioConfig, + createGalleryConfig, } = require("../webpack"); +const bothBuilds = (createConfigFunc, params) => [ + createConfigFunc({ ...params, latestBuild: true }), + createConfigFunc({ ...params, latestBuild: false }), +]; + +const runDevServer = ({ + compiler, + contentBase, + port, + listenHost = "localhost", +}) => + new WebpackDevServer(compiler, { + open: true, + watchContentBase: true, + contentBase, + }).listen(port, listenHost, function(err) { + if (err) { + throw err; + } + // Server listening + log("[webpack-dev-server]", `http://localhost:${port}`); + }); + const handler = (done) => (err, stats) => { if (err) { console.log(err.stack || err); @@ -32,20 +56,11 @@ const handler = (done) => (err, stats) => { }; gulp.task("webpack-watch-app", () => { - const compiler = webpack([ - createAppConfig({ - isProdBuild: false, - latestBuild: true, - isStatsBuild: false, - }), - createAppConfig({ - isProdBuild: false, - latestBuild: false, - isStatsBuild: false, - }), - ]); - compiler.watch({}, handler()); // we are not calling done, so this command will run forever + webpack([bothBuilds(createAppConfig, { isProdBuild: false })]).watch( + {}, + handler() + ); }); gulp.task( @@ -53,47 +68,17 @@ gulp.task( () => new Promise((resolve) => webpack( - [ - createAppConfig({ - isProdBuild: true, - latestBuild: true, - isStatsBuild: false, - }), - createAppConfig({ - isProdBuild: true, - latestBuild: false, - isStatsBuild: false, - }), - ], + bothBuilds(createAppConfig, { isProdBuild: true }), handler(resolve) ) ) ); gulp.task("webpack-dev-server-demo", () => { - const compiler = webpack([ - createDemoConfig({ - isProdBuild: false, - latestBuild: false, - isStatsBuild: false, - }), - createDemoConfig({ - isProdBuild: false, - latestBuild: true, - isStatsBuild: false, - }), - ]); - - new WebpackDevServer(compiler, { - open: true, - watchContentBase: true, - contentBase: path.resolve(paths.demo_dir, "dist"), - }).listen(8090, "localhost", function(err) { - if (err) { - throw err; - } - // Server listening - log("[webpack-dev-server]", "http://localhost:8090"); + runDevServer({ + compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })), + contentBase: paths.demo_root, + port: 8090, }); }); @@ -102,51 +87,22 @@ gulp.task( () => new Promise((resolve) => webpack( - [ - createDemoConfig({ - isProdBuild: true, - latestBuild: false, - isStatsBuild: false, - }), - createDemoConfig({ - isProdBuild: true, - latestBuild: true, - isStatsBuild: false, - }), - ], + bothBuilds(createDemoConfig, { + isProdBuild: true, + }), handler(resolve) ) ) ); gulp.task("webpack-dev-server-cast", () => { - const compiler = webpack([ - createCastConfig({ - isProdBuild: false, - latestBuild: false, - }), - createCastConfig({ - isProdBuild: false, - latestBuild: true, - }), - ]); - - new WebpackDevServer(compiler, { - open: true, - watchContentBase: true, - contentBase: path.resolve(paths.cast_dir, "dist"), - }).listen( - 8080, + runDevServer({ + compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })), + contentBase: paths.cast_root, + port: 8080, // Accessible from the network, because that's how Cast hits it. - "0.0.0.0", - function(err) { - if (err) { - throw err; - } - // Server listening - log("[webpack-dev-server]", "http://localhost:8080"); - } - ); + listenHost: "0.0.0.0", + }); }); gulp.task( @@ -154,16 +110,59 @@ gulp.task( () => new Promise((resolve) => webpack( - [ - createCastConfig({ - isProdBuild: true, - latestBuild: false, - }), - createCastConfig({ - isProdBuild: true, - latestBuild: true, - }), - ], + bothBuilds(createCastConfig, { + isProdBuild: true, + }), + + handler(resolve) + ) + ) +); + +gulp.task("webpack-watch-hassio", () => { + // we are not calling done, so this command will run forever + webpack( + createHassioConfig({ + isProdBuild: false, + latestBuild: false, + }) + ).watch({}, handler()); +}); + +gulp.task( + "webpack-prod-hassio", + () => + new Promise((resolve) => + webpack( + createHassioConfig({ + isProdBuild: true, + latestBuild: false, + }), + handler(resolve) + ) + ) +); + +gulp.task("webpack-dev-server-gallery", () => { + runDevServer({ + compiler: webpack( + createGalleryConfig({ latestBuild: true, isProdBuild: false }) + ), + contentBase: paths.gallery_root, + port: 8100, + }); +}); + +gulp.task( + "webpack-prod-gallery", + () => + new Promise((resolve) => + webpack( + createGalleryConfig({ + isProdBuild: true, + latestBuild: true, + }), + handler(resolve) ) ) diff --git a/build-scripts/paths.js b/build-scripts/paths.js index f59dc35399..4a26ab882c 100644 --- a/build-scripts/paths.js +++ b/build-scripts/paths.js @@ -20,4 +20,13 @@ module.exports = { cast_static: path.resolve(__dirname, "../cast/dist/static"), cast_output: path.resolve(__dirname, "../cast/dist/frontend_latest"), cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"), + + gallery_dir: path.resolve(__dirname, "../gallery"), + gallery_root: path.resolve(__dirname, "../gallery/dist"), + gallery_output: path.resolve(__dirname, "../gallery/dist/frontend_latest"), + gallery_static: path.resolve(__dirname, "../gallery/dist/static"), + + hassio_dir: path.resolve(__dirname, "../hassio"), + hassio_root: path.resolve(__dirname, "../hassio/build"), + hassio_publicPath: "/api/hassio/app", }; diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index bb69d91948..ac04587333 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -15,249 +15,246 @@ if (!version) { } version = version[0]; -const genMode = (isProdBuild) => (isProdBuild ? "production" : "development"); -const genDevTool = (isProdBuild) => - isProdBuild ? "source-map" : "inline-cheap-module-source-map"; -const genFilename = (isProdBuild, dontHash = new Set()) => ({ chunk }) => { - if (!isProdBuild || dontHash.has(chunk.name)) { - return `${chunk.name}.js`; - } - return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; -}; -const genChunkFilename = (isProdBuild, isStatsBuild) => - isProdBuild && !isStatsBuild ? "chunk.[chunkhash].js" : "[name].chunk.js"; - -const resolve = { - extensions: [".ts", ".js", ".json", ".tsx"], - alias: { - react: "preact-compat", - "react-dom": "preact-compat", - // Not necessary unless you consume a module using `createClass` - "create-react-class": "preact-compat/lib/create-react-class", - // Not necessary unless you consume a module requiring `react-dom-factories` - "react-dom-factories": "preact-compat/lib/react-dom-factories", - }, -}; - -const cssLoader = { - test: /\.css$/, - use: "raw-loader", -}; -const htmlLoader = { - test: /\.(html)$/, - use: { - loader: "html-loader", - options: { - exportAsEs6Default: true, - }, - }, -}; - -const plugins = [ - // Ignore moment.js locales - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), - // Color.js is bloated, it contains all color definitions for all material color sets. - new webpack.NormalModuleReplacementPlugin( - /@polymer\/paper-styles\/color\.js$/, - path.resolve(paths.polymer_dir, "src/util/empty.js") - ), - // Ignore roboto pointing at CDN. We use local font-roboto-local. - new webpack.NormalModuleReplacementPlugin( - /@polymer\/font-roboto\/roboto\.js$/, - path.resolve(paths.polymer_dir, "src/util/empty.js") - ), - // Ignore mwc icons pointing at CDN. - new webpack.NormalModuleReplacementPlugin( - /@material\/mwc-icon\/mwc-icon-font\.js$/, - path.resolve(paths.polymer_dir, "src/util/empty.js") - ), -]; - -const optimization = (latestBuild) => ({ - minimizer: [ - new TerserPlugin({ - cache: true, - parallel: true, - extractComments: true, - sourceMap: true, - terserOptions: { - safari10: true, - ecma: latestBuild ? undefined : 5, - }, - }), - ], -}); - -const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { - const isCI = process.env.CI === "true"; - - // Create an object mapping browser urls to their paths during build - const translationMetadata = require("../build-translations/translationMetadata.json"); - const workBoxTranslationsTemplatedURLs = {}; - const englishFP = translationMetadata.translations.en.fingerprints; - Object.keys(englishFP).forEach((key) => { - workBoxTranslationsTemplatedURLs[ - `/static/translations/${englishFP[key]}` - ] = `build-translations/output/${key}.json`; - }); - - const entry = { - app: "./src/entrypoints/app.ts", - authorize: "./src/entrypoints/authorize.ts", - onboarding: "./src/entrypoints/onboarding.ts", - core: "./src/entrypoints/core.ts", - compatibility: "./src/entrypoints/compatibility.ts", - "custom-panel": "./src/entrypoints/custom-panel.ts", - "hass-icons": "./src/entrypoints/hass-icons.ts", - }; - +const createWebpackConfig = ({ + entry, + outputRoot, + defineOverlay, + isProdBuild, + latestBuild, + isStatsBuild, +}) => { return { - mode: genMode(isProdBuild), - devtool: genDevTool(isProdBuild), + mode: isProdBuild ? "production" : "development", + devtool: isProdBuild ? "source-map" : "inline-cheap-module-source-map", entry, module: { - rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader], + rules: [ + babelLoaderConfig({ latestBuild }), + { + test: /\.css$/, + use: "raw-loader", + }, + { + test: /\.(html)$/, + use: { + loader: "html-loader", + options: { + exportAsEs6Default: true, + }, + }, + }, + ], + }, + optimization: { + minimizer: [ + new TerserPlugin({ + cache: true, + parallel: true, + extractComments: true, + sourceMap: true, + terserOptions: { + safari10: true, + ecma: latestBuild ? undefined : 5, + }, + }), + ], }, - optimization: optimization(latestBuild), plugins: [ new ManifestPlugin(), new webpack.DefinePlugin({ - __DEV__: JSON.stringify(!isProdBuild), - __DEMO__: false, + __DEV__: !isProdBuild, __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), __VERSION__: JSON.stringify(version), + __DEMO__: false, __STATIC_PATH__: "/static/", "process.env.NODE_ENV": JSON.stringify( isProdBuild ? "production" : "development" ), + ...defineOverlay, }), - ...plugins, - latestBuild && - new WorkboxPlugin.InjectManifest({ - swSrc: "./src/entrypoints/service-worker-hass.js", - swDest: "service_worker.js", - importWorkboxFrom: "local", - include: [/\.js$/], - templatedURLs: { - ...workBoxTranslationsTemplatedURLs, - "/static/icons/favicon-192x192.png": - "public/icons/favicon-192x192.png", - "/static/fonts/roboto/Roboto-Light.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2", - "/static/fonts/roboto/Roboto-Medium.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2", - "/static/fonts/roboto/Roboto-Regular.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2", - "/static/fonts/roboto/Roboto-Bold.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2", - }, - }), + // Ignore moment.js locales + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + // Color.js is bloated, it contains all color definitions for all material color sets. + new webpack.NormalModuleReplacementPlugin( + /@polymer\/paper-styles\/color\.js$/, + path.resolve(paths.polymer_dir, "src/util/empty.js") + ), + // Ignore roboto pointing at CDN. We use local font-roboto-local. + new webpack.NormalModuleReplacementPlugin( + /@polymer\/font-roboto\/roboto\.js$/, + path.resolve(paths.polymer_dir, "src/util/empty.js") + ), + // Ignore mwc icons pointing at CDN. + new webpack.NormalModuleReplacementPlugin( + /@material\/mwc-icon\/mwc-icon-font\.js$/, + path.resolve(paths.polymer_dir, "src/util/empty.js") + ), ].filter(Boolean), + resolve: { + extensions: [".ts", ".js", ".json", ".tsx"], + alias: { + react: "preact-compat", + "react-dom": "preact-compat", + // Not necessary unless you consume a module using `createClass` + "create-react-class": "preact-compat/lib/create-react-class", + // Not necessary unless you consume a module requiring `react-dom-factories` + "react-dom-factories": "preact-compat/lib/react-dom-factories", + }, + }, output: { - filename: genFilename(isProdBuild), - chunkFilename: genChunkFilename(isProdBuild, isStatsBuild), - path: latestBuild ? paths.output : paths.output_es5, + filename: ({ chunk }) => { + const dontHash = new Set(); + + if (!isProdBuild || dontHash.has(chunk.name)) { + return `${chunk.name}.js`; + } + return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; + }, + chunkFilename: + isProdBuild && !isStatsBuild + ? "chunk.[chunkhash].js" + : "[name].chunk.js", + path: path.resolve( + outputRoot, + latestBuild ? "frontend_latest" : "frontend_es5" + ), publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", // For workerize loader globalObject: "self", }, - resolve, }; }; +const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { + const config = createWebpackConfig({ + entry: { + app: "./src/entrypoints/app.ts", + authorize: "./src/entrypoints/authorize.ts", + onboarding: "./src/entrypoints/onboarding.ts", + core: "./src/entrypoints/core.ts", + compatibility: "./src/entrypoints/compatibility.ts", + "custom-panel": "./src/entrypoints/custom-panel.ts", + "hass-icons": "./src/entrypoints/hass-icons.ts", + }, + outputRoot: paths.root, + isProdBuild, + latestBuild, + isStatsBuild, + }); + + if (latestBuild) { + // Create an object mapping browser urls to their paths during build + const translationMetadata = require("../build-translations/translationMetadata.json"); + const workBoxTranslationsTemplatedURLs = {}; + const englishFP = translationMetadata.translations.en.fingerprints; + Object.keys(englishFP).forEach((key) => { + workBoxTranslationsTemplatedURLs[ + `/static/translations/${englishFP[key]}` + ] = `build-translations/output/${key}.json`; + }); + + config.plugins.push( + new WorkboxPlugin.InjectManifest({ + swSrc: "./src/entrypoints/service-worker-hass.js", + swDest: "service_worker.js", + importWorkboxFrom: "local", + include: [/\.js$/], + templatedURLs: { + ...workBoxTranslationsTemplatedURLs, + "/static/icons/favicon-192x192.png": + "public/icons/favicon-192x192.png", + "/static/fonts/roboto/Roboto-Light.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2", + "/static/fonts/roboto/Roboto-Medium.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2", + "/static/fonts/roboto/Roboto-Regular.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2", + "/static/fonts/roboto/Roboto-Bold.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2", + }, + }) + ); + } + + return config; +}; + const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { - return { - mode: genMode(isProdBuild), - devtool: genDevTool(isProdBuild), + return createWebpackConfig({ entry: { - main: "./demo/src/entrypoint.ts", - compatibility: "./src/entrypoints/compatibility.ts", - }, - module: { - rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader], - }, - optimization: optimization(latestBuild), - plugins: [ - new ManifestPlugin(), - new webpack.DefinePlugin({ - __DEV__: !isProdBuild, - __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), - __VERSION__: JSON.stringify(`DEMO-${version}`), - __DEMO__: true, - __STATIC_PATH__: "/static/", - "process.env.NODE_ENV": JSON.stringify( - isProdBuild ? "production" : "development" - ), - }), - ...plugins, - ].filter(Boolean), - resolve, - output: { - filename: genFilename(isProdBuild), - chunkFilename: genChunkFilename(isProdBuild, isStatsBuild), - path: path.resolve( - paths.demo_root, - latestBuild ? "frontend_latest" : "frontend_es5" + main: path.resolve(paths.demo_dir, "src/entrypoint.ts"), + compatibility: path.resolve( + paths.polymer_dir, + "src/entrypoints/compatibility.ts" ), - publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", - // For workerize loader - globalObject: "self", }, - }; + outputRoot: paths.demo_root, + defineOverlay: { + __VERSION__: JSON.stringify(`DEMO-${version}`), + __DEMO__: true, + }, + isProdBuild, + latestBuild, + isStatsBuild, + }); }; const createCastConfig = ({ isProdBuild, latestBuild }) => { - const isStatsBuild = false; const entry = { - launcher: "./cast/src/launcher/entrypoint.ts", + launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"), }; if (latestBuild) { - entry.receiver = "./cast/src/receiver/entrypoint.ts"; + entry.receiver = path.resolve(paths.cast_dir, "src/receiver/entrypoint.ts"); } - return { - mode: genMode(isProdBuild), - devtool: genDevTool(isProdBuild), + return createWebpackConfig({ entry, - module: { - rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader], + outputRoot: paths.cast_root, + isProdBuild, + latestBuild, + }); +}; + +const createHassioConfig = ({ isProdBuild, latestBuild }) => { + if (latestBuild) { + throw new Error("Hass.io does not support latest build!"); + } + const config = createWebpackConfig({ + entry: { + entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.js"), }, - optimization: optimization(latestBuild), - plugins: [ - new ManifestPlugin(), - new webpack.DefinePlugin({ - __DEV__: !isProdBuild, - __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), - __VERSION__: JSON.stringify(version), - __DEMO__: false, - __STATIC_PATH__: "/static/", - "process.env.NODE_ENV": JSON.stringify( - isProdBuild ? "production" : "development" - ), - }), - ...plugins, - ].filter(Boolean), - resolve, - output: { - filename: genFilename(isProdBuild), - chunkFilename: genChunkFilename(isProdBuild, isStatsBuild), - path: path.resolve( - paths.cast_root, - latestBuild ? "frontend_latest" : "frontend_es5" - ), - publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", - // For workerize loader - globalObject: "self", + outputRoot: "", + isProdBuild, + latestBuild, + }); + + config.output.path = paths.hassio_root; + config.output.publicPath = paths.hassio_publicPath; + + return config; +}; + +const createGalleryConfig = ({ isProdBuild, latestBuild }) => { + if (!latestBuild) { + throw new Error("Gallery only supports latest build!"); + } + const config = createWebpackConfig({ + entry: { + entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"), }, - }; + outputRoot: paths.gallery_root, + isProdBuild, + latestBuild, + }); + + return config; }; module.exports = { - resolve, - plugins, - optimization, createAppConfig, createDemoConfig, createCastConfig, + createHassioConfig, + createGalleryConfig, }; diff --git a/cast/webpack.config.js b/cast/webpack.config.js new file mode 100644 index 0000000000..bb9746cd11 --- /dev/null +++ b/cast/webpack.config.js @@ -0,0 +1,11 @@ +const { createCastConfig } = require("../build-scripts/webpack.js"); +const { isProdBuild } = require("../build-scripts/env.js"); + +// File just used for stats builds + +const latestBuild = true; + +module.exports = createCastConfig({ + isProdBuild, + latestBuild, +}); diff --git a/demo/webpack.config.js b/demo/webpack.config.js index 266523cdbe..9ccb790f8b 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -1,10 +1,9 @@ const { createDemoConfig } = require("../build-scripts/webpack.js"); +const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js"); -// This file exists because we haven't migrated the stats script yet +// File just used for stats builds -const isProdBuild = process.env.NODE_ENV === "production"; -const isStatsBuild = process.env.STATS === "1"; -const latestBuild = false; +const latestBuild = true; module.exports = createDemoConfig({ isProdBuild, diff --git a/gallery/public/index.html b/gallery/public/index.html deleted file mode 100644 index 7339634a60..0000000000 --- a/gallery/public/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - HAGallery - - - - - diff --git a/gallery/script/build_gallery b/gallery/script/build_gallery index cbf70c6d4e..3a7c77dde7 100755 --- a/gallery/script/build_gallery +++ b/gallery/script/build_gallery @@ -4,14 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -OUTPUT_DIR=dist - -rm -rf $OUTPUT_DIR - -cd .. -./node_modules/.bin/gulp build-translations gen-icons -cd gallery - -NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js +./node_modules/.bin/gulp build-gallery diff --git a/gallery/script/develop_gallery b/gallery/script/develop_gallery index 56bb2c678b..b346ea60bc 100755 --- a/gallery/script/develop_gallery +++ b/gallery/script/develop_gallery @@ -4,10 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -cd .. -./node_modules/.bin/gulp build-translations gen-icons -cd gallery - -../node_modules/.bin/webpack-dev-server +./node_modules/.bin/gulp develop-gallery diff --git a/gallery/src/components/demo-cards.js b/gallery/src/components/demo-cards.js index a73bc53a2f..db01c7fe13 100644 --- a/gallery/src/components/demo-cards.js +++ b/gallery/src/components/demo-cards.js @@ -26,7 +26,9 @@ class DemoCards extends PolymerElement {
- Show config + + Show config +
@@ -51,6 +53,10 @@ class DemoCards extends PolymerElement { }, }; } + + _showConfigToggled(ev) { + this._showConfig = ev.target.checked; + } } customElements.define("demo-cards", DemoCards); diff --git a/gallery/src/html/index.html.template b/gallery/src/html/index.html.template new file mode 100644 index 0000000000..3240e59bb0 --- /dev/null +++ b/gallery/src/html/index.html.template @@ -0,0 +1,22 @@ + + + + + + + HAGallery + + + + + + diff --git a/gallery/webpack.config.js b/gallery/webpack.config.js index dc03993c98..9bc82eedc3 100644 --- a/gallery/webpack.config.js +++ b/gallery/webpack.config.js @@ -1,6 +1,6 @@ const path = require("path"); const CopyWebpackPlugin = require("copy-webpack-plugin"); -const webpackBase = require("../build-scripts/webpack.js"); +const { createGalleryConfig } = require("../build-scripts/webpack.js"); const { babelLoaderConfig } = require("../build-scripts/babel.js"); const isProd = process.env.NODE_ENV === "production"; @@ -9,58 +9,64 @@ const buildPath = path.resolve(__dirname, "dist"); const publicPath = isProd ? "./" : "http://localhost:8080/"; const latestBuild = true; -module.exports = { - mode: isProd ? "production" : "development", - // Disabled in prod while we make Home Assistant able to serve the right files. - // Was source-map - devtool: isProd ? "none" : "inline-source-map", - entry: "./src/entrypoint.js", - module: { - rules: [ - babelLoaderConfig({ latestBuild }), - { - test: /\.css$/, - use: "raw-loader", - }, - { - test: /\.(html)$/, - use: { - loader: "html-loader", - options: { - exportAsEs6Default: true, +module.exports = createGalleryConfig({ + latestBuild: true, +}); + +const bla = () => { + const oldExports = { + mode: isProd ? "production" : "development", + // Disabled in prod while we make Home Assistant able to serve the right files. + // Was source-map + devtool: isProd ? "none" : "inline-source-map", + entry: "./src/entrypoint.js", + module: { + rules: [ + babelLoaderConfig({ latestBuild }), + { + test: /\.css$/, + use: "raw-loader", + }, + { + test: /\.(html)$/, + use: { + loader: "html-loader", + options: { + exportAsEs6Default: true, + }, }, }, - }, - ], - }, - optimization: webpackBase.optimization(latestBuild), - plugins: [ - new CopyWebpackPlugin([ - "public", - { from: "../public", to: "static" }, - { from: "../build-translations/output", to: "static/translations" }, - { - from: "../node_modules/leaflet/dist/leaflet.css", - to: "static/images/leaflet/", - }, - { - from: "../node_modules/roboto-fontface/fonts/roboto/*.woff2", - to: "static/fonts/roboto/", - }, - { - from: "../node_modules/leaflet/dist/images", - to: "static/images/leaflet/", - }, - ]), - ].filter(Boolean), - resolve: webpackBase.resolve, - output: { - filename: "[name].js", - chunkFilename: chunkFilename, - path: buildPath, - publicPath, - }, - devServer: { - contentBase: "./public", - }, + ], + }, + optimization: webpackBase.optimization(latestBuild), + plugins: [ + new CopyWebpackPlugin([ + "public", + { from: "../public", to: "static" }, + { from: "../build-translations/output", to: "static/translations" }, + { + from: "../node_modules/leaflet/dist/leaflet.css", + to: "static/images/leaflet/", + }, + { + from: "../node_modules/roboto-fontface/fonts/roboto/*.woff2", + to: "static/fonts/roboto/", + }, + { + from: "../node_modules/leaflet/dist/images", + to: "static/images/leaflet/", + }, + ]), + ].filter(Boolean), + resolve: webpackBase.resolve, + output: { + filename: "[name].js", + chunkFilename: chunkFilename, + path: buildPath, + publicPath, + }, + devServer: { + contentBase: "./public", + }, + }; }; diff --git a/hassio/script/build_hassio b/hassio/script/build_hassio index b8bd6d504e..193cbb0687 100755 --- a/hassio/script/build_hassio +++ b/hassio/script/build_hassio @@ -4,11 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -OUTPUT_DIR=build - -rm -rf $OUTPUT_DIR - -node script/gen-icons.js -NODE_ENV=production CI=false ../node_modules/.bin/webpack -p --config webpack.config.js +./node_modules/.bin/gulp build-hassio diff --git a/hassio/script/develop b/hassio/script/develop index c23fc2ea1f..0b62666b10 100755 --- a/hassio/script/develop +++ b/hassio/script/develop @@ -4,11 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -OUTPUT_DIR=build - -rm -rf $OUTPUT_DIR -mkdir $OUTPUT_DIR -node script/gen-icons.js -../node_modules/.bin/webpack --watch --progress +./node_modules/.bin/gulp develop-hassio diff --git a/hassio/script/gen-icons.js b/hassio/script/gen-icons.js deleted file mode 100755 index b355ef752b..0000000000 --- a/hassio/script/gen-icons.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node -const fs = require("fs"); -const { - findIcons, - generateIconset, - genMDIIcons, -} = require("../../build-scripts/gulp/gen-icons.js"); - -function genHassioIcons() { - const iconNames = findIcons("./src", "hassio"); - - for (const item of findIcons("../src", "hassio")) { - iconNames.add(item); - } - - fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames)); -} - -genMDIIcons(); -genHassioIcons(); diff --git a/hassio/webpack.config.js b/hassio/webpack.config.js index 630778c69e..02e44c0d74 100644 --- a/hassio/webpack.config.js +++ b/hassio/webpack.config.js @@ -1,63 +1,11 @@ -const webpack = require("webpack"); -const CompressionPlugin = require("compression-webpack-plugin"); -const zopfli = require("@gfx/zopfli"); +const { createHassioConfig } = require("../build-scripts/webpack.js"); +const { isProdBuild } = require("../build-scripts/env.js"); -const config = require("./config.js"); -const webpackBase = require("../build-scripts/webpack.js"); -const { babelLoaderConfig } = require("../build-scripts/babel.js"); +// File just used for stats builds -const isProdBuild = process.env.NODE_ENV === "production"; -const isCI = process.env.CI === "true"; -const chunkFilename = isProdBuild ? "chunk.[chunkhash].js" : "[name].chunk.js"; const latestBuild = false; -module.exports = { - mode: isProdBuild ? "production" : "development", - devtool: isProdBuild ? "source-map" : "inline-source-map", - entry: { - entrypoint: "./src/entrypoint.js", - }, - module: { - rules: [ - babelLoaderConfig({ latestBuild }), - { - test: /\.(html)$/, - use: { - loader: "html-loader", - options: { - exportAsEs6Default: true, - }, - }, - }, - ], - }, - optimization: webpackBase.optimization(latestBuild), - plugins: [ - new webpack.DefinePlugin({ - __DEV__: JSON.stringify(!isProdBuild), - __DEMO__: false, - __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), - "process.env.NODE_ENV": JSON.stringify( - isProdBuild ? "production" : "development" - ), - }), - isProdBuild && - !isCI && - new CompressionPlugin({ - cache: true, - exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/], - algorithm(input, compressionOptions, callback) { - return zopfli.gzip(input, compressionOptions, callback); - }, - }), - ].filter(Boolean), - resolve: { - extensions: [".ts", ".js", ".json"], - }, - output: { - filename: "[name].js", - chunkFilename, - path: config.buildDir, - publicPath: `${config.publicPath}/`, - }, -}; +module.exports = createHassioConfig({ + isProdBuild, + latestBuild, +}); diff --git a/webpack.config.js b/webpack.config.js index 49df18dfec..ff1b7462be 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,10 +1,8 @@ const { createAppConfig } = require("./build-scripts/webpack.js"); +const { isProdBuild, isStatsBuild } = require("./build-scripts/env.js"); // This file exists because we haven't migrated the stats script yet -const isProdBuild = process.env.NODE_ENV === "production"; -const isStatsBuild = process.env.STATS === "1"; - const configs = [ createAppConfig({ isProdBuild,