2025年4月22日火曜日

script type="module"に変更して外部ファイルを読み込む

今回のお題はメインとなるjsの可読性を上げようかなって話
まぁそういう意味では前回の「document.getElementById」を毎回書くのを止めようかなって話と同じですね。

javascriptで長いコードを外部化する方法

まず、古来からのjavascriptの読み込み方法というのは

<script type="text/JavaScript" src="./main.js"></script>

こんな感じです。
ですがこの読み込み方法では外部ファイルjsを参照出来ません。
jsのファイルの読み込み方法から変更する必要があります。

<script type="module" src="main.js"></script>

この様にタイプの指定をmoduleという指定方法でjavascriptを読み込みます。

①従来の読み込み方法

違いとしては古来の方法は「window.」というブラウザのグローバル空間で定義されていています。例えば
window.a=10
var a=10
と、どちらで定義しても同じです。「window.」のプロパティの形で登録されていて暗黙的に省略してるだけです。
通常の関数もそうです。

function aaa() { console.log("こんにちは");}
console.log(window.aaa); // 関数 aaa
aaa(); 


じゃあ、node.jsの場合は?となる人も居るかと思いますがnodeでは「global.」オブジェクトに定義されます。ですので、

global.myVar = 10;
console.log(global.myVar); // 10
console.log(myVar); // 10

nodeではこんな感じになります。


話しを戻して、例えば、
<script type="text/JavaScript" src="./main1.js"></script>
<script type="text/JavaScript" src="./main2.js"></script>
と、二つ読み込んだ場合はどちらにコードを記載してもどちらかでも変数にも関数にもアクセスできます。
main1に「function a(){ console.log("test");}」と記述しているならば、main2で「a()」と書くだけでアクセスできます。

ファイル自体は分かれていても、コードの処理領域・スコープが同じなのです。

更に言えば読み込んでいるhtml側もそうです。
例えば、
<button onclick="a()">ぼたん</button>
はボタンを押すだけで「a()」を実行できます。

②モジュールの読み込み方法

次にmoduleですが、こちらは処理領域がjsのファイル内に閉じられています。
<script type="module" src="main1.js"></script>
<script type="module" src="main2.js"></script>

古来の方法通り普通にhtmlに読み込んでるように見えますがhtml側とモジュール側では領域が断絶されています。jsのトップレベルにあるコードは実行されますがそれだけです。

例えばmain1.jsに「function a(){ console.log("test");}」記述されている時に
html側から「<button onclick="a()">ぼたん</button>」を押しても実行出来ません。
「window.」領域にモジュールの内容がいない為html側からアクセスできません。
対応については順番に説明します。

②-1:モジュールのスコープ

例えば、main1.jsに「var b=10」を定義した場合、この変数bを使えるのはmain1.jsだけです。html側からも他のjsファイルもアクセス出来ません。
関数を定義した場合も同様で、「const c = (x) => x+1」のように定義した場合、html側からも他のjsファイルからもアクセス出来ません。


②-2:他のjsファイルへのアクセス

htmlとのやり取りの前に先に他のjsとのやり取りを先に説明します
現状ではmain1.jsは完全に独立していて他のjsに影響されないしmain1.jsも他のjsへは影響を与える事は出来ません。
どうするかというと、他のjsへアクセスするにはエクスポートとインポートが必要になります。
インポートはエクスポートされているものしか参照出来ません。
つまりjsのファイル間で合意が必要になります。「使って良い処理を出す側」と「使いたい処理を受ける側」これが一致して初めて外部のファイルを参照して利用できます。
インポート側が一方的に好きな変数や関数を参照出来ないという事です。
この方式はコーディング的にスコープ周りは強固ですが面倒臭いのは否めない。


②-2-1:エクスポートする側

まず、エクスポートして貰わないとそもそもインポートも出来ない形式なので先にエクスポートの方法について。

1.外部参照させたい変数や関数の頭全てに「export 」を記述する方法
export const aa=1
export const bb=2
const cc=3
export function dd() {}
export function ee() {}
function ff() {}
「export 」が付いていないものは外部からは参照出来ません。
コードを流してみた時に頭に「export 」があるかどうかで外部から見れるかが直ぐに分かります。

2.一番最後に「export 」対象を列挙する方法①
const aa=1
const bb=2
const cc=3
function dd() {}
function ee() {}
function ff() {}
export { aa, bb, ee,ff};//まとめて指定
こちらは最後に対象の変数や関数を指定する方法です。各定義の頭に「export 」を付ける必要はありません。既存のコードがある場合は全ての対象にexport を付けなおす必要はないので楽かもしれません。
逆に言えば流し見した時にどれがエクスポート対象かは分かりません。最後のリストから名前で検索して内容を確認する必要があるかも。
また、ある程度量がある場合列挙するのがそれはそれで面倒かもしれない。

