Skip to content

Commit 7aca28d

Browse files
committed
feat: webpack-dev-server GC plugin & auto-restat on crash
1 parent d4ea806 commit 7aca28d

6 files changed

Lines changed: 49 additions & 38 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* eslint-disable no-console */
2+
import type {Compiler} from 'webpack';
3+
4+
/**
5+
* Custom webpack plugin that forces garbage collection every 5 compilations
6+
* and logs memory usage to help monitor memory consumption during development.
7+
*
8+
* Note: Requires Node.js to be started with --expose-gc flag to enable garbage collection.
9+
*/
10+
class ForceGarbageCollectionPlugin {
11+
private compilationCount = 0;
12+
13+
apply(compiler: Compiler) {
14+
if (gc && typeof gc === 'function') {
15+
compiler.hooks.done.tap(this.constructor.name, () => {
16+
this.compilationCount++;
17+
18+
// Log memory usage every compilation
19+
const memUsage = process.memoryUsage();
20+
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
21+
const heapTotalMB = Math.round(memUsage.heapTotal / 1024 / 1024);
22+
23+
console.log(`📊 Compilation #${this.compilationCount} - Heap: ${heapUsedMB}MB/${heapTotalMB}MB`);
24+
if (this.compilationCount % 5 === 0) {
25+
console.log(`🗑️ Forcing garbage collection after ${this.compilationCount} compilations`);
26+
// @ts-expect-error - gc is a global function provided when Node.js is started with --expose-gc flag
27+
gc();
28+
29+
// Log memory after garbage collection
30+
const memAfterGC = process.memoryUsage();
31+
const heapAfterMB = Math.round(memAfterGC.heapUsed / 1024 / 1024);
32+
console.log(`✅ Post-GC heap size: ${heapAfterMB}MB (freed ${heapUsedMB - heapAfterMB}MB)`);
33+
}
34+
});
35+
} else {
36+
console.warn('⚠️ ForceGarbageCollectionPlugin: gc() function not available. Start Node.js with --expose-gc flag to enable garbage collection.');
37+
}
38+
}
39+
}
40+
41+
export default ForceGarbageCollectionPlugin;

config/webpack/webpack.common.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ function mapEnvironmentToLogoSuffix(environmentFile: string): string {
6868
* Get a production grade config for web or desktop
6969
*/
7070
const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment): Configuration => {
71-
// Determine if this is a development build
7271
const isDevelopment = file === '.env' || file === '.env.development';
7372

7473
return {

config/webpack/webpack.dev.ts

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {Configuration} from 'webpack';
77
import {DefinePlugin} from 'webpack';
88
import type {Configuration as DevServerConfiguration} from 'webpack-dev-server';
99
import {merge} from 'webpack-merge';
10+
import ForceGarbageCollectionPlugin from './ForceGarbageCollectionPlugin';
1011
import type Environment from './types';
1112
import getCommonConfiguration from './webpack.common';
1213

@@ -65,38 +66,7 @@ const getConfiguration = (environment: Environment): Promise<Configuration> =>
6566
'process.env.NODE_ENV': JSON.stringify('development'),
6667
}),
6768
new ReactRefreshWebpackPlugin({overlay: {sockProtocol: 'wss'}}),
68-
// Custom plugin to force garbage collection every 5 compilations
69-
{
70-
apply(compiler) {
71-
let compilationCount = 0;
72-
if (gc && typeof gc === 'function') {
73-
compiler.hooks.done.tap('ForceGCEvery5Compilations', () => {
74-
compilationCount++;
75-
76-
// Log memory usage every compilation
77-
const memUsage = process.memoryUsage();
78-
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
79-
const heapTotalMB = Math.round(memUsage.heapTotal / 1024 / 1024);
80-
81-
// eslint-disable-next-line no-console
82-
console.log(`📊 Compilation #${compilationCount} - Heap: ${heapUsedMB}MB/${heapTotalMB}MB`);
83-
84-
if (compilationCount % 5 === 0) {
85-
// eslint-disable-next-line no-console
86-
console.log(`🗑️ Forcing garbage collection after ${compilationCount} compilations`);
87-
// @ts-expect-error - gc is a global function provided by Node.js when --expose-gc is used
88-
gc();
89-
90-
// Log memory after GC
91-
const memAfterGC = process.memoryUsage();
92-
const heapAfterMB = Math.round(memAfterGC.heapUsed / 1024 / 1024);
93-
// eslint-disable-next-line no-console
94-
console.log(`✅ Post-GC heap: ${heapAfterMB}MB (freed ${heapUsedMB - heapAfterMB}MB)`);
95-
}
96-
});
97-
}
98-
},
99-
},
69+
new ForceGarbageCollectionPlugin(),
10070
],
10171
// This prevents import error coming from react-native-tab-view/lib/module/TabView.js
10272
// where Pager is imported without extension due to having platform-specific implementations

