Skip to content

Commit

Permalink
feat: require(esm)
Browse files Browse the repository at this point in the history
  • Loading branch information
devsnek committed Sep 10, 2024
1 parent 4af98fa commit c1bcb5f
Show file tree
Hide file tree
Showing 18 changed files with 99 additions and 63 deletions.
13 changes: 5 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ repository = "https://github.com/denoland/deno"

[workspace.dependencies]
deno_ast = { version = "=0.42.0", features = ["transpiling"] }
deno_core = { version = "0.307.0" }
deno_core = { git = "https://github.com/denoland/deno_core", rev = "3c7c0ff" }

deno_bench_util = { version = "0.162.0", path = "./bench_util" }
deno_lockfile = "=0.23.0"
Expand Down
1 change: 1 addition & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ deno_core::extension!(deno_node,
ops::os::op_homedir<P>,
op_node_build_os,
op_npm_process_state,
ops::require::op_require_can_parse_as_esm,
ops::require::op_require_init_paths,
ops::require::op_require_node_module_paths<P>,
ops::require::op_require_proxy_path,
Expand Down
27 changes: 27 additions & 0 deletions ext/node/ops/require.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use deno_core::error::AnyError;
use deno_core::normalize_path;
use deno_core::op2;
use deno_core::url::Url;
use deno_core::v8;
use deno_core::JsRuntimeInspector;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
Expand Down Expand Up @@ -612,3 +613,29 @@ fn url_to_file_path(url: &Url) -> Result<PathBuf, AnyError> {
}
}
}

#[op2(fast)]
pub fn op_require_can_parse_as_esm(
scope: &mut v8::HandleScope,
#[string] source: &str,
) -> bool {
let scope = &mut v8::TryCatch::new(scope);
let Some(source) = v8::String::new(scope, source) else {
return false;
};
let origin = v8::ScriptOrigin::new(
scope,
source.into(),
0,
0,
false,
0,
None,
true,
false,
true,
None,
);
let mut source = v8::script_compiler::Source::new(source, Some(&origin));
v8::script_compiler::compile_module(scope, &mut source).is_some()
}
65 changes: 24 additions & 41 deletions ext/node/polyfills/01_require.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

import { core, internals, primordials } from "ext:core/mod.js";
import {
op_import_sync,
op_napi_open,
op_require_as_file_path,
op_require_break_on_next_statement,
op_require_can_parse_as_esm,
op_require_init_paths,
op_require_is_deno_dir_package,
op_require_is_request_relative,
Expand Down Expand Up @@ -900,16 +902,6 @@ Module.prototype.load = function (filename) {
pathDirname(this.filename),
);
const extension = findLongestRegisteredExtension(filename);
// allow .mjs to be overridden
if (
StringPrototypeEndsWith(filename, ".mjs") && !Module._extensions[".mjs"]
) {
throw createRequireEsmError(
filename,
moduleParentCache.get(this)?.filename,
);
}

Module._extensions[extension](this, this.filename);
this.loaded = true;

Expand Down Expand Up @@ -987,27 +979,24 @@ function wrapSafe(
if (process.mainModule === cjsModuleInstance) {
enrichCJSError(err.thrown);
}
if (isEsmSyntaxError(err.thrown)) {
throw createRequireEsmError(
filename,
moduleParentCache.get(cjsModuleInstance)?.filename,
);
} else {
throw err.thrown;
}
throw err.thrown;
}
return f;
}

