diff --git a/.eslintrc.json b/.eslintrc.json
index 553dba0c4a..78f9b4b623 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -20,7 +20,7 @@
   "settings": {
     "import/resolver": {
       "webpack": {
-        "config": "./webpack.config.js"
+        "config": "./webpack.config.cjs"
       }
     }
   },
diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml
index a79cfb5249..224b1b49e0 100644
--- a/.github/workflows/nightly.yaml
+++ b/.github/workflows/nightly.yaml
@@ -43,7 +43,7 @@ jobs:
           LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
 
       - name: Bump version
-        run: script/version_bump.js nightly
+        run: script/version_bump.cjs nightly
 
       - name: Build nightly Python wheels
         run: |
diff --git a/build-scripts/babel-plugins/inline-constants-plugin.js b/build-scripts/babel-plugins/inline-constants-plugin.cjs
similarity index 100%
rename from build-scripts/babel-plugins/inline-constants-plugin.js
rename to build-scripts/babel-plugins/inline-constants-plugin.cjs
diff --git a/build-scripts/bundle.js b/build-scripts/bundle.cjs
similarity index 98%
rename from build-scripts/bundle.js
rename to build-scripts/bundle.cjs
index fd4070ea59..801a766932 100644
--- a/build-scripts/bundle.js
+++ b/build-scripts/bundle.cjs
@@ -1,6 +1,6 @@
 const path = require("path");
-const env = require("./env.js");
-const paths = require("./paths.js");
+const env = require("./env.cjs");
+const paths = require("./paths.cjs");
 
 // 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
