Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 112 additions & 7 deletions crates/wasm-rquickjs/skeleton/src/builtin/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,20 @@ fn with_fs_mut<R>(
}

fn invalidate_module_resolution_probes(ctx: &rquickjs::Ctx<'_>) {
ctx.userdata::<crate::internal::runtime_services::RuntimeServices>()
.expect("runtime services not initialized")
.cjs_module_probe_session
.invalidate();
let services = ctx
.userdata::<crate::internal::runtime_services::RuntimeServices>()
.expect("runtime services not initialized");
let _missing_package_json = services.cjs_module_probe_session.invalidate();
#[cfg(feature = "typescript-compiler-profiling")]
if _missing_package_json > 0
&& let Some(profile) = services.execution_profile()
{
profile.increment("modules.packageJson.negativeCacheInvalidations");
profile.add(
"modules.packageJson.negativeCacheInvalidatedEntries",
_missing_package_json as u64,
);
}
}

fn normalize_mode_override(mode: u32) -> u32 {
Expand Down Expand Up @@ -309,11 +319,87 @@ fn rename_fd_path(ctx: &rquickjs::Ctx<'_>, old_path: &str, new_path: &str) {
});
}

#[derive(Clone, Copy)]
pub(crate) enum ModuleLoaderRealpathDomain {
CommonJs,
Esm,
}

pub(super) fn realpath_for_module_resolution(
_ctx: &rquickjs::Ctx<'_>,
ctx: &rquickjs::Ctx<'_>,
path: &str,
) -> Option<String> {
canonicalize_guest_path(path).ok()
domain: ModuleLoaderRealpathDomain,
) -> std::io::Result<String> {
// Wizer has no guest preopens. Touching wasi-libc's lazy preopen cache here
// would snapshot the empty build-time filesystem into every runtime.
if crate::internal::is_wizer_active() {
return Err(wizer_enoent_io());
}

let services = ctx
.userdata::<crate::internal::runtime_services::RuntimeServices>()
.expect("runtime services not initialized");
#[cfg(feature = "typescript-compiler-profiling")]
let profile = services.execution_profile();
#[cfg(feature = "typescript-compiler-profiling")]
if let Some(profile) = &profile {
profile.increment("modules.realpath.calls");
}

let cached = match domain {
ModuleLoaderRealpathDomain::CommonJs => services
.cjs_loader_realpath_cache
.borrow()
.get(path)
.cloned(),
ModuleLoaderRealpathDomain::Esm => services
.esm_loader_realpath_cache
.borrow()
.get(path)
.cloned(),
};
if let Some(resolved) = cached {
#[cfg(feature = "test-observability")]
services.record_loader_realpath_cache_hit();
#[cfg(feature = "typescript-compiler-profiling")]
if let Some(profile) = &profile {
profile.increment("modules.realpath.cacheHits");
}
return Ok(resolved);
}

let resolved = canonicalize_guest_path(path);
#[cfg(feature = "test-observability")]
services.record_loader_realpath_system_call();
#[cfg(feature = "typescript-compiler-profiling")]
if let Some(profile) = &profile {
profile.increment("modules.realpath.systemCalls");
profile.increment("filesystem.realpath.calls");
profile.increment(match &resolved {
Ok(_) => "filesystem.realpath.success",
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
"filesystem.realpath.notFound"
}
Err(_) => "filesystem.realpath.errors",
});
}
if let Ok(resolved) = &resolved {
match domain {
ModuleLoaderRealpathDomain::CommonJs => {
services
.cjs_loader_realpath_cache
.borrow_mut()
.insert(path.to_string(), resolved.clone());
}
ModuleLoaderRealpathDomain::Esm => {
services
.esm_loader_realpath_cache
.borrow_mut()
.insert(path.to_string(), resolved.clone());
}
}
}
resolved
}

fn canonicalize_guest_path(path: &str) -> std::io::Result<String> {
Expand Down Expand Up @@ -1472,6 +1558,25 @@ pub mod native_module {
}
}

