forked from airbnb/hypernova
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Module.js
137 lines (112 loc) · 3.42 KB
/
Module.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import NativeModule from 'module';
import has from 'has';
import path from 'path';
import { ok } from 'assert';
import { runInNewContext } from 'vm';
const NativeModules = process.binding('natives');
// This means that you won't be able to affect VM extensions by mutating require.extensions
// this is cool since we can now have different extensions for VM than for where your program is
// running.
// If you want to add an extension then you can use addExtension defined and exported below.
const moduleExtensions = { ...NativeModule._extensions };
function isNativeModule(id) {
return has(NativeModules, id);
}
// Creates a sandbox so we don't share globals across different runs.
function createContext() {
const sandbox = {
Buffer,
clearImmediate,
clearInterval,
clearTimeout,
setImmediate,
setInterval,
setTimeout,
console,
process,
};
sandbox.global = sandbox;
return sandbox;
}
// This class should satisfy the Module interface that NodeJS defines in their native module.js
// implementation.
class Module {
constructor(id, parent) {
const cache = parent ? parent.cache : null;
this.id = id;
this.exports = {};
this.cache = cache || {};
this.parent = parent;
this.filename = null;
this.loaded = false;
this.context = parent ? parent.context : createContext();
}
load(filename) {
ok(!this.loaded);
this.filename = filename;
this.paths = NativeModule._nodeModulePaths(path.dirname(filename));
}
run(filename) {
const ext = path.extname(filename);
const extension = moduleExtensions[ext] ? ext : '.js';
moduleExtensions[extension](this, filename);
this.loaded = true;
}
require(filePath) {
ok(typeof filePath === 'string', 'path must be a string');
return Module.loadFile(filePath, this);
}
_compile(content, filename) {
const self = this;
function require(filePath) {
return self.require(filePath);
}
require.resolve = request => NativeModule._resolveFilename(request, this);
require.main = process.mainModule;
require.extensions = moduleExtensions;
require.cache = this.cache;
const dirname = path.dirname(filename);
// create wrapper function
const wrapper = NativeModule.wrap(content);
const options = {
filename,
displayErrors: true,
};
const compiledWrapper = runInNewContext(wrapper, this.context, options);
return compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname);
}
static load(id, filename = id) {
const module = new Module(id);
module.load(filename);
module.run(filename);
return module;
}
static loadFile(file, parent) {
const filename = NativeModule._resolveFilename(file, parent);
if (parent) {
const cachedModule = parent.cache[filename];
if (cachedModule) return cachedModule.exports;
}
if (isNativeModule(filename)) {
// eslint-disable-next-line global-require, import/no-dynamic-require
return require(filename);
}
const module = new Module(filename, parent);
module.cache[filename] = module;
let hadException = true;
try {
module.load(filename);
module.run(filename);
hadException = false;
} finally {
if (hadException) {
delete module.cache[filename];
}
}
return module.exports;
}
static addExtension(ext, f) {
moduleExtensions[ext] = f;
}
}
export default Module;