Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: CJS export default #673

Merged
merged 5 commits into from
Nov 11, 2024
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
9 changes: 7 additions & 2 deletions llrt/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ async fn start_cli(vm: &Vm) {
},
"-e" | "--eval" => {
if let Some(source) = args.get(i + 1) {
vm.run(source.as_bytes(), false, true).await;
vm.run(source.as_bytes(), false, false).await;
}
return;
},
Expand Down Expand Up @@ -192,7 +192,12 @@ async fn start_cli(vm: &Vm) {
}
}
} else {
vm.run_file("index", true, false).await;
let index = if let Ok(dir) = std::env::current_dir() {
[dir.to_string_lossy().as_ref(), "/index"].concat()
} else {
"index".into()
};
vm.run_file(index, true, false).await;
}
}

Expand Down
78 changes: 51 additions & 27 deletions llrt_core/src/module_loader/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
BYTECODE_COMPRESSED, BYTECODE_FILE_EXT, BYTECODE_UNCOMPRESSED, BYTECODE_VERSION,
SIGNATURE_LENGTH,
},
module_loader::CJS_LOADER_PREFIX,
vm::COMPRESSION_DICT,
};

Expand Down Expand Up @@ -95,49 +96,72 @@ impl CustomLoader {
let cjs_specifier = [CJS_IMPORT_PREFIX, name].concat();
let require: Function = ctx.globals().get("require")?;
let export_object: Value = require.call((&cjs_specifier,))?;
let mut module = String::from("const value = require(\"");
let mut module = String::with_capacity(name.len() + 512);
module.push_str("const value = require(\"");

module.push_str(name);
module.push_str("\");export default value;");
module.push_str("\");export default value.default||value;");
if let Some(obj) = export_object.as_object() {
module.push_str("const{");
let keys: Result<Vec<String>> = obj.keys().collect();
let keys = keys?;
for (i, p) in keys.iter().enumerate() {
if i > 0 {

if !keys.is_empty() {
module.push_str("const{");

for p in keys.iter() {
if p == "default" {
continue;
}
module.push_str(p);
module.push(',');
}
module.push_str(p);
}
module.push_str("}=value;");
module.push_str("export{");
for (i, p) in keys.iter().enumerate() {
if i > 0 {
module.truncate(module.len() - 1);
module.push_str("}=value;");
module.push_str("export{");
for p in keys.iter() {
if p == "default" {
continue;
}
module.push_str(p);
module.push(',');
}
module.push_str(p);
module.truncate(module.len() - 1);
module.push_str("};");
}
module.push_str("};");
}
Module::declare(ctx, name, module)
}

fn load_module<'js>(name: &str, ctx: &Ctx<'js>) -> Result<(Module<'js>, Option<String>)> {
let mut from_cjs_import = false;
let path = if let Some(cjs_path) = name.strip_prefix(CJS_IMPORT_PREFIX) {
from_cjs_import = true;
cjs_path
} else {
name
};
fn normalize_name(name: &str) -> (bool, bool, &str, &str) {
if !name.starts_with("__") {
// If name doesn’t start with "__", return defaults
return (false, false, name, name);
}

if let Some(cjs_path) = name.strip_prefix(CJS_IMPORT_PREFIX) {
// If it starts with CJS_IMPORT_PREFIX, mark as from_cjs_import
return (true, false, name, cjs_path);
}

if let Some(cjs_path) = name.strip_prefix(CJS_LOADER_PREFIX) {
// If it starts with CJS_LOADER_PREFIX, mark as is_cjs
return (false, true, cjs_path, cjs_path);
}

// Default return if no prefixes match
(false, false, name, name)
}

fn load_module<'js>(name: &str, ctx: &Ctx<'js>) -> Result<(Module<'js>, Option<String>)> {
let ctx = ctx.clone();

trace!("Loading module: {}", name);
let (from_cjs_import, is_cjs, normalized_name, path) = Self::normalize_name(name);

trace!("Loading module: {}", normalized_name);

//json files can never be from CJS imports as they are handled by require
if !from_cjs_import {
if name.ends_with(".json") {
if normalized_name.ends_with(".json") {
let mut file = File::open(path)?;
let prefix = "export default JSON.parse(`";
let suffix = "`);";
Expand All @@ -148,9 +172,9 @@ impl CustomLoader {

return Ok((Module::declare(ctx, path, json)?, None));
}
if name.ends_with(".cjs") {
if is_cjs || normalized_name.ends_with(".cjs") {
let url = ["file://", path].concat();
return Ok((Self::load_cjs_module(name, ctx)?, Some(url)));
return Ok((Self::load_cjs_module(normalized_name, ctx)?, Some(url)));
}
}

Expand All @@ -166,7 +190,7 @@ impl CustomLoader {
let bytes = std::fs::read(path)?;
let mut bytes: &[u8] = &bytes;

if name.ends_with(BYTECODE_FILE_EXT) {
if normalized_name.ends_with(BYTECODE_FILE_EXT) {
trace!("Loading binary module: {}", path);
return Ok((Self::load_bytecode_module(ctx, bytes)?, Some(path.into())));
}
Expand All @@ -175,7 +199,7 @@ impl CustomLoader {
}

let url = ["file://", path].concat();
Ok((Module::declare(ctx, name, bytes)?, Some(url)))
Ok((Module::declare(ctx, normalized_name, bytes)?, Some(url)))
}
}

Expand Down
3 changes: 3 additions & 0 deletions llrt_core/src/module_loader/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub mod loader;
pub mod resolver;

// added when .cjs files are imported
pub const CJS_IMPORT_PREFIX: &str = "__cjs:";
// added to force CJS imports in loader
pub const CJS_LOADER_PREFIX: &str = "__cjsm:";
64 changes: 45 additions & 19 deletions llrt_core/src/module_loader/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ use llrt_modules::path::{
use llrt_utils::result::ResultExt;
use once_cell::sync::Lazy;
use rquickjs::{loader::Resolver, Ctx, Error, Result};
use simd_json::BorrowedValue;
use simd_json::{derived::ValueObjectAccessAsScalar, BorrowedValue};
use tracing::trace;

use crate::{
bytecode::BYTECODE_FILE_EXT,
module_loader::CJS_LOADER_PREFIX,
utils::io::{is_supported_ext, JS_EXTENSIONS, SUPPORTED_EXTENSIONS},
};

Expand Down Expand Up @@ -108,6 +108,9 @@ pub fn require_resolve<'a>(
return resolved_by_file_exists(x_normalized.into());
}

let x_is_absolute = path::is_absolute(x);
let x_starts_with_current_dir = x.starts_with("./");

// 2. If X begins with '/'
let y = if path::is_absolute(x) {
// a. set Y to be the file system root
Expand All @@ -118,16 +121,12 @@ pub fn require_resolve<'a>(

// Normalize path Y to generate dirname(Y)
let dirname_y = if Path::new(y).is_dir() {
path::resolve_path([y].iter())
path::resolve_path([y].iter())?
} else {
let dirname_y = path::dirname(y);
path::resolve_path([&dirname_y].iter())
path::resolve_path([&dirname_y].iter())?
};

let x_is_absolute = path::is_absolute(x);

let x_starts_with_current_dir = x.starts_with("./");

// 3. If X begins with './' or '/' or '../'
if x_starts_with_current_dir || x_is_absolute || x.starts_with("../") {
let y_plus_x = if x_is_absolute {
Expand All @@ -143,12 +142,12 @@ pub fn require_resolve<'a>(
// a. LOAD_AS_FILE(Y + X)
if let Ok(Some(path)) = load_as_file(ctx, y_plus_x.clone()) {
trace!("+- Resolved by `LOAD_AS_FILE`: {}\n", path);
return Ok(to_abs_path(path));
return to_abs_path(path);
} else {
// b. LOAD_AS_DIRECTORY(Y + X)
if let Ok(Some(path)) = load_as_directory(ctx, y_plus_x) {
trace!("+- Resolved by `LOAD_AS_DIRECTORY`: {}\n", path);
return Ok(to_abs_path(path));
return to_abs_path(path);
}
}

Expand All @@ -168,7 +167,7 @@ pub fn require_resolve<'a>(
// 5. LOAD_PACKAGE_SELF(X, dirname(Y))
if let Ok(Some(path)) = load_package_self(ctx, x, &dirname_y, is_esm) {
trace!("+- Resolved by `LOAD_PACKAGE_SELF`: {}\n", path);
return Ok(to_abs_path(path.into()));
return to_abs_path(path.into());
}

// 6. LOAD_NODE_MODULES(X, dirname(Y))
Expand All @@ -180,7 +179,7 @@ pub fn require_resolve<'a>(
// 6.5. LOAD_AS_FILE(X)
if let Ok(Some(path)) = load_as_file(ctx, Rc::new(x.to_owned())) {
trace!("+- Resolved by `LOAD_AS_FILE`: {}\n", path);
return Ok(to_abs_path(path));
return to_abs_path(path);
}

// 7. THROW "not found"
Expand All @@ -194,17 +193,17 @@ fn resolved_by_bytecode_cache(x: Cow<'_, str>) -> Result<Cow<'_, str>> {

fn resolved_by_file_exists(path: Cow<'_, str>) -> Result<Cow<'_, str>> {
trace!("+- Resolved by `FILE`: {}\n", path);
Ok(to_abs_path(path))
to_abs_path(path)
}

fn to_abs_path(path: Cow<'_, str>) -> Cow<'_, str> {
if !is_absolute(&path) {
resolve_path_with_separator([path], true).into()
fn to_abs_path(path: Cow<'_, str>) -> Result<Cow<'_, str>> {
Ok(if !is_absolute(&path) {
resolve_path_with_separator([path], true)?.into()
} else if cfg!(windows) {
replace_backslash(path).into()
} else {
path
}
})
}

// LOAD_AS_FILE(X)
Expand Down Expand Up @@ -267,7 +266,7 @@ fn load_index<'a>(ctx: &Ctx<'_>, x: Rc<String>) -> Result<Option<Cow<'a, str>>>
trace!("| load_index(x): {}", x);

// 1. If X/index.js is a file
for extension in [".js", ".mjs", ".cjs", BYTECODE_FILE_EXT].iter() {
for extension in SUPPORTED_EXTENSIONS.iter() {
let file = [x.as_str(), "/index", extension].concat();
if Path::new(&file).is_file() {
// a. Find the closest package scope SCOPE to X.
Expand Down Expand Up @@ -428,17 +427,37 @@ fn load_package_exports<'a>(

//2. If X does not match this pattern or DIR/NAME/package.json is not a file,
// return.
let mut package_json_path = [dir, "/"].concat();
let mut package_json_path = String::with_capacity(dir.len() + 64);
package_json_path.push_str(dir);
package_json_path.push('/');
let base_path_length = package_json_path.len();
package_json_path.push_str(scope);
package_json_path.push_str("/package.json");

let mut sub_module = None;

let (scope, name) = if name != "." && !Path::new(&package_json_path).exists() {
package_json_path.truncate(base_path_length);
package_json_path.push_str(x);
package_json_path.push_str("/package.json");
(x, ".")
} else {
for ext in JS_EXTENSIONS {
let path = [
&package_json_path[0..base_path_length],
scope,
name.as_ref().trim_start_matches("."),
*ext,
]
.concat();
if Path::new(&path).exists() {
if *ext == ".mjs" {
//we know its an ESM module
return Ok(path.into());
}
sub_module = Some(path);
}
}
(scope, name.as_ref())
};

Expand All @@ -454,6 +473,13 @@ fn load_package_exports<'a>(
let mut package_json = fs::read(&package_json_path).or_throw(ctx)?;
let package_json = simd_json::to_borrowed_value(&mut package_json).or_throw(ctx)?;

if let Some(sub_module) = sub_module {
if package_json.get_str("type") != Some("module") {
return Ok([CJS_LOADER_PREFIX, &sub_module].concat().into());
}
return Ok(sub_module.into());
}

let module_path = package_exports_resolve(&package_json, name, is_esm)?;

Ok(correct_extensions(
Expand Down
2 changes: 0 additions & 2 deletions llrt_core/src/modules/js/@llrt/test/SocketClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ class SocketClient extends EventEmitter {
const errorListener = (err: Error) => reject(err);
this.socket.on("error", errorListener);
this.socket.connect(this.port, this.host, () => {
console.log(`Connected to ${this.host}:${this.port}`);

this.socket.off("error", errorListener);
this.socket.on("error", (err) => this.emit("error", err));
resolve();
Expand Down
2 changes: 1 addition & 1 deletion llrt_core/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ fn init(ctx: &Ctx<'_>, module_names: HashSet<&'static str>) -> Result<()> {
} else {
let module_name = get_script_or_module_name(&ctx);
let module_name = module_name.trim_start_matches(CJS_IMPORT_PREFIX);
let abs_path = resolve_path([module_name].iter());
let abs_path = resolve_path([module_name].iter())?;

let resolved_path =
require_resolve(&ctx, &specifier, &abs_path, false)?.into_owned();
Expand Down
Loading