desktop/start.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ portfinder
1212
port: basePort,
1313
})
1414
.then((port) => {
15-
const devServer = `webpack-dev-server --config config/webpack/webpack.dev.ts --port ${port} --env platform=desktop`;
15+
const devServer = `./scripts/start-dev-with-auto-restart.sh --port ${port} --env platform=desktop --no-open`;
1616
const buildMain = 'webpack watch --config config/webpack/webpack.desktop.ts --config-name desktop-main --mode=development';
1717

1818
const env = {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"start": "npx rnef start",
2929
"web": "./scripts/set-pusher-suffix.sh && concurrently npm:web-proxy npm:web-server",
3030
"web-proxy": "ts-node web/proxy.ts",
31-
"web-server": "./scripts/web-server-with-restart.sh",
31+
"web-server": "./scripts/start-dev-with-auto-restart.sh",
3232
"build": "webpack --config config/webpack/webpack.common.ts --env file=.env.production && ts-node ./scripts/combine-web-sourcemaps.ts",
3333
"build-staging": "webpack --config config/webpack/webpack.common.ts --env file=.env.staging && ts-node ./scripts/combine-web-sourcemaps.ts",
3434
"build-adhoc": "webpack --config config/webpack/webpack.common.ts --env file=.env.adhoc && ts-node ./scripts/combine-web-sourcemaps.ts",
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22

33
# Auto-restart webpack-dev-server on memory crashes
44
# This script monitors for heap out of memory errors and automatically restarts
5+
# Usage: ./start-dev-with-auto-restart.sh [webpack-dev-server arguments]
56

7+
WDS_ARGS="$@"
68
MAX_RESTARTS=10
79
RESTART_COUNT=0
810
RESTART_DELAY=1
911

1012
echo "🚀 Starting webpack-dev-server with auto-restart (max restarts: $MAX_RESTARTS)"
1113

1214
run_wds () {
13-
node --expose-gc "$(npm root)/webpack-dev-server/bin/webpack-dev-server.js" $1 --config config/webpack/webpack.dev.ts
15+
node --expose-gc ./node_modules/.bin/webpack-dev-server $1 $WDS_ARGS --config config/webpack/webpack.dev.ts
1416
}
1517

1618
while [ $RESTART_COUNT -lt $MAX_RESTARTS ]; do
@@ -24,14 +26,13 @@ while [ $RESTART_COUNT -lt $MAX_RESTARTS ]; do
2426

2527
# Capture exit code
2628
EXIT_CODE=$?
27-
2829
# Check if it was a memory-related crash
2930
if [ $EXIT_CODE -eq 134 ] || [ $EXIT_CODE -eq 137 ] || [ $EXIT_CODE -eq 139 ]; then
3031
echo "💥 Memory crash detected (exit code: $EXIT_CODE)"
3132
RESTART_COUNT=$((RESTART_COUNT + 1))
3233

3334
if [ $RESTART_COUNT -lt $MAX_RESTARTS ]; then
34-
echo "🔄 Auto-restarting in $RESTART_DELAY seconds... (restart #$RESTART_COUNT)"
35+
echo "🔄 Auto-restarting... (restart #$RESTART_COUNT)"
3536
sleep $RESTART_DELAY
3637
else
3738
echo "❌ Max restarts reached ($MAX_RESTARTS). Exiting."

0 commit comments

Comments
 (0)