deps: Improve Webpack build performance (#2288)

- Upgrades webpack and webpack tool versions
- Updates dev source map to webpack recommendation
- Implements `webpack.DllPlugin` in dev for faster rebuilds
- Implements `thread-loader` to run `ts-loader` in a worker pool
This commit is contained in:
sua yoo 2025-01-14 12:55:12 -08:00 committed by GitHub
parent c53528332b
commit dd22fd11ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 882 additions and 390 deletions

View File

@ -78,13 +78,14 @@
"style-loader": "^3.3.0",
"tailwindcss": "^3.4.1",
"terser-webpack-plugin": "^5.3.10",
"thread-loader": "^4.0.4",
"ts-loader": "^9.2.6",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^5.3.3",
"update-dotenv": "^1.1.1",
"url-pattern": "^1.0.3",
"webpack": "^5.88.0",
"webpack-cli": "^4.8.0",
"webpack": "^5.97.1",
"webpack-cli": "^5.1.4",
"webpack-merge": "^5.8.0",
"yaml": "^2.0.0-11",
"zod": "^3.23.8"
@ -97,6 +98,7 @@
"build": "webpack --config webpack.prod.js",
"build-dev": "webpack --mode development",
"build:analyze": "BUNDLE_ANALYZER=true webpack --config webpack.prod.js",
"prestart": "webpack --mode development --config vendor.webpack.config.js",
"start": "webpack serve --mode=development --config webpack.dev.js",
"serve": "node scripts/serve.js",
"lint": "eslint --fix .",
@ -120,7 +122,7 @@
"sinon": "^12.0.1",
"ts-lit-plugin": "^2.0.1",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-dev-server": "^4.15.0"
"webpack-dev-server": "^5.2.0"
},
"optionalDependencies": {
"@esm-bundle/chai": "^4.3.4-fix.0",

View File

@ -1,3 +1,4 @@
// @ts-check
// Serve app locally without building with webpack, e.g. for e2e
const fs = require("fs");
const path = require("path");
@ -15,23 +16,36 @@ require("dotenv").config({
path: dotEnvPath,
});
const [devConfig] = require("../webpack.dev.js");
const devConfigs = require("../webpack.dev.js");
const [devConfig] = devConfigs;
const app = express();
const { devServer } = devConfig;
/** @type {import('webpack').Configuration['devServer']} */
const devServer = devConfig.devServer;
devServer.setupMiddlewares([], { app });
if (!devServer) {
throw new Error("Dev server not defined in `webpack.dev.js`");
}
if (devServer.setupMiddlewares) {
// @ts-ignore Express app is compatible with `Server`
devServer.setupMiddlewares([], { app });
}
if (Array.isArray(devServer.proxy)) {
devServer.proxy.forEach((proxy) => {
app.use(
// @ts-ignore
proxy.context,
createProxyMiddleware({
...proxy,
followRedirects: true,
}),
);
});
}
Object.keys(devServer.proxy).forEach((path) => {
app.use(
path,
createProxyMiddleware({
...devServer.proxy[path],
followRedirects: true,
}),
);
});
app.use("/", express.static(distPath));
app.get("/*", (req, res) => {
res.sendFile(path.join(distPath, "index.html"));

View File

@ -19,6 +19,9 @@
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<script src="/extra.js"></script>
<%if (environment === "development") { %>
<script defer src="/vendor/dll.lit.js"></script>
<% } %>
</head>
<body>
<script>

View File

@ -0,0 +1,23 @@
/**
* Separate vendor modules to speed up development rebuild
*/
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: {
lit: ["lit", "@lit/localize"],
},
output: {
path: path.join(__dirname, "dist/vendor"),
filename: "dll.[name].js",
library: "[name]_[fullhash]",
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, "dist/vendor", "[name]-manifest.json"),
name: "[name]_[fullhash]",
}),
],
};

View File

