Skip to content

Commit 74bcbf8

Browse files
committed
feat(node-smol-builder): implement VM-based bootstrap loader for async support
Replace static patch with dynamic template-based generation to support async bootstrap code. The bootstrap previously failed when loaded via synchronous require() but contained async code (main().catch()). Changes: - Add VM-based loader template using Module.wrap() + vm.runInThisContext() - Implement dynamic patch generation with base64 bootstrap embedding - Split 1.6MB base64 into 80-char chunks for git patch compatibility - Calculate hunk headers dynamically (21,579 lines after expansion) - Remove importModuleDynamically to avoid early ESM loader dependency The generated patch embeds the entire bootstrap (1.2MB) as base64, decoded at runtime. This approach matches Node.js's --require mechanism and properly handles async code execution in the background. Binary now executes correctly without dumping source to stderr.
1 parent 2616826 commit 74bcbf8

File tree

3 files changed

+259
-89
lines changed

3 files changed

+259
-89
lines changed

packages/node-smol-builder/patches/load-socketsecurity-bootstrap-v24-preexec.patch

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# @node-versions: v24.10.0+
2+
# @description: Load Socket security bootstrap using Module.wrap() + VM (supports async!)
3+
#
4+
# This approach uses the same mechanism as `--require` modules, which properly handles async code:
5+
# 1. Module.wrap() creates proper module context (exports, require, module, __filename, __dirname)
6+
# 2. vm.runInThisContext() compiles and executes the wrapped code
7+
# 3. Async code can run in the background while Node.js continues initialization
8+
#
9+
# The bootstrap code is embedded as base64 and decoded at runtime (no filesystem dependency).
10+
11+
--- a/lib/internal/process/pre_execution.js
12+
+++ b/lib/internal/process/pre_execution.js
13+
@@ -673,6 +673,53 @@ function runEmbedderPreload() {
14+
internalBinding('mksnapshot').runEmbedderPreload(process, require);
15+
}
16+
17+
function loadPreloadModules() {
18+
+ // Load Socket security bootstrap using Module.wrap() + VM approach.
19+
+ // This allows async code in the bootstrap (unlike direct require()).
20+
+ (function loadSocketBootstrap() {
21+
+ // Bootstrap code embedded as base64 (build system replaces this placeholder).
22+
+ // Split across multiple lines to avoid git patch line length limits.
23+
+ const SOCKET_BOOTSTRAP_B64 = (
24+
+ SOCKET_BOOTSTRAP_BASE64_PLACEHOLDER
25+
+ );
26+
+
27+
+ try {
28+
+ const Module = require('internal/modules/cjs/loader').Module;
29+
+ const vm = require('vm');
30+
+ const { Buffer } = require('buffer');
31+
+
32+
+ // Decode bootstrap from base64.
33+
+ const bootstrapCode = Buffer.from(SOCKET_BOOTSTRAP_B64, 'base64').toString('utf8');
34+
+
35+
+ // Create dummy module (same approach as Module._preloadModules).
36+
+ const bootstrapModule = new Module('socket:bootstrap', null);
37+
+ bootstrapModule.filename = 'socket:bootstrap';
38+
+ bootstrapModule.paths = [];
39+
+
40+
+ // Wrap code with proper module wrapper.
41+
+ // This adds: (function (exports, require, module, __filename, __dirname) { ... })
42+
+ const wrapped = Module.wrap(bootstrapCode);
43+
+
44+
+ // Compile using VM (this supports async code execution!).
45+
+ const compiledWrapper = vm.runInThisContext(wrapped, {
46+
+ filename: 'socket:bootstrap',
47+
+ lineOffset: 0,
48+
+ displayErrors: true,
49+
+ });
50+
+
51+
+ // Execute with module context.
52+
+ const exports = {};
53+
+ bootstrapModule.exports = exports;
54+
+
55+
+ // Call the wrapped function with proper context.
56+
+ // If bootstrap contains async code (main().catch(...)), it starts executing
57+
+ // but returns immediately - async operations run in background.
58+
+ compiledWrapper.call(
59+
+ exports,
60+
+ exports,
61+
+ require,
62+
+ bootstrapModule,
63+
+ 'socket:bootstrap',
64+
+ ''
65+
+ );
66+
+ } catch (err) {
67+
+ // Use stderr.write to avoid console dependencies during early bootstrap.
68+
+ process.stderr.write(`Socket bootstrap error: ${err.message}\n${err.stack}\n`);
69+
+ }
70+
+ })();
71+
+
72+
// For user code, we preload modules if `-r` is passed
73+
const preloadModules = getOptionValue('--require');
74+
if (preloadModules && preloadModules.length > 0) {

0 commit comments

Comments
 (0)