Table of Contents
この書籍について

1.1

jQuery1.2

ESLint1.3

Connect

1.4

gulp

1.5

Redux

1.6

CONTRIBUTING

1.7

1

この書籍について

JavaScript Plugin Architecture

test passing

この書籍はJavaScriptのライブラリやツールにおけるプラグインアーキテクチャについて⾒ていくことを⽬的としたものです。
次の形式で読むことができます。
Web版
PDF形式
ePub形式
Mobi形式
この書籍のソースコードは、次のGitHubリポジトリに公開されています。
azu/JavaScript-Plugin-Architecture: JavaScriptプラグインアーキテクチャの本
Twitterのハッシュタグは#js_plugin_book
更新情報はRSSやリリースノートから⾒ることができます。

はじめにJavaScriptの世界では1つの⼤きなライブラリよりも⼩さなライブラリを組み合わせていくようなスタイルが多く⾒られます。⼩さなものを組み合わせて作るためには、プラグインと呼ばれる拡張の仕組みが必要となります。またそのようなプラグインがたくさんあるエコシステムの⼟台を作るには、プラグインアーキテクチャが重要になるといえます。
ソフトウェアの構造に「プラグイン機構」を設け、ユーザコミュニティから開発者コミュニティへの質的な転換を図るのは、ソフトウェア設計からエコシステム設計へとつながる-- OSS開発の活発さの維持と良いソフトウェア設計の間には緊張関係があるのだろうか? - t-wadaのブログ
この書籍では、JavaScriptにおけるプラグインアーキテクチャやそのエコシステムを形成してるライブラリやツールなどの実装を学ぶことが⽬的となっています。
JavaScriptの基本的な⽂法などについては解説していないため、次の書籍を参照してください。
JavaScript Primer - 迷わないための⼊⾨書 #jsprimer

この書籍の内容について
jQueryjQueryのプラグインについて解説しています。 <script> タグをベースとしたプラグインアーキテクチャについて解説しています。

ESLintESLintのルールを拡張する仕組みについて解説しています。 ESLintではJavaScriptのコードをパースして作成された
ASTを元にコードのLintを⾏います。実際にESLintのルールを解釈できる⼩さな実装を作りながらプラグインの仕組みについて学びます。

2

この書籍についてConnect
Connectの middleware と呼ばれるプラグインアーキテクチャについて解説しています。 Node.js以外においてもRackなどHTTPサーバーでよく⾒られるプラグインを使った階層構造について学びます。

gulp
タスク⾃動化ツールとして知られるgulpのプラグインアーキテクチャについて解説しています。 gulpではデータの流れとして既存のNode.js Streamを使い、そこで流すデータとしてvinylオブジェクトを利⽤します。実際にgulpプ
ラグインを書きながら、gulpのプラグインの仕組みについて学びます。

Redux
アプリケーションのStateを管理ライブラリのReduxのプラグインアーキテクチャについて解説しています。 Redux
では middleware と呼ばれる拡張の仕組みを持っていますが、Connectとの類似点や相違点があります。⼩さな
Reduxの実装を作りながら middleware の仕組みについて学びます。

Contributing
この書籍は無料で読むことができ、同時に修正や新しいページを書く権利があります。
CONTRIBUTING.mdに、書籍で扱うべきプラグインアーキテクチャのProposalの書き⽅や Pull Request、コミットのやりかたなどが書かれています。
間違いやライブラリのアップデートへの追従など何かあれば、IssueやPull Requestをよろしくお願いします。
ソースコードはすべてGitHubに公開されています。
azu/JavaScript-Plugin-Architecture

Authorgithub/azu
twitter/azu_re

LicenseMIT/CC BY-NC © azu
コードはMITライセンスで利⽤できます
⽂章はCC BY-NC 4.0で利⽤できます3

jQuery

jQuery
この⽂章はjQuery 2.1.4を元に書かれています。
jQueryでは $.fn を拡張することで、 $() の返り値となるjQueryオブジェクトにメソッドを追加できます。
次の greenify プラグインでは、 $(document.body).greenify(); というメソッド呼び出しが可能になります。
(function ($) {$.fn.greenify = function () {this.css("color", "green");return this;};})(jQuery);

実際に利⽤する際は、 jquery.js を読み込んだ後に greenify.js を読み込ませる必要があります。
<script src="jquery.js"></script><script src="greenify.js"></script>

どのような仕組み?
このjQueryプラグインがどのような仕組みで動いているのかを⾒てみましょう。
jQueryプラグインはprototype拡張のように $.fn.greenify = function (){} と拡張するルールでした。
jQuery.fn の実装を⾒てみると、実態は jQuery.prototype なので、prototype拡張していることがわかります。