@ -5,9 +5,11 @@ const childProcess = require("child_process");
const fs = require("fs");
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const threadLoader = require("thread-loader");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const webpack = require("webpack");
@ -15,7 +17,7 @@ const defineConfig = require("./config/define.js");
// @ts-ignore
const packageJSON = require("./package.json");
const isDevServer = process.env.WEBPACK_SERVE;
const isDevServer = process.env.WEBPACK_SERVE === "true";
const dotEnvPath = path.resolve(
process.cwd(),
@ -75,6 +77,20 @@ const version = (() => {
return packageJSON.version;
})();
/** @type {Partial<import('ts-loader').Options>} */
const tsLoaderOptions = {
onlyCompileBundledFiles: true,
transpileOnly: true,
// Enables compatibility with thread-loader
happyPackMode: true,
};
const threadLoaderOptions = {
poolTimeout: isDevServer ? Infinity : 2000,
};
threadLoader.warmup(threadLoaderOptions, ["ts-loader"]);
/** @type {import('webpack').Configuration} */
const main = {
entry: "./src/index.ts",
@ -87,9 +103,11 @@ const main = {
module: {
rules: [
// Non-generated source files
{
test: /\.ts$/,
include: path.resolve(__dirname, "src"),
exclude: path.resolve(__dirname, "src/__generated__"),
use: [
{
loader: "postcss-loader",
@ -100,15 +118,30 @@ const main = {
},
},
},
{
loader: "thread-loader",
options: threadLoaderOptions,
},
{
loader: "ts-loader",
options: {
onlyCompileBundledFiles: true,
transpileOnly: true,
},
options: tsLoaderOptions,
},
],
},
{
// Generated source files
test: /\.ts$/,
include: path.resolve(__dirname, "src/__generated__"),
use: [
{
loader: "thread-loader",
options: threadLoaderOptions,
},
{
loader: "ts-loader",
options: tsLoaderOptions,
},
],
exclude: /node_modules/,
},
{
// Global styles and assets, like fonts and Shoelace,
@ -171,6 +204,11 @@ const main = {
configOverwrite: {
exclude: ["**/*.test.ts", "tests/**/*.ts", "playwright.config.ts"],
},
// Re-enable type checking when `happyPackMode` is enabled
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
}),

View File

@ -1,8 +1,8 @@
// @ts-check
const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");
const webpack = require("webpack");
const { merge } = require("webpack-merge");
const baseConfigs = require("./webpack.config.js");
@ -26,9 +26,42 @@ const RWP_BASE_URL =
const devBackendUrl = new URL(process.env.API_BASE_URL);
/** @type {import('webpack').Configuration['plugins']} */
const plugins = [
new ESLintPlugin({
extensions: ["ts", "js"],
}),
];
// Dev config may be used in Playwright E2E CI tests
if (process.env.WEBPACK_SERVE === "true") {
let litManifest;
try {
litManifest = require.resolve(
path.join(__dirname, "dist/vendor/lit-manifest.json"),
);
} catch {
console.warn(
"`lit-manifest.json` not found. If you're seeing this with `yarn start`, ensure the file exists. You can ignore this message otherwise.",
);
}
if (litManifest) {
plugins.unshift(
// Speed up rebuilds by excluding vendor modules
new webpack.DllReferencePlugin({
manifest: require.resolve(
path.join(__dirname, "dist/vendor/lit-manifest.json"),
),
}),
);
}
}
module.exports = [
merge(main, {
devtool: "eval-cheap-source-map",
devtool: "eval",
/** @type {import('webpack-dev-server').Configuration} */
devServer: {
watchFiles: ["src/**/*", __filename],
@ -40,24 +73,30 @@ module.exports = [
directory: shoelaceAssetsSrcPath,
publicPath: "/" + shoelaceAssetsPublicPath,
},
{
directory: path.join(__dirname, "dist/vendor"),
publicPath: "/vendor",
},
],
historyApiFallback: true,
proxy: {
"/api": {
proxy: [
{
context: "/api",
target: devBackendUrl.href,
headers: {
Host: devBackendUrl.host,
},
ws: true,
},
"/data": {
{
context: "/data",
target: devBackendUrl.href,
headers: {
Host: devBackendUrl.host,
},
},
},
],
setupMiddlewares: (middlewares, server) => {
// Serve replay service worker file
server.app?.get("/replay/sw.js", (req, res) => {
@ -93,11 +132,7 @@ module.exports = [
config: [__filename],
},
},
plugins: [
new ESLintPlugin({
extensions: ["ts", "js"],
}),
],
plugins,
}),
{
...vnc,

1091
frontend/yarn.lock generated

File diff suppressed because it is too large Load Diff