Skip to content

Commit ff08042

Browse files
committed
sea: add allowDynamicImportFromFileSystem config option
Signed-off-by: Ali Hassan <ali-hassan27@outlook.com>
1 parent 0fea430 commit ff08042

19 files changed

+303
-6
lines changed

doc/api/single-executable-applications.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ The configuration currently reads the following top-level fields:
111111
{
112112
"main": "/path/to/bundled/script.js",
113113
"mainFormat": "commonjs", // Default: "commonjs", options: "commonjs", "module"
114+
"allowDynamicImportFromFileSystem": false, // Default: false
114115
"executable": "/path/to/node/binary", // Optional, if not specified, uses the current Node.js binary
115116
"output": "/path/to/write/the/generated/executable",
116117
"disableExperimentalSEAWarning": true, // Default: false
@@ -451,9 +452,12 @@ injected main script with the following properties:
451452

452453
<!-- TODO(joyeecheung): support and document module.registerHooks -->
453454

454-
When using `"mainFormat": "module"`, `import()` can be used to dynamically
455-
load built-in modules. Attempting to use `import()` to load modules from
456-
the file system will throw an error.
455+
By default, `import()` in the injected main script can only load built-in
456+
modules. When `allowDynamicImportFromFileSystem` is set to `true` in the SEA
457+
configuration, `import()` can also load modules from the file system. This
458+
works for both `"mainFormat": "commonjs"` and `"mainFormat": "module"`
459+
entry points. Relative specifiers are resolved relative to the directory of the
460+
executable.
457461

458462
### Using native addons in the injected main script
459463

lib/internal/modules/esm/utils.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const {
4848
} = require('internal/modules/helpers');
4949

5050
let defaultConditions;
51+
let seaBinding;
5152
/**
5253
* Returns the default conditions for ES module loading.
5354
* @returns {object}
@@ -68,6 +69,10 @@ function getDefaultConditionsSet() {
6869
return defaultConditionsSet;
6970
}
7071

72+
function getSeaBinding() {
73+
return seaBinding ??= internalBinding('sea');
74+
}
75+
7176
/**
7277
* Initializes the default conditions for ESM module loading.
7378
* This function is called during pre-execution, before any user code is run.
@@ -238,15 +243,25 @@ function getBuiltinModuleWrapForEmbedder(specifier) {
238243
}
239244

240245
/**
241-
* Get the built-in module dynamically for embedder ESM.
246+
* Handle dynamic import for embedder entry points.
247+
* SEA can opt into the default loader via explicit configuration; other embedder
248+
* entry points continue to be limited to built-in modules.
242249
* @param {string} specifier - The module specifier string.
243250
* @param {number} phase - The module import phase. Ignored for now.
244251
* @param {Record<string, string>} attributes - The import attributes object. Ignored for now.
245252
* @param {string|null|undefined} referrerName - name of the referrer.
246-
* @returns {import('internal/modules/esm/loader.js').ModuleExports} - The imported module object.
253+
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
247254
*/
248255
function importModuleDynamicallyForEmbedder(specifier, phase, attributes, referrerName) {
249-
// Ignore phase and attributes for embedder ESM for now, because this only supports loading builtins.
256+
const { isSeaDynamicImportFromFileSystemEnabled } = getSeaBinding();
257+
if (isSeaDynamicImportFromFileSystemEnabled()) {
258+
return defaultImportModuleDynamicallyForScript(
259+
specifier,
260+
phase,
261+
attributes,
262+
referrerName,
263+
);
264+
}
250265
return getBuiltinModuleWrapForEmbedder(specifier).getNamespace();
251266
}
252267

src/node_sea.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,18 @@ void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
283283
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
284284
}
285285

286+
void IsSeaDynamicImportFromFileSystemEnabled(
287+
const FunctionCallbackInfo<Value>& args) {
288+
if (!IsSingleExecutable()) {
289+
args.GetReturnValue().Set(false);
290+
return;
291+
}
292+
293+
SeaResource sea_resource = FindSingleExecutableResource();
294+
args.GetReturnValue().Set(static_cast<bool>(
295+
sea_resource.flags & SeaFlags::kAllowDynamicImportFromFileSystem));
296+
}
297+
286298
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
287299
// Repeats argv[0] at position 1 on argv as a replacement for the missing
288300
// entry point file path.
@@ -444,6 +456,18 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
444456
if (use_code_cache_value) {
445457
result.flags |= SeaFlags::kUseCodeCache;
446458
}
459+
} else if (key == "allowDynamicImportFromFileSystem") {
460+
bool allow_dynamic_import_from_file_system;
461+
if (field.value().get_bool().get(allow_dynamic_import_from_file_system)) {
462+
FPrintF(stderr,
463+
"\"allowDynamicImportFromFileSystem\" field of %s is not a "
464+
"Boolean\n",
465+
config_path);
466+
return std::nullopt;
467+
}
468+
if (allow_dynamic_import_from_file_system) {
469+
result.flags |= SeaFlags::kAllowDynamicImportFromFileSystem;
470+
}
447471
} else if (key == "assets") {
448472
simdjson::ondemand::object assets_object;
449473
if (field.value().get_object().get(assets_object)) {
@@ -918,13 +942,18 @@ void Initialize(Local<Object> target,
918942
target,
919943
"isExperimentalSeaWarningNeeded",
920944
IsExperimentalSeaWarningNeeded);
945+
SetMethod(context,
946+
target,
947+
"isSeaDynamicImportFromFileSystemEnabled",
948+
IsSeaDynamicImportFromFileSystemEnabled);
921949
SetMethod(context, target, "getAsset", GetAsset);
922950
SetMethod(context, target, "getAssetKeys", GetAssetKeys);
923951
}
924952

925953
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
926954
registry->Register(IsSea);
927955
registry->Register(IsExperimentalSeaWarningNeeded);
956+
registry->Register(IsSeaDynamicImportFromFileSystemEnabled);
928957
registry->Register(GetAsset);
929958
registry->Register(GetAssetKeys);
930959
}

src/node_sea.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ enum class SeaFlags : uint32_t {
3030
kUseCodeCache = 1 << 2,
3131
kIncludeAssets = 1 << 3,
3232
kIncludeExecArgv = 1 << 4,
33+
kAllowDynamicImportFromFileSystem = 1 << 5,
3334
};
3435

3536
enum class SeaExecArgvExtension : uint8_t {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"main": "sea.js",
3+
"output": "sea",
4+
"allowDynamicImportFromFileSystem": true,
5+
"useCodeCache": true,
6+
"disableExperimentalSEAWarning": true
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"main": "sea.js",
3+
"output": "sea",
4+
"allowDynamicImportFromFileSystem": true,
5+
"disableExperimentalSEAWarning": true
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"main": "sea.js",
3+
"output": "sea",
4+
"disableExperimentalSEAWarning": true
5+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const { pathToFileURL } = require('node:url');
2+
const { dirname, join } = require('node:path');
3+
4+
const userURL = pathToFileURL(
5+
join(dirname(process.execPath), 'user.mjs'),
6+
).href;
7+
8+
import(userURL)
9+
.then(({ message }) => {
10+
console.log(message);
11+
})
12+
.catch((err) => {
13+
console.error(err);
14+
process.exit(1);
15+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
await Promise.resolve();
2+
3+
export const message = 'CJS SEA dynamic import executed successfully';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"main": "sea.mjs",
3+
"output": "sea",
4+
"mainFormat": "module",
5+
"allowDynamicImportFromFileSystem": true,
6+
"disableExperimentalSEAWarning": true
7+
}

0 commit comments

Comments
 (0)