// https://github.com/jquery/jquery/blob/2.1.4/src/core.js#L39
jQuery.fn = jQuery.prototype = {// prototypeの実装};$() は内部的に new をしてjQueryオブジェクトを返すので、このjQueryオブジェクトではprototypeに拡張したメ

ソッドが利⽤できます。
$(document.body); // 返り値はjQueryのインスタンス

つまり、jQueryプラグインはJavaScriptのprototypeをそのまま利⽤しているだけに過ぎないということがわかります。

どのような⽤途に向いている?
jQueryプラグインの仕組みがわかったのでどのような⽤途に有効な仕組みなのか考えてみましょう。
単純なprototype拡張なので、利点はJavaScriptのprototypeと同様です。動的にメソッドを追加するだけではなく、既存の実装を上書きするmonkey patchのようなものもプラグインとして追加できます。

どのような⽤途に向いていない?

4

jQuery

これもJavaScriptのprototypeと同様で、prototypeによる拡張は柔軟すぎるため、 jQuery⾃体がプラグインのコント
ロールをすることは難しいです。
また、プラグインが拡張するjQueryの実装に依存しやすいため、 jQueryのバージョンによって動かなくなるプラグインが発⽣しやすいです。
jQueryではドキュメント化されてないAPIを触っていけないというルールを設けていますが、これは必ずしも守られているわけではありません。

実装してみようcalculator という拡張可能な計算機をjQuery Pluginと同じ⽅法で作ってみたいと思います。
calculator

は次のような形となります。

function calculator(value = 0) {if (!(this instanceof calculator)) {return new calculator(value);}this.value = value;}// alias
calculator.fn = calculator.prototype;
export default calculator;$.fn と同様に prototype へのaliasを貼っているだけのただのコンストラクタ関数です。

calculator.fn = calculator.prototype;calculator(初期値) と書けるようにしているため、少し特殊なコンストラクタとなっていますが、この拡張の仕組みとは関係ないのでとりあえず置いておきましょう。
calculator.jsには何も実装が⼊ってないので、プラグインで四則演算の実装を追加してみます。
import calculator from "./calculator";
calculator.fn.add = function (x) {this.value += x;
return this;};calculator.fn.sub = function (x) {this.value -= x;
return this;};calculator.fn.multi = function (x) {this.value *= x;
return this;};calculator.fn.div = function (x) {this.value /= x;
return this;};calculator-plugin.jsでは、 calculator.fn.add というように add というメソッドを追加しています。
また、モジュールで依存関係を⽰していますがやっていることはjQueryと同じで、calculator.jsを読み込んでから
calculator-plugin.jsを読み込んでいるだけですね。
<script src="calculator.js"></script><script src="calculator-plugin.js"></script>

5

jQuery

これを使うと calculator#add といったメソッドが利⽤できるようになるので、次のように書くことができます。
import assert from "assert";
import calculator from "./calculator";
import "./calculator-plugin"; // Extend
const resultValue = calculator(0).add(10).multi(10).value;
assert.equal(resultValue, 10 * 10);

実装をみてもらうと分かりますが、JavaScriptの prototype の仕組みをそのまま利⽤しています。そのため、「拡張する時は calculator.prototype の代わりに calculator.fn を拡張する」というルールがあるだけともいえます。

エコシステムこのプラグインの仕組みはあるグローバルオブジェクトに依存しており、これはスクリプトを <script> 要素で読み込むだけで拡張することを前提とした作りです。
<script src="jquery.js"></script><script src="greenify.js"></script>

Node.jsで使われているCommonJSやES6 Modulesなどがなかった時代に作られた仕組みなので、それらと組み合わせる際には少し不向きな拡張の仕組みといえるかもしれません。

まとめ
ここではjQueryのプラグインアーキテクチャについて学びました。
jQueryプラグインは jQuery.fn を拡張する
jQuery.fn

は jQuery.prototype と同じである

jQueryプラグインとは jQuery.prototype を拡張したものといえる
何でもできるためプラグインが⾏うことを制御することは難しい

参考資料
Plugins | jQuery Learning Center
jQuery拡張の仕組み 〜 JSおくのほそ道 #013 - Qiita
The npm Blog ̶ Using jQuery plugins with npm

6

ESLint

ESLint
この⽂章はESLint v7.8.1を元に書かれています。
ESLintはJavaScriptのコードをJavaScriptで書かれたルールによって検証するLintツールです。
⼤まかな動作としては、検証したいJavaScriptのコードをパースしてできたAST(抽象構⽂⽊)をルールで検証し、エラーや警告を出⼒します。
このルールがプラグインとして書くことができ、ESLintのすべてのルールはプラグインとして実装されています。
今回はESLintのプラグインアーキテクチャがどうなっているかを⾒て⾏きましょう。

どう書ける?
ESLintでは .eslintrc という設定ファイルに利⽤するルールを設定して利⽤します。そのため、実⾏⽅法についてはドキュメントを参照してください。
Documentation - ESLint - Pluggable JavaScript linter
ESLintにおけるルールとは、次のような create メソッドをもつオブジェクトをexportしたモジュールです。
create メソッドには context オブジェクトが渡されるので、それに対して1つのオブジェクトを返すようにします。
module.exports = {meta: { /* ルールのメタ情報 */ },create: function (context) {return {"MemberExpression": function (node) {if (node.object.name === "console") {context.report({node,
message: "Unexpected console statement."
});
}
}
};
}
};

ESLintではコードを⽂字列ではなくASTを元にチェックしていきます。 ASTについてはここでは詳細を省きますが、コードをJavaScriptのオブジェクトで表現した⽊構造のデータだと思えば問題ないと思います。
たとえば、
console.log("Hello!");というコードをパースしてASTにすると次のようなオブジェクトとして取得できます。
{"type": "Program","body": [{"type": "ExpressionStatement","expression": {"type": "CallExpression","callee": {7

ESLint"type": "MemberExpression","computed": false,"object": {"type": "Identifier","name": "console"},"property": {"type": "Identifier","name": "log"}},"arguments": [{"type": "Literal","value": "Hello!","raw": "\"Hello!\""
}
]
}
}
],
"sourceType": "script"}

JavaScript AST explorer
ESLintではこのASTを使って、no-console.jsのように console.log などがコードに残ってないかなどをルールを元にチェックできます。
ルールをどう書けるかという話に戻すと、 context というオブジェクトはただのユーティリティ関数の集合と考えて問題ありません。ルールの本体は create メソッドが return してるメソッドをもったオブジェクトです。
このオブジェクトはNodeのtypeをキーとしたメソッドを持っています。そして、ASTを探索しながら「 "MemberExpression" typeのNodeに到達した」と登録したルールに対して通知(メソッド呼び出し)を繰り返しています。
先ほどの console.log のASTにおける MemberExpression typeのNodeとは次のオブジェクトのことを⾔います。
{"type": "MemberExpression","computed": false,"object": {"type": "Identifier","name": "console"},"property": {"type": "Identifier","name": "log"}}no-console.jsのルールを⾒ると MemberExpression typeのNodeが node.object.name === "console" となった場合に、console が残ってると判断してエラーレポートすると読めてくると思います。

ASTの探索がイメージしにくい場合は、次のルールで探索の動作を⾒てみると分かりやすいかもしれません。
azu.github.io/visualize_estraverse/
function debug(string){
console.log(string);}
debug("Hello");8

ESLint

00:00 / 00:00

その他、ESLintのルールの書き⽅についてはドキュメントや次の記事を⾒てみるといいでしょう。
Working with Rules - ESLint - Pluggable JavaScript linter
コードのバグはコードで⾒つけよう！｜サイバーエージェント 公式エンジニアブログ

どのような仕組み?
ESLintはコードをパースしてASTにして、そのASTをJavaScriptで書いたルールを使いチェックするという⼤まかな
仕組みは分かりました。
次に、このルールをプラグインとする仕組みがどのように動いているのか⾒て⾏きましょう。
ESLintのLintは次のような3つの⼿順で⾏われています。
1. ルール毎に使っている Node.type をイベント登録する
2. ASTをtraverseしながら、 Node.type のイベントを発⽕する
3. ルールから context.report() された内容を集めて表⽰する
このイベントの登録と発⽕にはEventEmitterを使い、 ESLint本体に対してルールは複数あるので、典型的な
Pub/Subパターンとなっています。
擬似的なコードで表現すると次のような流れでLintの処理が⾏われています。
import {parse} from "esprima";
import {traverse} from "estraverse";
import {EventEmitter} from "events";function lint(code){// コードをパースしてASTにする
const ast = parse(code);// イベントの登録場所
const emitter = new EventEmitter();
const results = [];emitter.on("report", message => {// 3. のためのreportされた内容を集める
results.push(message);});// 利⽤するルール⼀覧
const ruleList = getAllRules();// 1. ルール毎に使っている`Node.type`をイベント登録する
ruleList.forEach(rule => {// それぞれのルールに定義されているメソッド⼀覧を取得// e.g) MemberExpression(node){}// => {"MemberExpression" : function(node){}, ... } というオブジェクト
const methodObject = getDefinedMethod(rule);
Object.keys(methodObject).forEach(nodeType => {emitter.on(nodeType, methodObject[nodeType]);});});// 2. ASTをtraverseしながら、`Node.type`のイベントを発⽕する
traverse(ast, {// 1.で登録したNode.typeがあるならここで呼ばれる
enter: (node) => {9

ESLint

emitter.emit(node.type, node);},
leave: (node) => {emitter.emit(`${node.type}:exit`, node);}});// 3. ルールから`context.report()`された内容を集めて表⽰する
console.log(results.join("\n"));}Pub/Subパターンを上⼿く使うことで、ASTを⾛査するのが⼀度のみで、それぞれのルールに対してどのようなコードかという情報が emit で通知できていることがわかります。
もう少し具体的にするため、実装して動かせるようなものを作ってこの仕組みについて⾒ていきます。

実装してみよう今回は、ESLintのルールを解釈できるシンプルなLintの処理を書いてみます。
利⽤するルールは先ほども出てきたno-console.jsをそのまま使い、このルールを使って同じようにJavaScriptのコードを検証できる MyLinter を書いてみます。

MyLinter
MyLinterは単純な2つのメソッドをもつクラスとして実装しました。
MyLinter#loadRule(rule): void

利⽤するルールを登録する処理
rule はno-console.jsがexportしたもの
MyLinter#lint(code): string[]
code を受け取りルールによってLintした結果を返す

Lint結果はエラーメッセージの配列とする
実装したものが次のようになっています。
import { parse } from "esprima";
import { traverse } from "estraverse";
import { EventEmitter } from "events";
class RuleContext extends EventEmitter {report({ message }) {this.emit("report", message);}}
export default class MyLinter {constructor() {this._emitter = new EventEmitter();this._ruleContext = new RuleContext();}
loadRule(rule) {const ruleExports = rule.create(this._ruleContext);// on(nodeType, nodeTypeCallback);
Object.keys(ruleExports).forEach(nodeType => {this._emitter.on(nodeType, ruleExports[nodeType]);});}lint(code) {10

ESLint

const messages = [];const addMessage = (message) => {messages.push(message);};this._ruleContext.on("report", addMessage);const ast = parse(code);
traverse(ast, {enter: (node) => {this._emitter.emit(node.type, node);},
leave: (node) => {this._emitter.emit(`${node.type}:exit`, node);}});
this._ruleContext.removeListener("report", addMessage);
return messages;}}

このMyLinterを使って、 MyLinter#load でno-console.jsを読み込ませて、
function add(x, y){console.log(x, y);return x + y;}add(1, 3);というコードをLintしてみます。
import assert from "assert";
import MyLinter from "./MyLinter";import noConsole from "./no-console";
const linter = new MyLinter();linter.loadRule(noConsole);
const code = `function add(x, y){console.log(x, y);return x + y;}add(1, 3);`;const results = linter.lint(code);
assert(results.length > 0);
assert.equal(results[0], "Unexpected console statement.");コードには console という名前のオブジェクトが含まれているので、 "Unexpected console statement." というエラーメッセージが取得できました。

RuleContext
もう⼀度、MyLinter.jsを⾒てみると、 RuleContext というシンプルなクラスがあることに気づくと思います。
この RuleContext はルールから使えるユーティリティメソッドをまとめたものです。今回は RuleContext#report というエラーメッセージをルールからMyLinterへ通知するものだけを実装しています。
ルールの実装の⽅を⾒てみると、直接オブジェクトをexportしないで、 context として RuleContext のインスタンスを受け取っていることが分かると思います。
module.exports = {meta: { /* ルールのメタ情報 */ },create: function (context) {11

ESLint

return {"MemberExpression": function (node) {if (node.object.name === "console") {context.report({node,
message: "Unexpected console statement."
});
}
}
};
}
};

このようにして、ルールは context という与えられたものだけを使うので、ルールがMyLinter本体の実装の詳細を知らなくても良くなります。

どのような⽤途に向いている?
このプラグインアーキテクチャはPub/Subパターンを上⼿く使い、 ESLintのように与えられたコードを読み取ってチェックするような使い⽅に向いています。
つまり、複数のルールで同時にLintをするというread-onlyなプラグインアーキテクチャとしてはパフォーマンスも期待できると思います。
また、ルールは context オブジェクトという与えられたものだけを使うようになっているため、ルールと本体が密結合にはなりにくいです。そのため context に何を与えるかを決めることで、ルールができる範囲を制御しやすいといえます。

どのような⽤途に向いていない?
逆に与えられたコード(AST)を書き換える場合には、ルールを同時に処理を⾏うためルール間で競合するような変更がある場合に上⼿く整合性を保つ必要があります。
たとえば、あるルールが書き換えるとコードの位置に更新がかかるため、その後のルールはコードの位置更新の影響を受けます。そのため、コードの書き換えをするにはこの仕組みに加えて、もう1つ抽象レイヤーを設けないと対応は難しいです。
つまり、read-writeなプラグインアーキテクチャとしては、このパターンだけでは難しい部分が出てくると思います。
ESLint 2.0からautofix、つまり書き換えの機能の導⼊が導⼊されています。 ESLintでは、各ルールが書き換える位置や⽂字列を fixer オブジェクトという形で報告し、ESLint本体がルールの順番を考慮して最後に実際の書き換えを⾏うという抽象レイヤーを設けています。 これはCommand パターンを利⽤してトランザクション
的なふるまいを実現しています。
Implement autofixing · Issue #3134 · eslint/eslint

この仕組みを使っているもの
stylelint
CSSのLintするツール
textlint
テキストやMarkdownをパースしてASTにしてLintするツール

12

ESLint

エコシステムESLintのルールはただのJavaScriptモジュールなので、ルール⾃体をnpmで公開できます。
また、ESLintはデフォルトで有効なルールはありません。そのため、利⽤する際は設定ファイルを作る
か、sindresorhus/xoといったESLintのラッパーを利⽤する形となります。
ESLint公式の設定として eslint:recommended が⽤意されています。これを extends することで推奨の設定を継承できます。
{"extends": "eslint:recommended"}

これらの設定⾃体もJavaScriptで表現できるため、設定もnpmで公開して利⽤できるようになっています。
Shareable Configs - ESLint - Pluggable JavaScript linterコーディングルールが多種多様なように、ESLintで必要なルールも個⼈差があると思います。設定なしで使えると⼀番楽ですが、設定なしだと誰でも使えるツールにするのは難しいです。それを解消するために柔軟な設定のしくみと設定を共有しやすくしています。
これはPluggable JavaScript linterを表現している仕組みといえるかもしれません。

まとめ
ここではESLintのプラグインアーキテクチャについて学びました。
ESLintはJavaScriptでルールを書ける
ASTの⽊構造を⾛査しながらPub/Subパターンでチェックする
ルールは context を受け取る以外は本体の実装の詳細を知らなくてよい
ルールがread-onlyだと簡単で効率的
read-writeとする場合は気を付ける必要がある
設定をJavaScriptで表現できる設定をnpmで共有できる作りになっている

13

Connect

Connect
この⽂章はConnect 3.4.0を元に書かれています。
ConnectはNode.jsで動くHTTPサーバーフレームワークです。 middleware という拡張する仕組みを持ち、Connectがもつ機能⾃体はとても少ないです。
この章ではConnectの middleware の仕組みについて⾒て⾏きましょう。

どう書ける?
Connectを使い簡単なEchoサーバを書いてみましょう。 Echoサーバとは、送られてきたリクエストの内容をそのま
まレスポンスとして返すサーバのことです。
import connect from "connect";
import http from "http";
import fetch from "node-fetch";
import assert from "assert";
const app = connect();// add Error handlingapp.use(function (err, req, res, next) {console.error(err.stack);
res.status(500).send(err.message);next();});// request to response
app.use(function (req, res) {req.pipe(res);});//create node.js http server and listen on port
const server = http.createServer(app).listen(3000, request);// request => response
function request() {const closeServer = server.close.bind(server);const requestBody = {"key": "value"};
fetch("http://localhost:3000", {method: "POST",
body: JSON.stringify(requestBody)}).then(res => res.text()).then(text => {assert.deepEqual(text, requestBody);}).then(closeServer, closeServer);}

