mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-30 22:19:55 +00:00 
			
		
		
		
	Rspack (#22807)
* Add rspack * Remove TransformAsyncModulesPlugin from rspack * Migrate all webpack usage to rspack * Migrate tests to vitest * Fix test suites * Remove chai dependency * Fix compute_state_display tests * Fix resolveTimeZone * Reduces test pipeline * Revert test ci * optimize chunk filtering * Migrate landing-page to rspack * Update rspack dependencies * Add rsdoctor * Fix prod build bundle size * Use rsdoctor for demo stats * Remove unused webpack configs * Update build-scripts/rspack.cjs Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com> * Fix eslint * Update rspack * Remove unused code --------- Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										301
									
								
								build-scripts/rspack.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								build-scripts/rspack.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,301 @@ | ||||
| const { existsSync } = require("fs"); | ||||
| const path = require("path"); | ||||
| const rspack = require("@rspack/core"); | ||||
| const { RsdoctorRspackPlugin } = require("@rsdoctor/rspack-plugin"); | ||||
| const { StatsWriterPlugin } = require("webpack-stats-plugin"); | ||||
| const filterStats = require("@bundle-stats/plugin-webpack-filter").default; | ||||
| const TerserPlugin = require("terser-webpack-plugin"); | ||||
| const { WebpackManifestPlugin } = require("rspack-manifest-plugin"); | ||||
| const log = require("fancy-log"); | ||||
| const WebpackBar = require("webpackbar/rspack"); | ||||
| const paths = require("./paths.cjs"); | ||||
| const bundle = require("./bundle.cjs"); | ||||
|  | ||||
| class LogStartCompilePlugin { | ||||
|   ignoredFirst = false; | ||||
|  | ||||
|   apply(compiler) { | ||||
|     compiler.hooks.beforeCompile.tap("LogStartCompilePlugin", () => { | ||||
|       if (!this.ignoredFirst) { | ||||
|         this.ignoredFirst = true; | ||||
|         return; | ||||
|       } | ||||
|       log("Changes detected. Starting compilation"); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| const createRspackConfig = ({ | ||||
|   name, | ||||
|   entry, | ||||
|   outputPath, | ||||
|   publicPath, | ||||
|   defineOverlay, | ||||
|   isProdBuild, | ||||
|   latestBuild, | ||||
|   isStatsBuild, | ||||
|   isTestBuild, | ||||
|   isHassioBuild, | ||||
|   dontHash, | ||||
| }) => { | ||||
|   if (!dontHash) { | ||||
|     dontHash = new Set(); | ||||
|   } | ||||
|   const ignorePackages = bundle.ignorePackages({ latestBuild }); | ||||
|   return { | ||||
|     name, | ||||
|     mode: isProdBuild ? "production" : "development", | ||||
|     target: `browserslist:${latestBuild ? "modern" : "legacy"}`, | ||||
|     // For tests/CI, source maps are skipped to gain build speed | ||||
|     // For production, generate source maps for accurate stack traces without source code | ||||
|     // For development, generate "cheap" versions that can map to original line numbers | ||||
|     devtool: isTestBuild | ||||
|       ? false | ||||
|       : isProdBuild | ||||
|         ? "nosources-source-map" | ||||
|         : "eval-cheap-module-source-map", | ||||
|     entry, | ||||
|     node: false, | ||||
|     module: { | ||||
|       rules: [ | ||||
|         { | ||||
|           test: /\.m?js$|\.ts$/, | ||||
|           use: (info) => ({ | ||||
|             loader: "babel-loader", | ||||
|             options: { | ||||
|               ...bundle.babelOptions({ | ||||
|                 latestBuild, | ||||
|                 isProdBuild, | ||||
|                 isTestBuild, | ||||
|                 sw: info.issuerLayer === "sw", | ||||
|               }), | ||||
|               cacheDirectory: !isProdBuild, | ||||
|               cacheCompression: false, | ||||
|             }, | ||||
|           }), | ||||
|           resolve: { | ||||
|             fullySpecified: false, | ||||
|           }, | ||||
|           parser: { | ||||
|             worker: ["*context.audioWorklet.addModule()", "..."], | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           test: /\.css$/, | ||||
|           type: "asset/source", | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|     optimization: { | ||||
|       minimizer: [ | ||||
|         new TerserPlugin({ | ||||
|           parallel: true, | ||||
|           extractComments: true, | ||||
|           terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }), | ||||
|         }), | ||||
|       ], | ||||
|       moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||
|       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||
|       splitChunks: { | ||||
|         // Disable splitting for web workers and worklets because imports of | ||||
|         // external chunks are broken for: | ||||
|         chunks: !isProdBuild | ||||
|           ? // improve incremental build speed, but blows up bundle size | ||||
|             new RegExp( | ||||
|               `^(?!(${Object.keys(entry).join("|")}|.*work(?:er|let))$)` | ||||
|             ) | ||||
|           : // - ESM output: https://github.com/webpack/webpack/issues/17014 | ||||
|             // - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543 | ||||
|             (chunk) => | ||||
|               !chunk.canBeInitial() && | ||||
|               !new RegExp( | ||||
|                 `^.+-work${latestBuild ? "(?:let|er)" : "let"}$` | ||||
|               ).test(chunk.name), | ||||
|       }, | ||||
|     }, | ||||
|     plugins: [ | ||||
|       !isStatsBuild && new WebpackBar({ fancy: !isProdBuild }), | ||||
|       new WebpackManifestPlugin({ | ||||
|         // Only include the JS of entrypoints | ||||
|         filter: (file) => file.isInitial && !file.name.endsWith(".map"), | ||||
|       }), | ||||
|       new rspack.DefinePlugin( | ||||
|         bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) | ||||
|       ), | ||||
|       new rspack.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") || | ||||
|             // loaded by webpack dev server but doesn't exist. | ||||
|             resource === "webpack/hot" | ||||
|           ) { | ||||
|             return false; | ||||
|           } | ||||
|           let fullPath; | ||||
|           try { | ||||
|             fullPath = resource.startsWith(".") | ||||
|               ? path.resolve(context, resource) | ||||
|               : require.resolve(resource); | ||||
|           } catch (err) { | ||||
|             console.error( | ||||
|               "Error in Home Assistant ignore plugin", | ||||
|               resource, | ||||
|               context | ||||
|             ); | ||||
|             throw err; | ||||
|           } | ||||
|  | ||||
|           return ignorePackages.some((toIgnorePath) => | ||||
|             fullPath.startsWith(toIgnorePath) | ||||
|           ); | ||||
|         }, | ||||
|       }), | ||||
|       new rspack.NormalModuleReplacementPlugin( | ||||
|         new RegExp( | ||||
|           bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|") | ||||
|         ), | ||||
|         path.resolve(paths.polymer_dir, "src/util/empty.js") | ||||
|       ), | ||||
|       !isProdBuild && new LogStartCompilePlugin(), | ||||
|       isProdBuild && | ||||
|         new StatsWriterPlugin({ | ||||
|           filename: path.relative( | ||||
|             outputPath, | ||||
|             path.join(paths.build_dir, "stats", `${name}.json`) | ||||
|           ), | ||||
|           stats: { assets: true, chunks: true, modules: true }, | ||||
|           transform: (stats) => JSON.stringify(filterStats(stats)), | ||||
|         }), | ||||
|       isProdBuild && | ||||
|         isStatsBuild && | ||||
|         new RsdoctorRspackPlugin({ | ||||
|           reportDir: path.join(paths.build_dir, "rsdoctor"), | ||||
|           features: ["plugins", "bundle"], | ||||
|           supports: { | ||||
|             generateTileGraph: true, | ||||
|           }, | ||||
|         }), | ||||
|     ].filter(Boolean), | ||||
|     resolve: { | ||||
|       extensions: [".ts", ".js", ".json"], | ||||
|       alias: { | ||||
|         "lit/static-html$": "lit/static-html.js", | ||||
|         "lit/decorators$": "lit/decorators.js", | ||||
|         "lit/directive$": "lit/directive.js", | ||||
|         "lit/directives/until$": "lit/directives/until.js", | ||||
|         "lit/directives/class-map$": "lit/directives/class-map.js", | ||||
|         "lit/directives/style-map$": "lit/directives/style-map.js", | ||||
|         "lit/directives/if-defined$": "lit/directives/if-defined.js", | ||||
|         "lit/directives/guard$": "lit/directives/guard.js", | ||||
|         "lit/directives/cache$": "lit/directives/cache.js", | ||||
|         "lit/directives/repeat$": "lit/directives/repeat.js", | ||||
|         "lit/directives/live$": "lit/directives/live.js", | ||||
|         "lit/directives/keyed$": "lit/directives/keyed.js", | ||||
|         "lit/polyfill-support$": "lit/polyfill-support.js", | ||||
|         "@lit-labs/virtualizer/layouts/grid": | ||||
|           "@lit-labs/virtualizer/layouts/grid.js", | ||||
|         "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver": | ||||
|           "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js", | ||||
|         "@lit-labs/observers/resize-controller": | ||||
|           "@lit-labs/observers/resize-controller.js", | ||||
|       }, | ||||
|     }, | ||||
|     output: { | ||||
|       module: latestBuild, | ||||
|       filename: ({ chunk }) => | ||||
|         !isProdBuild || isStatsBuild || dontHash.has(chunk.name) | ||||
|           ? "[name].js" | ||||
|           : "[name].[contenthash].js", | ||||
|       chunkFilename: | ||||
|         isProdBuild && !isStatsBuild ? "[name].[contenthash].js" : "[name].js", | ||||
|       assetModuleFilename: | ||||
|         isProdBuild && !isStatsBuild ? "[id].[contenthash][ext]" : "[id][ext]", | ||||
|       crossOriginLoading: "use-credentials", | ||||
|       hashFunction: "xxhash64", | ||||
|       path: outputPath, | ||||
|       publicPath, | ||||
|       // To silence warning in worker plugin | ||||
|       globalObject: "self", | ||||
|       // Since production source maps don't include sources, we need to point to them elsewhere | ||||
|       // For dependencies, just provide the path (no source in browser) | ||||
|       // Otherwise, point to the raw code on GitHub for browser to load | ||||
|       ...Object.fromEntries( | ||||
|         ["", "Fallback"].map((v) => [ | ||||
|           `devtool${v}ModuleFilenameTemplate`, | ||||
|           !isTestBuild && isProdBuild | ||||
|             ? (info) => { | ||||
|                 if ( | ||||
|                   !path.isAbsolute(info.absoluteResourcePath) || | ||||
|                   !existsSync(info.resourcePath) || | ||||
|                   info.resourcePath.startsWith("./node_modules") | ||||
|                 ) { | ||||
|                   // Source URLs are unknown for dependencies, so we use a relative URL with a | ||||
|                   // non - existent top directory.  This results in a clean source tree in browser | ||||
|                   // dev tools, and they stay happy getting 404s with valid requests. | ||||
|                   return `/unknown${path.resolve("/", info.resourcePath)}`; | ||||
|                 } | ||||
|                 return new URL(info.resourcePath, bundle.sourceMapURL()).href; | ||||
|               } | ||||
|             : undefined, | ||||
|         ]) | ||||
|       ), | ||||
|     }, | ||||
|     experiments: { | ||||
|       layers: true, | ||||
|       outputModule: true, | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| const createAppConfig = ({ | ||||
|   isProdBuild, | ||||
|   latestBuild, | ||||
|   isStatsBuild, | ||||
|   isTestBuild, | ||||
| }) => | ||||
|   createRspackConfig( | ||||
|     bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) | ||||
|   ); | ||||
|  | ||||
| const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => | ||||
|   createRspackConfig( | ||||
|     bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) | ||||
|   ); | ||||
|  | ||||
| const createCastConfig = ({ isProdBuild, latestBuild }) => | ||||
|   createRspackConfig(bundle.config.cast({ isProdBuild, latestBuild })); | ||||
|  | ||||
| const createHassioConfig = ({ | ||||
|   isProdBuild, | ||||
|   latestBuild, | ||||
|   isStatsBuild, | ||||
|   isTestBuild, | ||||
| }) => | ||||
|   createRspackConfig( | ||||
|     bundle.config.hassio({ | ||||
|       isProdBuild, | ||||
|       latestBuild, | ||||
|       isStatsBuild, | ||||
|       isTestBuild, | ||||
|     }) | ||||
|   ); | ||||
|  | ||||
| const createGalleryConfig = ({ isProdBuild, latestBuild }) => | ||||
|   createRspackConfig(bundle.config.gallery({ isProdBuild, latestBuild })); | ||||
|  | ||||
| const createLandingPageConfig = ({ isProdBuild, latestBuild }) => | ||||
|   createRspackConfig(bundle.config.landingPage({ isProdBuild, latestBuild })); | ||||
|  | ||||
| module.exports = { | ||||
|   createAppConfig, | ||||
|   createDemoConfig, | ||||
|   createCastConfig, | ||||
|   createHassioConfig, | ||||
|   createGalleryConfig, | ||||
|   createRspackConfig, | ||||
|   createLandingPageConfig, | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user
	 Wendelin
					Wendelin