Skip to content

Commit ae3b8b0

Browse files
committed
perf(smol): use vm.compileFunction() and remove internal path remapping
Upgraded bootstrap loading to use modern C++ API: - vm.compileFunction() directly compiles with module parameters (single C++ call) - Removed Module.wrap() + vm.runInThisContext() (two-step process) - Better performance and cleaner code Removed internal path remapping: - No longer needed with vm.compileFunction() approach - Bootstrap now runs in proper module context with standard require() - Deleted ~90 lines of mapping code from esbuild plugin - Simplified plugin to only handle Unicode property transformations Benefits: - Faster bootstrap execution (direct C++ compilation) - Standard Node.js module resolution (no path hacks) - Easier maintenance (less custom code)
1 parent 64c8414 commit ae3b8b0

File tree

2 files changed

+16
-123
lines changed

2 files changed

+16
-123
lines changed

packages/bootstrap/.config/esbuild-plugin-smol-transform.mjs

Lines changed: 3 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,13 @@
11
/**
2-
* esbuild plugin to transform node:* requires to internal/* requires.
3-
* Also transforms Unicode property escapes to explicit character ranges.
2+
* esbuild plugin to transform Unicode property escapes to explicit character ranges.
43
*
5-
* This makes the bootstrap compatible with Node.js internal bootstrap context
6-
* for smol builds (built with --with-intl=none, no ICU support).
4+
* This makes the bootstrap compatible with Node.js smol builds
5+
* (built with --with-intl=none, no ICU support).
76
*/
87

98
import { parse } from '@babel/parser'
109
import MagicString from 'magic-string'
1110