このEchoサーバに対して、次のようなリクエストBodyを送信すると、レスポンスとして同じ値が返ってきます。
{"key": "value"}

app.use(middleware)という形で、 middleware と呼ばれる関数には request や response といったオブジェクトが渡されます。この request や response を middleware で処理することで、ログを取ったり、任意のレスポンスを返すことができます。

14

Connect

Echoサーバでは req.pipe(res); という形でリクエストをそのままレスポンスとして流すことで実現されています。

middlewareをモジュールとして実装
もう少し middleware をプラグインらしくモジュールとして実装したものを⾒てみます。
次のconnect-example.jsは、あらゆるリクエストに対して、 "response text" というレスポンスを "X-Content-TypeOptions" ヘッダを付けて返すだけのサーバです。

それぞれの処理を middleware としてファイルを分けて実装し、 app.use(middleware) で処理を追加しています。
function setHeaders(res, headers) {Object.keys(headers).forEach(key => {const value = headers[key];if (value !== null) {res.setHeader(key, value);}});}export default function () {return function nosniff(req, res, next) {setHeaders(res, {"X-Content-Type-Options": "nosniff"});
next();};}

export default function (text) {return function hello(req, res) {res.end(text);};}

export default function () {return function errorHandling(err, req, res, next) {res.writeHead(404);
res.write(err.message);res.end();next();};}

