From c5b223988aa91f124768b31c568853f8f91def32 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 22 May 2020 10:02:05 -0700 Subject: [PATCH] Split generic bundle config from webpack config (#5917) --- build-scripts/babel.js | 5 +- build-scripts/bundle.js | 160 ++++++++++++++++++++++++++++++++++++ build-scripts/webpack.js | 170 ++++++++++++--------------------------- 3 files changed, 213 insertions(+), 122 deletions(-) create mode 100644 build-scripts/bundle.js diff --git a/build-scripts/babel.js b/build-scripts/babel.js index 8e8abee81c..978d84899e 100644 --- a/build-scripts/babel.js +++ b/build-scripts/babel.js @@ -1,4 +1,4 @@ -const options = ({ latestBuild }) => ({ +module.exports.options = ({ latestBuild }) => ({ presets: [ !latestBuild && [require("@babel/preset-env").default, { modules: false }], require("@babel/preset-typescript").default, @@ -30,10 +30,11 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => { } return { test: /\.m?js$|\.tsx?$/, + // Are already ES5, cause warnings when babelified. exclude: [require.resolve("@mdi/js/mdi.js"), require.resolve("hls.js")], use: { loader: "babel-loader", - options: options({ latestBuild }), + options: module.exports.options({ latestBuild }), }, }; }; diff --git a/build-scripts/bundle.js b/build-scripts/bundle.js new file mode 100644 index 0000000000..7627d8588b --- /dev/null +++ b/build-scripts/bundle.js @@ -0,0 +1,160 @@ +const path = require("path"); +const env = require("./env.js"); +const paths = require("./paths.js"); + +// Files from NPM Packages that should not be imported +module.exports.ignorePackages = [ + // Bloats bundle and it's not used. + path.resolve(require.resolve("moment"), "../locale"), + // Part of yaml.js and only used for !!js functions that we don't use + require.resolve("esprima"), +]; + +// Files from NPM packages that we should replace with empty file +module.exports.emptyPackages = [ + // Contains all color definitions for all material color sets. + // We don't use it + require.resolve("@polymer/paper-styles/color.js"), + // Loads stuff from a CDN + require.resolve("@polymer/font-roboto/roboto.js"), + require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"), +]; + +module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ + __DEV__: !isProdBuild, + __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), + __VERSION__: JSON.stringify(env.version()), + __DEMO__: false, + __BACKWARDS_COMPAT__: false, + __STATIC_PATH__: "/static/", + "process.env.NODE_ENV": JSON.stringify( + isProdBuild ? "production" : "development" + ), + ...defineOverlay, +}); + +module.exports.terserOptions = (latestBuild) => ({ + safari10: true, + ecma: latestBuild ? undefined : 5, +}); + +const outputPath = (outputRoot, latestBuild) => + path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5"); + +const publicPath = (latestBuild) => + latestBuild ? "/frontend_latest/" : "/frontend_es5/"; + +/* +BundleConfig { + // Object with entrypoints that need to be bundled + entry: { [name: string]: pathToFile }, + // Folder where bundled files need to be written + outputPath: string, + // absolute url-path where bundled files can be found + publicPath: string, + // extra definitions that we need to replace in source + defineOverlay: {[name: string]: value }, + // if this is a production build + isProdBuild: boolean, + // If we're targeting latest browsers + latestBuild: boolean, + // If we're doing a stats build (create nice chunk names) + isStatsBuild: boolean, + // Names of entrypoints that should not be hashed + dontHash: Set +} +*/ + +module.exports.config = { + app({ isProdBuild, latestBuild, isStatsBuild }) { + return { + entry: { + service_worker: "./src/entrypoints/service_worker.ts", + 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", + }, + outputPath: outputPath(paths.root, latestBuild), + publicPath: publicPath(latestBuild), + isProdBuild, + latestBuild, + isStatsBuild, + }; + }, + + demo({ isProdBuild, latestBuild, isStatsBuild }) { + return { + entry: { + main: path.resolve(paths.demo_dir, "src/entrypoint.ts"), + compatibility: path.resolve( + paths.polymer_dir, + "src/entrypoints/compatibility.ts" + ), + }, + outputPath: outputPath(paths.demo_root, latestBuild), + publicPath: publicPath(latestBuild), + defineOverlay: { + __VERSION__: JSON.stringify(`DEMO-${env.version()}`), + __DEMO__: true, + }, + isProdBuild, + latestBuild, + isStatsBuild, + }; + }, + + cast({ isProdBuild, latestBuild }) { + const entry = { + launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"), + }; + + if (latestBuild) { + entry.receiver = path.resolve( + paths.cast_dir, + "src/receiver/entrypoint.ts" + ); + } + + return { + entry, + outputPath: outputPath(paths.cast_root, latestBuild), + publicPath: publicPath(latestBuild), + isProdBuild, + latestBuild, + defineOverlay: { + __BACKWARDS_COMPAT__: true, + }, + }; + }, + + hassio({ isProdBuild, latestBuild }) { + if (latestBuild) { + throw new Error("Hass.io does not support latest build!"); + } + return { + entry: { + entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"), + }, + outputPath: paths.hassio_root, + publicPath: paths.hassio_publicPath, + isProdBuild, + latestBuild, + dontHash: new Set(["entrypoint"]), + }; + }, + + gallery({ isProdBuild, latestBuild }) { + return { + entry: { + entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"), + }, + outputPath: outputPath(paths.gallery_root, latestBuild), + publicPath: publicPath(latestBuild), + isProdBuild, + latestBuild, + }; + }, +}; diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index f5b397888f..7b88936d8a 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -4,12 +4,13 @@ const TerserPlugin = require("terser-webpack-plugin"); const ManifestPlugin = require("webpack-manifest-plugin"); const WorkerPlugin = require("worker-plugin"); const paths = require("./paths.js"); -const env = require("./env.js"); const { babelLoaderConfig } = require("./babel.js"); +const bundle = require("./bundle"); const createWebpackConfig = ({ entry, - outputRoot, + outputPath, + publicPath, defineOverlay, isProdBuild, latestBuild, @@ -25,6 +26,7 @@ const createWebpackConfig = ({ ? "cheap-module-source-map" : "eval-cheap-module-source-map", entry, + node: false, module: { rules: [ babelLoaderConfig({ latestBuild }), @@ -34,9 +36,6 @@ const createWebpackConfig = ({ }, ], }, - externals: { - esprima: "esprima", - }, optimization: { minimizer: [ new TerserPlugin({ @@ -44,10 +43,7 @@ const createWebpackConfig = ({ parallel: true, extractComments: true, sourceMap: true, - terserOptions: { - safari10: true, - ecma: latestBuild ? undefined : 5, - }, + terserOptions: bundle.terserOptions(latestBuild), }), ], }, @@ -57,40 +53,40 @@ const createWebpackConfig = ({ // Only include the JS of entrypoints filter: (file) => file.isInitial && !file.name.endsWith(".map"), }), - new webpack.DefinePlugin({ - __DEV__: !isProdBuild, - __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), - __VERSION__: JSON.stringify(env.version()), - __DEMO__: false, - __BACKWARDS_COMPAT__: false, - __STATIC_PATH__: "/static/", - "process.env.NODE_ENV": JSON.stringify( - isProdBuild ? "production" : "development" - ), - ...defineOverlay, + new webpack.DefinePlugin( + bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) + ), + new webpack.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") + ) { + return false; + } + let fullPath; + try { + fullPath = resource.startsWith(".") + ? path.resolve(context, resource) + : require.resolve(resource); + } catch (err) { + console.error("Error in ignore plugin", resource, context); + throw err; + } + + return bundle.ignorePackages.some((toIgnorePath) => + fullPath.startsWith(toIgnorePath) + ); + }, }), - // 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$/, + new RegExp(bundle.emptyPackages.join("|")), 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") - ), - new webpack.NormalModuleReplacementPlugin( - /@vaadin\/vaadin-material-styles\/font-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"], }, @@ -105,11 +101,8 @@ const createWebpackConfig = ({ isProdBuild && !isStatsBuild ? "chunk.[chunkhash].js" : "[name].chunk.js", - path: path.resolve( - outputRoot, - latestBuild ? "frontend_latest" : "frontend_es5" - ), - publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", + path: outputPath, + publicPath, // To silence warning in worker plugin globalObject: "self", }, @@ -117,94 +110,31 @@ const createWebpackConfig = ({ }; const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { - return createWebpackConfig({ - entry: { - service_worker: "./src/entrypoints/service_worker.ts", - 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", - }, - outputRoot: paths.root, - isProdBuild, - latestBuild, - isStatsBuild, - }); + return createWebpackConfig( + bundle.config.app({ isProdBuild, latestBuild, isStatsBuild }) + ); }; const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { - return createWebpackConfig({ - entry: { - main: path.resolve(paths.demo_dir, "src/entrypoint.ts"), - compatibility: path.resolve( - paths.polymer_dir, - "src/entrypoints/compatibility.ts" - ), - }, - outputRoot: paths.demo_root, - defineOverlay: { - __VERSION__: JSON.stringify(`DEMO-${env.version()}`), - __DEMO__: true, - }, - isProdBuild, - latestBuild, - isStatsBuild, - }); + return createWebpackConfig( + bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) + ); }; const createCastConfig = ({ isProdBuild, latestBuild }) => { - const entry = { - launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"), - }; - - if (latestBuild) { - entry.receiver = path.resolve(paths.cast_dir, "src/receiver/entrypoint.ts"); - } - - return createWebpackConfig({ - entry, - outputRoot: paths.cast_root, - isProdBuild, - latestBuild, - defineOverlay: { - __BACKWARDS_COMPAT__: true, - }, - }); + return createWebpackConfig(bundle.config.cast({ 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.ts"), - }, - outputRoot: "", - isProdBuild, - latestBuild, - dontHash: new Set(["entrypoint"]), - }); - - config.output.path = paths.hassio_root; - config.output.publicPath = paths.hassio_publicPath; - - return config; + return createWebpackConfig( + bundle.config.hassio({ isProdBuild, latestBuild }) + ); }; const createGalleryConfig = ({ isProdBuild, latestBuild }) => { - const config = createWebpackConfig({ - entry: { - entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"), - }, - outputRoot: paths.gallery_root, - isProdBuild, - latestBuild, - }); - - return config; + return createWebpackConfig( + bundle.config.gallery({ isProdBuild, latestBuild }) + ); }; module.exports = {