tkLibraries.wrapper.mjs
による、複数javascriptライブラリの管理Web ページが増えてくると、各 HTML の <head>
に多数の JavaScript
ライブラリを並べて読み込む構成は管理が難しくなる。
たとえば従来は、次のように自作ライブラリと CDN ライブラリをページごとに列挙していた。
<script src="/js/tkDebug_le.js"></script>
<script src="/js/tkUtils_le.js"></script>
<script src="/js/tkCSS_le.js"></script>
<script src="/js/tkHTMLApplication_le.js"></script>
<script src="/js/tkfunctions/tkInsertHeader.js"></script>
<script src="/js/tkfunctions/tkSearch.js"></script>
<script src="https://cdnjs.cloudflare.com/.../xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/.../papaparse.min.js"></script>
...この方式には、次の問題がある。
<script>
タグを何度も記述する必要がある。onerror などのエラー処理も各
<script> タグに分散する。そこで、共通ライブラリの読み込みを 1 本の ES Module に集約する。
<script type="module" src="/js/tkLibraries.wrapper.mjs"></script>この方法では、HTML 側から見ると共通ライブラリの入口は
tkLibraries.wrapper.mjs だけになる。
ただし、現在の /js 以下の自作 JavaScript は本来の ES
Module として書かれているわけではなく、従来型の classic
script である。そのため今回の wrapper は、単純に
import "/js/tkUtils_le.js";とするのではなく、従来型 JavaScript を <script>
要素として順番に読み込む「ES Module 製ローダー」として実装している。
この文書では、この構成の考え方、tkLibraries.wrapper.mjs
の仕様、HTML 側での利用方法、既存 HTML からの移行方法をまとめる。
現在の基本構成は次のようになる。
HTML
|
+-- <script type="module"
| src="/js/tkLibraries.wrapper.mjs">
|
+-- <script nomodule
| src="/js/unsupportedBrowser.js">
|
+-- ページ固有の HTML
|
+-- <div id="..."></div>
<script>
tk-libraries-ready を待って
ページ固有処理を実行
</script>
tkLibraries.wrapper.mjs の内部では、従来型 JavaScript と
CDN ライブラリを順番に読み込む。
tkLibraries.wrapper.mjs
|
+-- tkDebug_le.js
+-- tkUtils_le.js
+-- XLSX
+-- PapaParse
+-- js-beautify
+-- highlight.js
+-- encoding-japanese
+-- tkCSS_le.js
+-- tkHTMLApplication_le.js
+-- tkInclude.js
+-- tkInsertHeader.js
+-- tkSearch.js
+-- event_js.js
|
+-- DOMContentLoaded を待つ
|
+-- 共通 UI を初期化
|
+-- "tk-libraries-ready" イベントを発行
ページ固有の JavaScript は、tk-libraries-ready
が発生してから実行する。
<script>通常の <script src="..."> は、HTML
を上から解析するときに読み込まれる。
<script src="A.js"></script>
<script src="B.js"></script>
<script src="C.js"></script>基本的には、
A.js を読み込んで実行
↓
B.js を読み込んで実行
↓
C.js を読み込んで実行
↓
次の HTML を処理
という順番になる。
そのため、後の HTML から A.js、B.js、C.js の関数を直接呼び出す構成が使いやすい。
<script type="module">ES Module は通常の <script>
と実行タイミングが異なる。
<script type="module" src="/js/tkLibraries.wrapper.mjs"></script>module script は HTML の解析を止めず、defer
に近い挙動をする。
したがって、HTML 内で単純に
const app = new tkHTMLApplication(4);と書くと、その時点では wrapper がまだライブラリを読み終えていない可能性がある。
この問題を避けるため、wrapper が全ライブラリの読み込み完了後に
tk-libraries-ready
という独自イベントを発行する。
ページ側はこのイベントを待ってから、ライブラリを利用する。
現在の構成では、tkLibraries.wrapper.mjs 自体は ES Module
であるが、読み込まれる既存の自作 JavaScript は従来型の classic script
のままである。
つまり、
ES Module
└─ classic script を順番にロードする
という移行用・互換用の構成である。
これは意図的な設計である。
既存の /js/*.js をすべて、
export function ...
import ...という本来の ES Module に一度に書き換える必要がなく、現在動作しているライブラリをほぼそのまま利用できる。
将来的には、必要に応じて個々のライブラリを本来の ES Module に変更し、
import { ... } from "./tkUtils.mjs";のような構成へ段階的に移行することも可能である。
tkLibraries.wrapper.mjs
の仕様現在の wrapper では、LIBRARIES
配列に共通ライブラリを登録している。
const LIBRARIES = [
// Basic D2MatE utilities
"/js/tkDebug_le.js",
"/js/tkUtils_le.js",
// CDN libraries
"https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.0/beautify.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.0/beautify-css.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.0/beautify-html.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/encoding-japanese/2.0.0/encoding.min.js",
// D2MatE UI / HTML helpers
"/js/tkCSS_le.js",
"/js/tkHTMLApplication_le.js",
"/js/tkInclude.js",
"/js/tkfunctions/tkInsertHeader.js",
"/js/tkfunctions/tkSearch.js",
"/js/tkfunctions/event_js.js",
];新しい共通ライブラリを追加する場合は、原則としてこの配列に追加する。
重要なのは 依存関係の順番 である。
たとえば B.js が A.js の関数を必要とするなら、
const LIBRARIES = [
"A.js",
"B.js",
];とする。
wrapper はこの順番を維持してロードする。
loadClassicScript()従来型 JavaScript をロードする関数である。
概略は次のようになっている。
function loadClassicScript(src) {
const promise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.async = false;
script.addEventListener(
"load",
() => resolve(src),
{ once: true }
);
script.addEventListener(
"error",
() => reject(
new Error(`Failed to load script: ${src}`)
),
{ once: true }
);
document.head.appendChild(script);
});
return promise;
}ES Module の import() ではなく、
document.createElement("script")で通常の <script>
要素を作るところが重要である。
これにより、既存 JavaScript を classic script として実行できる。
wrapper では、
const loadedScripts = new Map();を用意している。
既に同じ src が登録されている場合は、新しい
<script> を作らず、以前の Promise を返す。
概念的には、
if (loadedScripts.has(src)) {
return loadedScripts.get(src);
}である。
これにより、同じライブラリを wrapper 内で誤って複数回要求しても二重読み込みしにくくしている。
loadAllLibraries() では、
for (const src of LIBRARIES) {
try {
await loadClassicScript(src);
} catch (error) {
...
}
}としている。
await によって、
1 個目のロード完了
↓
2 個目をロード
↓
3 個目をロード
という順番を保証する。
既存 JavaScript は、前に読み込んだファイルの関数や変数を利用することがあるため、この順序保証が重要である。
windowオブジェクト への公開classic script で、
function insertHeader(...) {
...
}のように定義された関数は、多くの場合、
window.insertHeaderとして利用できる。
HTML 側では、
window.insertHeader(...)と明示的に書くと、グローバル関数を利用していることが分かりやすい。
class、let、const
の場合一方、
class tkHTMLApplication {
...
}や、
const tkCSS = ...のような top-level lexical binding は、classic script で実行されても、必ずしも
window.tkHTMLApplication
window.tkCSSとして公開されるとは限らない。
そこで wrapper では、classic script として小さな「橋渡しコード」を実行する。
if (typeof tkCSS !== "undefined") {
window.tkCSS = tkCSS;
}
if (typeof tkHTMLApplication !== "undefined") {
window.tkHTMLApplication = tkHTMLApplication;
}現在の wrapper では、次の名前を window
に明示的に公開している。
tkDebug
tkCSS
HTMLApp
tkHTMLApplication
この処理により、HTML では、
window.tkCSS.standard();あるいは、
window.app = new window.tkHTMLApplication(4);のように利用できる。
基本的な確認手順は次のとおりである。
LIBRARIES に対象 JavaScript
が含まれていることを確認する。tk-libraries-ready の後で処理を実行する。window
に目的の関数・オブジェクトが存在するか確認する。たとえば tkCSS.standard() を利用する場合は、
window.addEventListener(
"tk-libraries-ready",
function () {
if (
window.tkCSS &&
typeof window.tkCSS.standard === "function"
) {
window.tkCSS.standard();
}
},
{ once: true }
);とする。
ブラウザの開発者コンソールから、
typeof window.tkCSS
typeof window.tkCSS.standard
typeof window.createSearchForm
typeof window.insertHeader
typeof window.tkHTMLApplicationなどを実行すると、公開状況を確認できる。
期待される例は、
"object"
"function"
"function"
"function"
"function"
である。
window
に見つからない場合対象 JavaScript が読み込まれているにもかかわらず、
window.xxxが undefined
の場合は、class、let、const
などで定義されている可能性がある。
その場合は wrapper の exposeLegacyLexicalGlobals()
に橋渡しを追加する。
たとえば、
const myLibrary = ...を利用したい場合は、
if (typeof myLibrary !== "undefined") {
window.myLibrary = myLibrary;
}を追加する。
tk-libraries-ready
イベントwrapper は、
window に橋渡しする。DOMContentLoaded を待つ。tk-libraries-ready を発行する。このイベントが、HTML 側のページ固有 JavaScript の実行開始点になる。
<script>
window.addEventListener(
"tk-libraries-ready",
function (event) {
// ライブラリを使用する処理
},
{ once: true }
);
</script>{ once: true } を指定すると、このイベントハンドラは 1
回だけ実行される。
初期化処理の二重実行を避けるため、原則として付けておく。
<head>
の標準形基本的には次の 2 行でよい。
<script
type="module"
src="/js/tkLibraries.wrapper.mjs">
</script>
<script
nomodule
src="/js/unsupportedBrowser.js">
</script>共通ライブラリの <script> タグは各 HTML
に列挙しない。
HTTPsa-bano設定: .mjs の MIME typeApache が .mjs を正しい JavaScript MIME type
で返さない場合、ブラウザは ES Module のロードを拒否する。
典型的には、
Expected a JavaScript-or-Wasm module script
but the server responded with a MIME type of "text/plain"
のようなエラーになる。
Apache では、たとえば次の設定を追加する。
AddType text/javascript .mjs設定変更後は、環境に応じて Apache の設定確認と再読み込みを行う。
例:
sudo apachectl configtest
sudo systemctl reload httpd.mjs を利用することで、ファイル名だけでも ES Module
であることが分かりやすくなる。
今回の命名は、
tkLibraries.wrapper.mjs
としている。
wrapper は、複数の既存ライブラリを 1
つの入口から扱う役割を表している。
ES Modules に対応していないブラウザ向けには、
<script nomodule src="/js/unsupportedBrowser.js"></script>を使用する。
ES Modules 対応ブラウザでは nomodule script
は原則として実行されない。
ES Modules 非対応ブラウザでは module script
が利用できないため、unsupportedBrowser.js が、
このブラウザには対応していません。
このページを表示するには、ブラウザを最新版にしてください。
というメッセージを表示する。
unsupportedBrowser.js
自体は古いブラウザでも解釈しやすいよう、ES5
相当の記法を中心にしている。
現在の wrapper は、ライブラリ読み込み後に共通 UI も初期化する。
if (typeof window.injectCopyBoxCSS === "function") {
window.injectCopyBoxCSS();
}したがって、多くのページでは以前の、
injectCopyBoxCSS();を個別に書く必要がない。
if (
typeof window.initModal === "function" &&
!document.getElementById("messageDialog")
) {
window.initModal();
}initModal() が DOM
要素を作るため、messageDialog
が既に存在するときは再作成しない。
これにより modal の二重生成を防ぐ。
if (
window.hljs &&
typeof window.hljs.highlightAll === "function"
) {
window.hljs.highlightAll();
}したがって、HTML 側で従来の、
<script>
hljs.highlightAll();
</script>を毎回記述する必要はない。
各ライブラリのロードに失敗しても、wrapper は直ちに全体を停止せず、
failures.push({ src, error });として記録する。
全ロード試行終了後、
window.tkLibrariesFailures = failures;にも保存する。
tk-libraries-ready イベントでは、
event.detail.failuresからロード失敗一覧を取得できる。
例:
window.addEventListener(
"tk-libraries-ready",
function (event) {
const failures =
event.detail && event.detail.failures
? event.detail.failures
: [];
for (const failure of failures) {
console.error(
"Failed to load resource:",
failure.src,
failure.error
);
}
},
{ once: true }
);displayError()
が利用できる場合は、ページ内のエラー表示欄へ出すこともできる。
if (typeof window.displayError === "function") {
window.displayError(
"Failed to load resource: " + failure.src,
"#errorMessages"
);
}insertErrorHandlers()
の実行従来は、
<script>
insertErrorHandlers("errorMessages");
</script>のように、ライブラリ読み込み直後に実行していた。
wrapper 化後は、
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
if (
typeof window.insertErrorHandlers === "function"
) {
window.insertErrorHandlers("errorMessages");
}
},
{ once: true }
);
</script>とする。
tk-libraries-ready は DOMContentLoaded
後に発行されるため、DOM が存在することも保証しやすい。
従来は、
document.addEventListener(
"DOMContentLoaded",
function () {
insertHeader(
"/D2MatE/d2mate_header.html"
).catch(console.error);
}
);としていた。
wrapper は DOMContentLoaded を待ってから
tk-libraries-ready を発行するため、ページ側で再び
DOMContentLoaded を待つ必要はない。
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
if (
typeof window.insertHeader === "function"
) {
window.insertHeader(
"/D2MatE/d2mate_header.html"
).catch(console.error);
}
},
{ once: true }
);
</script>tkHTMLApplication
の初期化ページ内の複数箇所から app
を使用する場合は、共通初期化部分で、
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
if (
typeof window.tkHTMLApplication === "function"
) {
window.app =
new window.tkHTMLApplication(4);
window.attrs = {
target: "_blank"
};
window.fontAttrs = {};
}
},
{ once: true }
);
</script>のように window.app へ保存する。
別の <script> からは、
const app = window.app;として取得できる。
<div>
の近くに <script> を残す方法大きな HTML では、すべてのページ固有 JavaScript を 1 箇所にまとめるより、
<div id="target"></div>
<script>
...
</script>のように、内容を挿入する <div> のすぐ近くに
JavaScript を置く方が、HTML を読んだときの対応関係が分かりやすい。
wrapper を利用しても、この構造はそのまま維持できる。
<div id="maze_animation2"></div>
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
const app = window.app;
if (!app) {
console.error(
"tkHTMLApplication is not initialized."
);
return;
}
const dir =
"/Lecture/FundamentalsCMS/student2025/";
const path =
"maze_animation2.py";
const attrs =
window.attrs || { target: "_blank" };
const content = app.urlContent(
path,
dir + path,
{},
attrs,
{ target: "_blank" },
path
);
app.append(
"maze_animation2",
content
);
},
{ once: true }
);
</script><div> とそれを埋める JavaScript
が隣接するので、ページの保守性が高い。
<div id="20250110"></div>
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
const app = window.app;
if (!app) {
console.error(
"tkHTMLApplication is not initialized."
);
return;
}
const dir =
"/Lecture/FundamentalsCMS/";
const paths = [
"01/data.csv",
"01/test.ipynb",
"01/test.py",
"01/read.ipynb"
];
const attrs =
window.attrs || { target: "_blank" };
const fontAttrs =
window.fontAttrs || {};
const ulItems = [];
for (
let i = 0;
i < paths.length;
i++
) {
const path = paths[i];
ulItems.push(
app.li([
app.urlContent(
path,
dir + path,
{},
attrs,
{ target: "_blank" },
path
)
])
);
}
const ulList = app.ul(
ulItems,
{ class: "unordered-list" }
);
app.append(
"20250110",
app.font(
ulList,
fontAttrs
)
);
},
{ once: true }
);
</script>重要なのは、dir、paths、ulItems
などをその script ブロック内のローカル変数にすることである。
従来のように、
dir = ...
path = ...
ulItems = ...を別の <script>
間で暗黙に共有すると、ページが大きくなったときに変数の上書きが起こりやすい。
wrapper 化を機会に、
const dir = ...
const paths = ...
const ulItems = ...として局所化すると保守しやすい。
tkSearch.js を wrapper から読み込んでいれば、
<div id="searchContainer"></div>
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
if (
typeof window.createSearchForm ===
"function"
) {
window.createSearchForm(
"searchContainer",
"searchInput",
"検索",
"検索キーワードを入力"
);
}
},
{ once: true }
);
</script>のように利用できる。
tkCSS のページ固有処理tkCSS 自体の読み込みは wrapper が担当する。
ページごとに必要な CSS 初期化だけを tk-libraries-ready
後に呼ぶ。
例:
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
if (
window.tkCSS &&
typeof window.tkCSS.standard ===
"function"
) {
window.tkCSS.standard();
}
},
{ once: true }
);
</script>つまり、
tkCSS_le.js を読み込む
ことと、
tkCSS.standard() を実行する
ことを分離して考える。
前者は wrapper の仕事であり、後者はページ固有処理である。
<script> を分割したまま使うときの注意同じ tk-libraries-ready イベントを待つ
<script> が複数あってもよい。
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
// 共通 app 初期化
},
{ once: true }
);
</script>
...
<div id="target1"></div>
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
// target1 を作成
},
{ once: true }
);
</script>
...
<div id="target2"></div>
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
// target2 を作成
},
{ once: true }
);
</script>HTML の解析中にこれらのイベントハンドラが順番に登録されれば、同じイベントに対して登録順に実行される。
したがって、window.app の初期化 script を後続 script
より先に HTML 内へ置く構成が分かりやすい。
wrapper はイベント発行時に、
window.tkLibrariesReady = true;も設定する。
通常の静的 HTML では、inline script が先にイベントハンドラを登録するため問題にならない。
しかし、AJAX などで後から HTML
断片を読み込み、その中のコードを実行する場合は、既に
tk-libraries-ready が発行済みの可能性がある。
そのような場合には、次のような helper を作ると安全である。
function whenTkLibrariesReady(callback) {
if (window.tkLibrariesReady) {
callback({
detail: {
failures:
window.tkLibrariesFailures || []
}
});
return;
}
window.addEventListener(
"tk-libraries-ready",
callback,
{ once: true }
);
}利用例:
whenTkLibrariesReady(function () {
window.insertHeader(
"/D2MatE/d2mate_header.html"
);
});静的 HTML だけで利用する場合は必須ではない。
たとえば、
/js/tkfunctions/myFunction.js
を追加したい場合を考える。
LIBRARIES に追加するconst LIBRARIES = [
...
"/js/tkfunctions/myFunction.js",
];依存するライブラリがある場合は、その後に配置する。
開発者コンソールで、
typeof window.myFunctionを確認する。
"function" なら、そのまま利用できる。
window に無い場合JavaScript 内で、
const myFunction = ...あるいは、
class MyClass {
...
}として定義されている場合は、wrapper の橋渡しへ追加する。
if (
typeof MyClass !== "undefined"
) {
window.MyClass = MyClass;
}window.addEventListener(
"tk-libraries-ready",
function () {
if (
typeof window.myFunction ===
"function"
) {
window.myFunction();
}
},
{ once: true }
);CDN の JavaScript も同じ LIBRARIES
配列へ追加できる。
const LIBRARIES = [
...
"https://example.com/library.min.js",
];ただし、共通 wrapper に入れると、その wrapper を使うすべてのページでロードされる。
したがって、
という使い分けが望ましい。
wrapper に CDN URL を集約すると、ライブラリのバージョンを 1 箇所で管理できる。
これは大きな利点であるが、逆に wrapper のバージョンを変更すると、利用するすべてのページへ影響する。
たとえば XLSX を、
0.16.9
から、
0.17.0
へ変更すると、wrapper を利用するすべてのページが新しい版を使う。
したがって、共通 wrapper のライブラリバージョンを変更する場合は、代表的なページで動作確認してから反映する。
現在 wrapper は、デバッグや後から読み込まれたコードのために次を公開する。
window.tkLibrariesReady
window.tkLibrariesFailureswindow.tkLibrariesReadyライブラリ初期化完了後、
window.tkLibrariesReady = true;となる。
window.tkLibrariesFailuresロードに失敗したライブラリを配列として保持する。
console.log(
window.tkLibrariesFailures
);で確認できる。
tkLibraries.wrapper.mjs は ES Module なので、次も export
している。
export const ready =
loadAllLibraries();
export {
loadClassicScript
};別の ES Module からは、
import {
ready,
loadClassicScript
} from "/js/tkLibraries.wrapper.mjs";と利用できる。
たとえば、
import {
ready
} from "/js/tkLibraries.wrapper.mjs";
await ready;とすれば、ライブラリロード完了を Promise として待つこともできる。
通常の HTML inline script では tk-libraries-ready
イベントを利用する方が簡単である。
<head>
<script src="/js/tkDebug_le.js"></script>
<script src="/js/tkUtils_le.js"></script>
<script src="/js/tkCSS_le.js"></script>
<script src="/js/tkHTMLApplication_le.js"></script>
<script src="/js/tkfunctions/tkSearch.js"></script>
<script src="https://cdnjs.cloudflare.com/..."></script>
</head>
<body>
<script>
insertErrorHandlers(
"errorMessages"
);
</script>
<div id="searchContainer"></div>
<script>
document.addEventListener(
"DOMContentLoaded",
function () {
createSearchForm(...);
}
);
</script>
</body><head>
<script
type="module"
src="/js/tkLibraries.wrapper.mjs">
</script>
<script
nomodule
src="/js/unsupportedBrowser.js">
</script>
</head>
<body>
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
if (
typeof window.insertErrorHandlers
=== "function"
) {
window.insertErrorHandlers(
"errorMessages"
);
}
},
{ once: true }
);
</script>
<div id="searchContainer"></div>
<script>
window.addEventListener(
"tk-libraries-ready",
function () {
if (
typeof window.createSearchForm
=== "function"
) {
window.createSearchForm(...);
}
},
{ once: true }
);
</script>
</body>HTML のページ固有構造は維持したまま、共通ライブラリの管理だけを wrapper へ集約できる。
<head> に並んでいる共通 JavaScript
を確認する。LIBRARIES
に必要なライブラリが含まれているか確認する。<script src="..."> を削除する。<head> に次を追加する。<script
type="module"
src="/js/tkLibraries.wrapper.mjs">
</script>
<script
nomodule
src="/js/unsupportedBrowser.js">
</script>tk-libraries-ready
の中へ移す。tkHTMLApplication、tkCSS などは
window.xxx から利用する。typeof ... === "function" などで存在確認する。<div> を生成する script は、必要なら
<div> の近くに残す。dir、path、content
などは、できるだけ各ブロックの const / let
にする。.mjs を JavaScript MIME type
で返すことを確認する。window.tkLibrariesReady が
true になることを確認する。.mjs が
text/plain として返される症状:
Expected a JavaScript-or-Wasm module script
but the server responded with a MIME type of "text/plain"
対策:
AddType text/javascript .mjsを Apache に設定する。
xxx is not defined原因として、
LIBRARIES に入っていない。window に公開されていない。ことが考えられる。
まず、
window.tkLibrariesReady
window.tkLibrariesFailures
typeof window.xxxを Console で確認する。
class
は読み込まれているのに window.xxx が無いtop-level class や const
の可能性がある。
wrapper の exposeLegacyLexicalGlobals()
に橋渡しを追加する。
イベントハンドラに、
{ once: true }を付ける。
また、DOM 要素を生成する共通初期化では、既存要素の有無を確認する。
現在の wrapper はロード失敗を failures
に記録し、次のライブラリのロード試行を続ける。
そのため、オプションの CDN が 1 つ取得できなくても、関係のない機能は動作を継続できる可能性がある。
ただし、後続ライブラリがその CDN に依存している場合は、その機能は正常に動かない可能性がある。
今回の構成では、役割を次のように分ける。
window への必要な bridgetk-libraries-ready の通知<div> への内容挿入tkCSS.standard() などの呼び出しこの分離により、
共通ライブラリ管理
↓
tkLibraries.wrapper.mjs
ページ固有ロジック
↓
各 HTML
という責任分担になる。
現在の方式は、既存資産を活かしながら ES Modules の入口を導入する現実的な移行方法である。
将来的には次の段階へ進められる。
すべてのページで巨大な共通 wrapper を使うのではなく、
tkLibraries.core.mjs
tkLibraries.graph.mjs
tkLibraries.presentation.mjs
tkLibraries.webapp.mjs
などに分ける方法がある。
ページごとに必要なライブラリだけをロードできる。
たとえば、
export function insertHeader(...) {
...
}として、
import {
insertHeader
} from "./tkInsertHeader.mjs";と利用する。
この段階では window を介した global 共有を減らせる。
大きなページでは、
<script type="module">
import ...
</script>あるいはページ専用 .mjs に分離することもできる。
ただし、HTML 内で <div> と処理する
<script>
を近くに置く現在の構造は可読性が高いため、無理にすべてを外部ファイルへ移す必要はない。
今回の tkLibraries.wrapper.mjs は、
多数の既存 JavaScript
↓
1 本の ES Module wrapper
↓
tk-libraries-ready
↓
ページ固有 JavaScript
という構造を作る。
重要な点は次のとおりである。
<head> に共通 JavaScript
を大量に列挙しなくてよい。class、const などは必要に応じて
window へ bridge する。tk-libraries-ready
を待ってから関数を呼ぶ。<script> は <div>
の近くに分割したまま置ける。.mjs を Apache から正しい MIME type
で返す必要がある。nomodule
を利用して古いブラウザへ更新案内を表示できる。この方式を使うと、既存サイトの構造を大きく壊さずに JavaScript の依存関係を整理でき、今後の Web ページ追加・ライブラリ更新・保守が容易になる。
この Markdown は Pandoc で Word および HTML に変換できる。
pandoc ESM_wrapper_guide.md \
--toc \
-o ESM_wrapper_guide.docx既存の Word 書式を使いたい場合は、reference document を指定できる。
pandoc ESM_wrapper_guide.md \
--toc \
--reference-doc=reference.docx \
-o ESM_wrapper_guide.docxpandoc ESM_wrapper_guide.md \
--standalone \
--toc \
-o ESM_wrapper_guide.html必要に応じて CSS を指定する。
pandoc ESM_wrapper_guide.md \
--standalone \
--toc \
--css=style.css \
-o ESM_wrapper_guide.html| 名前 | 種類 | 用途 |
|---|---|---|
window.tkLibrariesReady |
boolean | 共通ライブラリ初期化完了の確認 |
window.tkLibrariesFailures |
Array | ロード失敗したライブラリ一覧 |
tk-libraries-ready |
Window event | HTML 側へ初期化完了を通知 |
event.detail.failures |
Array | イベントから取得するロード失敗一覧 |
window.tkCSS |
object | tkCSS_le.js の機能 |
window.tkHTMLApplication |
class | HTML 構築用クラス |
window.insertHeader |
function | 共通 header 挿入 |
window.createSearchForm |
function | 検索フォーム作成 |
ready |
ESM export / Promise | module 側から全ロード完了を待つ |
loadClassicScript() |
ESM export / function | classic script を追加ロードする |