Node.js 7 が出てたのでなんとなく module の読み込みを調べてみた
◆ module の実行空間でもグローバルは共有してた
気がつけば Node.js も 7 なんですね
ブラウザ JavaScript や Node.js で ES module はいつくらいから使えるなるんだろうとふと思って
そういえば今の Node.js のモジュールってどうなってるんだろうと思ったので少し調べてみました
メモ書きを軽くまとめたものなのであんまり説明なしです
バージョン
node.js のモジュール読み込みと言えば require
エラーぽい値を入れてみる
require のソース
self ってなんだろ
さっきの StackTrace は
Module.require?
インスタンスのメソッドかも
Module._load を呼んでる
けど必要なところは これくらいかな
Module 関数
tryModuleLoad みればよさそう
だけど この関数スコープ的に Node.js の REPL 上だと無理そう
名前的に load ぽいメソッド呼び出しに try catch してるだけだと思うけど
メソッド一覧見てみる
_extensions は
.node なんて拡張子もロードできるんだ
とりあえず .js を見ます
_compile
ここで気づいたけど最初の方とインデントレベルが違う
Node.js 全体で統一はされてないみたい
必要なところはこんなの?
wrap は実行してみたほうが分かりやすかったので
Function 関数で関数にしないでまだ関数定義の書かれた文字列
__filename とか require が使えるのは引数として渡されてたからだったんだねー
vm.runInThisContext
script.runInThisContext の方は
実行前のコンパイルはよくわからなかったけど .js ファイルの中身を本体にした関数を作って実行してるみたい
ロードする module にこういうの書いて
ロード後に
モジュールの実行は完全に別の空間かと思ってましたが グローバルは共有してるんですね
ブラウザ JavaScript や Node.js で ES module はいつくらいから使えるなるんだろうとふと思って
そういえば今の Node.js のモジュールってどうなってるんだろうと思ったので少し調べてみました
メモ書きを軽くまとめたものなのであんまり説明なしです
バージョン
> process.version
'v7.0.0'
'v7.0.0'
node.js のモジュール読み込みと言えば require
エラーぽい値を入れてみる
> require(1)
AssertionError: path must be a string
at Module.require (module.js:499:3)
at require (internal/module.js:20:19)
at repl:1:1
~~
native code じゃなくて JavaScript っぽいAssertionError: path must be a string
at Module.require (module.js:499:3)
at require (internal/module.js:20:19)
at repl:1:1
~~
require のソース
> console.log(require.toString())
function require(path) {
try {
exports.requireDepth += 1;
return self.require(path);
} finally {
exports.requireDepth -= 1;
}
}
self.require を呼んでるfunction require(path) {
try {
exports.requireDepth += 1;
return self.require(path);
} finally {
exports.requireDepth -= 1;
}
}
self ってなんだろ
さっきの StackTrace は
at Module.require (module.js:499:3)
at require (internal/module.js:20:19)
だったのでat require (internal/module.js:20:19)
Module.require?
> module.__proto__.constructor.name
'Module'
> var m = module.__proto__.constructor
> console.log(m.require.toString())
TypeError: Cannot read property 'toString' of undefined
Module.require ないみたい'Module'
> var m = module.__proto__.constructor
> console.log(m.require.toString())
TypeError: Cannot read property 'toString' of undefined
インスタンスのメソッドかも
> console.log(m.prototype.require.toString())
function (path) {
assert(path, 'missing path');
assert(typeof path === 'string', 'path must be a string');
return Module._load(path, this, /* isMain */ false);
}
assert にさっきのエラーもあるのでこれみたいですねfunction (path) {
assert(path, 'missing path');
assert(typeof path === 'string', 'path must be a string');
return Module._load(path, this, /* isMain */ false);
}
Module._load を呼んでる
> console.log(m._load.toString())
function (request, parent, isMain) {
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
}
var filename = Module._resolveFilename(request, parent, isMain);
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
return NativeModule.require(filename);
}
var module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
tryModuleLoad(module, filename);
return module.exports;
}
長い……function (request, parent, isMain) {
if (parent) {
debug('Module._load REQUEST %s parent: %s', request, parent.id);
}
var filename = Module._resolveFilename(request, parent, isMain);
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
return NativeModule.require(filename);
}
var module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
tryModuleLoad(module, filename);
return module.exports;
}
けど必要なところは これくらいかな
var module = new Module(filename, parent);
tryModuleLoad(module, filename);
tryModuleLoad(module, filename);
Module 関数
> console.log(m.toString())
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
プロパティセットしてるだけでここではロードしてないですfunction Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
tryModuleLoad みればよさそう
だけど この関数スコープ的に Node.js の REPL 上だと無理そう
名前的に load ぽいメソッド呼び出しに try catch してるだけだと思うけど
メソッド一覧見てみる
> m.prototype.
m.prototype.__defineGetter__ m.prototype.__defineSetter__
m.prototype.__lookupGetter__ m.prototype.__lookupSetter__
m.prototype.__proto__ m.prototype.constructor
m.prototype.hasOwnProperty m.prototype.isPrototypeOf
m.prototype.propertyIsEnumerable m.prototype.toLocaleString
m.prototype.toString m.prototype.valueOf
m.prototype._compile m.prototype.load
m.prototype.require
load メソッドというのがあるのでこれの予感m.prototype.__defineGetter__ m.prototype.__defineSetter__
m.prototype.__lookupGetter__ m.prototype.__lookupSetter__
m.prototype.__proto__ m.prototype.constructor
m.prototype.hasOwnProperty m.prototype.isPrototypeOf
m.prototype.propertyIsEnumerable m.prototype.toLocaleString
m.prototype.toString m.prototype.valueOf
m.prototype._compile m.prototype.load
m.prototype.require
> console.log(m.prototype.load.toString())
function (filename) {
debug('load %j for module %j', filename, this.id);
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
}
拡張子ごとに用意された関数呼び出してるfunction (filename) {
debug('load %j for module %j', filename, this.id);
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
}
_extensions は
> console.log(m._extensions)
{ '.js': [Function], '.json': [Function], '.node': [Function] }
3種類だけみたい{ '.js': [Function], '.json': [Function], '.node': [Function] }
.node なんて拡張子もロードできるんだ
とりあえず .js を見ます
> console.log(m._extensions[".js"].toString())
function (module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(internalModule.stripBOM(content), filename);
}
ファイル読み込んでBOM削除してコンパイルしてるfunction (module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(internalModule.stripBOM(content), filename);
}
_compile
> console.log(m.prototype._compile.toString())
function (content, filename) {
// Remove shebang
var contLen = content.length;
if (contLen >= 2) {
if (content.charCodeAt(0) === 35/*#*/ &&
content.charCodeAt(1) === 33/*!*/) {
if (contLen === 2) {
// Exact match
content = '';
} else {
// Find end of shebang line and slice it off
var i = 2;
for (; i < contLen; ++i) {
var code = content.charCodeAt(i);
if (code === 10/*\n*/ || code === 13/*\r*/)
break;
}
if (i === contLen)
content = '';
else {
// Note that this actually includes the newline character(s) in the
// new output. This duplicates the behavior of the regular expression
// that was previously used to replace the shebang line
content = content.slice(i);
}
}
}
}
// create wrapper function
var wrapper = Module.wrap(content);
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
if (process._debugWaitConnect) {
if (!resolvedArgv) {
// we enter the repl if we're not given a filename argument.
if (process.argv[1]) {
resolvedArgv = Module._resolveFilename(process.argv[1], null);
} else {
resolvedArgv = 'repl';
}
}
// Set breakpoint on module start
if (filename === resolvedArgv) {
delete process._debugWaitConnect;
const Debug = vm.runInDebugContext('Debug');
Debug.setBreakPoint(compiledWrapper, 0, 0);
}
}
var dirname = path.dirname(filename);
var require = internalModule.makeRequireFunction.call(this);
var args = [this.exports, require, this, filename, dirname];
var depth = internalModule.requireDepth;
if (depth === 0) stat.cache = new Map();
var result = compiledWrapper.apply(this.exports, args);
if (depth === 0) stat.cache = null;
return result;
}
ながああぁぁぁぁああいfunction (content, filename) {
// Remove shebang
var contLen = content.length;
if (contLen >= 2) {
if (content.charCodeAt(0) === 35/*#*/ &&
content.charCodeAt(1) === 33/*!*/) {
if (contLen === 2) {
// Exact match
content = '';
} else {
// Find end of shebang line and slice it off
var i = 2;
for (; i < contLen; ++i) {
var code = content.charCodeAt(i);
if (code === 10/*\n*/ || code === 13/*\r*/)
break;
}
if (i === contLen)
content = '';
else {
// Note that this actually includes the newline character(s) in the
// new output. This duplicates the behavior of the regular expression
// that was previously used to replace the shebang line
content = content.slice(i);
}
}
}
}
// create wrapper function
var wrapper = Module.wrap(content);
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
if (process._debugWaitConnect) {
if (!resolvedArgv) {
// we enter the repl if we're not given a filename argument.
if (process.argv[1]) {
resolvedArgv = Module._resolveFilename(process.argv[1], null);
} else {
resolvedArgv = 'repl';
}
}
// Set breakpoint on module start
if (filename === resolvedArgv) {
delete process._debugWaitConnect;
const Debug = vm.runInDebugContext('Debug');
Debug.setBreakPoint(compiledWrapper, 0, 0);
}
}
var dirname = path.dirname(filename);
var require = internalModule.makeRequireFunction.call(this);
var args = [this.exports, require, this, filename, dirname];
var depth = internalModule.requireDepth;
if (depth === 0) stat.cache = new Map();
var result = compiledWrapper.apply(this.exports, args);
if (depth === 0) stat.cache = null;
return result;
}
ここで気づいたけど最初の方とインデントレベルが違う
Node.js 全体で統一はされてないみたい
必要なところはこんなの?
var wrapper = Module.wrap(content);
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
var result = compiledWrapper.apply(this.exports, args);
.js のファイルの中身を wrap して this context を指定して実行してるみたいvar compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
var result = compiledWrapper.apply(this.exports, args);
wrap は実行してみたほうが分かりやすかったので
> m.wrap("###")
'(function (exports, require, module, __filename, __dirname) { ###\n});'
.js ファイルの中身を関数の本体にしてる'(function (exports, require, module, __filename, __dirname) { ###\n});'
Function 関数で関数にしないでまだ関数定義の書かれた文字列
__filename とか require が使えるのは引数として渡されてたからだったんだねー
vm.runInThisContext
> console.log(vm.runInThisContext.toString())
function (code, options) {
var script = new Script(code, options);
return script.runInThisContext(options);
}
Script はたぶん vm.Scriptfunction (code, options) {
var script = new Script(code, options);
return script.runInThisContext(options);
}
> console.log(vm.Script.toString())
function ContextifyScript() { [native code] }
とうとう native code キターー!function ContextifyScript() { [native code] }
script.runInThisContext の方は
> console.log(vm.Script.prototype.runInThisContext.toString())
function (options) {
if (options && options.breakOnSigint) {
return sigintHandlersWrap(() => {
return realRunInThisContext.call(this, options);
});
} else {
return realRunInThisContext.call(this, options);
}
}
見れた けど realRunInThisContext が見当たらないし native code が入ってきたのでここまでfunction (options) {
if (options && options.breakOnSigint) {
return sigintHandlersWrap(() => {
return realRunInThisContext.call(this, options);
});
} else {
return realRunInThisContext.call(this, options);
}
}
実行前のコンパイルはよくわからなかったけど .js ファイルの中身を本体にした関数を作って実行してるみたい
ロードする module にこういうの書いて
var local_val = 1
global_val = 2
global_val = 2
ロード後に
console.log(global_val)
console.log(local_val)
ってすると global_val は見れて local_val は存在しなくてリファレンスエラーですconsole.log(local_val)
モジュールの実行は完全に別の空間かと思ってましたが グローバルは共有してるんですね
COMMENT
コメント一覧 (2)
-
- 2016/11/22 06:25
-
この辺のJavaScriptで書かれた内部コードはソースコード見たほうが楽ですよ。
tarballのlibディレクトリの中なんで配置を把握する手間も殆ど要りません。
インデントの深さが違うのは
// Invoke with makeRequireFunction.call(module) where |module| is the
// Module object to use as the context for the require() function.
function makeRequireFunction() {
const Module = this.constructor;
const self = this;
function require(path) {
・・・みたいなクロージャになってるからみたいです。
> 実行前のコンパイル
解説のない部分って「#!~」なシェバング行(シェバング使ってnode.jsなどでで実行する実行可能形式ファイルの読み込みを想定しているっぽい)を削ってるだけでは?
-
- 2016/11/22 22:24
-
基本 JavaScript のコンソールをいじってるのでそこからコード見るのは気が楽なのですけど ソースファイル探すのはちょっと気が重いんですよねー
なので今回は気が向いた程度のものだったので ソース読まないとなところはパスしました
> クロージャになってるから
なるほどですー
最初の行だけインデントずれてるの違和感ありましたけどそういうことだったのですね
> 実行前のコンパイル
わかりにくくてすみません
"_compile" の前半の処理じゃなくて ContextifyScript 関数のところです
関数にラップしたコードを渡していますのでここが実際のコンパイルなところかなーと思うのですが NativeCode なのですよね
気が向いたときにソース見てみます
ありがとうございました