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

645b8063e1e2c623852f8bda4c05cb546e9a870d

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +72 -20Ignore whitespace
src/middleware/webpack-serve.js+7 -5
@@ -26,16 +26,18 @@ export default function getWebpackServeMiddleware() {
2626 /**
2727 * Wait until Webpack is done compiling.
2828 * @param {object} param Parameters.
2929 * @param {boolean} [param.forceDist=false] Whether to force the use the /dist folder.
30+ * @param {boolean} [param.pruneCache=false] Whether to prune old cache directories before compiling.
3031 * @returns {Promise<void>}
3132 */
3233 devMiddleware.runWebpackCompiler = ({ forceDist = false, pruneCache = false } = {}) => {
33- const publicLibConfig = getPublicLibConfig(forceDist);
34+ console.log();
35+ console.log('Compiling frontend libraries...');
36+
37+ const publicLibConfig = getPublicLibConfig({ forceDist, pruneCache });
3438 const compiler = webpack(publicLibConfig);
3539
3640 return new Promise((resolve) => {
37- console.log();
38- console.log('Compiling frontend libraries...');
3941 compiler.run((_error, stats) => {
4042 const output = stats?.toString(publicLibConfig.stats);
4143 if (output) {
src/server-main.js+1 -1
@@ -328,7 +328,7 @@ async function preSetupTasks() {
328328 initRequestProxy({ enabled: cliArgs.requestProxyEnabled, url: cliArgs.requestProxyUrl, bypass: cliArgs.requestProxyBypass });
329329
330330 // Wait for frontend libs to compile
331331 await webpackMiddleware.runWebpackCompiler({ pruneCache: true });
332332}
333333
334334/**
webpack.config.js+64 -14
@@ -1,45 +1,95 @@
11import process from 'node:process';
22import path from 'node:path';
3+import fs from 'node:fs';
4+import crypto from 'node:crypto';
35import isDocker from 'is-docker';
46import webpack from 'webpack';
57import { serverDirectory } from './src/server-directory.js';
8+import { getVersion, color } from './src/util.js';
9+
10+/**
11+ * Generate a cache version string based on the application version, Git revision, and Webpack version.
12+ * @returns {string} The cache version string.
13+ */
14+function getWebpackCacheVersion() {
15+ return crypto.createHash('shake256', { outputLength: 8 })
16+ .update(JSON.stringify([appVersion.pkgVersion, appVersion.gitRevision, webpack.version]))
17+ .digest('hex');
18+}
19+
20+/**
21+ * Prune old Webpack cache directories that do not match the current cache version.
22+ * @param {string} webpackRoot The root directory where Webpack caches are stored.
23+ * @param {string} currentCacheVersion The current cache version to keep.
24+ */
25+function pruneWebpackCache(webpackRoot, currentCacheVersion) {
26+ try {
27+ if (!fs.existsSync(webpackRoot)) {
28+ return;
29+ }
30+
31+ const cacheDirectories = fs.readdirSync(webpackRoot, { withFileTypes: true })
32+ .filter(dirent => dirent.isDirectory())
33+ .map(dirent => dirent.name);
34+
35+ for (const dir of cacheDirectories) {
36+ const dirPath = path.join(webpackRoot, dir);
37+ if (dir !== currentCacheVersion) {
38+ try {
39+ fs.rmSync(dirPath, { recursive: true, force: true });
40+ console.debug(`Removed outdated cache directory: ${color.yellow(dir)}`);
41+ } catch (error) {
42+ console.error(`Failed to remove Webpack cache directory: ${color.red(dir)}`, error);
43+ }
44+ }
45+ }
46+ } catch (error) {
47+ console.error('Failed to read Webpack cache directories for pruning.', error);
48+ }
49+}
50+
51+const appVersion = await getVersion();
652
753/**
854 * Get the Webpack configuration for the public/lib.js file.
955 * 1. Docker has got cache and the output file pre-baked.
1056 * 2. Non-Docker environments use the global DATA_ROOT variable to determine the cache and output directories.
1157 * @param {booleanobject} forceDist Whether to force the use theoptions /distConfiguration folderoptions.
58+ * @param {boolean} [options.forceDist=false] Whether to force the use the /dist folder.
59+ * @param {boolean} [options.pruneCache=false] Whether to prune old cache directories.
1260 * @returns {import('webpack').Configuration}
1361 * @throws {Error} If the DATA_ROOT variable is not set.
1462 * */
1563export default function getPublicLibConfig({ forceDist = false, pruneCache = false } = {}) {
1664 function getCacheDirectorygetWebpackRoot() {
1765 if (forceDist || isDocker()) {
1866 return path.resolve(process.cwd(), 'dist', '_webpack', webpack.version, 'cache');
1967 }
2068
2169 if (typeof globalThis.DATA_ROOT === 'string') {
2270 return path.resolve(globalThis.DATA_ROOT, '_webpack', webpack.version, 'cache');
2371 }
2472
2573 throw new Error('DATA_ROOT variable is not set.');
2674 }
2775
2876 function getOutputDirectorygetCacheDirectory() {
29- if (forceDist || isDocker()) {
77+ return path.join(webpackRoot, cacheVersion, 'cache');
30- return path.resolve(process.cwd(), 'dist', '_webpack', webpack.version, 'output');
78+ }
31- }
32-
33- if (typeof globalThis.DATA_ROOT === 'string') {
34- return path.resolve(globalThis.DATA_ROOT, '_webpack', webpack.version, 'output');
35- }
3679
37- throw new Error('DATA_ROOT variable is not set.');
80+ function getOutputDirectory() {
81+ return path.join(webpackRoot, cacheVersion, 'output');
3882 }
3983
84+ const webpackRoot = getWebpackRoot();
85+ const cacheVersion = getWebpackCacheVersion();
4086 const cacheDirectory = getCacheDirectory();
4187 const outputDirectory = getOutputDirectory();
4288
89+ if (pruneCache) {
90+ pruneWebpackCache(webpackRoot, cacheVersion);
91+ }
92+
4393 return {
4494 mode: 'production',
4595 entry: path.join(serverDirectory, 'public/lib.js'),