今朝の続き
ここまでの復習(自分用)
Fastifyのコアはプラグインシステム。
そのプラグインのコンテキストは親子関係を持っていて、子は親のコンテキストに登録された情報へアクセス可能だが、親は子のコンテキストに登録された情報へはアクセスできない。それによって、プラグインの影響範囲をカプセル化している。
そのコアとなるプラグインシステムの管理にはavvioを使っている。Fastifyのregisterを呼び出すとavvioのuseが呼び出されるようになっている。
親子関係の具体例
fastify.register()
を呼び出して関数を渡すと、その関数がプラグインとして登録される。そのプラグイン関数が呼び出されるときにパラメータに渡されるコンテキストは、元のコンテキストの子になっている。こういう感じ。
await parentContext.register(async (childContext, opts) => { childContext.decorate('aaaa', 1111); childContext.get('/child', async (req, rep) => { return { hello: childContext.aaaa }; }); });
ここでdecorateしたaaaaは、parentContextからはアクセスできない。
await parentContext.register(async (childContext, opts) => { childContext.decorate('aaaa', 1111); childContext.get('/child', async (req, rep) => { return { hello: childContext.aaaa }; }); }); parentContext.get('/parent', async (req, rep) => { return { hello: parentContext.aaaa }; });
それぞれ叩いたらこうなる
❯ curl localhost:3000/parent {} ❯ curl localhost:3000/child {"hello":1111}
親のコンテキストに定義したものは子のコンテキストからもアクセス可能
await parentContext.register(async (childContext, opts) => { childContext.decorate('aaaa', 1111); childContext.get('/child', async (req, rep) => { return { aaaa: childContext.aaaa, bbbb: childContext.bbbb }; }); }); parentContext.get('/parent', async (req, rep) => { return { aaaa: parentContext.aaaa, bbbb: parentContext.bbbb }; }); parentContext.decorate('bbbb', 2222);
❯ curl localhost:3000/parent {"bbbb":2222} ❯ curl localhost:3000/child {"aaaa":1111,"bbbb":2222}
fastify-plugin
このカプセル化を破って、親のコンテキストを操作したい場合はfastify-pluginを利用してラッピングしてあげる。
await parentContext.register(async (childContext, opts) => { console.log(`fastify-pluginを使っていない場合:${parentContext === childContext}`); }); await parentContext.register( fp(async (childContext, opts) => { console.log(`fastify-pluginを使った場合:${parentContext === childContext}`); }), );
fastify-pluginを使っていない場合:false fastify-pluginを使った場合:true
fastify-pluginでラップすると、こんな風に、プラグインに親のコンテキスト渡されるようになる。
どうやって?
で、この記事の本題。fastify-pluginはどうやって親のコンテキストを渡してるんだろう?というのが気になったので見てみた。
ソースコードを読んでみたら、fastify-pluginが直接registerの親子関係をどうこうしているわけではなさそう。
やってるのは fn[Symbol.for('skip-override')]
を true
にしているだけ。この skip-override
はFastifyで使ってる隠しプロパティ。
なので、fastifyのコードに戻ろう
skip-override
は lib/pluginUtils.js
で参照されている
function shouldSkipOverride (fn) { return !!fn[Symbol.for('skip-override')] }
この shouldSkipOverride
はインターナルな関数で、exportされているのはこちら
function registerPlugin (fn) { const pluginName = registerPluginName.call(this, fn) checkPluginHealthiness.call(this, fn, pluginName) checkVersion.call(this, fn) checkDecorators.call(this, fn) checkDependencies.call(this, fn) return shouldSkipOverride(fn) }
この registerPlugin
を呼び出しているのが lib/pluginOverride.js
こんな感じで使っている
const shouldSkipOverride = pluginUtils.registerPlugin.call(old, fn) const fnName = pluginUtils.getPluginName(fn) || pluginUtils.getFuncPreview(fn) if (shouldSkipOverride) { // after every plugin registration we will enter a new name old[kPluginNameChain].push(fnName) return old }
shouldSkipOverride
が true
のときは、old
のコンテキストにプラグイン関数の名前を登録している。
false
のときは、その下で新しいコンテキストを作成してそちらにプラグイン関数の名前を登録している。
わかったきになれた!
ほんとうに読んだ通りに動いているのかは、もっと詳しく見てみないとわかんないし、登録した名前がどう使われているのかも気になるところだけど、いったんここまでで、fastifyのPluginの概要と、fastify-pluginの動きの雰囲気が分かったのでよしとしようかな。
面白かった!