1
0
Fork 0
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:
Kevin (Kun) "Kassimo" Qian 2019-11-19 13:44:59 -08:00 committed by Ry Dahl
parent 2ac107f548
commit 6708fcc386
8 changed files with 132 additions and 47 deletions

View file

@ -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
View file

@ -0,0 +1 @@
window["global"] = window;

View file

@ -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
View 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
View file

@ -0,0 +1 @@
export * from "../path/mod.ts";

View file

@ -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);
});

View 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
};

View file

@ -0,0 +1 @@
module.exports = { isIndex: true };