webpackでレガシーなJavaScriptをオブジェクト指向っぽくしてみる
こんにちは。深緑です。
今回はレガシーなJavaScriptをwebpackを用いてなんとか改善することを考えてみます。
はじめに
本記事におけるレガシーなJavaScriptとは、HTMLとJavaScriptが一体化しているソースです。
10年以上前に作ったWEBシステムを改造しながら使い続けてる現場において、
限られたリソースでなんとか改善しようとしているとお考えください。
モダンなJavaScriptから何を取り込むことかを考える
モダンなJavaScriptを考えるとVue.jsやReactが思い浮かびますが、
既存システムにそれらを導入するのは無理なので何かのエッセンスだけでも取り込むことを考えます。
レガシーなJavaScriptを考えた場合、以下の問題点が浮かびます。
- HTMLとJavaScriptが一体化しているので、静的解析やステップカウント(注1)が通らない。
- ある程度共通化はしているものの、ソースが追いづらい
これを踏まえて、Vue.jsを見たところwebpackが使われてるのを見つけました。
→Vue3のvue.config.jsにconfigureWebpackの記述がある。
webpackはそのメリットに
「ファイルを分割する開発ができる」という言葉を見かけたのでこれを取り込むことにしてみます。
第1ステップ HTMLに書いてあるJavaSciptを別ファイルにする
各画面のHTMLには、ボタンなどのイベントハンドラ・通信の処理が書いてあります。
まずはこれをHTMLとは別のファイルとし、画面ID.jsのような感じにします。
画面ごとにJSファイルがあるイメージですね。
簡単な図にするとこんな感じです。
ソースコード
<html>
<head>
<script src="common.js"></script>
<script src="sample1.js"></script>
</head>
<body>
<h1>Sample1</h1>
<p>This is a sample page.</p>
<button id="button">Button</button>
</body>
</html>
<html>
<head>
<script src="common.js"></script>
<script src="sample2.js"></script>
</head>
<body>
<h1>Sample2</h1>
<p>This is a sample page.</p>
<button id="button">Button</button>
</body>
</html>
function showMessage(message) {
alert(message);
}
window.addEventListener('DOMContentLoaded', (event) => {
document.getElementById("button").onclick = () => {
showMessage("Sample1");
};
});
window.addEventListener('DOMContentLoaded', (event) => {
document.getElementById("button").onclick = () => {
showMessage("Sample2");
};
});
問題点がたくさん見つかります。
HTML側は共通関数のファイルと画面毎のJSを読む形になりました。
画面側でロードするファイルを切り替えてるの嫌ですね・・・。
そもそも複数ファイルをロードするのは、ロードする順番を気にする必要があるので嫌です。
画面毎のJSで共通関数を呼んだ場合、動きはしますがそれが共通関数かどうか判別しづらいのも気になります。
この図はサンプルなので単純ですが、もっと複雑化したら共通関数もいいけど親クラスとかできないか?と思ってしまいますね。
素のJavaScriptでもオブジェクト指向はできますが、このスタイルで突き進むのはやめた方が良さそうです。
第2ステップ webpackを用いてオブジェクト指向っぽくしてみる
ここで、webpackを導入してみます。
webpackが依存関係を解決してくれるので、モジュール分割がしやすくなりました。
なので、思い切ってオブジェクト指向っぽくしてみます。
ソースコード
<html>
<head>
<script src="sample.js"></script>
</head>
<body>
<h1>Sample1</h1>
<p>This is a sample page.</p>
<button id="button">Button</button>
</body>
</html>
<html>
<head>
<script src="sample.js"></script>
</head>
<body>
<h1>Sample2</h1>
<p>This is a sample page.</p>
<button id="button">Button</button>
</body>
</html>
import { Common } from 'common/common';
import { Sample1 } from 'controller/sample1';
import { Sample2 } from 'controller/sample2';
class Sample {
constructor() {
this.execute();
}
private execute(): void {
console.log(document.location.pathname);
switch (document.location.pathname) {
case '/sample1.html':
new Sample1();
break;
case '/sample2.html':
new Sample2();
break;
default:
Common.showMessage('error');
break;
}
}
}
window.addEventListener("DOMContentLoaded", () => new Sample());
export class Common {
public static showMessage(message: string) {
alert(message);
}
}
import { Common } from 'common/common';
export class Sample1 {
constructor() {
document.getElementById("button").onclick = () => {
Common.showMessage("Sample1");
};
}
}
import { Common } from 'common/common';
export class Sample2 {
constructor() {
document.getElementById("button").onclick = () => {
Common.showMessage("Sample2");
};
}
}
このサンプルはTypeScriptを導入していますが、
ここはJavaScriptのままでも良いと思っています。
ていうか実際の現場でも現状凄く緩いほぼJavaScriptまんまのTypeScriptですね。(注2)
webpackがファイルをバンドル(まとめる)してくれるので、HTMLがロードするファイルが一つになりました。
こっちの方が好きです。
第1ステップで画面毎のJSを作りましたが、このルールに倣い画面毎のクラスにします。
そして、メインの処理で画面のパスに応じてインスタンス化するクラスを変えることで、
第1ステップ(引いては改善前のコード)に近い感覚にします。
既存メンバーの混乱を抑えるのが狙いです。
この辺りの構造は一旦これぐらいに抑えるのが良さそうです。
共通関数もCommon.関数名となり判別しやすくなりましたね。
この構造なら今後画面の親クラスを作ってもなんとかなりそうです。
まとめ
現状、ここまでです。
取り急ぎ静的解析とステップカウントが取れるようになりました。
今のところ既存システムの一部にしかこの方式を採用してませんが、
他の画面もこの方式に変えてってもいいかなあという感覚になっています。
モダンなフレームワークを見てこれは無理かなと思ったこともありますが、
使われてる技術の一部だけでも導入するという発想もいいですね。
参考書籍
webpack 実践入門 第2版: webpackの基礎をしっかり理解して使いこなす
注釈
注1
ステップカウントについてはその重要性に疑問を持たれる方をいると思いますが、
私はステップカウントから算出される試験密度などには有効性があると思うので、
ステップカウントが取りづらいのはデメリットと考えています。
注2
TypeScriptは奥が深いのでまずは導入すること自体をゴールとします。
Discussion