#[rquickjs::function]
pub fn fs_loader_realpath(ctx: Ctx<'_>, path: String) -> Object<'_> {
let result = Object::new(ctx.clone()).unwrap();
match super::realpath_for_module_resolution(
&ctx,
&path,
super::ModuleLoaderRealpathDomain::CommonJs,
) {
Ok(resolved) => result.set("result", resolved).unwrap(),
Err(error) => result
.set(
"error",
super::make_fs_error(&ctx, &error, "realpath", Some(&path)),
)
.unwrap(),
}
result
}

#[rquickjs::function]
pub fn fs_realpath(ctx: Ctx<'_>, path: String) -> Object<'_> {
let result = Object::new(ctx.clone()).unwrap();
Expand Down
13 changes: 10 additions & 3 deletions crates/wasm-rquickjs/skeleton/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,18 @@ mod sqlite {
pub use super::sqlite_disabled::*;
}

pub(crate) fn realpath_for_module_resolution(
pub(crate) fn realpath_for_cjs_module_resolution(
ctx: &rquickjs::Ctx<'_>,
path: &str,
) -> Option<String> {
fs::realpath_for_module_resolution(ctx, path)
) -> std::io::Result<String> {
fs::realpath_for_module_resolution(ctx, path, fs::ModuleLoaderRealpathDomain::CommonJs)
}

pub(crate) fn realpath_for_esm_module_resolution(
ctx: &rquickjs::Ctx<'_>,
path: &str,
) -> std::io::Result<String> {
fs::realpath_for_module_resolution(ctx, path, fs::ModuleLoaderRealpathDomain::Esm)
}

pub fn add_module_resolvers(
Expand Down
17 changes: 16 additions & 1 deletion crates/wasm-rquickjs/skeleton/src/builtin/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as pathModule from 'node:path';
import * as pathPosix from 'node:path/posix';
import * as pathWin32 from 'node:path/win32';
import * as fsModule from 'node:fs';
import * as fsNative from '__wasm_rquickjs_builtin/fs_native';
import * as util from 'node:util';
import * as buffer from 'node:buffer';
import * as os from 'node:os';
Expand Down Expand Up @@ -53,6 +54,7 @@ import * as sqlite from 'node:sqlite';
import * as internalHttp from '__wasm_rquickjs_builtin/internal/http';
import { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_MISSING_ARGS } from '__wasm_rquickjs_builtin/internal/errors';
import * as internalErrors from '__wasm_rquickjs_builtin/internal/errors';
import { createSystemError as createFsSystemError } from '__wasm_rquickjs_builtin/internal/fs/shared';
import * as internalFsUtils from '__wasm_rquickjs_builtin/internal/fs/utils';
import * as internalUrl from '__wasm_rquickjs_builtin/internal/url';
import * as internalUtil from '__wasm_rquickjs_builtin/internal/util';
Expand Down Expand Up @@ -736,7 +738,20 @@ function shouldPreserveSymlinks(isMainModuleLoad) {

function toCjsCanonicalFilename(filename, isMainModuleLoad) {
if (shouldPreserveSymlinks(isMainModuleLoad)) return filename;
return fsModule.realpathSync.native(filename);
// Node resolves against process.cwd() before consulting its loader realpath
// cache. Keep the native bridge limited to absolute, normalized guest paths.
const normalized = pathModule.resolve(filename);
const outcome = fsNative.fs_loader_realpath(normalized);
if (outcome.error) throw createFsSystemError(outcome.error);
return outcome.result;
}

if (testObservabilityEnabledNative()) {
Object.defineProperty(globalThis, '__wasm_rquickjs_test_cjs_canonical_filename', {
value: filename => toCjsCanonicalFilename(filename, false),
writable: false,
configurable: false,
});
}

function tryReadFile(filename) {
Expand Down
13 changes: 10 additions & 3 deletions crates/wasm-rquickjs/skeleton/src/builtin_p3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,18 @@ mod zlib {
#[path = "builtin/websocket.rs"]
mod websocket;

pub(crate) fn realpath_for_module_resolution(
pub(crate) fn realpath_for_cjs_module_resolution(
ctx: &rquickjs::Ctx<'_>,
path: &str,
) -> Option<String> {
fs::realpath_for_module_resolution(ctx, path)
) -> std::io::Result<String> {
fs::realpath_for_module_resolution(ctx, path, fs::ModuleLoaderRealpathDomain::CommonJs)
}

pub(crate) fn realpath_for_esm_module_resolution(
ctx: &rquickjs::Ctx<'_>,
path: &str,
) -> std::io::Result<String> {
fs::realpath_for_module_resolution(ctx, path, fs::ModuleLoaderRealpathDomain::Esm)
}

/// Registers builtin native and JavaScript module names with the resolver.
Expand Down
Loading
Loading