3.一番最後に「export 」対象を列挙する方法②「デフォルトエクスポート」
const aa=1
const bb=2
const cc=3
function dd() {}
function ee() {}
function ff() {}
export { aa, bb, ee,ff};//まとめて指定
export default { bb, dd, ee,ff}; // デフォルトエクスポート
モジュールにつき1回だけ可能な指定方法、手間的には対象を列挙①と変わりません。
インポート時に楽が出来るかどうかの違いです。詳細はインポート側で説明。

②-2-2:インポートする側

さて、エクスポート対象が決まったらやっとインポート側で参照できるようになります。
エクスポート側では対象全てをエクスポートするみたいな事は出来ませんがインポート側は多少は柔軟でエクスポートを一括で取り込むことも可能

1.エクスポートされた変数関数をそのまま使えるようにする
import { aa, bb, dd } from './db.js';
console.log(aa);
console.log(dd());
エクスポートされた変数や関数の名前を指定して名前を変えずそのままそれを使えるようにします

2.一括取り込み①「export default」読み込み
import datas from './db.js'; 
console.log(datas.aa);
console.log(datas.dd());
オブジェクトの様に一括で取り込み、プロパティの様に対象全てを利用する事が出来ます。
オブジェクト名は好きな名前を付ける事が出来ます。
記述時に「{}」が不要な所は注意
モジュール1つにつき1回だけ可能。

3.一括取り込み②名前空間で「*」全指定する
import * as datas2 from './db.js';
console.log(datas2.aa);
console.log(datas2.dd());
エクスポート対象を*で全て対象にしてasで名前を付けて纏めています
こちらもオブジェクトとプロパティの様に対象対象全てを利用できます

4.エクスポートされた変数関数の名前を変更して取り込む
import { aa as zz, dd as yy} from './db.js';
console.log(zz);
console.log(yy());
エクスポートされた変数や関数の名前を指定した後にasで変数関数の名前を変更して取り込みます。

5.一旦全て取り込んで個別に取り出す(分割代入)
import * as datas2 from './db.js';
const { aa, dd } = datas2 ;
console.log(aa);
console.log(dd());
一旦全て取り込むのは既に紹介しましたがオブジェクトとプロパティのようなアクセスでした。分割代入を使えば元の名前で変数関数を取り出して使う事ができます。

html側からアクションを起こす方法

コード側のやり取りの説明が終わったので今度はhtmlからのアクションはどうするのかについてです。
「window.」空間外にjavascriptがいる為アクセス出来ません。しかもjsと違ってhtml側からではインポートの構文が使えません。解決方法は主に2つ。

①「window.」空間に来てもらう

html側からアクセス出来ないのは「window.」空間に対象の変数も関数も居ない為アクセスできないのですから、js側で「window.」に定義してhtml側の空間に下りてきて貰えばいいのです。
■js(module)
function dd() {}
window.dd=dd;
■html
<button onclick="dd()">ボタンをクリック</button>
こうする事でモジュール空間にあった変数や関数を「window.」空間に定義してhtml側からでもアクセスできるようになります

②イベントリスナーでjs側からhtml側アクション待ちで待機する

■js(module)
function dd() {}
document.getElementById("pp").addEventListener("click", dd);
■html
<button id="pp">ボタンをクリック</button>
onclickと違い一方方向にアクションが進んでいくのではなくjs側で待ち状態にしてhtml側のアクションの後にjs側でアクションされた対象を特定して実行する
どのボタンかの特定が必要に足る為idなどの識別子は必ず必要になる。

③「window.」空間の上にオブジェクトの空間を挟む

■js(module)
function dd() {}
window.obj1= window.App || {};
window.obj1.dd= dd;
■html
<button onclick="obj1.dd()">ボタンをクリック</button>
これは①のちょっと安全版みたいなもので完全なグローバル空間の「window.」の上にオブジェクトを作成して、一階層浮かせたグローバル空間で処理をする方法です。
一階層浮かせているのでonclickで実行する時にオブジェクト名も必要になります。

━━━━━━━━━━━━━
■終わりに
現状のjavascriptの一部を分離するために色々と調べたんだけど今から分割するのは手間が掛かり過ぎるかも・・・うーん困った。
調べる前は外部ファイルに定義して必要なものだけ引っ張ってこればいいのかなと思っていたけどエクスポートとインポートが相互で処理が必要で思ったより簡単ではなかった。
修正するかもしれないけど一旦保留にして次何か作る時はちょっとモジュール思考で考えてみよう。

0 件のコメント:

コメントを投稿