From 645b8063e1e2c623852f8bda4c05cb546e9a870d Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 15 Mar 2026 23:30:57 +0200 Subject: [PATCH] Intellectual Webpack cache management (#5295) * Intellectual Webpack cache management * Check if webpackRoot exists. * Wrap webpack directory reading in try-catch * Enhance cache pruning console output --- src/middleware/webpack-serve.js | 12 ++--- src/server-main.js | 2 +- webpack.config.js | 78 +++++++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/middleware/webpack-serve.js b/src/middleware/webpack-serve.js index acad3212a..f028573cb 100644 --- a/src/middleware/webpack-serve.js +++ b/src/middleware/webpack-serve.js @@ -26,16 +26,18 @@ export default function getWebpackServeMiddleware() { /** * Wait until Webpack is done compiling. * @param {object} param Parameters. - * @param {boolean} [param.forceDist] Whether to force the use the /dist folder. + * @param {boolean} [param.forceDist=false] Whether to force the use the /dist folder. + * @param {boolean} [param.pruneCache=false] Whether to prune old cache directories before compiling. * @returns {Promise} */ - devMiddleware.runWebpackCompiler = ({ forceDist = false } = {}) => { - const publicLibConfig = getPublicLibConfig(forceDist); + devMiddleware.runWebpackCompiler = ({ forceDist = false, pruneCache = false } = {}) => { + console.log(); + console.log('Compiling frontend libraries...'); + + const publicLibConfig = getPublicLibConfig({ forceDist, pruneCache }); const compiler = webpack(publicLibConfig); return new Promise((resolve) => { - console.log(); - console.log('Compiling frontend libraries...'); compiler.run((_error, stats) => { const output = stats?.toString(publicLibConfig.stats); if (output) { diff --git a/src/server-main.js b/src/server-main.js index 2cf29f4dd..077f2c50a 100644 --- a/src/server-main.js +++ b/src/server-main.js @@ -328,7 +328,7 @@ async function preSetupTasks() { initRequestProxy({ enabled: cliArgs.requestProxyEnabled, url: cliArgs.requestProxyUrl, bypass: cliArgs.requestProxyBypass }); // Wait for frontend libs to compile - await webpackMiddleware.runWebpackCompiler(); + await webpackMiddleware.runWebpackCompiler({ pruneCache: true }); } /** diff --git a/webpack.config.js b/webpack.config.js index 3f771854f..2d2fcf3e5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,45 +1,95 @@ import process from 'node:process'; import path from 'node:path'; +import fs from 'node:fs'; +import crypto from 'node:crypto'; import isDocker from 'is-docker'; import webpack from 'webpack'; import { serverDirectory } from './src/server-directory.js'; +import { getVersion, color } from './src/util.js'; + +/** + * Generate a cache version string based on the application version, Git revision, and Webpack version. + * @returns {string} The cache version string. + */ +function getWebpackCacheVersion() { + return crypto.createHash('shake256', { outputLength: 8 }) + .update(JSON.stringify([appVersion.pkgVersion, appVersion.gitRevision, webpack.version])) + .digest('hex'); +} + +/** + * Prune old Webpack cache directories that do not match the current cache version. + * @param {string} webpackRoot The root directory where Webpack caches are stored. + * @param {string} currentCacheVersion The current cache version to keep. + */ +function pruneWebpackCache(webpackRoot, currentCacheVersion) { + try { + if (!fs.existsSync(webpackRoot)) { + return; + } + + const cacheDirectories = fs.readdirSync(webpackRoot, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + + for (const dir of cacheDirectories) { + const dirPath = path.join(webpackRoot, dir); + if (dir !== currentCacheVersion) { + try { + fs.rmSync(dirPath, { recursive: true, force: true }); + console.debug(`Removed outdated cache directory: ${color.yellow(dir)}`); + } catch (error) { + console.error(`Failed to remove Webpack cache directory: ${color.red(dir)}`, error); + } + } + } + } catch (error) { + console.error('Failed to read Webpack cache directories for pruning.', error); + } +} + +const appVersion = await getVersion(); /** * Get the Webpack configuration for the public/lib.js file. * 1. Docker has got cache and the output file pre-baked. * 2. Non-Docker environments use the global DATA_ROOT variable to determine the cache and output directories. - * @param {boolean} forceDist Whether to force the use the /dist folder. + * @param {object} options Configuration options. + * @param {boolean} [options.forceDist=false] Whether to force the use the /dist folder. + * @param {boolean} [options.pruneCache=false] Whether to prune old cache directories. * @returns {import('webpack').Configuration} * @throws {Error} If the DATA_ROOT variable is not set. * */ -export default function getPublicLibConfig(forceDist = false) { - function getCacheDirectory() { +export default function getPublicLibConfig({ forceDist = false, pruneCache = false } = {}) { + function getWebpackRoot() { if (forceDist || isDocker()) { - return path.resolve(process.cwd(), 'dist', '_webpack', webpack.version, 'cache'); + return path.resolve(process.cwd(), 'dist', '_webpack'); } if (typeof globalThis.DATA_ROOT === 'string') { - return path.resolve(globalThis.DATA_ROOT, '_webpack', webpack.version, 'cache'); + return path.resolve(globalThis.DATA_ROOT, '_webpack'); } throw new Error('DATA_ROOT variable is not set.'); } + function getCacheDirectory() { + return path.join(webpackRoot, cacheVersion, 'cache'); + } + function getOutputDirectory() { - if (forceDist || isDocker()) { - return path.resolve(process.cwd(), 'dist', '_webpack', webpack.version, 'output'); - } - - if (typeof globalThis.DATA_ROOT === 'string') { - return path.resolve(globalThis.DATA_ROOT, '_webpack', webpack.version, 'output'); - } - - throw new Error('DATA_ROOT variable is not set.'); + return path.join(webpackRoot, cacheVersion, 'output'); } + const webpackRoot = getWebpackRoot(); + const cacheVersion = getWebpackCacheVersion(); const cacheDirectory = getCacheDirectory(); const outputDirectory = getOutputDirectory(); + if (pruneCache) { + pruneWebpackCache(webpackRoot, cacheVersion); + } + return { mode: 'production', entry: path.join(serverDirectory, 'public/lib.js'),