mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 21:50:00 -05:00
std/node: add some Node.js polyfill to require() (#3382)
This commit is contained in:
parent
2ac107f548
commit
6708fcc386
8 changed files with 132 additions and 47 deletions
|
@ -5,3 +5,20 @@ This module is meant to have a compatibility layer for the
|
|||
|
||||
**Warning** : Any function of this module should not be referred anywhere in the
|
||||
deno standard library as it's a compatiblity module.
|
||||
|
||||
## CommonJS Module Loading
|
||||
|
||||
`createRequire(...)` is provided to create a `require` function for loading CJS
|
||||
modules.
|
||||
|
||||
```ts
|
||||
import { createRequire } from "https://deno.land/std/node/module.ts";
|
||||
|
||||
const require_ = createRequire(import.meta.url);
|
||||
// Loads native module polyfill.
|
||||
const path = require_("path");
|
||||
// Loads extensionless module.
|
||||
const cjsModule = require_("./my_mod");
|
||||
// Visits node_modules.
|
||||
const leftPad = require_("left-pad");
|
||||
```
|
||||
|
|
1
std/node/global.ts
Normal file
1
std/node/global.ts
Normal file
|
@ -0,0 +1 @@
|
|||
window["global"] = window;
|
|
@ -19,6 +19,12 @@
|
|||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import "./global.ts";
|
||||
|
||||
import * as nodeFS from "./fs.ts";
|
||||
import * as nodeUtil from "./util.ts";
|
||||
import * as nodePath from "./path.ts";
|
||||
|
||||
import * as path from "../path/mod.ts";
|
||||
import { assert } from "../testing/asserts.ts";
|
||||
|
||||
|
@ -80,8 +86,7 @@ class Module {
|
|||
this.paths = [];
|
||||
this.path = path.dirname(id);
|
||||
}
|
||||
// TODO: populate this with polyfills!
|
||||
static builtinModules: Module[] = [];
|
||||
static builtinModules: string[] = [];
|
||||
static _extensions: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: (module: Module, filename: string) => any;
|
||||
|
@ -191,7 +196,11 @@ class Module {
|
|||
isMain: boolean,
|
||||
options?: { paths: string[] }
|
||||
): string {
|
||||
// Native module code removed
|
||||
// Polyfills.
|
||||
if (nativeModuleCanBeRequiredByUsers(request)) {
|
||||
return request;
|
||||
}
|
||||
|
||||
let paths: string[];
|
||||
|
||||
if (typeof options === "object" && options !== null) {
|
||||
|
@ -355,7 +364,9 @@ class Module {
|
|||
return cachedModule.exports;
|
||||
}
|
||||
|
||||
// Native module NOT supported.
|
||||
// Native module polyfills
|
||||
const mod = loadNativeModule(filename, request);
|
||||
if (mod) return mod.exports;
|
||||
|
||||
// Don't call updateChildren(), Module constructor already does.
|
||||
const module = new Module(filename, parent);
|
||||
|
@ -475,6 +486,20 @@ class Module {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `require` function that can be used to import CJS modules.
|
||||
* Follows CommonJS resolution similar to that of Node.js,
|
||||
* with `node_modules` lookup and `index.js` lookup support.
|
||||
* Also injects available Node.js builtin module polyfills.
|
||||
*
|
||||
* const require_ = createRequire(import.meta.url);
|
||||
* const fs = require_("fs");
|
||||
* const leftPad = require_("left-pad");
|
||||
* const cjsModule = require_("./cjs_mod");
|
||||
*
|
||||
* @param filename path or URL to current module
|
||||
* @return Require function to import CJS modules
|
||||
*/
|
||||
static createRequire(filename: string | URL): RequireFunction {
|
||||
let filepath: string;
|
||||
if (
|
||||
|
@ -540,6 +565,32 @@ class Module {
|
|||
}
|
||||
}
|
||||
|
||||
// Polyfills.
|
||||
const nativeModulePolyfill = new Map<string, Module>();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function createNativeModule(id: string, exports: any): Module {
|
||||
const mod = new Module(id);
|
||||
mod.exports = exports;
|
||||
mod.loaded = true;
|
||||
return mod;
|
||||
}
|
||||
nativeModulePolyfill.set("fs", createNativeModule("fs", nodeFS));
|
||||
nativeModulePolyfill.set("util", createNativeModule("util", nodeUtil));
|
||||
nativeModulePolyfill.set("path", createNativeModule("path", nodePath));
|
||||
function loadNativeModule(
|
||||
_filename: string,
|
||||
request: string
|
||||
): Module | undefined {
|
||||
return nativeModulePolyfill.get(request);
|
||||
}
|
||||
function nativeModuleCanBeRequiredByUsers(request: string): boolean {
|
||||
return nativeModulePolyfill.has(request);
|
||||
}
|
||||
// Populate with polyfill names
|
||||
for (const id of nativeModulePolyfill.keys()) {
|
||||
Module.builtinModules.push(id);
|
||||
}
|
||||
|
||||
let modulePaths = [];
|
||||
|
||||
// Given a module name, and a list of paths to test, returns the first
|
||||
|
@ -1171,19 +1222,6 @@ function pathToFileURL(filepath: string): URL {
|
|||
return outURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `require` function that can be used to import CJS modules
|
||||
* @param path path of this module
|
||||
*/
|
||||
function makeRequire(filePath: string): RequireFunction {
|
||||
let mod: Module;
|
||||
const fullPath = path.resolve(filePath);
|
||||
if (fullPath in Module._cache) {
|
||||
mod = Module._cache[fullPath];
|
||||
} else {
|
||||
mod = new Module(fullPath);
|
||||
}
|
||||
return makeRequireFunction(mod);
|
||||
}
|
||||
|
||||
export { makeRequire };
|
||||
export const builtinModules = Module.builtinModules;
|
||||
export const createRequire = Module.createRequire;
|
||||
export default Module;
|
44
std/node/module_test.ts
Normal file
44
std/node/module_test.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { test } from "../testing/mod.ts";
|
||||
import { assertEquals, assert } from "../testing/asserts.ts";
|
||||
import { createRequire } from "./module.ts";
|
||||
|
||||
// TS compiler would try to resolve if function named "require"
|
||||
// Thus suffixing it with require_ to fix this...
|
||||
const require_ = createRequire(import.meta.url);
|
||||
|
||||
test(function requireSuccess() {
|
||||
// Relative to import.meta.url
|
||||
const result = require_("./tests/cjs/cjs_a.js");
|
||||
assert("helloA" in result);
|
||||
assert("helloB" in result);
|
||||
assert("C" in result);
|
||||
assert("leftPad" in result);
|
||||
assertEquals(result.helloA(), "A");
|
||||
assertEquals(result.helloB(), "B");
|
||||
assertEquals(result.C, "C");
|
||||
assertEquals(result.leftPad("pad", 4), " pad");
|
||||
});
|
||||
|
||||
test(function requireCycle() {
|
||||
const resultA = require_("./tests/cjs/cjs_cycle_a");
|
||||
const resultB = require_("./tests/cjs/cjs_cycle_b");
|
||||
assert(resultA);
|
||||
assert(resultB);
|
||||
});
|
||||
|
||||
test(function requireBuiltin() {
|
||||
const fs = require_("fs");
|
||||
assert("readFileSync" in fs);
|
||||
const { readFileSync, isNull, extname } = require_("./tests/cjs/cjs_builtin");
|
||||
assertEquals(
|
||||
readFileSync("./node/testdata/hello.txt", { encoding: "utf8" }),
|
||||
"hello world"
|
||||
);
|
||||
assert(isNull(null));
|
||||
assertEquals(extname("index.html"), ".html");
|
||||
});
|
||||
|
||||
test(function requireIndexJS() {
|
||||
const { isIndex } = require_("./tests/cjs");
|
||||
assert(isIndex);
|
||||
});
|
1
std/node/path.ts
Normal file
1
std/node/path.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "../path/mod.ts";
|
|
@ -1,27 +0,0 @@
|
|||
import { test } from "../testing/mod.ts";
|
||||
import { assertEquals, assert } from "../testing/asserts.ts";
|
||||
import { makeRequire } from "./require.ts";
|
||||
|
||||
const selfPath = window.unescape(import.meta.url.substring(7));
|
||||
// TS compiler would try to resolve if function named "require"
|
||||
// Thus suffixing it with require_ to fix this...
|
||||
const require_ = makeRequire(selfPath);
|
||||
|
||||
test(function requireSuccess() {
|
||||
const result = require_("./node/tests/cjs/cjs_a.js");
|
||||
assert("helloA" in result);
|
||||
assert("helloB" in result);
|
||||
assert("C" in result);
|
||||
assert("leftPad" in result);
|
||||
assertEquals(result.helloA(), "A");
|
||||
assertEquals(result.helloB(), "B");
|
||||
assertEquals(result.C, "C");
|
||||
assertEquals(result.leftPad("pad", 4), " pad");
|
||||
});
|
||||
|
||||
test(function requireCycle() {
|
||||
const resultA = require_("./node/tests/cjs/cjs_cycle_a");
|
||||
const resultB = require_("./node/tests/cjs/cjs_cycle_b");
|
||||
assert(resultA);
|
||||
assert(resultB);
|
||||
});
|
10
std/node/tests/cjs/cjs_builtin.js
Normal file
10
std/node/tests/cjs/cjs_builtin.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/* eslint-disable */
|
||||
const fs = require("fs");
|
||||
const util = require("util");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
readFileSync: fs.readFileSync,
|
||||
isNull: util.isNull,
|
||||
extname: path.extname
|
||||
};
|
1
std/node/tests/cjs/index.js
Normal file
1
std/node/tests/cjs/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = { isIndex: true };
|
Loading…
Add table
Reference in a new issue