import errorHandler from "./errorHandler";import hello from "./hello";
import nosniff from "./nosniff";
import assert from "assert";
import connect from "connect";
import http from "http";
import fetch from "node-fetch";
const responseText = "response text";
const app = connect();// add Error handlingapp.use(errorHandler());// add "X-Content-Type-Options" to response
app.use(nosniff());// respond to all requests
app.use(hello(responseText));//create node.js http server and listen on port
const server = http.createServer(app).listen(3000, request);

15

Connectfunction request() {const closeServer = server.close.bind(server);fetch("http://localhost:3000").then(res => res.text()).then(text => {assert.equal(text, responseText);
server.close();}).catch(console.error.bind(console)).then(closeServer, closeServer);}基本的にどの middleware も app.use(middleware) という形で拡張でき、モジュールとして実装すれば再利⽤もしや
すい形となっています。
middleware となる関数の引数が4つであると、それはエラーハンドリングの middleware とするという、Connect独⾃のルールがあります。

どのような仕組み?
Connectの middleware がどのような仕組みで動いているのかを⾒ていきます。
app に登録した middleware は、リクエスト時に呼び出されています。そのため、 app のどこかに利⽤する

middleware を保持していることは推測できると思います。
Connectでは app.stack に middleware を配列として保持しています。次のようにして app.stack の中⾝を表⽰してみると、 middleware が登録順で保持されていることがわかります。
import errorHandler from "./errorHandler";import hello from "./hello";
import nosniff from "./nosniff";
import connect from "connect";const responseText = "response text";
const app = connect();// add Error handlingapp.use(errorHandler());// add "X-Content-Type-Options" to response
app.use(nosniff());// respond to all requests
app.use(hello(responseText));// print middleware list
app.stack.map(({handle}) => console.log(handle));/* =>[Function: errorHandling][Function: nosniff][Function: hello]*/

Connectは登録された middleware を、サーバがリクエストを受け取りそれぞれ順番に呼び出しています。
上記の例だと次の順番で middleware が呼び出されることになります。
nosniff
helloerrorHandler
エラーハンドリングの middleware は処理中にエラーが起きた時のみ呼ばれます。

16

Connect

そのため、通常は nosniff.js → hello.js の順で呼び出されます。
function setHeaders(res, headers) {Object.keys(headers).forEach(key => {const value = headers[key];if (value !== null) {res.setHeader(key, value);}});}export default function () {return function nosniff(req, res, next) {setHeaders(res, {"X-Content-Type-Options": "nosniff"});
next();};}

nosniff.js は、HTTPヘッダを設定し終わったら next() を呼び出し、この next() が次の middleware へ⾏くという意味になります。
次に、 hello.js を⾒てみると、 next() がありません。
export default function (text) {return function hello(req, res) {res.end(text);};}

next() がないということは hello.js がこの連続する middleware の最後となっていることがわかります。仮に、これより先に middleware が登録されていたとしても無視されます。
つまり、処理的には次のようにstackを先頭から⼀個づつ取り出し、処理していくという⽅法が取られています。
Connectの⾏っている処理を抽象的なコードで書くと次のような形になっています。
const req = "...",
res = "...";
function next(){
const middleware = app.stack.shift();// nextが呼ばれれば次のmiddleware
middleware(req, res, next);}
next();// 初回

このような middleware を繋げたものをmiddleware stackと呼ぶことがあります。
middleware stack で構成されるHTTPサーバとして、PythonのWSGI middlewareやRubyのRackなどがあります。
ConnectはRackと同じく use で middleware を指定することからも分かりますが、 Rackを参考にした実装となっています。
Ruby - Rack解説 - Rackの構造とRack DSL - Qiita
次は、先ほど抽象的なコードとなっていたものを具体的な実装にしながら⾒ていきます。

実装してみようConnectライクな middleware をサポートしたJunctionというクラスを作成してみます。

17

Connect

Junctionは、 use(middleware) と process(value, (error, result) => { }); を持っているシンプルなクラスです。
function isErrorHandingMiddleware(middleware) {// middleware(error, text, next)
const arity = middleware.length;
return arity === 3;}function applyMiddleware(error, response, middleware, next) {let errorOnMiddleware = null;try {
if (error && isErrorHandingMiddleware(middleware)) {middleware(error, response, next);} else {middleware(response, next);}
return;} catch (error) {errorOnMiddleware = error;}// skip the middleware or Error on the middleware
next(errorOnMiddleware, response);}
export default class Junction {constructor() {this.stack = [];}use(middleware) {this.stack.push(middleware);}process(initialValue, callback) {const response = {value: initialValue};
const next = (error) => {const middleware = this.stack.shift();if (!middleware) {return callback(error, response);}
applyMiddleware(error, response, middleware, next);};
next();}}