Module.prototype._compile = function (content, filename, format) {
const compiledWrapper = wrapSafe(filename, content, this, format);

if (format === "module") {
// TODO(https://github.com/denoland/deno/issues/24822): implement require esm
throw createRequireEsmError(
filename,
moduleParentCache.get(module)?.filename,
);
return loadESMFromCJS(this, filename, content);
}

let compiledWrapper;
try {
compiledWrapper = wrapSafe(filename, content, this, format);
} catch (err) {
if (err instanceof SyntaxError && op_require_can_parse_as_esm(content)) {
return loadESMFromCJS(this, filename, content);
}
throw err;
}

const dirname = pathDirname(filename);
Expand Down Expand Up @@ -1065,12 +1054,7 @@ Module._extensions[".js"] = function (module, filename) {
if (StringPrototypeEndsWith(filename, ".js")) {
const pkg = op_require_read_closest_package_json(filename);
if (pkg?.typ === "module") {
// TODO(https://github.com/denoland/deno/issues/24822): implement require esm
format = "module";
throw createRequireEsmError(
filename,
moduleParentCache.get(module)?.filename,
);
} else if (pkg?.type === "commonjs") {
format = "commonjs";
}
Expand All @@ -1081,20 +1065,19 @@ Module._extensions[".js"] = function (module, filename) {
module._compile(content, filename, format);
};

function createRequireEsmError(filename, parent) {
let message = `require() of ES Module ${filename}`;

if (parent) {
message += ` from ${parent}`;
}
function loadESMFromCJS(module, filename, code) {
const namespace = op_import_sync(
url.pathToFileURL(filename).toString(),
code,
);

message +=
` not supported. Instead change the require to a dynamic import() which is available in all CommonJS modules.`;
const err = new Error(message);
err.code = "ERR_REQUIRE_ESM";
return err;
module.exports = namespace;
}

Module._extensions[".mjs"] = function (module, filename) {
loadESMFromCJS(module, filename);
};

function stripBOM(content) {
if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) {
content = StringPrototypeSlice(content, 1);
Expand Down
3 changes: 0 additions & 3 deletions tests/integration/npm_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,18 @@ itest!(cjs_require_esm_error {
output: "npm/cjs_require_esm_error/main.out",
envs: env_vars_for_npm_tests(),
http_server: true,
exit_code: 1,
});

itest!(cjs_require_esm_mjs_error {
args: "run --allow-read --quiet npm/cjs_require_esm_mjs_error/main.ts",
output: "npm/cjs_require_esm_mjs_error/main.out",
envs: env_vars_for_npm_tests(),
http_server: true,
exit_code: 1,
});

itest!(require_esm_error {
args: "run --allow-read --quiet node/require_esm_error/main.ts",
output: "node/require_esm_error/main.out",
exit_code: 1,
});

itest!(dynamic_import_deno_ts_from_npm {
Expand Down
5 changes: 5 additions & 0 deletions tests/specs/run/require_esm/__test__.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"args": "run -A main.cjs",
"output": "main.out",
"exitCode": 1
}
2 changes: 2 additions & 0 deletions tests/specs/run/require_esm/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const async_js = 1;
await {};
5 changes: 5 additions & 0 deletions tests/specs/run/require_esm/main.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";

console.log(require("./sync.js"));
console.log(require("./sync.mjs"));
require("./async.js");
13 changes: 13 additions & 0 deletions tests/specs/run/require_esm/main.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Module: null prototype] { sync_js: 1 }
[Module: null prototype] { sync_mjs: 1 }
error: Uncaught (in promise) Error: Top-level await is not allowed in synchronous evaluation
at loadESMFromCJS (node:module:[WILDCARD])
at Module._compile (node:module:[WILDCARD])
at Object.Module._extensions..js (node:module:[WILDCARD])
at Module.load (node:module:[WILDCARD])
at Function.Module._load (node:module:[WILDCARD])
at Module.require (node:module:[WILDCARD])
at require (node:module:[WILDCARD])
at Object.<anonymous> (file:[WILDCARD]/tests/specs/run/require_esm/main.cjs:[WILDCARD])
at Object.<anonymous> (file:[WILDCARD]/tests/specs/run/require_esm/main.cjs:[WILDCARD])
at Module._compile (node:module:[WILDCARD])
1 change: 1 addition & 0 deletions tests/specs/run/require_esm/sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const sync_js = 1;
1 change: 1 addition & 0 deletions tests/specs/run/require_esm/sync.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const sync_mjs = 1;
4 changes: 1 addition & 3 deletions tests/testdata/node/require_esm_error/main.out
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
error: Uncaught (in promise) Error: require() of ES Module [WILDCARD]esm.js from [WILDCARD]main.ts not supported. Instead change the require to a dynamic import() which is available in all CommonJS modules.
at [WILDCARD]
at file:///[WILDCARD]/require_esm_error/main.ts:5:1
[Module: null prototype] { Test: [class Test] }
2 changes: 1 addition & 1 deletion tests/testdata/node/require_esm_error/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { createRequire } from "node:module";

const require = createRequire(import.meta.url);

require("./esm.js");
console.log(require("./esm.js"));
6 changes: 4 additions & 2 deletions tests/testdata/npm/cjs_require_esm_error/main.out
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
error: Uncaught (in promise) Error: require() of ES Module [WILDCARD]my_es_module.js from [WILDCARD]index.js not supported. Instead change the require to a dynamic import() which is available in all CommonJS modules.
[WILDCARD]
[Module: null prototype] {
Test: [Module: null prototype] { Test: [class Test] },
default: { Test: [Module: null prototype] { Test: [class Test] } }
}
3 changes: 2 additions & 1 deletion tests/testdata/npm/cjs_require_esm_error/main.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import "npm:@denotest/cjs-require-esm-error";
import * as ns from "npm:@denotest/cjs-require-esm-error";
console.log(ns);
6 changes: 4 additions & 2 deletions tests/testdata/npm/cjs_require_esm_mjs_error/main.out
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
error: Uncaught (in promise) Error: require() of ES Module [WILDCARD]esm_mjs.mjs from [WILDCARD]require_mjs.js not supported. Instead change the require to a dynamic import() which is available in all CommonJS modules.
[WILDCARD]
[Module: null prototype] {
Test: [Module: null prototype] { Test: [class Test] },
default: { Test: [Module: null prototype] { Test: [class Test] } }
}
3 changes: 2 additions & 1 deletion tests/testdata/npm/cjs_require_esm_mjs_error/main.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import "npm:@denotest/cjs-require-esm-error/require_mjs.js";
import * as ns from "npm:@denotest/cjs-require-esm-error/require_mjs.js";
console.log(ns);

0 comments on commit c1bcb5f

Please sign in to comment.