mirror of
https://github.com/denoland/deno.git
synced 2025-02-08 07:16:56 -05:00
feat: require(esm) (#25501)
implement require(esm) using `op_import_sync` from deno_core. possible future changes: - cts and mts - replace Deno.core.evalContext to optimize esm syntax detection Fixes: https://github.com/denoland/deno/issues/25487
This commit is contained in:
parent
ee3829a778
commit
f9007d3386
29 changed files with 106 additions and 67 deletions
|
@ -339,6 +339,7 @@ deno_core::extension!(deno_node,
|
||||||
ops::os::op_homedir<P>,
|
ops::os::op_homedir<P>,
|
||||||
op_node_build_os,
|
op_node_build_os,
|
||||||
op_npm_process_state,
|
op_npm_process_state,
|
||||||
|
ops::require::op_require_can_parse_as_esm,
|
||||||
ops::require::op_require_init_paths,
|
ops::require::op_require_init_paths,
|
||||||
ops::require::op_require_node_module_paths<P>,
|
ops::require::op_require_node_module_paths<P>,
|
||||||
ops::require::op_require_proxy_path,
|
ops::require::op_require_proxy_path,
|
||||||
|
|
|
@ -6,6 +6,7 @@ use deno_core::error::AnyError;
|
||||||
use deno_core::normalize_path;
|
use deno_core::normalize_path;
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
|
use deno_core::v8;
|
||||||
use deno_core::JsRuntimeInspector;
|
use deno_core::JsRuntimeInspector;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
|
@ -614,3 +615,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()
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
import { core, internals, primordials } from "ext:core/mod.js";
|
import { core, internals, primordials } from "ext:core/mod.js";
|
||||||
import {
|
import {
|
||||||
|
op_import_sync,
|
||||||
op_napi_open,
|
op_napi_open,
|
||||||
op_require_as_file_path,
|
op_require_as_file_path,
|
||||||
op_require_break_on_next_statement,
|
op_require_break_on_next_statement,
|
||||||
|
op_require_can_parse_as_esm,
|
||||||
op_require_init_paths,
|
op_require_init_paths,
|
||||||
op_require_is_deno_dir_package,
|
op_require_is_deno_dir_package,
|
||||||
op_require_is_request_relative,
|
op_require_is_request_relative,
|
||||||
|
@ -900,16 +902,6 @@ Module.prototype.load = function (filename) {
|
||||||
pathDirname(this.filename),
|
pathDirname(this.filename),
|
||||||
);
|
);
|
||||||
const extension = findLongestRegisteredExtension(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);
|
Module._extensions[extension](this, this.filename);
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
|
@ -987,27 +979,24 @@ function wrapSafe(
|
||||||
if (process.mainModule === cjsModuleInstance) {
|
if (process.mainModule === cjsModuleInstance) {
|
||||||
enrichCJSError(err.thrown);
|
enrichCJSError(err.thrown);
|
||||||
}
|
}
|
||||||
if (isEsmSyntaxError(err.thrown)) {
|
throw err.thrown;
|
||||||
throw createRequireEsmError(
|
|
||||||
filename,
|
|
||||||
moduleParentCache.get(cjsModuleInstance)?.filename,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw err.thrown;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.prototype._compile = function (content, filename, format) {
|
Module.prototype._compile = function (content, filename, format) {
|
||||||
const compiledWrapper = wrapSafe(filename, content, this, format);
|
|
||||||
|
|
||||||
if (format === "module") {
|
if (format === "module") {
|
||||||
// TODO(https://github.com/denoland/deno/issues/24822): implement require esm
|
return loadESMFromCJS(this, filename, content);
|
||||||
throw createRequireEsmError(
|
}
|
||||||
filename,
|
|
||||||
moduleParentCache.get(module)?.filename,
|
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);
|
const dirname = pathDirname(filename);
|
||||||
|
@ -1065,12 +1054,7 @@ Module._extensions[".js"] = function (module, filename) {
|
||||||
if (StringPrototypeEndsWith(filename, ".js")) {
|
if (StringPrototypeEndsWith(filename, ".js")) {
|
||||||
const pkg = op_require_read_closest_package_json(filename);
|
const pkg = op_require_read_closest_package_json(filename);
|
||||||
if (pkg?.typ === "module") {
|
if (pkg?.typ === "module") {
|
||||||
// TODO(https://github.com/denoland/deno/issues/24822): implement require esm
|
|
||||||
format = "module";
|
format = "module";
|
||||||
throw createRequireEsmError(
|
|
||||||
filename,
|
|
||||||
moduleParentCache.get(module)?.filename,
|
|
||||||
);
|
|
||||||
} else if (pkg?.type === "commonjs") {
|
} else if (pkg?.type === "commonjs") {
|
||||||
format = "commonjs";
|
format = "commonjs";
|
||||||
}
|
}
|
||||||
|
@ -1081,20 +1065,19 @@ Module._extensions[".js"] = function (module, filename) {
|
||||||
module._compile(content, filename, format);
|
module._compile(content, filename, format);
|
||||||
};
|
};
|
||||||
|
|
||||||
function createRequireEsmError(filename, parent) {
|
function loadESMFromCJS(module, filename, code) {
|
||||||
let message = `require() of ES Module ${filename}`;
|
const namespace = op_import_sync(
|
||||||
|
url.pathToFileURL(filename).toString(),
|
||||||
|
code,
|
||||||
|
);
|
||||||
|
|
||||||
if (parent) {
|
module.exports = namespace;
|
||||||
message += ` from ${parent}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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._extensions[".mjs"] = function (module, filename) {
|
||||||
|
loadESMFromCJS(module, filename);
|
||||||
|
};
|
||||||
|
|
||||||
function stripBOM(content) {
|
function stripBOM(content) {
|
||||||
if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) {
|
if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) {
|
||||||
content = StringPrototypeSlice(content, 1);
|
content = StringPrototypeSlice(content, 1);
|
||||||
|
|
|
@ -58,26 +58,23 @@ itest!(cjs_invalid_name_exports {
|
||||||
http_server: true,
|
http_server: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(cjs_require_esm_error {
|
itest!(cjs_require_esm {
|
||||||
args: "run --allow-read --quiet npm/cjs_require_esm_error/main.ts",
|
args: "run --allow-read --quiet npm/cjs_require_esm/main.ts",
|
||||||
output: "npm/cjs_require_esm_error/main.out",
|
output: "npm/cjs_require_esm/main.out",
|
||||||
envs: env_vars_for_npm_tests(),
|
envs: env_vars_for_npm_tests(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
exit_code: 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(cjs_require_esm_mjs_error {
|
itest!(cjs_require_esm_mjs {
|
||||||
args: "run --allow-read --quiet npm/cjs_require_esm_mjs_error/main.ts",
|
args: "run --allow-read --quiet npm/cjs_require_esm_mjs/main.ts",
|
||||||
output: "npm/cjs_require_esm_mjs_error/main.out",
|
output: "npm/cjs_require_esm_mjs/main.out",
|
||||||
envs: env_vars_for_npm_tests(),
|
envs: env_vars_for_npm_tests(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
exit_code: 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(require_esm_error {
|
itest!(require_esm {
|
||||||
args: "run --allow-read --quiet node/require_esm_error/main.ts",
|
args: "run --allow-read --quiet node/require_esm/main.ts",
|
||||||
output: "node/require_esm_error/main.out",
|
output: "node/require_esm/main.out",
|
||||||
exit_code: 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(dynamic_import_deno_ts_from_npm {
|
itest!(dynamic_import_deno_ts_from_npm {
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@denotest/cjs-require-esm-error",
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "@denotest/cjs-require-esm",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
5
tests/specs/run/require_esm/__test__.jsonc
Normal file
5
tests/specs/run/require_esm/__test__.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"args": "run -A main.cjs",
|
||||||
|
"output": "main.out",
|
||||||
|
"exitCode": 1
|
||||||
|
}
|
2
tests/specs/run/require_esm/async.js
Normal file
2
tests/specs/run/require_esm/async.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const async_js = 1;
|
||||||
|
await {};
|
5
tests/specs/run/require_esm/main.cjs
Normal file
5
tests/specs/run/require_esm/main.cjs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
console.log(require("./sync.js"));
|
||||||
|
console.log(require("./sync.mjs"));
|
||||||
|
require("./async.js");
|
13
tests/specs/run/require_esm/main.out
Normal file
13
tests/specs/run/require_esm/main.out
Normal file
|
@ -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
tests/specs/run/require_esm/sync.js
Normal file
1
tests/specs/run/require_esm/sync.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const sync_js = 1;
|
1
tests/specs/run/require_esm/sync.mjs
Normal file
1
tests/specs/run/require_esm/sync.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const sync_mjs = 1;
|
1
tests/testdata/node/require_esm/main.out
vendored
Normal file
1
tests/testdata/node/require_esm/main.out
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[Module: null prototype] { Test: [class Test] }
|
|
@ -2,4 +2,4 @@ import { createRequire } from "node:module";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
require("./esm.js");
|
console.log(require("./esm.js"));
|
|
@ -1,3 +0,0 @@
|
||||||
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
|
|
4
tests/testdata/npm/cjs_require_esm/main.out
vendored
Normal file
4
tests/testdata/npm/cjs_require_esm/main.out
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[Module: null prototype] {
|
||||||
|
Test: [Module: null prototype] { Test: [class Test] },
|
||||||
|
default: { Test: [Module: null prototype] { Test: [class Test] } }
|
||||||
|
}
|
2
tests/testdata/npm/cjs_require_esm/main.ts
vendored
Normal file
2
tests/testdata/npm/cjs_require_esm/main.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import * as ns from "npm:@denotest/cjs-require-esm";
|
||||||
|
console.log(ns);
|
|
@ -1,2 +0,0 @@
|
||||||
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]
|
|
|
@ -1 +0,0 @@
|
||||||
import "npm:@denotest/cjs-require-esm-error";
|
|
4
tests/testdata/npm/cjs_require_esm_mjs/main.out
vendored
Normal file
4
tests/testdata/npm/cjs_require_esm_mjs/main.out
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[Module: null prototype] {
|
||||||
|
Test: [Module: null prototype] { Test: [class Test] },
|
||||||
|
default: { Test: [Module: null prototype] { Test: [class Test] } }
|
||||||
|
}
|
2
tests/testdata/npm/cjs_require_esm_mjs/main.ts
vendored
Normal file
2
tests/testdata/npm/cjs_require_esm_mjs/main.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import * as ns from "npm:@denotest/cjs-require-esm/require_mjs.js";
|
||||||
|
console.log(ns);
|
|
@ -1,2 +0,0 @@
|
||||||
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]
|
|
|
@ -1 +0,0 @@
|
||||||
import "npm:@denotest/cjs-require-esm-error/require_mjs.js";
|
|
Loading…
Add table
Reference in a new issue