summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2022-08-09 21:06:01 +0200
committerGitHub <noreply@github.com>2022-08-09 21:06:01 +0200
commit1f54d877895ea25258a941818f07c6e84d44a7a2 (patch)
tree1999d04ec926d464c94ed2c5a9fe10fb84f3de24
parentaf618e3b8fb11f3947ab5ded9523cdca9cf77ced (diff)
feat: add ext/node for require support (#15362)
This commit adds "ext/node" extension that implementes CommonJS module system. In the future this extension might be extended to actually contain implementation of Node compatibility layer in favor of "deno_std/node". Currently this functionality is not publicly exposed, it is available via "Deno[Deno.internal].require" namespace and is meant to be used by other functionality to be landed soon. This is a minimal first pass, things that still don't work: support for dynamic imports in CJS conditional exports
-rw-r--r--Cargo.lock9
-rw-r--r--cli/Cargo.toml1
-rw-r--r--cli/compat/mod.rs46
-rw-r--r--cli/tests/integration/compat_tests.rs19
-rw-r--r--cli/tests/testdata/commonjs/data.json3
-rw-r--r--cli/tests/testdata/commonjs/example.js11
-rw-r--r--cli/tests/testdata/commonjs/init.js17
-rw-r--r--cli/tests/testdata/commonjs/node_modules/colorette/index.cjs74
-rw-r--r--cli/tests/testdata/commonjs/node_modules/colorette/index.js75
-rw-r--r--cli/tests/testdata/commonjs/node_modules/colorette/package.json23
-rw-r--r--cli/tests/testdata/commonjs/node_modules/imports_exports/import_export.js6
-rw-r--r--cli/tests/testdata/commonjs/node_modules/imports_exports/import_polyfill.js3
-rw-r--r--cli/tests/testdata/commonjs/node_modules/imports_exports/package.json17
-rw-r--r--cli/tests/testdata/commonjs/node_modules/imports_exports/require_export.cjs6
-rw-r--r--cli/tests/testdata/commonjs/node_modules/imports_exports/require_polyfill.js3
-rw-r--r--cli/tests/testdata/commonjs/node_modules/left-pad/README.md41
-rw-r--r--cli/tests/testdata/commonjs/node_modules/left-pad/index.js54
-rw-r--r--cli/tests/testdata/commonjs/node_modules/left-pad/package.json68
-rw-r--r--cli/tests/testdata/commonjs/package.json17
-rw-r--r--ext/node/01_require.js815
-rw-r--r--ext/node/Cargo.toml17
-rw-r--r--ext/node/lib.rs317
-rw-r--r--runtime/Cargo.toml2
-rw-r--r--runtime/build.rs1
-rw-r--r--runtime/lib.rs1
-rw-r--r--runtime/web_worker.rs1
-rw-r--r--runtime/worker.rs1
27 files changed, 1648 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9ba6b3dab..1be1a5a0e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -801,6 +801,7 @@ dependencies = [
"deno_graph",
"deno_lint",
"deno_net",
+ "deno_node",
"deno_runtime",
"deno_task_shell",
"deno_url",
@@ -1115,6 +1116,13 @@ dependencies = [
]
[[package]]
+name = "deno_node"
+version = "0.1.0"
+dependencies = [
+ "deno_core",
+]
+
+[[package]]
name = "deno_ops"
version = "0.24.0"
dependencies = [
@@ -1139,6 +1147,7 @@ dependencies = [
"deno_ffi",
"deno_http",
"deno_net",
+ "deno_node",
"deno_tls",
"deno_url",
"deno_web",
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index e275e1d99..345260f11 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -31,6 +31,7 @@ deno_core = { version = "0.146.0", path = "../core" }
deno_crypto = { version = "0.78.0", path = "../ext/crypto" }
deno_fetch = { version = "0.87.0", path = "../ext/fetch" }
deno_net = { version = "0.56.0", path = "../ext/net" }
+deno_node = { version = "0.1.0", path = "../ext/node" }
deno_url = { version = "0.64.0", path = "../ext/url" }
deno_web = { version = "0.95.0", path = "../ext/web" }
deno_webgpu = { version = "0.65.0", path = "../ext/webgpu" }
diff --git a/cli/compat/mod.rs b/cli/compat/mod.rs
index 98af2ea61..df5174fd3 100644
--- a/cli/compat/mod.rs
+++ b/cli/compat/mod.rs
@@ -75,12 +75,18 @@ static NODE_COMPAT_URL: Lazy<String> = Lazy::new(|| {
static GLOBAL_URL_STR: Lazy<String> =
Lazy::new(|| format!("{}node/global.ts", NODE_COMPAT_URL.as_str()));
+static PROCESS_URL_STR: Lazy<String> =
+ Lazy::new(|| format!("{}node/process.ts", NODE_COMPAT_URL.as_str()));
+
pub static GLOBAL_URL: Lazy<Url> =
Lazy::new(|| Url::parse(&GLOBAL_URL_STR).unwrap());
static MODULE_URL_STR: Lazy<String> =
Lazy::new(|| format!("{}node/module.ts", NODE_COMPAT_URL.as_str()));
+static MODULE_ALL_URL_STR: Lazy<String> =
+ Lazy::new(|| format!("{}node/module_all.ts", NODE_COMPAT_URL.as_str()));
+
pub static MODULE_URL: Lazy<Url> =
Lazy::new(|| Url::parse(&MODULE_URL_STR).unwrap());
@@ -106,6 +112,46 @@ fn try_resolve_builtin_module(specifier: &str) -> Option<Url> {
}
}
+#[allow(unused)]
+pub async fn load_builtin_node_modules(
+ js_runtime: &mut JsRuntime,
+) -> Result<(), AnyError> {
+ let source_code = &format!(
+ r#"(async function loadBuiltinNodeModules(moduleAllUrl, processUrl) {{
+ const [moduleAll, processModule] = await Promise.all([
+ import(moduleAllUrl),
+ import(processUrl)
+ ]);
+ Deno[Deno.internal].require.initializeCommonJs(moduleAll.default, processModule.default);
+ }})('{}', '{}');"#,
+ MODULE_ALL_URL_STR.as_str(),
+ PROCESS_URL_STR.as_str(),
+ );
+
+ let value =
+ js_runtime.execute_script(&located_script_name!(), source_code)?;
+ js_runtime.resolve_value(value).await?;
+ Ok(())
+}
+
+#[allow(unused)]
+pub fn load_cjs_module_from_ext_node(
+ js_runtime: &mut JsRuntime,
+ module: &str,
+ main: bool,
+) -> Result<(), AnyError> {
+ let source_code = &format!(
+ r#"(function loadCjsModule(module) {{
+ Deno[Deno.internal].require.Module._load(module, null, {main});
+ }})('{module}');"#,
+ main = main,
+ module = escape_for_single_quote_string(module),
+ );
+
+ js_runtime.execute_script(&located_script_name!(), source_code)?;
+ Ok(())
+}
+
pub fn load_cjs_module(
js_runtime: &mut JsRuntime,
module: &str,
diff --git a/cli/tests/integration/compat_tests.rs b/cli/tests/integration/compat_tests.rs
index ce89cc623..e8e6316bf 100644
--- a/cli/tests/integration/compat_tests.rs
+++ b/cli/tests/integration/compat_tests.rs
@@ -172,3 +172,22 @@ fn native_modules_as_global_vars() {
);
assert!(out.contains("true"));
}
+
+#[test]
+fn ext_node_cjs_execution() {
+ let (out, _err) = util::run_and_collect_output_with_args(
+ true,
+ vec![
+ "run",
+ "-A",
+ "--unstable",
+ "--quiet",
+ "commonjs/init.js",
+ "./example.js",
+ ],
+ None,
+ Some(vec![("DENO_NODE_COMPAT_URL".to_string(), std_file_url())]),
+ false,
+ );
+ assert!(out.contains("{ hello: \"world\" }"));
+}
diff --git a/cli/tests/testdata/commonjs/data.json b/cli/tests/testdata/commonjs/data.json
new file mode 100644
index 000000000..f2a886f39
--- /dev/null
+++ b/cli/tests/testdata/commonjs/data.json
@@ -0,0 +1,3 @@
+{
+ "hello": "world"
+}
diff --git a/cli/tests/testdata/commonjs/example.js b/cli/tests/testdata/commonjs/example.js
new file mode 100644
index 000000000..785078037
--- /dev/null
+++ b/cli/tests/testdata/commonjs/example.js
@@ -0,0 +1,11 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+// deno-lint-ignore no-undef
+const processMod = require("process");
+const osMod = require("node:os");
+console.log("process.pid", processMod.pid);
+console.log("os.EOL", osMod.EOL);
+const leftPad = require("left-pad");
+const json = require("./data");
+console.log(json);
+console.log(leftPad("foo", 5)); // => " foo"
+console.log("main module", processMod.mainModule.filename);
diff --git a/cli/tests/testdata/commonjs/init.js b/cli/tests/testdata/commonjs/init.js
new file mode 100644
index 000000000..142ae7c76
--- /dev/null
+++ b/cli/tests/testdata/commonjs/init.js
@@ -0,0 +1,17 @@
+import { fromFileUrl } from "../../../../test_util/std/path/mod.ts";
+
+const DENO_NODE_COMPAT_URL = Deno.env.get("DENO_NODE_COMPAT_URL");
+const moduleAllUrl = `${DENO_NODE_COMPAT_URL}node/module_all.ts`;
+const processUrl = `${DENO_NODE_COMPAT_URL}node/process.ts`;
+let moduleName = import.meta.resolve(Deno.args[0]);
+moduleName = fromFileUrl(moduleName);
+
+const [moduleAll, processModule] = await Promise.all([
+ import(moduleAllUrl),
+ import(processUrl),
+]);
+Deno[Deno.internal].require.initializeCommonJs(
+ moduleAll.default,
+ processModule.default,
+);
+Deno[Deno.internal].require.Module._load(moduleName, null, true);
diff --git a/cli/tests/testdata/commonjs/node_modules/colorette/index.cjs b/cli/tests/testdata/commonjs/node_modules/colorette/index.cjs
new file mode 100644
index 000000000..0590d668d
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/colorette/index.cjs
@@ -0,0 +1,74 @@
+// Copyright Jorge Bucaran. All rights reserved. MIT license.
+let enabled = !("NO_COLOR" in process.env) &&
+ ("FORCE_COLOR" in process.env ||
+ process.platform === "win32" ||
+ (process.stdout != null &&
+ process.stdout.isTTY &&
+ process.env.TERM &&
+ process.env.TERM !== "dumb"));
+
+const raw = (open, close, searchRegex, replaceValue) =>
+ (s) =>
+ enabled
+ ? open +
+ (~(s += "").indexOf(close, 4) // skip opening \x1b[
+ ? s.replace(searchRegex, replaceValue)
+ : s) +
+ close
+ : s;
+
+const init = (open, close) => {
+ return raw(
+ `\x1b[${open}m`,
+ `\x1b[${close}m`,
+ new RegExp(`\\x1b\\[${close}m`, "g"),
+ `\x1b[${open}m`,
+ );
+};
+
+exports.options = Object.defineProperty({}, "enabled", {
+ get: () => enabled,
+ set: (value) => (enabled = value),
+});
+
+exports.reset = init(0, 0);
+exports.bold = raw("\x1b[1m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[1m");
+exports.dim = raw("\x1b[2m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[2m");
+exports.italic = init(3, 23);
+exports.underline = init(4, 24);
+exports.inverse = init(7, 27);
+exports.hidden = init(8, 28);
+exports.strikethrough = init(9, 29);
+exports.black = init(30, 39);
+exports.red = init(31, 39);
+exports.green = init(32, 39);
+exports.yellow = init(33, 39);
+exports.blue = init(34, 39);
+exports.magenta = init(35, 39);
+exports.cyan = init(36, 39);
+exports.white = init(37, 39);
+exports.gray = init(90, 39);
+exports.bgBlack = init(40, 49);
+exports.bgRed = init(41, 49);
+exports.bgGreen = init(42, 49);
+exports.bgYellow = init(43, 49);
+exports.bgBlue = init(44, 49);
+exports.bgMagenta = init(45, 49);
+exports.bgCyan = init(46, 49);
+exports.bgWhite = init(47, 49);
+exports.blackBright = init(90, 39);
+exports.redBright = init(91, 39);
+exports.greenBright = init(92, 39);
+exports.yellowBright = init(93, 39);
+exports.blueBright = init(94, 39);
+exports.magentaBright = init(95, 39);
+exports.cyanBright = init(96, 39);
+exports.whiteBright = init(97, 39);
+exports.bgBlackBright = init(100, 49);
+exports.bgRedBright = init(101, 49);
+exports.bgGreenBright = init(102, 49);
+exports.bgYellowBright = init(103, 49);
+exports.bgBlueBright = init(104, 49);
+exports.bgMagentaBright = init(105, 49);
+exports.bgCyanBright = init(106, 49);
+exports.bgWhiteBright = init(107, 49);
diff --git a/cli/tests/testdata/commonjs/node_modules/colorette/index.js b/cli/tests/testdata/commonjs/node_modules/colorette/index.js
new file mode 100644
index 000000000..6e83866f3
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/colorette/index.js
@@ -0,0 +1,75 @@
+// Copyright Jorge Bucaran. All rights reserved. MIT license.
+// deno-lint-ignore-file no-undef no-control-regex
+let enabled = !("NO_COLOR" in process.env) &&
+ ("FORCE_COLOR" in process.env ||
+ process.platform === "win32" ||
+ (process.stdout != null &&
+ process.stdout.isTTY &&
+ process.env.TERM &&
+ process.env.TERM !== "dumb"));
+
+const raw = (open, close, searchRegex, replaceValue) =>
+ (s) =>
+ enabled
+ ? open +
+ (~(s += "").indexOf(close, 4) // skip opening \x1b[
+ ? s.replace(searchRegex, replaceValue)
+ : s) +
+ close
+ : s;
+
+const init = (open, close) => {
+ return raw(
+ `\x1b[${open}m`,
+ `\x1b[${close}m`,
+ new RegExp(`\\x1b\\[${close}m`, "g"),
+ `\x1b[${open}m`,
+ );
+};
+
+export const options = Object.defineProperty({}, "enabled", {
+ get: () => enabled,
+ set: (value) => (enabled = value),
+});
+
+export const reset = init(0, 0);
+export const bold = raw("\x1b[1m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[1m");
+export const dim = raw("\x1b[2m", "\x1b[22m", /\x1b\[22m/g, "\x1b[22m\x1b[2m");
+export const italic = init(3, 23);
+export const underline = init(4, 24);
+export const inverse = init(7, 27);
+export const hidden = init(8, 28);
+export const strikethrough = init(9, 29);
+export const black = init(30, 39);
+export const red = init(31, 39);
+export const green = init(32, 39);
+export const yellow = init(33, 39);
+export const blue = init(34, 39);
+export const magenta = init(35, 39);
+export const cyan = init(36, 39);
+export const white = init(37, 39);
+export const gray = init(90, 39);
+export const bgBlack = init(40, 49);
+export const bgRed = init(41, 49);
+export const bgGreen = init(42, 49);
+export const bgYellow = init(43, 49);
+export const bgBlue = init(44, 49);
+export const bgMagenta = init(45, 49);
+export const bgCyan = init(46, 49);
+export const bgWhite = init(47, 49);
+export const blackBright = init(90, 39);
+export const redBright = init(91, 39);
+export const greenBright = init(92, 39);
+export const yellowBright = init(93, 39);
+export const blueBright = init(94, 39);
+export const magentaBright = init(95, 39);
+export const cyanBright = init(96, 39);
+export const whiteBright = init(97, 39);
+export const bgBlackBright = init(100, 49);
+export const bgRedBright = init(101, 49);
+export const bgGreenBright = init(102, 49);
+export const bgYellowBright = init(103, 49);
+export const bgBlueBright = init(104, 49);
+export const bgMagentaBright = init(105, 49);
+export const bgCyanBright = init(106, 49);
+export const bgWhiteBright = init(107, 49);
diff --git a/cli/tests/testdata/commonjs/node_modules/colorette/package.json b/cli/tests/testdata/commonjs/node_modules/colorette/package.json
new file mode 100644
index 000000000..fdca1aa24
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/colorette/package.json
@@ -0,0 +1,23 @@
+{
+ "author": {
+ "name": "Jorge Bucaran"
+ },
+ "description": "Color your terminal using pure idiomatic JavaScript.",
+ "exports": {
+ "./package.json": "./package.json",
+ ".": {
+ "require": "./index.cjs",
+ "import": "./index.js"
+ }
+ },
+ "license": "MIT",
+ "main": "index.cjs",
+ "module": "index.js",
+ "name": "colorette",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/jorgebucaran/colorette.git"
+ },
+ "type": "module",
+ "version": "1.2.1"
+}
diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/import_export.js b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_export.js
new file mode 100644
index 000000000..3ebd222ea
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_export.js
@@ -0,0 +1,6 @@
+import dep from "#dep";
+
+export default {
+ bar: "bar",
+ dep,
+};
diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/import_polyfill.js b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_polyfill.js
new file mode 100644
index 000000000..76716a3ef
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/import_polyfill.js
@@ -0,0 +1,3 @@
+export default {
+ polyfill: "import",
+};
diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/package.json b/cli/tests/testdata/commonjs/node_modules/imports_exports/package.json
new file mode 100644
index 000000000..5d26359db
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/package.json
@@ -0,0 +1,17 @@
+{
+ "version": "1.0.0",
+ "name": "imports_exports",
+ "main": "./require_export.cjs",
+ "imports": {
+ "#dep": {
+ "import": "./import_polyfill.js",
+ "require": "./require_polyfill.js"
+ }
+ },
+ "exports": {
+ ".": {
+ "import": "./import_export.js",
+ "require": "./require_export.cjs"
+ }
+ }
+}
diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/require_export.cjs b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_export.cjs
new file mode 100644
index 000000000..48923b8d8
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_export.cjs
@@ -0,0 +1,6 @@
+const dep = require("#dep");
+
+module.exports = {
+ foo: "foo",
+ dep,
+};
diff --git a/cli/tests/testdata/commonjs/node_modules/imports_exports/require_polyfill.js b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_polyfill.js
new file mode 100644
index 000000000..1023fd65c
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/imports_exports/require_polyfill.js
@@ -0,0 +1,3 @@
+module.exports = {
+ polyfill: "require",
+};
diff --git a/cli/tests/testdata/commonjs/node_modules/left-pad/README.md b/cli/tests/testdata/commonjs/node_modules/left-pad/README.md
new file mode 100644
index 000000000..0e70d461e
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/left-pad/README.md
@@ -0,0 +1,41 @@
+## left-pad
+
+String left pad
+
+[![Build Status][travis-image]][travis-url]
+
+## Install
+
+```bash
+$ npm install left-pad
+```
+
+## Usage
+
+```js
+const leftPad = require("left-pad");
+
+leftPad("foo", 5);
+// => " foo"
+
+leftPad("foobar", 6);
+// => "foobar"
+
+leftPad(1, 2, "0");
+// => "01"
+
+leftPad(17, 5, 0);
+// => "00017"
+```
+
+**NOTE:** The third argument should be a single `char`. However the module
+doesn't throw an error if you supply more than one `char`s. See
+[#28](https://github.com/stevemao/left-pad/pull/28).
+
+**NOTE:** Characters having code points outside of
+[BMP plan](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane)
+are considered a two distinct characters. See
+[#58](https://github.com/stevemao/left-pad/issues/58).
+
+[travis-image]: https://travis-ci.org/stevemao/left-pad.svg?branch=master
+[travis-url]: https://travis-ci.org/stevemao/left-pad
diff --git a/cli/tests/testdata/commonjs/node_modules/left-pad/index.js b/cli/tests/testdata/commonjs/node_modules/left-pad/index.js
new file mode 100644
index 000000000..a439e91de
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/left-pad/index.js
@@ -0,0 +1,54 @@
+// deno-lint-ignore-file
+
+/* This program is free software. It comes without any warranty, to
+ * the extent permitted by applicable law. You can redistribute it
+ * and/or modify it under the terms of the Do What The Fuck You Want
+ * To Public License, Version 2, as published by Sam Hocevar. See
+ * http://www.wtfpl.net/ for more details. */
+"use strict";
+module.exports = leftPad;
+
+var cache = [
+ "",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+];
+
+function leftPad(str, len, ch) {
+ // convert `str` to a `string`
+ str = str + "";
+ // `len` is the `pad`'s length now
+ len = len - str.length;
+ // doesn't need to pad
+ if (len <= 0) return str;
+ // `ch` defaults to `' '`
+ if (!ch && ch !== 0) ch = " ";
+ // convert `ch` to a `string` cuz it could be a number
+ ch = ch + "";
+ // cache common use cases
+ if (ch === " " && len < 10) return cache[len] + str;
+ // `pad` starts with an empty string
+ var pad = "";
+ // loop
+ while (true) {
+ // add `ch` to `pad` if `len` is odd
+ if (len & 1) pad += ch;
+ // divide `len` by 2, ditch the remainder
+ len >>= 1;
+ // "double" the `ch` so this operation count grows logarithmically on `len`
+ // each time `ch` is "doubled", the `len` would need to be "doubled" too
+ // similar to finding a value in binary search tree, hence O(log(n))
+ if (len) ch += ch;
+ // `len` is 0, exit the loop
+ else break;
+ }
+ // pad `str`!
+ return pad + str;
+}
diff --git a/cli/tests/testdata/commonjs/node_modules/left-pad/package.json b/cli/tests/testdata/commonjs/node_modules/left-pad/package.json
new file mode 100644
index 000000000..57be04271
--- /dev/null
+++ b/cli/tests/testdata/commonjs/node_modules/left-pad/package.json
@@ -0,0 +1,68 @@
+{
+ "_from": "left-pad",
+ "_id": "left-pad@1.3.0",
+ "_inBundle": false,
+ "_integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==",
+ "_location": "/left-pad",
+ "_phantomChildren": {},
+ "_requested": {
+ "type": "tag",
+ "registry": true,
+ "raw": "left-pad",
+ "name": "left-pad",
+ "escapedName": "left-pad",
+ "rawSpec": "",
+ "saveSpec": null,
+ "fetchSpec": "latest"
+ },
+ "_requiredBy": [
+ "#USER",
+ "/"
+ ],
+ "_resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
+ "_shasum": "5b8a3a7765dfe001261dde915589e782f8c94d1e",
+ "_spec": "left-pad",
+ "_where": "/Users/kun/Projects/Deno/deno/std/node/tests",
+ "author": {
+ "name": "azer"
+ },
+ "bugs": {
+ "url": "https://github.com/stevemao/left-pad/issues"
+ },
+ "bundleDependencies": false,
+ "deprecated": "use String.prototype.padStart()",
+ "description": "String left pad",
+ "devDependencies": {
+ "benchmark": "^2.1.0",
+ "fast-check": "0.0.8",
+ "tape": "*"
+ },
+ "homepage": "https://github.com/stevemao/left-pad#readme",
+ "keywords": [
+ "leftpad",
+ "left",
+ "pad",
+ "padding",
+ "string",
+ "repeat"
+ ],
+ "license": "WTFPL",
+ "main": "index.js",
+ "maintainers": [
+ {
+ "name": "Cameron Westland",
+ "email": "camwest@gmail.com"
+ }
+ ],
+ "name": "left-pad",
+ "repository": {
+ "url": "git+ssh://git@github.com/stevemao/left-pad.git",
+ "type": "git"
+ },
+ "scripts": {
+ "bench": "node perf/perf.js",
+ "test": "node test"
+ },
+ "types": "index.d.ts",
+ "version": "1.3.0"
+}
diff --git a/cli/tests/testdata/commonjs/package.json b/cli/tests/testdata/commonjs/package.json
new file mode 100644
index 000000000..9ce08a900
--- /dev/null
+++ b/cli/tests/testdata/commonjs/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "example",
+ "version": "0.0.1",
+ "description": "",
+ "main": "example.js",
+ "dependencies": {
+ "colorette": "1.2.1",
+ "left-pad": "1.3.0",
+ "imports_exports": "1.0.0"
+ },
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/ext/node/01_require.js b/ext/node/01_require.js
new file mode 100644
index 000000000..5a29617b1
--- /dev/null
+++ b/ext/node/01_require.js
@@ -0,0 +1,815 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+// deno-lint-ignore-file
+
+"use strict";
+
+((window) => {
+ const {
+ ArrayIsArray,
+ ArrayPrototypeIncludes,
+ ArrayPrototypeIndexOf,
+ ArrayPrototypeJoin,
+ ArrayPrototypePush,
+ ArrayPrototypeSlice,
+ ArrayPrototypeSplice,
+ ObjectGetOwnPropertyDescriptor,
+ ObjectGetPrototypeOf,
+ ObjectEntries,
+ ObjectPrototypeHasOwnProperty,
+ ObjectSetPrototypeOf,
+ ObjectKeys,
+ ObjectCreate,
+ SafeMap,
+ SafeWeakMap,
+ JSONParse,
+ StringPrototypeEndsWith,
+ StringPrototypeIndexOf,
+ StringPrototypeSlice,
+ StringPrototypeStartsWith,
+ StringPrototypeCharCodeAt,
+ RegExpPrototypeTest,
+ } = window.__bootstrap.primordials;
+ const core = window.Deno.core;
+
+ // Map used to store CJS parsing data.
+ const cjsParseCache = new SafeWeakMap();
+
+ function pathDirname(filepath) {
+ return core.opSync("op_require_path_dirname", filepath);
+ }
+
+ function pathResolve(...args) {
+ return core.opSync("op_require_path_resolve", args);
+ }
+
+ function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+ }
+
+ // TODO:
+ function isProxy() {
+ return false;
+ }
+
+ // TODO(bartlomieju): verify in other parts of this file that
+ // we have initialized the system before making APIs work
+ let cjsInitialized = false;
+ let processGlobal = null;
+ const nativeModulePolyfill = new SafeMap();
+ const nativeModuleExports = ObjectCreate(null);
+
+ const relativeResolveCache = ObjectCreate(null);
+ let requireDepth = 0;
+ let statCache = null;
+ let isPreloading = false;
+ let mainModule = null;
+
+ function stat(filename) {
+ // TODO: required only on windows
+ // filename = path.toNamespacedPath(filename);
+ if (statCache !== null) {
+ const result = statCache.get(filename);
+ if (result !== undefined) {
+ return result;
+ }
+ }
+ const result = core.opSync("op_require_stat", filename);
+ if (statCache !== null && result >= 0) {
+ statCache.set(filename, result);
+ }
+
+ return result;
+ }
+
+ function updateChildren(parent, child, scan) {
+ if (!parent) {
+ return;
+ }
+
+ const children = parent.children;
+ if (children && !(scan && ArrayPrototypeIncludes(children, child))) {
+ ArrayPrototypePush(children, child);
+ }
+ }
+
+ function tryFile(requestPath, _isMain) {
+ const rc = stat(requestPath);
+ if (rc !== 0) return;
+ return toRealPath(requestPath);
+ }
+
+ function tryPackage(requestPath, exts, isMain, originalPath) {
+ // const pkg = readPackage(requestPath)?.main;
+ let pkg = false;
+
+ if (!pkg) {
+ return tryExtensions(
+ pathResolve(requestPath, "index"),
+ exts,
+ isMain,
+ );
+ }
+
+ const filename = path.resolve(requestPath, pkg);
+ let actual = tryFile(filename, isMain) ||
+ tryExtensions(filename, exts, isMain) ||
+ tryExtensions(
+ pathResolve(filename, "index"),
+ exts,
+ isMain,
+ );
+ if (actual === false) {
+ actual = tryExtensions(
+ pathResolve(requestPath, "index"),
+ exts,
+ isMain,
+ );
+ if (!actual) {
+ // eslint-disable-next-line no-restricted-syntax
+ const err = new Error(
+ `Cannot find module '${filename}'. ` +
+ 'Please verify that the package.json has a valid "main" entry',
+ );
+ err.code = "MODULE_NOT_FOUND";
+ err.path = pathResolve(
+ requestPath,
+ "package.json",
+ );
+ err.requestPath = originalPath;
+ throw err;
+ } else {
+ const jsonPath = pathResolve(
+ requestPath,
+ "package.json",
+ );
+ process.emitWarning(
+ `Invalid 'main' field in '${jsonPath}' of '${pkg}'. ` +
+ "Please either fix that or report it to the module author",
+ "DeprecationWarning",
+ "DEP0128",
+ );
+ }
+ }
+ return actual;
+ }
+
+ const realpathCache = new SafeMap();
+ function toRealPath(requestPath) {
+ const maybeCached = realpathCache.get(requestPath);
+ if (maybeCached) {
+ return maybeCached;
+ }
+ const rp = core.opSync("op_require_real_path", requestPath);
+ realpathCache.set(requestPath, rp);
+ return rp;
+ }
+
+ function tryExtensions(p, exts, isMain) {
+ for (let i = 0; i < exts.length; i++) {
+ const filename = tryFile(p + exts[i], isMain);
+
+ if (filename) {
+ return filename;
+ }
+ }
+ return false;
+ }
+
+ // Find the longest (possibly multi-dot) extension registered in
+ // Module._extensions
+ function findLongestRegisteredExtension(filename) {
+ const name = core.opSync("op_require_path_basename", filename);
+ let currentExtension;
+ let index;
+ let startIndex = 0;
+ while ((index = StringPrototypeIndexOf(name, ".", startIndex)) !== -1) {
+ startIndex = index + 1;
+ if (index === 0) continue; // Skip dotfiles like .gitignore
+ currentExtension = StringPrototypeSlice(name, index);
+ if (Module._extensions[currentExtension]) {
+ return currentExtension;
+ }
+ }
+ return ".js";
+ }
+
+ function getExportsForCircularRequire(module) {
+ if (
+ module.exports &&
+ !isProxy(module.exports) &&
+ ObjectGetPrototypeOf(module.exports) === ObjectPrototype &&
+ // Exclude transpiled ES6 modules / TypeScript code because those may
+ // employ unusual patterns for accessing 'module.exports'. That should
+ // be okay because ES6 modules have a different approach to circular
+ // dependencies anyway.
+ !module.exports.__esModule
+ ) {
+ // This is later unset once the module is done loading.
+ ObjectSetPrototypeOf(
+ module.exports,
+ CircularRequirePrototypeWarningProxy,
+ );
+ }
+
+ return module.exports;
+ }
+
+ // A Proxy that can be used as the prototype of a module.exports object and
+ // warns when non-existent properties are accessed.
+ const CircularRequirePrototypeWarningProxy = new Proxy({}, {
+ get(target, prop) {
+ // Allow __esModule access in any case because it is used in the output
+ // of transpiled code to determine whether something comes from an
+ // ES module, and is not used as a regular key of `module.exports`.
+ if (prop in target || prop === "__esModule") return target[prop];
+ // TODO:
+ // emitCircularRequireWarning(prop);
+ console.log("TODO: emitCircularRequireWarning");
+ return undefined;
+ },
+
+ getOwnPropertyDescriptor(target, prop) {
+ if (
+ ObjectPrototypeHasOwnProperty(target, prop) || prop === "__esModule"
+ ) {
+ return ObjectGetOwnPropertyDescriptor(target, prop);
+ }
+ // TODO:
+ // emitCircularRequireWarning(prop);
+ console.log("TODO: emitCircularRequireWarning");
+ return undefined;
+ },
+ });
+
+ const moduleParentCache = new SafeWeakMap();
+ function Module(id = "", parent) {
+ this.id = id;
+ this.path = pathDirname(id);
+ this.exports = {};
+ moduleParentCache.set(this, parent);
+ updateChildren(parent, this, false);
+ this.filename = null;
+ this.loaded = false;
+ this.children = [];
+ }
+
+ const builtinModules = [];
+ // TODO(bartlomieju): handle adding native modules
+ Module.builtinModules = builtinModules;
+
+ Module._extensions = Object.create(null);
+ Module._cache = Object.create(null);
+ Module._pathCache = Object.create(null);
+ let modulePaths = [];
+ Module.globalPaths = modulePaths;
+
+ const CHAR_FORWARD_SLASH = 47;
+ const TRAILING_SLASH_REGEX = /(?:^|\/)\.?\.$/;
+ Module._findPath = function (request, paths, isMain) {
+ const absoluteRequest = core.opSync("op_require_path_is_absolute", request);
+ if (absoluteRequest) {
+ paths = [""];
+ } else if (!paths || paths.length === 0) {
+ return false;
+ }
+
+ const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00");
+ const entry = Module._pathCache[cacheKey];
+ if (entry) {
+ return entry;
+ }
+
+ let exts;
+ let trailingSlash = request.length > 0 &&
+ StringPrototypeCharCodeAt(request, request.length - 1) ===
+ CHAR_FORWARD_SLASH;
+ if (!trailingSlash) {
+ trailingSlash = RegExpPrototypeTest(TRAILING_SLASH_REGEX, request);
+ }
+
+ // For each path
+ for (let i = 0; i < paths.length; i++) {
+ // Don't search further if path doesn't exist
+ const curPath = paths[i];
+ if (curPath && stat(curPath) < 1) continue;
+
+ if (!absoluteRequest) {
+ const exportsResolved = false;
+ // TODO:
+ console.log("TODO: Module._findPath resolveExports");
+ // const exportsResolved = resolveExports(curPath, request);
+ if (exportsResolved) {
+ return exportsResolved;
+ }
+ }
+
+ const basePath = pathResolve(curPath, request);
+ let filename;
+
+ const rc = stat(basePath);
+ if (!trailingSlash) {
+ if (rc === 0) { // File.
+ if (!isMain) {
+ filename = toRealPath(basePath);
+ } else {
+ filename = toRealPath(basePath);
+ }
+ }
+
+ if (!filename) {
+ // Try it with each of the extensions
+ if (exts === undefined) {
+ exts = ObjectKeys(Module._extensions);
+ }
+ filename = tryExtensions(basePath, exts, isMain);
+ }
+ }
+
+ if (!filename && rc === 1) { // Directory.
+ // try it with each of the extensions at "index"
+ if (exts === undefined) {
+ exts = ObjectKeys(Module._extensions);
+ }
+ filename = tryPackage(basePath, exts, isMain, request);
+ }
+
+ if (filename) {
+ Module._pathCache[cacheKey] = filename;
+ return filename;
+ }
+ }
+
+ return false;
+ };
+
+ Module._nodeModulePaths = function (from) {
+ return core.opSync("op_require_node_module_paths", from);
+ };
+
+ Module._resolveLookupPaths = function (request, parent) {
+ return core.opSync(
+ "op_require_resolve_lookup_paths",
+ request,
+ parent?.paths,
+ parent?.filename ?? "",
+ );
+ };
+
+ Module._load = function (request, parent, isMain) {
+ let relResolveCacheIdentifier;
+ if (parent) {
+ // Fast path for (lazy loaded) modules in the same directory. The indirect
+ // caching is required to allow cache invalidation without changing the old
+ // cache key names.
+ relResolveCacheIdentifier = `${parent.path}\x00${request}`;
+ const filename = relativeResolveCache[relResolveCacheIdentifier];
+ if (filename !== undefined) {
+ const cachedModule = Module._cache[filename];
+ if (cachedModule !== undefined) {
+ updateChildren(parent, cachedModule, true);
+ if (!cachedModule.loaded) {
+ return getExportsForCircularRequire(cachedModule);
+ }
+ return cachedModule.exports;
+ }
+ delete relativeResolveCache[relResolveCacheIdentifier];
+ }
+ }
+
+ const filename = Module._resolveFilename(request, parent, isMain);
+ if (StringPrototypeStartsWith(filename, "node:")) {
+ // Slice 'node:' prefix
+ const id = StringPrototypeSlice(filename, 5);
+
+ const module = loadNativeModule(id, id);
+ if (!module) {
+ // TODO:
+ // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename);
+ throw new Error("Unknown built-in module");
+ }
+
+ return module.exports;
+ }
+
+ const cachedModule = Module._cache[filename];
+ if (cachedModule !== undefined) {
+ updateChildren(parent, cachedModule, true);
+ if (!cachedModule.loaded) {
+ const parseCachedModule = cjsParseCache.get(cachedModule);
+ if (!parseCachedModule || parseCachedModule.loaded) {
+ return getExportsForCircularRequire(cachedModule);
+ }
+ parseCachedModule.loaded = true;
+ } else {
+ return cachedModule.exports;
+ }
+ }
+
+ const mod = loadNativeModule(filename, request);
+ if (
+ mod
+ ) {
+ return mod.exports;
+ }
+
+ // Don't call updateChildren(), Module constructor already does.
+ const module = cachedModule || new Module(filename, parent);
+
+ if (isMain) {
+ processGlobal.mainModule = module;
+ module.id = ".";
+ }
+
+ Module._cache[filename] = module;
+ if (parent !== undefined) {
+ relativeResolveCache[relResolveCacheIdentifier] = filename;
+ }
+
+ let threw = true;
+ try {
+ module.load(filename);
+ threw = false;
+ } finally {
+ if (threw) {
+ delete Module._cache[filename];
+ if (parent !== undefined) {
+ delete relativeResolveCache[relResolveCacheIdentifier];
+ const children = parent?.children;
+ if (ArrayIsArray(children)) {
+ const index = ArrayPrototypeIndexOf(children, module);
+ if (index !== -1) {
+ ArrayPrototypeSplice(children, index, 1);
+ }
+ }
+ }
+ } else if (
+ module.exports &&
+ !isProxy(module.exports) &&
+ ObjectGetPrototypeOf(module.exports) ===
+ CircularRequirePrototypeWarningProxy
+ ) {
+ ObjectSetPrototypeOf(module.exports, ObjectPrototype);
+ }
+ }
+
+ return module.exports;
+ };
+
+ Module._resolveFilename = function (
+ request,
+ parent,
+ isMain,
+ options,
+ ) {
+ if (
+ StringPrototypeStartsWith(request, "node:") ||
+ nativeModuleCanBeRequiredByUsers(request)
+ ) {
+ return request;
+ }
+
+ let paths;
+
+ if (typeof options === "object" && options !== null) {
+ if (ArrayIsArray(options.paths)) {
+ const isRelative = core.opSync(
+ "op_require_specifier_is_relative",
+ request,
+ );
+
+ if (isRelative) {
+ paths = options.paths;
+ } else {
+ const fakeParent = new Module("", null);
+
+ paths = [];
+
+ for (let i = 0; i < options.paths.length; i++) {
+ const path = options.paths[i];
+ fakeParent.paths = Module._nodeModulePaths(path);
+ const lookupPaths = Module._resolveLookupPaths(request, fakeParent);
+
+ for (let j = 0; j < lookupPaths.length; j++) {
+ if (!ArrayPrototypeIncludes(paths, lookupPaths[j])) {
+ ArrayPrototypePush(paths, lookupPaths[j]);
+ }
+ }
+ }
+ }
+ } else if (options.paths === undefined) {
+ paths = Module._resolveLookupPaths(request, parent);
+ } else {
+ // TODO:
+ // throw new ERR_INVALID_ARG_VALUE("options.paths", options.paths);
+ throw new Error("Invalid arg value options.paths", options.path);
+ }
+ } else {
+ paths = Module._resolveLookupPaths(request, parent);
+ }
+
+ if (parent?.filename) {
+ if (request[0] === "#") {
+ console.log("TODO: Module._resolveFilename with #specifier");
+ // const pkg = readPackageScope(parent.filename) || {};
+ // if (pkg.data?.imports != null) {
+ // try {
+ // return finalizeEsmResolution(
+ // packageImportsResolve(
+ // request,
+ // pathToFileURL(parent.filename),
+ // cjsConditions,
+ // ),
+ // parent.filename,
+ // pkg.path,
+ // );
+ // } catch (e) {
+ // if (e.code === "ERR_MODULE_NOT_FOUND") {
+ // throw createEsmNotFoundErr(request);
+ // }
+ // throw e;
+ // }
+ // }
+ }
+ }
+
+ // Try module self resolution first
+ // TODO(bartlomieju): make into a single op
+ const parentPath = core.opSync(
+ "op_require_try_self_parent_path",
+ !!parent,
+ parent?.filename,
+ parent?.id,
+ );
+ // const selfResolved = core.opSync("op_require_try_self", parentPath, request);
+ const selfResolved = false;
+ if (selfResolved) {
+ const cacheKey = request + "\x00" +
+ (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, "\x00"));
+ Module._pathCache[cacheKey] = selfResolved;
+ return selfResolved;
+ }
+
+ // Look up the filename first, since that's the cache key.
+ const filename = Module._findPath(request, paths, isMain, false);
+ if (filename) return filename;
+ const requireStack = [];
+ for (let cursor = parent; cursor; cursor = moduleParentCache.get(cursor)) {
+ ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
+ }
+ let message = `Cannot find module '${request}'`;
+ if (requireStack.length > 0) {
+ message = message + "\nRequire stack:\n- " +
+ ArrayPrototypeJoin(requireStack, "\n- ");
+ }
+ // eslint-disable-next-line no-restricted-syntax
+ const err = new Error(message);
+ err.code = "MODULE_NOT_FOUND";
+ err.requireStack = requireStack;
+ throw err;
+ };
+
+ Module.prototype.load = function (filename) {
+ assert(!this.loaded);
+ this.filename = filename;
+ this.paths = Module._nodeModulePaths(
+ pathDirname(filename),
+ );
+ const extension = findLongestRegisteredExtension(filename);
+ // allow .mjs to be overriden
+ if (
+ StringPrototypeEndsWith(filename, ".mjs") && !Module._extensions[".mjs"]
+ ) {
+ // TODO: use proper error class
+ throw new Error("require ESM", filename);
+ }
+
+ Module._extensions[extension](this, filename);
+ this.loaded = true;
+
+ // TODO: do caching
+ };
+
+ // Loads a module at the given file path. Returns that module's
+ // `exports` property.
+ Module.prototype.require = function (id) {
+ if (typeof id !== "string") {
+ // TODO(bartlomieju): it should use different error type
+ // ("ERR_INVALID_ARG_VALUE")
+ throw new TypeError("Invalid argument type");
+ }
+
+ if (id === "") {
+ // TODO(bartlomieju): it should use different error type
+ // ("ERR_INVALID_ARG_VALUE")
+ throw new TypeError("id must be non empty");
+ }
+ requireDepth++;
+ try {
+ return Module._load(id, this, /* isMain */ false);
+ } finally {
+ requireDepth--;
+ }
+ };
+
+ Module.wrapper = [
+ // TODO:
+ // We provide non standard timer APIs in the CommonJS wrapper
+ // to avoid exposing them in global namespace.
+ "(function (exports, require, module, __filename, __dirname, setTimeout, clearTimeout, setInterval, clearInterval) { (function (exports, require, module, __filename, __dirname) {",
+ "\n}).call(this, exports, require, module, __filename, __dirname); })",
+ ];
+ Module.wrap = function (script) {
+ script = script.replace(/^#!.*?\n/, "");
+ return `${Module.wrapper[0]}${script}${Module.wrapper[1]}`;
+ };
+
+ function wrapSafe(
+ filename,
+ content,
+ _cjsModuleInstance,
+ ) {
+ const wrapper = Module.wrap(content);
+ const [f, err] = core.evalContext(wrapper, filename);
+ if (err) {
+ console.log("TODO: wrapSafe check if main module");
+ throw err.thrown;
+ }
+ return f;
+ }
+
+ Module.prototype._compile = function (content, filename) {
+ const compiledWrapper = wrapSafe(filename, content, this);
+
+ const dirname = pathDirname(filename);
+ const require = makeRequireFunction(this);
+ const exports = this.exports;
+ const thisValue = exports;
+ const module = this;
+ if (requireDepth === 0) {
+ statCache = new SafeMap();
+ }
+ const result = compiledWrapper.call(
+ thisValue,
+ exports,
+ require,
+ this,
+ filename,
+ dirname,
+ );
+ if (requireDepth === 0) {
+ statCache = null;
+ }
+ return result;
+ };
+
+ Module._extensions[".js"] = function (module, filename) {
+ const content = core.opSync("op_require_read_file", filename);
+
+ console.log(`TODO: Module._extensions[".js"] is ESM`);
+
+ module._compile(content, filename);
+ };
+
+ function stripBOM(content) {
+ if (content.charCodeAt(0) === 0xfeff) {
+ content = content.slice(1);
+ }
+ return content;
+ }
+
+ // Native extension for .json
+ Module._extensions[".json"] = function (module, filename) {
+ const content = core.opSync("op_require_read_file", filename);
+
+ try {
+ module.exports = JSONParse(stripBOM(content));
+ } catch (err) {
+ err.message = filename + ": " + err.message;
+ throw err;
+ }
+ };
+
+ // Native extension for .node
+ Module._extensions[".node"] = function (module, filename) {
+ throw new Error("not implemented loading .node files");
+ };
+
+ function createRequireFromPath(filename) {
+ const proxyPath = core.opSync("op_require_proxy_path", filename);
+ const mod = new Module(proxyPath);
+ mod.filename = proxyPath;
+ mod.paths = Module._nodeModulePaths(mod.path);
+ return makeRequireFunction(mod);
+ }
+
+ function makeRequireFunction(mod) {
+ const require = function require(path) {
+ return mod.require(path);
+ };
+
+ function resolve(request, options) {
+ return Module._resolveFilename(request, mod, false, options);
+ }
+
+ require.resolve = resolve;
+
+ function paths(request) {
+ return Module._resolveLookupPaths(request, mod);
+ }
+
+ resolve.paths = paths;
+ require.main = mainModule;
+ // Enable support to add extra extension types.
+ require.extensions = Module._extensions;
+ require.cache = Module._cache;
+
+ return require;
+ }
+
+ function createRequire(filename) {
+ // FIXME: handle URLs and validation
+ return createRequireFromPath(filename);
+ }
+
+ Module.createRequire = createRequire;
+
+ Module._initPaths = function () {
+ const paths = core.opSync("op_require_init_paths");
+ modulePaths = paths;
+ Module.globalPaths = ArrayPrototypeSlice(modulePaths);
+ };
+
+ Module.syncBuiltinESMExports = function syncBuiltinESMExports() {
+ throw new Error("not implemented");
+ };
+
+ Module.Module = Module;
+
+ const m = {
+ _cache: Module._cache,
+ _extensions: Module._extensions,
+ _findPath: Module._findPath,
+ _initPaths: Module._initPaths,
+ _load: Module._load,
+ _nodeModulePaths: Module._nodeModulePaths,
+ _pathCache: Module._pathCache,
+ _preloadModules: Module._preloadModules,
+ _resolveFilename: Module._resolveFilename,
+ _resolveLookupPaths: Module._resolveLookupPaths,
+ builtinModules: Module.builtinModules,
+ createRequire: Module.createRequire,
+ globalPaths: Module.globalPaths,
+ Module,
+ wrap: Module.wrap,
+ };
+
+ nativeModuleExports.module = m;
+
+ function loadNativeModule(_id, request) {
+ if (nativeModulePolyfill.has(request)) {
+ return nativeModulePolyfill.get(request);
+ }
+ const modExports = nativeModuleExports[request];
+ if (modExports) {
+ const nodeMod = new Module(request);
+ nodeMod.exports = modExports;
+ nodeMod.loaded = true;
+ nativeModulePolyfill.set(request, nodeMod);
+ return nodeMod;
+ }
+ return undefined;
+ }
+
+ function nativeModuleCanBeRequiredByUsers(request) {
+ return !!nativeModuleExports[request];
+ }
+
+ function initializeCommonJs(nodeModules, process) {
+ assert(!cjsInitialized);
+ cjsInitialized = true;
+ for (const [name, exports] of ObjectEntries(nodeModules)) {
+ nativeModuleExports[name] = exports;
+ ArrayPrototypePush(Module.builtinModules, name);
+ }
+ processGlobal = process;
+ }
+
+ function readPackageScope() {
+ throw new Error("not implemented");
+ }
+
+ window.__bootstrap.internals = {
+ ...window.__bootstrap.internals ?? {},
+ require: {
+ Module,
+ wrapSafe,
+ toRealPath,
+ cjsParseCache,
+ readPackageScope,
+ initializeCommonJs,
+ },
+ };
+})(globalThis);
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
new file mode 100644
index 000000000..719de8032
--- /dev/null
+++ b/ext/node/Cargo.toml
@@ -0,0 +1,17 @@
+# Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "deno_node"
+version = "0.1.0"
+authors = ["the Deno authors"]
+edition = "2021"
+license = "MIT"
+readme = "README.md"
+repository = "https://github.com/denoland/deno"
+description = "Node compatibility for Deno"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+deno_core = { version = "0.146.0", path = "../../core" }
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
new file mode 100644
index 000000000..5d6542dc5
--- /dev/null
+++ b/ext/node/lib.rs
@@ -0,0 +1,317 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::AnyError;
+use deno_core::include_js_files;
+use deno_core::normalize_path;
+use deno_core::op;
+use deno_core::Extension;
+use std::path::PathBuf;
+
+pub fn init() -> Extension {
+ Extension::builder()
+ .js(include_js_files!(
+ prefix "deno:ext/node",
+ "01_require.js",
+ ))
+ .ops(vec![
+ op_require_init_paths::decl(),
+ op_require_node_module_paths::decl(),
+ op_require_proxy_path::decl(),
+ op_require_is_request_relative::decl(),
+ op_require_resolve_lookup_paths::decl(),
+ op_require_try_self_parent_path::decl(),
+ op_require_try_self::decl(),
+ op_require_real_path::decl(),
+ op_require_path_is_absolute::decl(),
+ op_require_path_dirname::decl(),
+ op_require_stat::decl(),
+ op_require_path_resolve::decl(),
+ op_require_path_basename::decl(),
+ op_require_read_file::decl(),
+ ])
+ .build()
+}
+
+#[op]
+pub fn op_require_init_paths() -> Vec<String> {
+ let (home_dir, node_path) = if cfg!(windows) {
+ (
+ std::env::var("USERPROFILE").unwrap_or_else(|_| "".into()),
+ std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()),
+ )
+ } else {
+ (
+ std::env::var("HOME").unwrap_or_else(|_| "".into()),
+ std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()),
+ )
+ };
+
+ let mut prefix_dir = std::env::current_exe().unwrap();
+ if cfg!(windows) {
+ prefix_dir = prefix_dir.join("..").join("..")
+ } else {
+ prefix_dir = prefix_dir.join("..")
+ }
+
+ let mut paths = vec![prefix_dir.join("lib").join("node")];
+
+ if !home_dir.is_empty() {
+ paths.insert(0, PathBuf::from(&home_dir).join(".node_libraries"));
+ paths.insert(0, PathBuf::from(&home_dir).join(".nod_modules"));
+ }
+
+ let mut paths = paths
+ .into_iter()
+ .map(|p| p.to_string_lossy().to_string())
+ .collect();
+
+ if !node_path.is_empty() {
+ let delimiter = if cfg!(windows) { ";" } else { ":" };
+ let mut node_paths: Vec<String> = node_path
+ .split(delimiter)
+ .filter(|e| !e.is_empty())
+ .map(|s| s.to_string())
+ .collect();
+ node_paths.append(&mut paths);
+ paths = node_paths;
+ }
+
+ paths
+}
+
+#[op]
+pub fn op_require_node_module_paths(from: String) -> Vec<String> {
+ // Guarantee that "from" is absolute.
+ let from = deno_core::resolve_path(&from)
+ .unwrap()
+ .to_file_path()
+ .unwrap();
+
+ if cfg!(windows) {
+ // return root node_modules when path is 'D:\\'.
+ let from_str = from.to_str().unwrap();
+ if from_str.len() >= 3 {
+ let bytes = from_str.as_bytes();
+ if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':'
+ {
+ let p = from_str.to_owned() + "node_modules";
+ return vec![p];
+ }
+ }
+ } else {
+ // Return early not only to avoid unnecessary work, but to *avoid* returning
+ // an array of two items for a root: [ '//node_modules', '/node_modules' ]
+ if from.to_string_lossy() == "/" {
+ return vec!["/node_modules".to_string()];
+ }
+ }
+
+ let mut paths = vec![];
+ let mut current_path = from.as_path();
+ let mut maybe_parent = Some(current_path);
+ while let Some(parent) = maybe_parent {
+ if !parent.ends_with("/node_modules") {
+ paths.push(parent.join("node_modules").to_string_lossy().to_string());
+ current_path = parent;
+ maybe_parent = current_path.parent();
+ }
+ }
+
+ if !cfg!(windows) {
+ // Append /node_modules to handle root paths.
+ paths.push("/node_modules".to_string());
+ }
+
+ paths
+}
+
+#[op]
+fn op_require_proxy_path(filename: String) -> String {
+ // Allow a directory to be passed as the filename
+ let trailing_slash = if cfg!(windows) {
+ filename.ends_with('\\')
+ } else {
+ filename.ends_with('/')
+ };
+
+ if trailing_slash {
+ let p = PathBuf::from(filename);
+ p.join("noop.js").to_string_lossy().to_string()
+ } else {
+ filename
+ }
+}
+
+#[op]
+fn op_require_is_request_relative(request: String) -> bool {
+ if request.starts_with("./") {
+ return true;
+ }
+
+ if request.starts_with("../") {
+ return true;
+ }
+
+ if cfg!(windows) {
+ if request.starts_with(".\\") {
+ return true;
+ }
+
+ if request.starts_with("..\\") {
+ return true;
+ }
+ }
+
+ false
+}
+
+#[op]
+fn op_require_resolve_lookup_paths(
+ request: String,
+ maybe_parent_paths: Option<Vec<String>>,
+ parent_filename: String,
+) -> Option<Vec<String>> {
+ if !request.starts_with('.')
+ || (request.len() > 1
+ && !request.starts_with("..")
+ && !request.starts_with("./")
+ && (!cfg!(windows) || !request.starts_with(".\\")))
+ {
+ let module_paths = vec![];
+ let mut paths = module_paths;
+ if let Some(mut parent_paths) = maybe_parent_paths {
+ if !parent_paths.is_empty() {
+ paths.append(&mut parent_paths);
+ }
+ }
+
+ if !paths.is_empty() {
+ return Some(paths);
+ } else {
+ return None;
+ }
+ }
+
+ // In REPL, parent.filename is null.
+ // if (!parent || !parent.id || !parent.filename) {
+ // // Make require('./path/to/foo') work - normally the path is taken
+ // // from realpath(__filename) but in REPL there is no filename
+ // const mainPaths = ['.'];
+
+ // debug('looking for %j in %j', request, mainPaths);
+ // return mainPaths;
+ // }
+
+ let p = PathBuf::from(parent_filename);
+ Some(vec![p.parent().unwrap().to_string_lossy().to_string()])
+}
+
+#[op]
+fn op_require_path_is_absolute(p: String) -> bool {
+ PathBuf::from(p).is_absolute()
+}
+
+#[op]
+fn op_require_stat(filename: String) -> i32 {
+ if let Ok(metadata) = std::fs::metadata(&filename) {
+ if metadata.is_file() {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ -1
+}
+
+#[op]
+fn op_require_real_path(request: String) -> Result<String, AnyError> {
+ let mut canonicalized_path = PathBuf::from(request).canonicalize()?;
+ if cfg!(windows) {
+ canonicalized_path = PathBuf::from(
+ canonicalized_path
+ .display()
+ .to_string()
+ .trim_start_matches("\\\\?\\"),
+ );
+ }
+ Ok(canonicalized_path.to_string_lossy().to_string())
+}
+
+#[op]
+fn op_require_path_resolve(parts: Vec<String>) -> String {
+ assert!(!parts.is_empty());
+ let mut p = PathBuf::from(&parts[0]);
+ if parts.len() > 1 {
+ for part in &parts[1..] {
+ p = p.join(part);
+ }
+ }
+ normalize_path(p).to_string_lossy().to_string()
+}
+
+#[op]
+fn op_require_path_dirname(request: String) -> String {
+ let p = PathBuf::from(request);
+ p.parent().unwrap().to_string_lossy().to_string()
+}
+
+#[op]
+fn op_require_path_basename(request: String) -> String {
+ let p = PathBuf::from(request);
+ p.file_name().unwrap().to_string_lossy().to_string()
+}
+
+#[op]
+fn op_require_try_self_parent_path(
+ has_parent: bool,
+ maybe_parent_filename: Option<String>,
+ maybe_parent_id: Option<String>,
+) -> Option<String> {
+ if !has_parent {
+ return None;
+ }
+
+ if let Some(parent_filename) = maybe_parent_filename {
+ return Some(parent_filename);
+ }
+
+ if let Some(parent_id) = maybe_parent_id {
+ if parent_id == "<repl>" || parent_id == "internal/preload" {
+ if let Ok(cwd) = std::env::current_dir() {
+ return Some(cwd.to_string_lossy().to_string());
+ }
+ }
+ }
+ None
+}
+
+#[op]
+fn op_require_try_self(
+ has_parent: bool,
+ maybe_parent_filename: Option<String>,
+ maybe_parent_id: Option<String>,
+) -> Option<String> {
+ if !has_parent {
+ return None;
+ }
+
+ if let Some(parent_filename) = maybe_parent_filename {
+ return Some(parent_filename);
+ }
+
+ if let Some(parent_id) = maybe_parent_id {
+ if parent_id == "<repl>" || parent_id == "internal/preload" {
+ if let Ok(cwd) = std::env::current_dir() {
+ return Some(cwd.to_string_lossy().to_string());
+ }
+ }
+ }
+ None
+}
+
+#[op]
+fn op_require_read_file(filename: String) -> Result<String, AnyError> {
+ let contents = std::fs::read_to_string(filename)?;
+ Ok(contents)
+}
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 3c94e833a..fb1b32846 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -30,6 +30,7 @@ deno_fetch = { version = "0.87.0", path = "../ext/fetch" }
deno_ffi = { version = "0.51.0", path = "../ext/ffi" }
deno_http = { version = "0.58.0", path = "../ext/http" }
deno_net = { version = "0.56.0", path = "../ext/net" }
+deno_node = { version = "0.1.0", path = "../ext/node" }
deno_tls = { version = "0.51.0", path = "../ext/tls" }
deno_url = { version = "0.64.0", path = "../ext/url" }
deno_web = { version = "0.95.0", path = "../ext/web" }
@@ -53,6 +54,7 @@ deno_fetch = { version = "0.87.0", path = "../ext/fetch" }
deno_ffi = { version = "0.51.0", path = "../ext/ffi" }
deno_http = { version = "0.58.0", path = "../ext/http" }
deno_net = { version = "0.56.0", path = "../ext/net" }
+deno_node = { version = "0.1.0", path = "../ext/node" }
deno_tls = { version = "0.51.0", path = "../ext/tls" }
deno_url = { version = "0.64.0", path = "../ext/url" }
deno_web = { version = "0.95.0", path = "../ext/web" }
diff --git a/runtime/build.rs b/runtime/build.rs
index eea7a3602..b389462b5 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -158,6 +158,7 @@ mod not_docs {
deno_broadcast_channel::InMemoryBroadcastChannel::default(),
false, // No --unstable.
),
+ deno_node::init(),
deno_ffi::init::<Permissions>(false),
deno_net::init::<Permissions>(
None, false, // No --unstable.
diff --git a/runtime/lib.rs b/runtime/lib.rs
index 543d3a0a2..dfbfaafaa 100644
--- a/runtime/lib.rs
+++ b/runtime/lib.rs
@@ -8,6 +8,7 @@ pub use deno_fetch;
pub use deno_ffi;
pub use deno_http;
pub use deno_net;
+pub use deno_node;
pub use deno_tls;
pub use deno_url;
pub use deno_web;
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index 83ba380a6..ec6cd5041 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -419,6 +419,7 @@ impl WebWorker {
unstable,
options.unsafely_ignore_certificate_errors.clone(),
),
+ deno_node::init(),
ops::os::init_for_worker(),
ops::permissions::init(),
ops::process::init(),
diff --git a/runtime/worker.rs b/runtime/worker.rs
index 5100f42da..c90129ab3 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -161,6 +161,7 @@ impl MainWorker {
unstable,
options.unsafely_ignore_certificate_errors.clone(),
),
+ deno_node::init(),
ops::os::init(exit_code.clone()),
ops::permissions::init(),
ops::process::init(),