@@ -99,7 +99,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
     [
       path.resolve(
         paths.polymer_dir,
-        "build-scripts/babel-plugins/inline-constants-plugin.js"
+        "build-scripts/babel-plugins/inline-constants-plugin.cjs"
       ),
       {
         modules: ["@mdi/js"],
diff --git a/build-scripts/env.js b/build-scripts/env.cjs
similarity index 95%
rename from build-scripts/env.js
rename to build-scripts/env.cjs
index 034e069181..bb0d6b3d00 100644
--- a/build-scripts/env.js
+++ b/build-scripts/env.cjs
@@ -1,6 +1,6 @@
 const fs = require("fs");
 const path = require("path");
-const paths = require("./paths.js");
+const paths = require("./paths.cjs");
 
 module.exports = {
   useRollup() {
diff --git a/build-scripts/gulp/app.js b/build-scripts/gulp/app.cjs
similarity index 74%
rename from build-scripts/gulp/app.js
rename to build-scripts/gulp/app.cjs
index f97a210930..0b6c49354c 100644
--- a/build-scripts/gulp/app.js
+++ b/build-scripts/gulp/app.cjs
@@ -1,18 +1,18 @@
 // Run HA develop mode
 
 const gulp = require("gulp");
-const env = require("../env");
-require("./clean.js");
-require("./translations.js");
-require("./locale-data.js");
-require("./gen-icons-json.js");
-require("./gather-static.js");
-require("./compress.js");
-require("./webpack.js");
-require("./service-worker.js");
-require("./entry-html.js");
-require("./rollup.js");
-require("./wds.js");
+const env = require("../env.cjs");
+require("./clean.cjs");
+require("./translations.cjs");
+require("./locale-data.cjs");
+require("./gen-icons-json.cjs");
+require("./gather-static.cjs");
+require("./compress.cjs");
+require("./webpack.cjs");
+require("./service-worker.cjs");
+require("./entry-html.cjs");
+require("./rollup.cjs");
+require("./wds.cjs");
 
 gulp.task(
   "develop-app",
diff --git a/build-scripts/gulp/cast.js b/build-scripts/gulp/cast.cjs
similarity index 77%
rename from build-scripts/gulp/cast.js
rename to build-scripts/gulp/cast.cjs
index 3623cc9925..4dbc9b1eff 100644
--- a/build-scripts/gulp/cast.js
+++ b/build-scripts/gulp/cast.cjs
@@ -1,14 +1,13 @@
 const gulp = require("gulp");
+const env = require("../env.cjs");
 
-const env = require("../env");
-
-require("./clean.js");
-require("./translations.js");
-require("./gather-static.js");
-require("./webpack.js");
-require("./service-worker.js");
-require("./entry-html.js");
-require("./rollup.js");
+require("./clean.cjs");
+require("./translations.cjs");
+require("./gather-static.cjs");
+require("./webpack.cjs");
+require("./service-worker.cjs");
+require("./entry-html.cjs");
+require("./rollup.cjs");
 
 gulp.task(
   "develop-cast",
diff --git a/build-scripts/gulp/clean.js b/build-scripts/gulp/clean.cjs
similarity index 92%
rename from build-scripts/gulp/clean.js
rename to build-scripts/gulp/clean.cjs
index 726e932ced..1dbeca11a3 100644
--- a/build-scripts/gulp/clean.js
+++ b/build-scripts/gulp/clean.cjs
@@ -1,7 +1,7 @@
 const del = import("del");
 const gulp = require("gulp");
-const paths = require("../paths");
-require("./translations");
+const paths = require("../paths.cjs");
+require("./translations.cjs");
 
 gulp.task(
   "clean",
diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.cjs
similarity index 97%
rename from build-scripts/gulp/compress.js
rename to build-scripts/gulp/compress.cjs
index d247d0d1c9..73d4090de3 100644
--- a/build-scripts/gulp/compress.js
+++ b/build-scripts/gulp/compress.cjs
@@ -4,7 +4,7 @@ const gulp = require("gulp");
 const zopfli = require("gulp-zopfli-green");
 const merge = require("merge-stream");
 const path = require("path");
-const paths = require("../paths");
+const paths = require("../paths.cjs");
 
 const zopfliOptions = { threshold: 150 };
 
diff --git a/build-scripts/gulp/demo.js b/build-scripts/gulp/demo.cjs
similarity index 77%
rename from build-scripts/gulp/demo.js
rename to build-scripts/gulp/demo.cjs
index 25f1670d26..65d8035c66 100644
--- a/build-scripts/gulp/demo.js
+++ b/build-scripts/gulp/demo.cjs
@@ -1,16 +1,15 @@
 // Run demo develop mode
 const gulp = require("gulp");
+const env = require("../env.cjs");
 
-const env = require("../env");
-
-require("./clean.js");
-require("./translations.js");
-require("./gen-icons-json.js");
-require("./gather-static.js");
-require("./webpack.js");
-require("./service-worker.js");
-require("./entry-html.js");
-require("./rollup.js");
+require("./clean.cjs");
+require("./translations.cjs");
+require("./gen-icons-json.cjs");
+require("./gather-static.cjs");
+require("./webpack.cjs");
+require("./service-worker.cjs");
+require("./entry-html.cjs");
+require("./rollup.cjs");
 
 gulp.task(
   "develop-demo",
diff --git a/build-scripts/gulp/download_translations.js b/build-scripts/gulp/download_translations.cjs
similarity index 100%
rename from build-scripts/gulp/download_translations.js
rename to build-scripts/gulp/download_translations.cjs
diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.cjs
similarity index 99%
rename from build-scripts/gulp/entry-html.js
rename to build-scripts/gulp/entry-html.cjs
index 14ead700c9..ffb569f7ed 100644
--- a/build-scripts/gulp/entry-html.js
+++ b/build-scripts/gulp/entry-html.cjs
@@ -4,9 +4,9 @@ const fs = require("fs-extra");
 const path = require("path");
 const template = require("lodash.template");
 const { minify } = require("html-minifier-terser");
-const paths = require("../paths.js");
-const env = require("../env.js");
-const { htmlMinifierOptions, terserOptions } = require("../bundle.js");
+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`);
diff --git a/build-scripts/gulp/fetch-nightly-translations.js b/build-scripts/gulp/fetch-nightly-translations.cjs
similarity index 100%
rename from build-scripts/gulp/fetch-nightly-translations.js
rename to build-scripts/gulp/fetch-nightly-translations.cjs
diff --git a/build-scripts/gulp/gallery.js b/build-scripts/gulp/gallery.cjs
similarity index 92%
rename from build-scripts/gulp/gallery.js
rename to build-scripts/gulp/gallery.cjs
index a1529ff7ec..25b53332ce 100644
--- a/build-scripts/gulp/gallery.js
+++ b/build-scripts/gulp/gallery.cjs
@@ -6,17 +6,17 @@ const { marked } = require("marked");
 const glob = require("glob");
 const yaml = require("js-yaml");
 
-const env = require("../env");
-const paths = require("../paths");
+const env = require("../env.cjs");
+const paths = require("../paths.cjs");
 
-require("./clean.js");
-require("./translations.js");
-require("./gen-icons-json.js");
-require("./gather-static.js");
-require("./webpack.js");
-require("./service-worker.js");
-require("./entry-html.js");
-require("./rollup.js");
+require("./clean.cjs");
+require("./translations.cjs");
+require("./gen-icons-json.cjs");
+require("./gather-static.cjs");
+require("./webpack.cjs");
+require("./service-worker.cjs");
+require("./entry-html.cjs");
+require("./rollup.cjs");
 
 gulp.task("gather-gallery-pages", async function gatherPages() {
   const pageDir = path.resolve(paths.gallery_dir, "src/pages");
@@ -89,9 +89,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 
   // Generate sidebar
   const sidebarPath = path.resolve(paths.gallery_dir, "sidebar.js");
-  // To make watch work during development
-  delete require.cache[sidebarPath];
-  const sidebar = require(sidebarPath);
+  const sidebar = (await import(sidebarPath)).default;
 
   const pagesToProcess = {};
   for (const key of processed) {
diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.cjs
similarity index 99%
rename from build-scripts/gulp/gather-static.js
rename to build-scripts/gulp/gather-static.cjs
index 306ae3158a..fe1ed0e443 100644
--- a/build-scripts/gulp/gather-static.js
+++ b/build-scripts/gulp/gather-static.cjs
@@ -3,7 +3,7 @@
 const gulp = require("gulp");
 const path = require("path");
 const fs = require("fs-extra");
-const paths = require("../paths");
+const paths = require("../paths.cjs");
 
 const npmPath = (...parts) =>
   path.resolve(paths.polymer_dir, "node_modules", ...parts);
diff --git a/build-scripts/gulp/gen-icons-json.js b/build-scripts/gulp/gen-icons-json.cjs
similarity index 97%
rename from build-scripts/gulp/gen-icons-json.js
rename to build-scripts/gulp/gen-icons-json.cjs
index 8a69ba97af..44dafc3a7a 100644
--- a/build-scripts/gulp/gen-icons-json.js
+++ b/build-scripts/gulp/gen-icons-json.cjs
@@ -134,11 +134,11 @@ gulp.task("gen-icons-json", (done) => {
   });
 
   const file = fs.readFileSync(PACKAGE_PATH, { encoding });
-  const package = JSON.parse(file);
+  const packageMeta = JSON.parse(file);
 
   fs.writeFileSync(
     path.resolve(OUTPUT_DIR, "iconMetadata.json"),
-    JSON.stringify({ version: package.version, parts })
+    JSON.stringify({ version: packageMeta.version, parts })
   );
 
   fs.writeFileSync(
diff --git a/build-scripts/gulp/hassio.js b/build-scripts/gulp/hassio.cjs
similarity index 78%
rename from build-scripts/gulp/hassio.js
rename to build-scripts/gulp/hassio.cjs
index e1bfcb86e6..562ed3b646 100644
--- a/build-scripts/gulp/hassio.js
+++ b/build-scripts/gulp/hassio.cjs
@@ -1,13 +1,13 @@
 const gulp = require("gulp");
-const env = require("../env");
-require("./clean.js");
-require("./gen-icons-json.js");
-require("./webpack.js");
-require("./compress.js");
-require("./rollup.js");
-require("./gather-static.js");
-require("./translations.js");
-require("./gen-icons-json.js");
+const env = require("../env.cjs");
+require("./clean.cjs");
+require("./compress.cjs");
+require("./entry-html.cjs");
+require("./gather-static.cjs");
+require("./gen-icons-json.cjs");
+require("./rollup.cjs");
+require("./translations.cjs");
+require("./webpack.cjs");
 
 gulp.task(
   "develop-hassio",
diff --git a/build-scripts/gulp/locale-data.js b/build-scripts/gulp/locale-data.cjs
similarity index 98%
rename from build-scripts/gulp/locale-data.js
rename to build-scripts/gulp/locale-data.cjs
index 328ddccf31..00bf705f43 100755
--- a/build-scripts/gulp/locale-data.js
+++ b/build-scripts/gulp/locale-data.cjs
@@ -2,7 +2,7 @@ const del = import("del");
 const path = require("path");
 const gulp = require("gulp");
 const fs = require("fs");
-const paths = require("../paths");
+const paths = require("../paths.cjs");
 
 const outDir = "build/locale-data";
 
diff --git a/build-scripts/gulp/rollup.js b/build-scripts/gulp/rollup.cjs
similarity index 97%
rename from build-scripts/gulp/rollup.js
rename to build-scripts/gulp/rollup.cjs
index fb16127eb0..8637ff8879 100644
--- a/build-scripts/gulp/rollup.js
+++ b/build-scripts/gulp/rollup.cjs
@@ -6,8 +6,8 @@ const handler = require("serve-handler");
 const http = require("http");
 const log = require("fancy-log");
 const open = require("open");
-const rollupConfig = require("../rollup");
-const paths = require("../paths");
+const rollupConfig = require("../rollup.cjs");
+const paths = require("../paths.cjs");
 
 const bothBuilds = (createConfigFunc, params) =>
   gulp.series(
@@ -46,7 +46,7 @@ function createServer(serveOptions) {
   );
 }
 
-function watchRollup(createConfig, extraWatchSrc = [], serveOptions) {
+function watchRollup(createConfig, extraWatchSrc = [], serveOptions = null) {
   const { inputOptions, outputOptions } = createConfig({
     isProdBuild: false,
     latestBuild: true,
diff --git a/build-scripts/gulp/service-worker.js b/build-scripts/gulp/service-worker.cjs
similarity index 98%
rename from build-scripts/gulp/service-worker.js
rename to build-scripts/gulp/service-worker.cjs
index 05b4cd39aa..c32c4226c8 100644
--- a/build-scripts/gulp/service-worker.js
+++ b/build-scripts/gulp/service-worker.cjs
@@ -5,7 +5,7 @@ const path = require("path");
 const fs = require("fs-extra");
 const workboxBuild = require("workbox-build");
 const sourceMapUrl = require("source-map-url");
-const paths = require("../paths.js");
+const paths = require("../paths.cjs");
 
 const swDest = path.resolve(paths.app_output_root, "service_worker.js");
 
diff --git a/build-scripts/gulp/translations.js b/build-scripts/gulp/translations.cjs
similarity index 98%
rename from build-scripts/gulp/translations.js
rename to build-scripts/gulp/translations.cjs
index d3f9b7e1d2..bfa76891dc 100755
--- a/build-scripts/gulp/translations.js
+++ b/build-scripts/gulp/translations.cjs
@@ -9,11 +9,11 @@ const flatmap = require("gulp-flatmap");
 const merge = require("gulp-merge-json");
 const rename = require("gulp-rename");
 const transform = require("gulp-json-transform");
-const { mapFiles } = require("../util");
-const env = require("../env");
-const paths = require("../paths");
+const { mapFiles } = require("../util.cjs");
+const env = require("../env.cjs");
+const paths = require("../paths.cjs");
 
-require("./fetch-nightly-translations");
+require("./fetch-nightly-translations.cjs");
 
 const inFrontendDir = "translations/frontend";
 const inBackendDir = "translations/backend";
diff --git a/build-scripts/gulp/wds.js b/build-scripts/gulp/wds.cjs
similarity index 100%
rename from build-scripts/gulp/wds.js
rename to build-scripts/gulp/wds.cjs
diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.cjs
similarity index 97%
rename from build-scripts/gulp/webpack.js
rename to build-scripts/gulp/webpack.cjs
index ed9fdb3ba6..a82dc29f04 100644
--- a/build-scripts/gulp/webpack.js
+++ b/build-scripts/gulp/webpack.cjs
@@ -5,15 +5,15 @@ const webpack = require("webpack");
 const WebpackDevServer = require("webpack-dev-server");
 const log = require("fancy-log");
 const path = require("path");
-const env = require("../env");
-const paths = require("../paths");
+const env = require("../env.cjs");
+const paths = require("../paths.cjs");
 const {
   createAppConfig,
   createDemoConfig,
   createCastConfig,
   createHassioConfig,
   createGalleryConfig,
-} = require("../webpack");
+} = require("../webpack.cjs");
 
 const bothBuilds = (createConfigFunc, params) => [
   createConfigFunc({ ...params, latestBuild: true }),
diff --git a/build-scripts/paths.js b/build-scripts/paths.cjs
similarity index 100%
rename from build-scripts/paths.js
rename to build-scripts/paths.cjs
diff --git a/build-scripts/rollup-plugins/dont-hash-plugin.js b/build-scripts/rollup-plugins/dont-hash-plugin.cjs
similarity index 100%
rename from build-scripts/rollup-plugins/dont-hash-plugin.js
rename to build-scripts/rollup-plugins/dont-hash-plugin.cjs
diff --git a/build-scripts/rollup-plugins/ignore-plugin.js b/build-scripts/rollup-plugins/ignore-plugin.cjs
similarity index 100%
rename from build-scripts/rollup-plugins/ignore-plugin.js
rename to build-scripts/rollup-plugins/ignore-plugin.cjs
diff --git a/build-scripts/rollup-plugins/manifest-plugin.js b/build-scripts/rollup-plugins/manifest-plugin.cjs
similarity index 100%
rename from build-scripts/rollup-plugins/manifest-plugin.js
rename to build-scripts/rollup-plugins/manifest-plugin.cjs
diff --git a/build-scripts/rollup-plugins/worker-plugin.js b/build-scripts/rollup-plugins/worker-plugin.cjs
similarity index 98%
rename from build-scripts/rollup-plugins/worker-plugin.js
rename to build-scripts/rollup-plugins/worker-plugin.cjs
index 5706475d24..007d5eadb8 100644
--- a/build-scripts/rollup-plugins/worker-plugin.js
+++ b/build-scripts/rollup-plugins/worker-plugin.cjs
@@ -103,7 +103,7 @@ module.exports = function (opts = {}) {
         }
         delete optionsObject.type;
 
-        if (!new RegExp("^.*/").test(workerFile)) {
+        if (!/^.*\//.test(workerFile)) {
           this.warn(
             `Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
           );
diff --git a/build-scripts/rollup.js b/build-scripts/rollup.cjs
similarity index 92%
rename from build-scripts/rollup.js
rename to build-scripts/rollup.cjs
index dbc21dad9c..f321e9f33e 100644
--- a/build-scripts/rollup.js
+++ b/build-scripts/rollup.cjs
@@ -3,18 +3,18 @@ const path = require("path");
 const commonjs = require("@rollup/plugin-commonjs");
 const resolve = require("@rollup/plugin-node-resolve");
 const json = require("@rollup/plugin-json");
-const babel = require("@rollup/plugin-babel").babel;
+const { babel } = require("@rollup/plugin-babel");
 const replace = require("@rollup/plugin-replace");
 const visualizer = require("rollup-plugin-visualizer");
 const { string } = require("rollup-plugin-string");
 const { terser } = require("rollup-plugin-terser");
-const manifest = require("./rollup-plugins/manifest-plugin");
-const worker = require("./rollup-plugins/worker-plugin");
-const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin");
-const ignore = require("./rollup-plugins/ignore-plugin");
+const manifest = require("./rollup-plugins/manifest-plugin.cjs");
+const worker = require("./rollup-plugins/worker-plugin.cjs");
+const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin.cjs");
+const ignore = require("./rollup-plugins/ignore-plugin.cjs");
 
-const bundle = require("./bundle");
-const paths = require("./paths");
+const bundle = require("./bundle.cjs");
+const paths = require("./paths.cjs");
 
 const extensions = [".js", ".ts"];
 
diff --git a/build-scripts/util.js b/build-scripts/util.cjs
similarity index 100%
rename from build-scripts/util.js
rename to build-scripts/util.cjs
diff --git a/build-scripts/webpack.js b/build-scripts/webpack.cjs
similarity index 97%
rename from build-scripts/webpack.js
rename to build-scripts/webpack.cjs
index bce6291b98..7a6e943a72 100644
--- a/build-scripts/webpack.js
+++ b/build-scripts/webpack.cjs
@@ -4,8 +4,8 @@ const TerserPlugin = require("terser-webpack-plugin");
 const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
 const log = require("fancy-log");
 const WebpackBar = require("webpackbar");
-const paths = require("./paths.js");
-const bundle = require("./bundle.js");
+const paths = require("./paths.cjs");
+const bundle = require("./bundle.cjs");
 
 class LogStartCompilePlugin {
   ignoredFirst = false;
@@ -64,6 +64,9 @@ const createWebpackConfig = ({
               cacheCompression: false,
             },
           },
+          resolve: {
+            fullySpecified: false,
+          },
         },
         {
           test: /\.css$/,
diff --git a/cast/rollup.config.js b/cast/rollup.config.js
index 5460a82216..f598f1a4fa 100644
--- a/cast/rollup.config.js
+++ b/cast/rollup.config.js
@@ -1,5 +1,5 @@
-const rollup = require("../build-scripts/rollup.js");
-const env = require("../build-scripts/env.js");
+import rollup from "../build-scripts/rollup.cjs";
+import env from "../build-scripts/env.cjs";
 
 const config = rollup.createCastConfig({
   isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createCastConfig({
   isStatsBuild: env.isStatsBuild(),
 });
 
-module.exports = { ...config.inputOptions, output: config.outputOptions };
+export default { ...config.inputOptions, output: config.outputOptions };
diff --git a/cast/webpack.config.js b/cast/webpack.config.js
index e44575344d..b612b358ea 100644
--- a/cast/webpack.config.js
+++ b/cast/webpack.config.js
@@ -1,8 +1,8 @@
-const { createCastConfig } = require("../build-scripts/webpack.js");
-const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
+import webpack from "../build-scripts/webpack.cjs";
+import env from "../build-scripts/env.cjs";
 
-module.exports = createCastConfig({
-  isProdBuild: isProdBuild(),
-  isStatsBuild: isStatsBuild(),
+export default webpack.createCastConfig({
+  isProdBuild: env.isProdBuild(),
+  isStatsBuild: env.isStatsBuild(),
   latestBuild: true,
 });
diff --git a/demo/rollup.config.js b/demo/rollup.config.js
index d236491002..90cff26a95 100644
--- a/demo/rollup.config.js
+++ b/demo/rollup.config.js
@@ -1,5 +1,5 @@
-const rollup = require("../build-scripts/rollup.js");
-const env = require("../build-scripts/env.js");
+import rollup from "../build-scripts/rollup.cjs";
+import env from "../build-scripts/env.cjs";
 
 const config = rollup.createDemoConfig({
   isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createDemoConfig({
   isStatsBuild: env.isStatsBuild(),
 });
 
-module.exports = { ...config.inputOptions, output: config.outputOptions };
+export default { ...config.inputOptions, output: config.outputOptions };
diff --git a/demo/webpack.config.js b/demo/webpack.config.js
index a297932311..52cfe8b83d 100644
--- a/demo/webpack.config.js
+++ b/demo/webpack.config.js
@@ -1,12 +1,11 @@
-const { createDemoConfig } = require("../build-scripts/webpack.js");
-const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
+import webpack from "../build-scripts/webpack.cjs";
+import env from "../build-scripts/env.cjs";
 
 // File just used for stats builds
-
 const latestBuild = true;
 
-module.exports = createDemoConfig({
-  isProdBuild: isProdBuild(),
-  isStatsBuild: isStatsBuild(),
+export default webpack.createDemoConfig({
+  isProdBuild: env.isProdBuild(),
+  isStatsBuild: env.isStatsBuild(),
   latestBuild,
 });
diff --git a/gallery/rollup.config.js b/gallery/rollup.config.js
index 787bf5a448..8504074822 100644
--- a/gallery/rollup.config.js
+++ b/gallery/rollup.config.js
@@ -1,5 +1,5 @@
-const rollup = require("../build-scripts/rollup.js");
-const env = require("../build-scripts/env.js");
+import rollup from "../build-scripts/rollup.cjs";
+import env from "../build-scripts/env.cjs";
 
 const config = rollup.createGalleryConfig({
   isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createGalleryConfig({
   isStatsBuild: env.isStatsBuild(),
 });
 
-module.exports = { ...config.inputOptions, output: config.outputOptions };
+export default { ...config.inputOptions, output: config.outputOptions };
diff --git a/gallery/sidebar.js b/gallery/sidebar.js
index 156d7c2969..767aecf64c 100644
--- a/gallery/sidebar.js
+++ b/gallery/sidebar.js
@@ -1,4 +1,4 @@
-module.exports = [
+export default [
   {
     // This section has no header and so all page links are shown directly in the sidebar
     category: "concepts",
diff --git a/gallery/webpack.config.js b/gallery/webpack.config.js
index d7ecb07da6..76e83341cd 100644
--- a/gallery/webpack.config.js
+++ b/gallery/webpack.config.js
@@ -1,8 +1,8 @@
-const { createGalleryConfig } = require("../build-scripts/webpack.js");
-const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
+import webpack from "../build-scripts/webpack.cjs";
+import env from "../build-scripts/env.cjs";
 
-module.exports = createGalleryConfig({
-  isProdBuild: isProdBuild(),
-  isStatsBuild: isStatsBuild(),
+export default webpack.createGalleryConfig({
+  isProdBuild: env.isProdBuild(),
+  isStatsBuild: env.isStatsBuild(),
   latestBuild: true,
 });
diff --git a/gulpfile.js b/gulpfile.js
index 78578bad7b..6178d2779d 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -1,3 +1,13 @@
-var requireDir = require("require-dir");
+import { globIterate } from "glob";
 
-requireDir("./build-scripts/gulp/");
+const gulpImports = [];
+
+for await (const gulpModule of globIterate("build-scripts/gulp/*.?(c|m)js", {
+  dotRelative: true,
+})) {
+  gulpImports.push(import(gulpModule));
+}
+
+// Since all tasks are currently registered with gulp.task(), this is enough
+// If any are converted to named exports, need to loop and aggregate exports here
+await Promise.all(gulpImports);
diff --git a/hassio/config.js b/hassio/config.cjs
similarity index 100%
rename from hassio/config.js
rename to hassio/config.cjs
diff --git a/hassio/rollup.config.js b/hassio/rollup.config.js
index 4beee60e48..41835a7fa8 100644
--- a/hassio/rollup.config.js
+++ b/hassio/rollup.config.js
@@ -1,5 +1,5 @@
-const rollup = require("../build-scripts/rollup.js");
-const env = require("../build-scripts/env.js");
+import rollup from "../build-scripts/rollup.cjs";
+import env from "../build-scripts/env.cjs";
 
 const config = rollup.createHassioConfig({
   isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createHassioConfig({
   isStatsBuild: env.isStatsBuild(),
 });
 
-module.exports = { ...config.inputOptions, output: config.outputOptions };
+export default { ...config.inputOptions, output: config.outputOptions };
diff --git a/hassio/webpack.config.js b/hassio/webpack.config.js
index 429c98e6b2..1121f1776c 100644
--- a/hassio/webpack.config.js
+++ b/hassio/webpack.config.js
@@ -1,8 +1,8 @@
-const { createHassioConfig } = require("../build-scripts/webpack.js");
-const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
+import webpack from "../build-scripts/webpack.cjs";
+import env from "../build-scripts/env.cjs";
 
-module.exports = createHassioConfig({
-  isProdBuild: isProdBuild(),
-  isStatsBuild: isStatsBuild(),
+export default webpack.createHassioConfig({
+  isProdBuild: env.isProdBuild(),
+  isStatsBuild: env.isStatsBuild(),
   latestBuild: true,
 });
diff --git a/lint-staged.config.js b/lint-staged.config.js
index 1a5df4fdc1..be7d7f2094 100644
--- a/lint-staged.config.js
+++ b/lint-staged.config.js
@@ -1,5 +1,5 @@
-module.exports = {
-  "*.{js,ts}": ["prettier --write", "eslint --fix"],
+export default {
+  "*.?(c|m){js,ts}": ["eslint --fix", "prettier --write"],
   "!(/translations)*.{json,css,md,html}": "prettier --write",
   "translations/*/*.json": (files) =>
     'printf "%s\n" "Translation files should not be added or modified here. Instead, make the necessary modifications in src/translations/en.json. Other languages are managed externally. Please see https://developers.home-assistant.io/docs/translations/ for details." ' +
diff --git a/package.json b/package.json
index 203a4dc43f..24efbc6e82 100644
--- a/package.json
+++ b/package.json
@@ -19,10 +19,11 @@
     "postinstall": "husky install",
     "prepack": "pinst --disable",
     "postpack": "pinst --enable",
-    "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\""
+    "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
   },
   "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
   "license": "Apache-2.0",
+  "type": "module",
   "dependencies": {
     "@braintree/sanitize-url": "6.0.2",
     "@codemirror/autocomplete": "6.4.2",
@@ -227,7 +228,6 @@
     "open": "8.4.2",
     "pinst": "3.0.0",
     "prettier": "2.8.7",
-    "require-dir": "1.2.0",
     "rollup": "2.79.1",
     "rollup-plugin-string": "3.0.0",
     "rollup-plugin-terser": "7.0.2",
@@ -254,7 +254,6 @@
     "@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
     "@material/mwc-button@^0.25.3": "^0.27.0"
   },
-  "main": "src/home-assistant.js",
   "prettier": {
     "trailingComma": "es5",
     "arrowParens": "always"
diff --git a/rollup.config.js b/rollup.config.js
index 2eb552dbfe..0b59a654d2 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -1,5 +1,5 @@
-const rollup = require("./build-scripts/rollup.js");
-const env = require("./build-scripts/env.js");
+import rollup from "../build-scripts/rollup.cjs";
+import env from "../build-scripts/env.cjs";
 
 const config = rollup.createAppConfig({
   isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createAppConfig({
   isStatsBuild: env.isStatsBuild(),
 });
 
-module.exports = { ...config.inputOptions, output: config.outputOptions };
+export default { ...config.inputOptions, output: config.outputOptions };
diff --git a/script/version_bump.js b/script/version_bump.cjs
similarity index 100%
rename from script/version_bump.js
rename to script/version_bump.cjs
diff --git a/test/setup.js b/test/setup.cjs
similarity index 100%
rename from test/setup.js
rename to test/setup.cjs
diff --git a/test/webpack.config.js b/test/webpack.config.js
index 8c2df94e27..20e1e163e3 100644
--- a/test/webpack.config.js
+++ b/test/webpack.config.js
@@ -1,7 +1,8 @@
-const { createAppConfig } = require("../build-scripts/webpack.js");
+import webpack from "../build-scripts/webpack.cjs";
 
-module.exports = createAppConfig({
+export default webpack.createAppConfig({
   isProdBuild: false,
   latestBuild: true,
   isStatsBuild: false,
+  isTestBuild: true,
 });
diff --git a/tsconfig.json b/tsconfig.json
index d23b0f2d39..f6cfe71d68 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,20 +1,26 @@
 {
   "compilerOptions": {
-    "target": "es2017",
-    "module": "esnext",
+    // Language Options
+    "target": "ES2017",
+    "lib": ["ES2017", "DOM", "DOM.Iterable", "WebWorker"],
+    "experimentalDecorators": true,
+    // Modules
+    "module": "ESNext",
     "moduleResolution": "node",
-    "lib": ["es2017", "dom", "dom.iterable", "WebWorker"],
+    "resolveJsonModule": true,
+    // Babel handles transpiling and no need for declaration files
     "noEmit": true,
+    // Type checking options
     "noUnusedLocals": true,
     "noUnusedParameters": true,
     "noImplicitReturns": true,
     "noFallthroughCasesInSwitch": true,
     "strict": true,
     "noImplicitAny": false,
+    // Do not check type declaration files
     "skipLibCheck": true,
-    "resolveJsonModule": true,
-    "experimentalDecorators": true,
-    "allowSyntheticDefaultImports": true,
+    // Interop with CommonJS and other tools
+    "esModuleInterop": true,
     "plugins": [
       {
         "name": "ts-lit-plugin",
diff --git a/web-dev-server.config.js b/web-dev-server.config.js
index 4bde115579..f0e3557a60 100644
--- a/web-dev-server.config.js
+++ b/web-dev-server.config.js
@@ -1,7 +1,6 @@
-const cors = require("@koa/cors");
-const { rollupAdapter } = require("@web/dev-server-rollup");
-
-const rollup = require("./build-scripts/rollup");
+import cors from "@koa/cors";
+import { rollupAdapter } from "@web/dev-server-rollup";
+import rollup from "./build-scripts/rollup.cjs";
 
 const rollupWDSPlugins = rollup
   .createAppConfig({
@@ -13,7 +12,7 @@ const rollupWDSPlugins = rollup
   );
 
 /** @type import("@web/dev-server/src/config/DevServerConfig.ts") */
-module.exports = {
+export default {
   mimeTypes: {
     "**/*.ts": "js",
     "**/*.json": "js",
diff --git a/webpack.config.cjs b/webpack.config.cjs
new file mode 100644
index 0000000000..63ccb551ec
--- /dev/null
+++ b/webpack.config.cjs
@@ -0,0 +1,28 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+// Needs to remain CommonJS until eslint-import-resolver-webpack supports ES modules
+const webpack = require("./build-scripts/webpack.cjs");
+const env = require("./build-scripts/env.cjs");
+
+// This file exists because we haven't migrated the stats script yet
+
+const configs = [
+  webpack.createAppConfig({
+    isProdBuild: env.isProdBuild(),
+    isStatsBuild: env.isStatsBuild(),
+    isTestBuild: env.isTestBuild(),
+    latestBuild: true,
+  }),
+];
+
+if (env.isProdBuild() && !env.isStatsBuild()) {
+  configs.push(
+    webpack.createAppConfig({
+      isProdBuild: env.isProdBuild(),
+      isStatsBuild: env.isStatsBuild(),
+      isTestBuild: env.isTestBuild(),
+      latestBuild: false,
+    })
+  );
+}
+
+module.exports = configs;
diff --git a/webpack.config.js b/webpack.config.js
deleted file mode 100644
index fb6e7d6a2f..0000000000
--- a/webpack.config.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* eslint-disable import/extensions */
-/* eslint-disable @typescript-eslint/no-var-requires */
-
-const { createAppConfig } = require("./build-scripts/webpack.js");
-const {
-  isProdBuild,
-  isStatsBuild,
-  isTestBuild,
-} = require("./build-scripts/env.js");
-
-// This file exists because we haven't migrated the stats script yet
-
-const configs = [
-  createAppConfig({
-    isProdBuild: isProdBuild(),
-    isStatsBuild: isStatsBuild(),
-    isTestBuild: isTestBuild(),
-    latestBuild: true,
-  }),
-];
-// const configs = [createConfig(isProdBuild, /* latestBuild */ true)];
-if (isProdBuild && !isStatsBuild) {
-  configs.push(
-    createAppConfig({
-      isProdBuild: isProdBuild(),
-      isStatsBuild: isStatsBuild(),
-      isTestBuild: isTestBuild(),
-      latestBuild: false,
-    })
-  );
-}
-
-module.exports = configs;
diff --git a/yarn.lock b/yarn.lock
index cbdbcb85be..1a9cf379bd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9571,7 +9571,6 @@ __metadata:
     qr-scanner: 1.4.2
     qrcode: 1.5.1
     regenerator-runtime: 0.13.11
-    require-dir: 1.2.0
     resize-observer-polyfill: 1.5.1
     roboto-fontface: 0.10.0
     rollup: 2.79.1
@@ -13443,13 +13442,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"require-dir@npm:1.2.0":
-  version: 1.2.0
-  resolution: "require-dir@npm:1.2.0"
-  checksum: 8bbc4757d7e3fae0799e24cb363f165a89e0cc83172de9d5fd95f8e75cf2368764199e5fd557434d6318ab1f271415cc9d3ef190076393e6e993a7b1add8ef27
-  languageName: node
-  linkType: hard
-
 "require-directory@npm:^2.1.1":
   version: 2.1.1
   resolution: "require-directory@npm:2.1.1"