[SPA]TypeScriptのソースファイル一つにバックエンドとフロントエンドのコードを集約する
1.今回の内容
今回のソースはTypeScriptのファイル一つだけです。それだけでフロントエンドとバックエンドのSPAのプログラムを同時に組むという内容です。そして実行にts-nodeは使うものの、手動コンパイルは一切行いません。もちろん.jsファイルも出力しません。
通常であれば、バックエンドはts-nodeによって裏でコンパイルされる形を作ることは出来ますが、フロントエンドはそうはいきません。ブラウザがTypeScriptを直接解釈できない以上、.tsを.jsに変換し、そのファイルを用意しておかなければなりません。
フロントエンドとバックエンドは同じ言語を使ったとしても、全く別の環境で実行されます。ソースコード一つで両方動かすことなど通常ならあり得ません。今回はその不可能を可能にする方法を紹介したいと思います。
2.準備
-
Node.jsのインストール
-
適当なディレクトリの作成
-
作成したディレクトリで以下のコマンドを入力
npm -y init
npm i @types/express express ts-node typescript
npx tsc --init
3.ソースコードの作成
※ソースコードを短くするため、本来必要なチェックを色々と省いています
import express from "express";
import * as bodyParser from "body-parser";
interface Message {
id: number;
name: string;
msg: string;
}
/**
* フロントエンド側(スクリプト送信時には文字列に変換される)
*/
function front() {
//ページ読み込み後に処理する内容
addEventListener("DOMContentLoaded", () => {
//データ送受信処理
const send = (name?: string, msg?: string) => {
const req = new XMLHttpRequest();
req.open("POST", "/", true);
req.setRequestHeader("Content-Type", "application/json");
req.addEventListener("loadend", () => {
if (req.readyState == 4) {
if (req.status == 200) {
const values = <Message[]>JSON.parse(req.responseText);
let output = "";
for (const value of values) {
output =
`<div>[${value.id}]${value.name}:${value.msg}</div><hr>\n` +
output;
}
div.innerHTML = output;
}
}
});
req.send(JSON.stringify({name,msg}));
};
//各インスタンスの作成
const inputName = document.createElement("input");
inputName.value = "名前";
document.body.appendChild(inputName);
const inputMessage = document.createElement("input");
inputMessage.value = "メッセージ";
document.body.appendChild(inputMessage);
const button = document.createElement("button");
button.innerHTML = "送信";
document.body.appendChild(button);
const div = document.createElement("div");
document.body.appendChild(div);
//ボタンのイベント
button.addEventListener("click", () => {
send(inputName.value, inputMessage.value);
inputMessage.value = "";
});
//初期データ要求
send();
});
}
/**
* バックエンド側
*/
const app = express();
app.get("/", (req, res) => {
//スクリプトデータを返す
//「(${front.toString()})();」がフロントエンド側での実行トリガーとなる
res.send(
`<!DOCTYPE html>
<html>
<head>
<title>TS-Test</title>
<script>
(${front.toString()})();
</script>
</head>
<body></body>
</html>`
);
});
//POSTデータの処理
const messageData: Message[] = [];
app.use(bodyParser.json());
app.post("/", (req, res) => {
if (req.body.name && req.body.msg)
messageData.push(Object.assign({ id: messageData.length + 1 }, req.body));
res.send(JSON.stringify(messageData));
});
//待ち受けポート
app.listen(8080);
4.実行
ts-node Test01
5.確認
SPAの掲示板風プログラムです。データをファイルやDBに出力しているわけでは無いので、バックエンドが停止した時点で消失します。
6.解説
やっていることは単純です。
function front()
このバックエンド側で自動コンパイルされたfunctionの内容を、フロントエンド側に送っているだけです。今回は生成コードを<script>タグに挟み込んでいますが、expressと連携させてファイルとして返す組み方も可能です。また今回はソースコードが一つですが、やろうと思えば別ファイルに分離することも可能です。
今回の内容が出来るのは、フロントエンドとバックエンドの実行時の言語が一致しているおかげです。もちろん他の言語でもバックエンドからフロントエンドにスクリプトを流し込むことは可能ですが、それはあくまで生成済みの文字列としてです。普通にプログラムを組んだものを単純な変換で渡せるのは、同じ言語故の恩恵です。
もちろんこれだけでまともなシステムを作るのは難しいです。しかしこれを部分的に利用し、特定の動作をカスタマイズするような用途であれば使えるかもしれません。どんなものも発想しだいで可能性が広がるのです。もちろんTypeScriptの練習やSPAのプログラムの実験などにも使えます。
7.まとめ
今回の内容はフロントエンドのTypeScriptのプログラムをコンパイル無しで作ることは出来ないか、そんなことを考えて実験した結果です。思いついたことはすぐに実験用のコードを作って試しています。そのハードルを下げているのは、手軽な実行環境です。
これを使っています。コードを書いてF5キーで即実行出来るので、ちょっとした動作検証から、DBアクセスの確認、外部APIの実験などが気軽に行えます。ということで。色々なコードがこれで作ったディレクトリに溜まっています。
思いついたらとりあえずコードを書く、今後もそんな感じでやっていきたいと思っています。