12-
// Prefix-only modules that have no unprefixed form.
13-
// These ONLY support node:* syntax.
14-
const prefixOnlyModules = new Set([
15-
'node:sea',
16-
'node:sqlite',
17-
'node:test',
18-
'node:test/reporters',
19-
])
20-
21-
// Map core module requires to their internal bootstrap equivalents.
22-
// Based on Module.builtinModules from Node.js v24.10.0.
23-
// Format: [moduleName, bootstrapPath]
24-
// Handles both 'node:x' and plain 'x' variants (except prefix-only modules).
25-
const requireMappings = new Map([
26-
// Core modules with internal equivalents.
27-
['child_process', 'internal/child_process'],
28-
['fs', 'fs'],
29-
['fs/promises', 'internal/fs/promises'],
30-
31-
// Stream internals.
32-
['stream', 'stream'],
33-
['stream/promises', 'stream/promises'],
34-
['stream/web', 'internal/webstreams/readablestream'],
35-
36-
// Path variants.
37-
['path', 'path'],
38-
['path/posix', 'path'],
39-
['path/win32', 'path'],
40-
41-
// Core modules available at top level.
42-
['assert', 'assert'],
43-
['assert/strict', 'assert/strict'],
44-
['async_hooks', 'async_hooks'],
45-
['buffer', 'buffer'],
46-
['cluster', 'cluster'],
47-
['console', 'console'],
48-
['constants', 'constants'],
49-
['crypto', 'crypto'],
50-
['dgram', 'dgram'],
51-
['diagnostics_channel', 'diagnostics_channel'],
52-
['dns', 'dns'],
53-
['dns/promises', 'dns/promises'],
54-
['domain', 'domain'],
55-
['events', 'events'],
56-
['http', 'http'],
57-
['http2', 'http2'],
58-
['https', 'https'],
59-
['inspector', 'inspector'],
60-
['inspector/promises', 'inspector/promises'],
61-
['module', 'module'],
62-
['net', 'net'],
63-
['os', 'os'],
64-
['perf_hooks', 'perf_hooks'],
65-
['process', 'process'],
66-
['punycode', 'punycode'],
67-
['querystring', 'querystring'],
68-
['readline', 'readline'],
69-
['readline/promises', 'readline/promises'],
70-
['repl', 'repl'],
71-
['string_decoder', 'string_decoder'],
72-
['sys', 'sys'],
73-
['timers', 'timers'],
74-
['timers/promises', 'timers/promises'],
75-
['tls', 'tls'],
76-
['trace_events', 'trace_events'],
77-
['tty', 'tty'],
78-
['url', 'url'],
79-
['util', 'util'],
80-
['util/types', 'internal/util/types'],
81-
['v8', 'v8'],
82-
['vm', 'vm'],
83-
['wasi', 'wasi'],
84-
['worker_threads', 'worker_threads'],
85-
['zlib', 'zlib'],
86-
])
87-
8811
/**
8912
* Map of Unicode property escapes to explicit character ranges.
9013
* These are used when Node.js is built without ICU support (--with-intl=none).
@@ -340,23 +263,6 @@ export function smolTransformPlugin() {
340263
// This is necessary for Node.js built without ICU (--with-intl=none).
341264
content = replaceUnicodeProperties(content)
342265

343-
// Replace require("node:X") and require("X") with correct bootstrap path.
344-
for (const [moduleName, bootstrapPath] of requireMappings) {
345-
// Handle node:x variant.
346-
content = content.replace(
347-
new RegExp(`require\\(["']node:${moduleName}["']\\)`, 'g'),
348-
`require("${bootstrapPath}")`,
349-
)
350-
// Handle plain x variant (if different from bootstrap path).
351-
// Skip if this is a prefix-only module.
352-
if (moduleName !== bootstrapPath && !prefixOnlyModules.has(`node:${moduleName}`)) {
353-
content = content.replace(
354-
new RegExp(`require\\(["']${moduleName}["']\\)`, 'g'),
355-
`require("${bootstrapPath}")`,
356-
)
357-
}
358-
}
359-
360266
// Update the output content.
361267
output.contents = Buffer.from(content, 'utf8')
362268
}

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

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
# @node-versions: v24.10.0+
2-
# @description: Load Socket security bootstrap using Module.wrap() + VM (supports async!)
2+
# @description: Load Socket security bootstrap using vm.compileFunction() C++ API
33
#
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
4+
# This approach uses the modern C++ API for optimal performance:
5+
# 1. vm.compileFunction() directly compiles code with module context parameters
6+
# 2. Single C++ call (no wrapping + compilation steps)
77
# 3. Async code can run in the background while Node.js continues initialization
88
#
99
# The bootstrap code is embedded as base64 and decoded at runtime (no filesystem dependency).
1010

1111
--- a/lib/internal/process/pre_execution.js
1212
+++ b/lib/internal/process/pre_execution.js
13-
@@ -673,6 +673,53 @@ function runEmbedderPreload() {
13+
@@ -673,6 +673,46 @@ function runEmbedderPreload() {
1414
internalBinding('mksnapshot').runEmbedderPreload(process, require);
1515
}
1616

1717
function loadPreloadModules() {
18-
+ // Load Socket security bootstrap using Module.wrap() + VM approach.
18+
+ // Load Socket security bootstrap using vm.compileFunction() C++ API.
1919
+ // This allows async code in the bootstrap (unlike direct require()).
2020
+ (function loadSocketBootstrap() {
2121
+ // Bootstrap code embedded as base64 (build system replaces this placeholder).
@@ -32,37 +32,24 @@
3232
+ // Decode bootstrap from base64.
3333
+ const bootstrapCode = Buffer.from(SOCKET_BOOTSTRAP_B64, 'base64').toString('utf8');
3434
+
35-
+ // Create dummy module (same approach as Module._preloadModules).
35+
+ // Create module context.
3636
+ const bootstrapModule = new Module('socket:bootstrap', null);
3737
+ bootstrapModule.filename = 'socket:bootstrap';
3838
+ bootstrapModule.paths = [];
39+
+ const exports = {};
40+
+ bootstrapModule.exports = exports;
3941
+
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, {
42+
+ // Compile using C++ API with module parameters.
43+
+ const compiledFn = vm.compileFunction(bootstrapCode, ['exports', 'require', 'module', '__filename', '__dirname'], {
4644
+ filename: 'socket:bootstrap',
4745
+ lineOffset: 0,
48-
+ displayErrors: true,
46+
+ columnOffset: 0,
4947
+ });
5048
+
5149
+ // Execute with module context.
52-
+ const exports = {};
53-
+ bootstrapModule.exports = exports;
54-
+
55-
+ // Call the wrapped function with proper context.
5650
+ // If bootstrap contains async code (main().catch(...)), it starts executing
5751
+ // but returns immediately - async operations run in background.
58-
+ compiledWrapper.call(
59-
+ exports,
60-
+ exports,
61-
+ require,
62-
+ bootstrapModule,
63-
+ 'socket:bootstrap',
64-
+ ''
65-
+ );
52+
+ compiledFn.call(exports, exports, require, bootstrapModule, 'socket:bootstrap', '');
6653
+ } catch (err) {
6754
+ // Use stderr.write to avoid console dependencies during early bootstrap.
6855
+ process.stderr.write(`Socket bootstrap error: ${err.message}\n${err.stack}\n`);

0 commit comments

Comments
 (0)