9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.jsでModule hack (require hack)する

Last updated at Posted at 2019-09-29

Node.jsにはrequireというモジュールを読み込む機能があります。これはNode.js Module APIで提供されている機能で、割と簡単にハックすることができます。よく Module hack とか require hack と呼ばれるものです。

などが有名どころでしょうか。これらのモジュールでModule hackをすると、本来のNode.jsでは動作しないTypeScriptや拡張されたJavaScriptが動作します。

この記事では Module hack のやり方について説明します。

Module hackのやり方

// TypeScriptの場合
export const Module = require('module') as any

まず module を読み込みます。

拡張子に応じたハックをするのは簡単です。

Module._extensions[ext] = function(m: any, filename: string) {
  return m._compile('console.log("hoge")', filename)
}

先ほどから、Module を as anyany アサーションをしたり、今回の関数の第一引数 m もanyで定義していますが、これは、@types/node で定義されている Module 型の定義では、非公開プロパティ、たとえば _extensions_compile にアクセスできないためです。

実例

// test.js
const Module = require('module')

Module._extensions['.js'] = function(m, name) {
  return m._compile('console.log("piyopiyo")', name)
}

require('./hoge')
// hoge.js
console.log('hoge')

これら2つのファイルを同じディレクトリにおいて、node test.jsを実行すると、Module hack をしてなければ本来 hoge と表示されるところが piyopiyo と表示されます。

解説

Module._extensions は、拡張子ごとにファイルの処理をする関数群です。.js のようにドット込みの拡張子を指定します。

ts-node/registerや@babel/registerのように、ソースコードをトランスパイルして実行する場合には、大体定番となるのが、

Module._extensions['.js'] = function(m, name) {
  return m._compile('console.log("piyopiyo")', name)
}

のように最終的にNode.jsが解釈できる状態にまでトランスパイルしたソースコードを、m._compile(code, name) のようにすることです。

補足

Module._extensionsを書き換えると、もどす時に工夫は必要となります。

export const originalExts: { [props: string]: any } = {}

Object.keys(Module._extensions).forEach(ext => {
  originalExts[ext] = Module._extensions[ext]
})

大体はこのように、本来の関数を保存しておきます。

もっとも、このような hack を元に戻す必要がある機会はあまりないでしょう。

モジュール読み込み自体をhackする

ここまでの hack で、拡張子ごとの hack は可能になりましたが、モジュールそのものを hack するためには別の隠しプロパティにアクセスする必要があります。

const Module = require('module')

const originalLoad = Module._load

Module._load = function(name, parent, isMain) {
  if (name === 'hoge') {
    return { hoge: () => console.log('hoge') }
  }
  return originalLoad(name, parent, isMain)
}

const { hoge } = require('hoge')
hoge()

Module._loadを書き換えると、モジュール読み込みそのものにちょっかいを出せます。ただし本来の関数は保存しておいた方が良いため、const originalLoad = Module._loadで保存しておきます。

このコードでは、hogeという名前のモジュールだけhackします。他のモジュールを読み込んだ場合は、従来読み込むべきモジュールが読み込まれます。

任意のソースコードをモジュールとして返す

先ほどのhackでは、ダイレクトに書いたコードをそのまま実行しています。もちろん、それはそれで用途もあるかもしれませんが、拡張子hackのときと同じように任意のコードを実行して、その結果を返したいときもあるでしょう。

const module = new Module()
module._compile(code, filename)
return module.exports

さきほどもでてきた _compileは、Node.jsの実行環境に応じた処理をやってくれるので便利です。同等の処理を自前、たとえばeval Function vm.runInContextなどでやろうとするとかなり面倒です。

注意点

ここに書いた undocumented なものです。これで今後も含めて正しく動作することは保証されていません。また他の黒魔術を使ったソースコードで問題が生じる可能性があります。

pirates というnpmモジュールを使うと、より安全に Module hack を試すことはできます。自前でやるよりはいいかもしれません。

参考: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js

宣伝

技術書典7で出した本のPDF電子版を、Pixiv Boothで頒布しております。よろしければどうぞ。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?