実装を⾒てみると、 use で middleware を登録して、 process で登録した middleware を順番に実⾏していきます。そのため、 Junction ⾃体は渡されたデータの処理をせずに、 middleware の中継のみをしています。
登録する middleware はConnectと同じもので、処理をしたら next を呼んで、次の middleware が処理するというのを繰り返しています。
使い⽅はConnectと引数の違いはありますが、ほぼ同じような形で利⽤できます。
import Junction from "./junction";
import assert from "assert";
const junction = new Junction();junction.use(function toUpperCase(res, next) {res.value = res.value.toUpperCase();next();});
junction.use(function exclamationMark(res, next) {res.value = res.value + "!";next();});
junction.use(function errorHandling(error, res, next) {console.error(error.stack);

18

Connectnext();});
const text = "hello world";
junction.process(text, function (error, result) {if (error) {console.error(error);}const value = result.value;
assert.equal(value, "HELLO WORLD!");});どのような⽤途に向いている?
ConnectやJunctionの実装を⾒てみると分かりますが、このアーキテクチャでは機能の詳細を middleware で実装できます。そのため、本体の実装は middleware に提供するインタフェースの決定、エラーハンドリングの⼿段を提供するだけでとても⼩さいものとなっています。
今回は紹介していませんが、Connectにはルーティングに関する機能があります。しかし、この機能も「与えられたパスにマッチした場合のみに反応する middleware を登録する」という単純なものです。
app.use("/foo", function fooMiddleware(req, res, next) {// req.url starts with "/foo"next();});

このアーキテクチャは、⼊⼒と出⼒がある場合にコアとなる部分は⼩さく実装できることが分かります。
そのため、ConnectやRackなどのHTTPサーバでは「リクエストに対してレスポンスを返す」というのが決まっているので、このアーキテクチャは適しています。

どのような⽤途に向いていない?
このアーキテクチャでは機能の詳細が middleware で実装できます。しかし、多くの機能を middleware で実装していくと、 middleware 間に依存関係を作ってしまうことがあります。
この場合、 use(middleware) で登録する順番により挙動が変わるため、利⽤者が middleware 間の依存関係を解決する必要があります。
そのため、プラグイン同⼠の強い独⽴性や明確な依存関係を扱いたい場合には不向きといえるでしょう。
これらを解消するためにコアはそのままにして、最初から幾つかのmiddleware stackを作ったものが提供されるケースもあります。

エコシステムConnect⾃体の機能は少ないですが、その分 middleware の種類が多くあります。
github.com/senchalabs/connect#middleware
Express middleware
また、それぞれの middleware が⼩さな単機能となっていて、それを組み合わせて使うように作られているケースが多いです。
これは、 middleware が層として重なっている作り、つまり middleware stack の形を取ることが多いためだといえます。

19

Connect

ミドルウェアでラップするプロセスは、概念的にたまねぎの中の層と同様の構造をもたらします。 WSGI ミド
ルウェアより引⽤

この仕組みを使っているもの
Express
Connectと middleware の互換性がある
元々はConnectを利⽤していたが4.0.0で⾃前の実装に変更
wooorm/retext
use でプラグイン登録していくテキスト処理ライブラリ

r7kamura/stackable-fetcher
use でプラグイン登録して処理を追加できるHTTPクライアントライブラリ

まとめ
ここではConnectのプラグインアーキテクチャについて学びました。
Connectは middleware を使ったHTTPサーバーライブラリである
Connect⾃体の機能は少ない
複数の middleware を組み合わせることでHTTPサーバを作ることができる

参考資料
Ruby - Rack解説 - Rackの構造とRack DSL - Qiita
Pylons のコンセプト ̶ Pylons 0.9.7 documentation

20

Connect

21

gulp

gulp
この⽂章はgulp 3.9.0を元に書かれています。
gulpはNode.jsを使ったタスク⾃動化ツールです。ビルドやテストなどといったタスクを実⾏するためのツールで、それぞれのタスクをJavaScriptで書くことができるようになっています。
ここでいうタスクとは複数の処理からなる処理の固まりのことです。このタスクを定義するAPIとして gulp.task が⽤意されています。また、それぞれの処理はNode.jsのStreamを使いつなげることで、複数の処理を⼀時ファイルなしでできるようになっています。
それぞれの処理はgulpのプラグインという形でモジュール化されているため、利⽤者はモジュールを読み込
み、 pipe() で繋ぐだけでタスクの定義ができるツールとなっています。

どう書ける?
たとえば、Sassで書いたファイルを次のように処理したいとします。
1.

sass/*.scss のファイルを読み込む

2. 読み込んだsassファイルを sass でコンパイル
3. CSSとなったファイルに autoprefixture で接頭辞をつける
4. CSSファイルをそれぞれ minify で圧縮する
5. 圧縮したCSSファイルをそれぞれ css ディレクトリに出⼒する
この⼀連の処理は次のようなタスクとして定義できます。
import gulp from "gulp";
import sass from "gulp-sass";
import autoprefixer from "gulp-autoprefixer";
import minify from "gulp-minify-css";
gulp.task("sass", function() {return gulp.src("sass/*.scss").pipe(sass()).pipe(autoprefixer()).pipe(minify()).pipe(gulp.dest("css"));});

ここでは、gulpプラグインの仕組みについて扱うので、gulpの使い⽅について詳しくは以下を参照してください。
gulp/docs at master · gulpjs/gulp
現場で使えるgulp⼊⾨ - gulpとは何か | CodeGrid
gulp⼊⾨ (全12回) - プログラミングならドットインストール

どのような仕組み?
実際にgulpプラグインを書きながら、どのような仕組みで処理同⼠が連携を取り動作しているのかを⾒ていきましょう。
先ほどのgulpタスクの例では、すでにモジュール化された処理を pipe で繋げただけで、それぞれの処理がどのように実装されているかはよく分かりませんでした。

22

gulp

ここでは gulp-prefixer というgulpプラグインを書いていきます。 gulp-prefixer は与えられたそれぞれのファイルに対して先頭に特定の⽂字列の追加するプラグインです。
同様の名前のプラグインが公式のドキュメントで「プラグインの書き⽅」の例として紹介されているので合わせて⾒るとよいでしょう。
gulp/docs/writing-a-plugin
gulp/dealing-with-streams.md
多くのgulpプラグインはオプションを受け取り、NodeのStreamを返す関数として実装されます。
import {Transform} from "stream";
export function prefixBuffer(buffer, prefix) {return Buffer.concat([Buffer(prefix), buffer]);}
export function prefixStream(prefix) {return new Transform({transform: function (chunk, encoding, next) {const buffer = prefixBuffer(chunk, prefix);
this.push(buffer);next();}});}
const gulpPrefixer = function (prefix) {// enable `objectMode` of the stream for vinyl File objects.
return new Transform({// Takes in vinyl File objects
writableObjectMode: true,// Outputs vinyl File objects
readableObjectMode: true,transform: function (file, encoding, next) {if (file.isBuffer()) {file.contents = prefixBuffer(file.contents, prefix);}
if (file.isStream()) {file.contents = file.contents.pipe(prefixStream(prefix));}
this.push(file);
next();}});};
export default gulpPrefixer;

ここで実装した gulp-prefixer は、次のようにしてタスクに組み込むことができます。
import gulp from "gulp";
import gulpPrefixer from "./gulp-prefixer";
gulp.task("default", function () {return gulp.src("./*.*").pipe(gulpPrefixer("prefix text")).pipe(gulp.dest("build")).on("error", (error) => {console.error(error);});});

この default タスクは次のような処理が⾏われます。

23

gulp

1.

./*.* にマッチするファイルを取得(すべてのファイル)

2. 取得したファイルの先頭に"prefix text"という⽂字列を追加する
3. 変更したファイルを build/ ディレクトリに出⼒する

Streamgulp-prefixer.jsを⾒てみると、 gulpPrefixer というTransform Streamのインスタンスを返していることが分かります。
const gulpPrefixer = function (prefix) {// enable `objectMode` of the stream for vinyl File objects.
return new Transform({// Takes in vinyl File objects
writableObjectMode: true,// Outputs vinyl File objects
readableObjectMode: true,transform: function (file, encoding, next) {if (file.isBuffer()) {file.contents = prefixBuffer(file.contents, prefix);}
if (file.isStream()) {file.contents = file.contents.pipe(prefixStream(prefix));}
this.push(file);
next();}});};
export default gulpPrefixer;

Transform Streamというものが出てきましたが、Node.jsのStreamは次の4種類があります。
Readable Stream
Transform Stream
Writable Stream
Duplex Stream
今回の default タスクの処理をそれぞれ当てはめると次のようになっています。
1.

./*.* にマッチするファイルを取得 = Readable Stream

2. 取得したファイルの先頭に"prefix text"という⽂字列を追加する = Transform Stream
3. 変更したファイルを build/ ディレクトリに出⼒する = Writable Stream
あるファイルを Read して、 Transform したものを、別のところに Write としているというよくあるデータの流れといえます。
gulp-prefixer.jsでは、gulpから流れてきたデータをStreamで受け取り、そのデータを変更したもの次へ渡す
Transform Streamとなっています。
「gulpから流れてきたデータ」を扱うために readableObjectMode と writableObjectMode をそれぞれ true にしています。この ObjectMode というのは名前のとおり、Streamでオブジェクトを流すための設定です。
通常のNode.js StreamはBufferというバイナリーデータを扱います。このBufferはStringと相互変換が可能できます。しかし、⼀⽅で複数の値を持ったオブジェクトのようなものは扱えません。
そのため、Node.js StreamにはObject Modeがあり、有効の場合はBufferやString以外のJavaScriptオブジェクトをStreamで流せるようになっています。
Node.js Streamについては以下を合わせて参照するといいでしょう。

24

gulp

Stream Node.js Manual & Documentation
substack/stream-handbook

vinyl
gulpではvinylオブジェクトがStreamで流れてきます。このvinylは Virtual file format という呼ばれているもので、ファイル情報と中⾝をラップしたgulp⽤に作成された抽象フォーマットです。
なぜこういった抽象フォーマットが必要なのかは次のことを考えてみると分かりやすいです。
Streamで流れてきたデータの拡張⼦を知りたい
Streamで流れてきたデータの読み取り属性をチェックしたい
Streamで流れてきたデータと同じ場所にファイルを書き出したい
ファイルの中⾝だけがStreamで流れた場合は、ファイルのパスや読み取り属性などの詳細な情報を知ることができません。そのため、 gulp.src で読み込んだファイルはvinylでラップされ、ファイルの中⾝は contents として参照できるようになっています。

vinylの中⾝を処理する
次はTransform Streamの具体的な処理を⾒てみましょう。
// file は `vinyl` オブジェクト
if (file.isBuffer()) {file.contents = prefixBuffer(file.contents, prefix);}
if (file.isStream()) {file.contents = file.contents.pipe(prefixStream(prefix));}

vinyl 抽象フォーマットの contents プロパティには、読み込んだファイルのBufferまたはStreamが格納されています。そのため両⽅のパターンに対応したコードする場合はどちらが来ても問題ないように書く必要があります。
: gulp pluginは必ずしも両⽅のパターンに対応しないといけないのではなく、Bufferだけに対応したものも多いです。しかし、その場合にStreamが来た時のErrorイベントを通知することがガイドラインで推奨されています。 - gulp/guidelines.md at master · gulpjs/gulp

contents にどちらのタイプが格納されているかは、ひとつ前のStreamで決定されます。

gulp.src("./*.*").pipe(gulpPrefixer("prefix text")).pipe(gulp.dest("build"));

この場合は、 gulp.src により決定されます。 gulp.src はデフォルトでは、 contents にBufferを格納するので、この場合はBufferで処理されることになります。
gulp.src はオプションに { buffer: false } を渡すことで contents にStreamを流すことも可能です。

gulp.src("./*.*", { buffer: false }).pipe(gulpPrefixer("prefix text")).pipe(gulp.dest("build"));変換処理
最後にBufferとStreamそれぞれの変換処理を⾒てみます。

25

gulp

export function prefixBuffer(buffer, prefix) {return Buffer.concat([Buffer(prefix), buffer]);}
export function prefixStream(prefix) {return new Transform({transform: function (chunk, encoding, next) {// ObjectMode:falseのTransform Stream// StreamのchunkにはBufferが流れてくる
const buffer = prefixBuffer(chunk, prefix);
this.push(buffer);next();}});}

やってきたBufferの先頭に prefix の⽂字列をBufferとして結合して返すだけの処理が⾏われています。
この変換処理はgulpに依存したものではないため、通常のライブラリに渡して処理するということが可能です。
BufferはStringと相互変換が可能なので、多くのgulpプラグインと呼ばれるものは、 gulpPrefixer と prefixBuffer にあたる部分だけを実装しています。
つまり、prefixを付けるといった変換処理は、既存のライブラリで⾏うことができるようになっています。
gulpプラグインはvinylオブジェクトのデータをプラグイン同⼠でやり取りし、そのインタフェースとして既存のNode.js Streamを使っているといえます。

エコシステムgulpのプラグインが⾏う処理は「⼊⼒に対して出⼒を返す」が主となっています。この受け渡すデータとしてvinylオ
ブジェクトを使い、受け渡すAPIのインタフェースとしてNode.js Streamを使っています。
gulpではプラグインがもつ機能は1つ(単機能)とすることを推奨しています。
Your plugin should only do one thing, and do it well. -- gulp/guidelines.md
gulpは既存のNode.js Streamに乗ることで独⾃のAPIを使わずに解決しています。
元々、Transform Streamは1つの変換処理を⾏うことが得意なので、その変換処理を pipe を繋げることで複数の処
理を⾏うことができます。
また、gulpはタスク⾃動化ツールなので、既存のライブラリをそのままタスクとして使いやすくすることが重要だといえます。 Node.js Streamのデフォルトでは流れるデータが Buffer となり、そのままでは既存のライブラリでは扱いにくい問題をデータとしてvinylオブジェクトを流すことで緩和しています。
このようにして、gulpはタスクに必要な単機能のプラグインを既存のライブラリで作りやすくしています。これにより再利⽤できるプラグインが多くできることでエコシステムを構築しているといえます。

どのような⽤途に向いている?
gulp⾃体はデータの流れを管理するだけで、タスクを実現するためにはプラグインが重要になります。タスクにはさ
まざまな処理が想定されるため、必要になるプラグインも種類がさまざまなものとなります。
gulpではvinylオブジェクトを中間フォーマットと決めたことで、既存のライブラリをラップしただけのプラグインが作りやすくなっています。
またgulpは、Gruntとは異なり、タスクをJavaScriptのコードして表現します。これにより、プラグインの組み合わせだけだと実現できない場合に、直接コードを書くことで対応するといった対処法を取ることができます。

26

gulp

そのため、プラグインの⾏う処理が予測できない場合に、中間フォーマットとデータの流し⽅だけを決めるというやり⽅は向いています。
まとめると既存のライブラリをプラグイン化しやすい
必要なプラグインがない場合も、設定としてコードを書くことで対応できる

どのような⽤途に向いていない?
プラグインを複数組み合わせ扱うものに共通することですが、プラグインの組み合わせ問題はgulpでも発⽣します。
たとえば、BrowserifyはNode.js Streamを扱えますが、変換の開始点としていない場合に問題が発⽣します。
gulp/browserify-transforms.md at master · gulpjs/gulp
また、gulpは単機能のプラグインを推奨していますが、これはAPIとしてそういう制限があるわけではないためあくまでルールとなっています。
このような問題に対してgulpはガイドラインやレシピといったドキュメントを充実させることで対処しています。
gulp/docs at master · gulpjs/gulp
既存のライブラリをプラグイン化しやすい⼀⽅、プラグインとライブラリのオプションが異なったり、利⽤者はプラグイン化したライブラリの扱い⽅を学ぶ必要があります。
ライブラリとプラグインの作者が異なるケースも多いため、同様の機能をもつプラグインが複数できたり、質もバラ
バラとなりやすいです。
まとめるとプラグインの組み合わせ問題は利⽤者が解決しないといけない
同様の機能をもつプラグインが⽣まれやすい

この仕組みを使っているもの
sighjs/sighgulpプラグインそのものをサポートしています。

まとめ
ここではgulpのプラグインアーキテクチャについて学びました。
gulp
どう書ける?
どのような仕組み?
Streamvinyl
vinylの中⾝を処理する
変換処理
エコシステムどのような⽤途に向いている?
どのような⽤途に向いていない?
この仕組みを使っているもの

27

gulp

まとめ

28

ReduxRedux
この⽂章はRedux 3.5.2を元に書かれています。
ReduxはJavaScriptアプリケーションのStateを管理するライブラリで、 Reactなどと組み合わせアプリケーションを作成するために利⽤されています。
ReduxはFluxアーキテクチャに類似する仕組みです。そのため、事前にFluxについて学習しているとよいです。
ReduxにはThree Principles(以下、三原則)と呼ばれる3つの制約の上で成⽴しています。
Single source of truth
アプリケーション全体のStateは1つのStateツリーとして保存されるState is read-onlyStateはActionを経由しないと書き換えることができない
Changes are made with pure functions
Actionを受け取りStateを書き換えるReducerと呼ばれるpure functionを作る
この三原則についての詳細はドキュメントなどを参照してください。
Read Me | Redux
Getting Started with Redux - Course by @dan_abramov @eggheadio
Reduxの使い⽅についてはここでは解説しませんが、Reduxの拡張機能となる middleware も、この三原則に基づいた仕組みとなっています。
middleware という名前からも分かるように、Connectの仕組みと類似点があります。 Connectの違いを意識しながら、Reduxの middleware の仕組みを⾒ていきましょう。

どう書ける?
簡潔にReduxの仕組みを書くと次のようになります。
操作を表現するオブジェクトをActionと呼ぶ
⼀般的なコマンドパターンのコマンドと同様のもの
Actionを受け取りStateを書き換える関数を Reducer と呼ぶ
ReducerはStoreへ事前に登録する
ActionをDispatch( store.dispatch(action) )することで、ActionをReducerへ通知する
Reduxの例として次のようなコードを⾒てみます。
import {createStore, applyMiddleware} from "redux";
import createLogger from "./logger";import timestamp from "./timestamp";// 4. Actionを受け取り新しいStateを返すReducer関数
const reducer = (state = {}, action) => {switch (action.type) {case "AddTodo":
return Object.assign({}, state, {title: action.title});default:
return state;}};// 1. `logger`と`crashReporter`のmiddlewareを適⽤した`createStore`関数を作る
const createStoreWithMiddleware = applyMiddleware(createLogger(), timestamp)(createStore);// 2. Reducerを登録したStoreを作成

29

Reduxconst store = createStoreWithMiddleware(reducer);store.subscribe(() => {// 5. Stateが変更されたら呼ばれる
const state = store.getState();// 現在のStateを取得
console.log(state);});// 3. Storeの変更をするActionをdispatch
store.dispatch({
type: "AddTodo",
title: "Todo title"});1.

logger と crashReporter のmiddlewareを適⽤した createStore 関数を作る

2. Reducerを登録したStoreを作成
3. (Storeの変更をする)Actionをdispatch
4. Actionを受け取り新しいStateを返すReducer関数
5. Stateが変更されたら呼ばれるというような流れで動作します。
上記の処理のうち、 3から4の間が middleware が処理する場所となっています。
dispatch(action) -> (_middleware_ の処理) -> reducerにより新しいStateの作成 -> (Stateが変わったら) -> subscribeで登録したコールバックを呼ぶ

via staltz.com/unidirectional-user-interface-architectures.html
次は middleware によりどのような拡張ができるのかを⾒ていきます。

30

Redux

middleware
Reduxでは第三者が拡張できる仕組みを middleware と呼んでいます。
Middleware | Redux
どのような拡張を middleware で書けるのか、実際の例を⾒てみます。次の middleware はStoreがdispatchした
Actionと、その前後でStateにどのような変更があったのかを出⼒するロガーです。
// LICENSE : MITconst defaultOptions = {// default: logger use console API
logger: console};/*** create logger middleware* @param {{logger: *}} options* @returns {Function} middleware function*/
export default function createLogger(options = defaultOptions) {const logger = options.logger || defaultOptions.logger;
return store => next => action => {logger.log(action);const value = next(action);logger.log(store.getState());
return value;};}

この middleware は次のようにReduxに対して適⽤できます。
import {createStore, applyMiddleware} from "redux";
const createStoreWithMiddleware = applyMiddleware(createLogger())(createStore);

このとき、⾒た⽬上は store に対して middleware が適⽤されているように⾒えますが、実際には store.dispatch に対して適⽤され、拡張された dispatch メソッドが作成されています。
これにより、 dispatch を実⾏する際に middleware の処理を挟むことができます。これがReduxの middleware による拡張ポイントになっています。
store.dispatch({
type: "AddTodo",
title: "Todo title"});

先ほどの logger.js をもう⼀度⾒てみます。
export default function createLogger(options = defaultOptions) {const logger = options.logger || defaultOptions.logger;
return store => next => action => {logger.log(action);const value = next(action);logger.log(store.getState());
return value;};}

createLogger は、loggerにオプションを渡すためのものなので置いておき、 return している⾼階関数の連なりがmiddleware の本体となります。

31

Reduxconst middleware = store => next => action => {};上記のArrowFunctionの連なりが⼀⾒すると何をしているのかが分かりにくいですが、これは次のように展開できます。
const middleware = (store) => {return (next) => {return (action) => {// Middlewareの処理
};
};
};

ただ単に関数を返す関数(⾼階関数)を作っているだけだと分かります。
これを踏まえて logger.js をもう⼀度⾒てみると、 next(action) の前後にログ表⽰を挟んでいることが分かります。
// LICENSE : MITconst defaultOptions = {// default: logger use console API
logger: console};/*** create logger middleware* @param {{logger: *}} options* @returns {Function} middleware function*/
export default function createLogger(options = defaultOptions) {const logger = options.logger || defaultOptions.logger;
return store => next => action => {logger.log(action);const value = next(action);logger.log(store.getState());
return value;};}

この middleware は次のようなイメージで動作します。

32

Redux

この場合の next は dispatch と⾔い換えても問題ありませんが、複数の middleware を適⽤した場合は、 次のmiddleware を呼び出すということを表現しています。
Reduxの middleware の仕組みは単純ですが、⾒慣れないデザインなので複雑に⾒えます。実際に同じ仕組みを実装しながら、Reduxの middleware について学んでいきましょう。

どのような仕組み?
middleware は dispatch をラップする処理ですが、そもそも dispatch とはどういうことをしているのでしょうか?
簡潔に書くと、Reduxの store.dispatch(action) は store.subscribe(callback) で登録した callback に action を渡し呼び出すだけです。
これはよくみるPub/Subのパターンですが、今回はこのPub/Subパターンの実装からみていきましょう。

Dispatcher
ESLintと同様でEventEmitterを使い、 dispatch と subscribe をもつ Dispatcher を実装すると次のようになります。
const EventEmitter = require("events");
export const ON_DISPATCH = "__ON_DISPATCH__";33

Redux/*** The action object that must have `type` property.
* @typedef {Object} Action* @property {String} type The event type to dispatch.
* @public*/
export default class Dispatcher extends EventEmitter {/*** subscribe `dispatch` and call handler. it return release function* @param {function(Action)} actionHandler* @returns {Function} call the function and release handler*/
subscribe(actionHandler) {this.on(ON_DISPATCH, actionHandler);
return this.removeListener.bind(this, ON_DISPATCH, actionHandler);}/*** dispatch action object.
* @param {Action} action*/
dispatch(action) {this.emit(ON_DISPATCH, action);}}

Dispatcher はActionオブジェクトを dispatch すると、 subscribe で登録されていたコールバック関数を呼び出すという単純なものです。
また、この Dispatcher の実装はReduxのものとは異なるので、あくまで理解のための参考実装です。
Unlike Flux, Redux does not have the concept of a Dispatcher. This is because it relies on pure functions
instead of event emitters -- Prior Art | Redux

applyMiddleware
次に、 middleware を適⽤する処理となる applyMiddleware を実装していきます。先ほども書いたように、
middleware は dispatch を拡張する仕組みです。
applyMiddleware は dispatch と middleware を受け取り、 middleware で拡張した dispatchを返す関数です。

/*=> api - middleware api=> next - next/dispatch function=> action - action object*/
const applyMiddleware = (...middlewares) => {return middlewareAPI => {const originalDispatch = (action) => {middlewareAPI.dispatch(action);};// `api` is middlewareAPI
const wrapMiddleware = middlewares.map(middleware => {return middleware(middlewareAPI);});// apply middleware order by first
const last = wrapMiddleware[wrapMiddleware.length - 1];
const rest = wrapMiddleware.slice(0, -1);
const roundDispatch = rest.reduceRight((oneMiddle, middleware) => {return middleware(oneMiddle);}, last);return roundDispatch(originalDispatch);
};
};

34

Reduxexport default applyMiddleware;

この applyMiddleware はReduxのものと同じなので、次のように middleware を適⽤した dispatch 関数を作成できます。
import Dispatcher from "./Dispatcher";import applyMiddleware from "./apply-middleware";import timestamp from "./timestamp";
import createLogger from "./logger";const dispatcher = new Dispatcher();
dispatcher.subscribe(action => {console.log(action);/*{ timeStamp: 1463742440479, type: 'FOO' }*/});// Redux compatible middleware API
const state = {};const middlewareAPI = {getState(){// shallow-copy state
return Object.assign({}, state);},
dispatch(action){
dispatcher.dispatch(action);}};// create `dispatch` function that wrapped with middlewareconst dispatchWithMiddleware = applyMiddleware(createLogger(), timestamp)(middlewareAPI);
dispatchWithMiddleware({type: "FOO"});

applyMiddleware で timestamp をActionに付加する middleware を適⽤しています。これにより dispatchWithMiddleware(action) した action には⾃動的に timestamp プロパティが追加されています。
const dispatchWithMiddleware = applyMiddleware(createLogger(), timestamp)(middlewareAPI);
dispatchWithMiddleware({type: "FOO"});

ここで middleware には middlewareAPI として定義した2つのメソッドをもつオブジェクトが渡されています。しかし、 getState は読み込みのみで、middlewareにはStateを直接書き換える⼿段が⽤意されていません。また、もう1
つの dispatch もActionオブジェクトを書き換えられますが、結局できることは dispatch するだけです。
このことから middleware にも三原則が適⽤されていることが分かります。
State is read-onlyStateはActionを経由しないと書き換えることができない
middleware という仕組み⾃体はConnectと似ています。しかし、 middleware が結果(State)を直接書き換えることはできません。
Connectの middleware は最終的な結果( response )を書き換えできます。⼀⽅、Reduxの middleware は扱える範囲
が「 dispatch からReducerまで」と線引されている違いといえます。

どういうことに向いている?
Reduxの middleware そのものも三原則に基づいた仕組みとなっています。 middleware はActionオブジェクトを⾃由に書き換えたり、Actionを無視したりできます。⼀⽅、Stateを直接は書き換えることができません。

35

Redux

多くのプラグインの仕組みでは、プラグインに特権的な機能を与えていることが多いですが、 Reduxの middleware
は書き込みのような特権的な要素も制限されています。
middleware に与えられている特権的なAPIとしては、 getState() と dispatch() ですが、どちらも書き込みをする
ようなAPIではありません。
このように、プラグインに対して⼀定の権限をもつAPIを与えつつ、原則を壊すような特権を与えないことを⽬的としている場合に向いています。

どういうことに向いていない?
⼀⽅、プラグインにも書き込み権限を与えないためには、プラグイン間でやり取りする中間的なデータが必要になります。
ReduxではActionオブジェクトというような命令(コマンド)を表現したオブジェクトに対して、 Reducerという命令を元に新しいStateを作り出す仕組みを設けていました。
つまり、プラグインそのものだけですべての処理が完結するわけではありません。プラグインで処理した結果を受け取り、その結果を処理する実装も同時に必要となっています。 Reduxでは middleware を前提とした処理を実装として書くことも多いです。
そういう意味ではプラグインと実装が密接といえるかもしれません。
そのため、プラグインのみで全処理が完結するような機能を作る仕組みは向いていません。

まとめ
ここではReduxのプラグインアーキテクチャについて学びました。
Reduxの middleware はActionオブジェクトに対する処理を書ける
middleware に対しても三原則が適⽤されているmiddleware に対しても扱える機能の制限を適⽤しやすい
middleware のみですべての処理が完結するわけではない

参考Middleware | Redux
10. Middleware · happypoulp/redux-tutorial Wiki
Brian Troncone – Redux Middleware: Behind the Scenes
ReduxのMiddlewareについて理解したいマン | moxt
Understanding Redux Middleware ̶ Medium

36

CONTRIBUTING

Contribute
Installation
インストールにはNode.jsが必要です
git clone https://github.com/azu/JavaScript-Plugin-Architecture.git
cd JavaScript-Plugin-Architecture
npm install

Usage
この書籍はHonKitを使い書かれています。

表⽰の確認
npm start でHonKitのローカルサーバを⽴ち上げて表⽰を確認できます。

npm start

テスト
npm test でコードや⽂章の単語チェックを⾏えます

npm test

⽂章カバレッジ
textlintとtextlint-formatter-codecovを使って出してる⽂章に対するカバレッジ
100％を理想的⽬標として、それに対する現実的な値をカバレッジの％として表現しています。
https://codecov.io/github/azu/JavaScript-Plugin-Architecture?branch=master
現在の⽂章カバレッジは次のコマンドでも確認できます。
npm run textlint:coverageContributeのやりかた
この書籍ではJavaScriptにおけるプラグインアーキテクチャを⾊々なライブラリやツールを元に紹介する形をとっています。
Contributeは⼤きく分けて、既存の⽂章の修正や執筆とProposalの提案などがあります。

⽂章の修正
typoなどを⾒つけた場合は、1⽂字の修正からでも問題無いので、Pull Requestを送っていただけると助かります。

37

CONTRIBUTING

表記揺れを発⾒した場合は単純にIssueを⽴ててもらうか、Pull Requestでの修正をいただけると嬉しいです。
また、この書籍ではtest/prh.ymlで定義した辞書を使い表記揺れを辞書でテストできるようにしています。辞書による表記揺れの検知が可能なら、そちらも合わせてご指摘いただけるとありがたいです。
textlint + prhで表記ゆれを検出する | Web Scratch

新しいプラグインの仕組みを書く
この書籍に載せたいプラグインアーキテクチャがある場合は、Issueを⽴ててください。
たとえば、XXXというライブラリ/ツールのアーキテクチャについてのIssueを⽴てる場合、次のようなことが1⾏とかでもいいので書かれているとよさそうです。
仕組みについて調べるのが⼤変な場合は、あとで調べれば問題ないため空欄で問題ありません。 JavaScriptはとにかく柔軟な⾔語なので、こういうプラグインの形式を取ってるというのを知らせるだけでも有⽤だと思います。
新しいプラグインの仕組みについて書く場合は、次のテンプレートを参照してください。
# XXXのアーキテクチャ## どう書ける?
- 実際のコード例## どういう仕組み?
- prototypeを拡張しているなど具体的な仕組み- その機構のコードへのリンク- その仕組みやプラグインについてドキュメントへのリンク## どういうことに向いている?
- どういう⽤途で使われてる(ユースケース)- 変換する毎にファイルとして吐き出さないので、⾼速に複数の変換をするのに向いている等## どういうことに向いていない?
- 変換の仕組み上書き換え等を⾏うプラグインを扱いにくい等## この仕組みを使っているもの- XXX以外にも同様の仕組みを使っているものがあるなら---## チェックリスト- [ ] どう書ける?
- [ ] どういう仕組み?
- [ ] どういうことに向いている?
- [ ] どういうことに向いていない?
- [ ] この仕組みを使っているもの- [ ] 実装してみよう- [ ] エコシステム

以下からこのテンプレートで使ったIssueを⽴てることができます。
新しいProposalを書く

Proposalの具体例

38

CONTRIBUTING

現在あるProposal⼀覧を参考にしてみるとよいかもしれません。
jQuery Plugin · Issue #8 · azu/JavaScript-Plugin-Architecture

テスト$ npm test

を実⾏するとコードや⽂章に対するテストが実⾏されます。

npm test

⽂章はtextlintによる単語のチェックが⾏われます。

コミットメッセージ
AngularJSのGit Commit Guidelinesをベースとしています。
conventional-changelog/angular.md at master · ajoslin/conventional-changelog
次のような形で1⾏⽬に概要、3⾏⽬から本⽂、最後に関連するIssue(任意)を書きます。
feat(ngInclude): add template url parameter to events
The `src` (i.e. the url of the template to load) is now provided to the`$includeContentRequested`, `$includeContentLoaded` and `$includeContentError`
events.
Closes #8453
Closes #8454

scopecommit type\commit title

/

/

|

|

feat(ngInclude): add template url parameter to events
body ->The 'src` (i.e. the url of the template to load) is now provided to the`$includeContentRequested`, `$includeContentLoaded` and `$includeContentError`
events.

referenced
issues->Closes #8453
Closes #8454

1⾏の feat や fix といったcommit typeは、迷ったらとりあえず chore と書いて、 scope も省略して問題ないので次のような形でも問題ありません。
chore: コミットメッセージ

このコミットメッセージの規約はconventional-changelogによる⾃動⽣成のためでもありますが、取り込むときに調整できるので無視してもらっても問題はありません。
以下を⾒てみるとよいかもしれません。
良いChangeLog、良くないChangeLog | Web Scratch
われわれは、いかにして変更点を追うか

39

CONTRIBUTING

40