「MathJaxをデバッグしてJavaScriptの理解を深める」の版間の差分
(→序盤その1) |
(→序盤その2) |
||
133行目: | 133行目: | ||
何やってるか、想像がつきそうなのが2個3個あればいいな。 | 何やってるか、想像がつきそうなのが2個3個あればいいな。 | ||
<syntaxhighlight lang="javascript"> | |||
9515: function(t, e, r) { | |||
var n = this && this.__values || function(t) { | |||
var e = "function" == typeof Symbol && Symbol.iterator, | |||
r = e && t[e], | |||
n = 0; | |||
if(r) return r.call(t); | |||
if(t && "number" == typeof t.length) return { | |||
next: function() { | |||
return t && n >= t.length && (t = void 0), { | |||
value: t && t[n++], | |||
done: !t | |||
} | |||
} | |||
}; | |||
throw new TypeError(e ? "Object is not iterable." : "Symbol.iterator is not defined.") | |||
}; | |||
Object.defineProperty(e, "__esModule", { | |||
value: !0 | |||
}), e.MathJax = e.combineWithMathJax = e.combineDefaults = e.combineConfig = e.isObject = void 0; | |||
var o = r(3282); | |||
function i(t) { | |||
return "object" == typeof t && null !== t | |||
} | |||
function s(t, e) { | |||
var r, o; | |||
try { | |||
for(var a = n(Object.keys(e)), l = a.next(); !l.done; l = a.next()) { | |||
var c = l.value; | |||
"__esModule" !== c && (!i(t[c]) || !i(e[c]) || e[c] instanceof Promise ? null !== e[c] && void 0 !== e[c] && (t[c] = e[c]) : s(t[c], e[c])) | |||
} | |||
} catch(t) { | |||
r = { | |||
error: t | |||
} | |||
} finally { | |||
try { | |||
l && !l.done && (o = a.return) && o.call(a) | |||
} finally { | |||
if(r) throw r.error | |||
} | |||
} | |||
return t | |||
} | |||
e.isObject = i, e.combineConfig = s, e.combineDefaults = function t(e, r, o) { | |||
var s, a; | |||
e[r] || (e[r] = {}), e = e[r]; | |||
try { | |||
for(var l = n(Object.keys(o)), c = l.next(); !c.done; c = l.next()) { | |||
var u = c.value; | |||
i(e[u]) && i(o[u]) ? t(e, u, o[u]) : null == e[u] && null != o[u] && (e[u] = o[u]) | |||
} | |||
} catch(t) { | |||
s = { | |||
error: t | |||
} | |||
} finally { | |||
try { | |||
c && !c.done && (a = l.return) && a.call(l) | |||
} finally { | |||
if(s) throw s.error | |||
} | |||
} | |||
return e | |||
}, e.combineWithMathJax = function(t) { | |||
return s(e.MathJax, t) | |||
}, void 0 === r.g.MathJax && (r.g.MathJax = {}), r.g.MathJax.version || (r.g.MathJax = { | |||
version: o.VERSION, | |||
_: {}, | |||
config: r.g.MathJax | |||
}), e.MathJax = r.g.MathJax | |||
}, | |||
</syntaxhighlight> | |||
2022年11月15日 (火) 23:56時点における版
概要
JavaScriptで書かれたMathJaxライブラリは、TeXのような組版処理をサービスを提供しているCDN(Content Delivery Network:コンテンツ配信サービス)あるいは自前のサーバーに配置するかするようにして構築するものです。主に数式の組版をするために提供されているサービスです。
このような組版処理はいくらやってもらってもいいという感じで、楽譜とかも組版出来たらいいのにと夢みたいなことをおもっていたのですが、一部には実際に楽譜が組めるようになっているサービスもあるようですが、なっとくいかない仕上がりです。納得しろやオマエ。って思うでしょ。そうなんすよ。作れないくせになっとくしてないんすよ。もっとやりたいことは簡単な楽譜処理なんすよ。そういうのはおいといて、数式の組版を実現したMathJaxはスゴイと思います。それで、これを理解したら自分にも役に立つJavaScriptが作れるようになるんじゃないかと思ったりしました。JavaScript初心者ですよ。
MathJaxに使われている技術を理解してく記事です。そして理解するための工夫や解析によってわかったMathJaxの考え方をここで書いていきます。自分が使ったことのあるのはMathJaxの中の/es5/tex-mml-chtml.jsで、これをちょろっと除くとまぁエグイ。一行目で終わってるプログラム。さすが、一行の長さがエグイ。インデントしてみたら5万行にもわたるプログラムが1行になってる。改行なしのプログラムあるんだなぁ。
まずは、インデント処理を自動でやってくれるサービスと出会うことから始めました。
いろいろなプログラムを自動整形するサービス
MathJaxを理解する作業をはじめて、いきなりありがたいサービスにまた出会う。このサービスのプログラムもすごいね。完璧すぎる自動整形インデント処理をしてくれます。そのJavaScriptの自動整形サイトが以下です。
- Online Javascript Beautifier - BeautifyConverter.com
他にもBeautifier Minifier/CSV/Excel/TSV/HTML/JSON/SQL/XML/YAML/Validators/CSS/Gen/Unit/Escape/IMG/PDF/IP/Color/Othersという整形処理ができる。自分で作ったプログラムが画期的である場合にはこのような自動整形処理を他人に任せるのは知的財産の流出の危険があるので、気を付けてください。信頼できるサイトもあるかもしれませんが、自分は知りません。サービス自体はありがたい。Microsoftがこういう機能をVisualStudioCodeにつけていたら楽だったのにな。あるっちゃあるんすよ。Shift+Alt+Fらしい。あまり自動整形されないっす。
とはいいつつも自動整形が必要なのはここまでヒドイと思ったプログラムだけですけどね。自分でプログラム作成して整形したものが最高に美しい。余計なことをするのがMicrosoftのエディタって感じですけど、Microsoftの考え方と共鳴しながらのコーディングをして、美しさを求めるのが自分流。MathJaxを自動整形して見えてくるものはある。最初の方に関数が連想配列でつめこまれて1000から3000くらいの関数が配列化されている。しかも番号だけで管理している。手ごわい。いみわからん。まったくいみわからん。変数名も1文字が多い。意味わからん。そんなことできるの?ってぐらいスゴイ。なんかそういう風にプログラムを意味わからんくする自動ツールがあるのかもしれん。でも自動整形したら少しましになった。あー、ありましたねminifyとかってサービスこれのことかな。意味のあるプログラム変数名を圧縮してしまう技。こういうのを使ってるのかもしれない。でもソースで配布してるのおかしくね。MathJaxの中の人。ちがうのか。謎のままおいておこう。
デバッグ環境を整える
基本的にはVScodeで簡易WebServerの元でデバッグで記述した方法によってVSCodeでデバッグします。ソースのダウンロードにはGitコマンドっていうのを使いたくて、Git for Windowsみたいなのをインストールしました。Gitのインストーラーがややこしい。Ver2.38.1ですが、CommandPromptでGitコマンドが使えりゃいいかなと思ったりするだけなんですけど、ややこしい。基本デフォルト設定でインストールするだけかなと思ったんですけどところどころ触ってやらないとCommandPromptで使えるような状態にはならないらしい。インストーラはhttps://gitforwindows.org/からデカデカとあるDownloadから入手。手を加えたところを太字にするとして、おおまかな流れは
実行→アプリがデバイスに変更[はい]→ライセンス確認[I Agree]→インストール先[Next]→フォルダ作成確認[はい]→
インストールオプション[Next](右クリックメニューにGitShell起動とGitBash起動を追加/大きいファイルサポート/*.gitファイルをテキストエディタで編集/*.shファイルをGitBashで起動)→スタートメニューのグループ名(GITのまま)[Next]→コミットメッセージで使うエディタは何[Use Vim(the ubiquitous text editor) as Git's default editor]-[Next]→Git initで作られるブランチ名(master (Let Git Decide:Gitに決定させよう))[Next]→パスの設定[Git from the command line and also from 3rd-party software(path追加Gitインストール先\Git\cmd)]-[Next]→OpenSSHのインストール[Use bundled OpenSSH]-[Next]→HTTPSリモートリポジトリ接続[Use the OpenSSL library]-[Next]→改行コード自動変換[Checkout Windows-style,commit Unix-style line endings(コミット処理でなんかうまくいかないときはこの設定が邪魔していることも覚えておかないといけませんよ。あまり使わない機能なので大丈夫だと思いますけど)]-[Next]→Git Bashターミナルを選択[Use MinTTY]-[Next]→Git pullの設定[Default(git pull --ff)]-[Next]→Credentioal設定[Git CredentialManager]-[Next]→オプション[Enable file system caching のみ ON]-[Next]→試験運用オプション[check無し]-[Install]
あらゆるプロジェクトにおいて、GitHubでソースコードを管理する波は来ています。確実に。Gitコマンドつかえるようになったほうが良いと思います。GitHubの一人勝ちだな。一般向けに最初に納得いくものを作った人たち偉い。会社には会社の中のコード管理があると思います。Visual Source Safeなつかしい。そんなのつかっているひといるのかな。ソース管理とかしたことないからわからないことばかりだけど、必要だよね。
例えばC:¥JavaScriptフォルダを作って、そこにソースをダウンロードする場合。
cd C:¥JavaScript
git clone git://github.com/mathjax/MathJax.git MathJax
これだけでC:¥JavaScript¥MathJaxが出来て、その中にes5フォルダが出来上がります。es5とはEcmaScriptというJavaScriptのもう一つの規格名がありまして、これにしたがっているのがes5です。何がどう違うかは自分は完全には理解していません。これに対応している関数や配列なんかには __esModule= true という値が取得できるようになっていたりします。高度なJavaScriptではこの値を制御するみたいな記事もあります。自分はまだ理解していません。
いろいろな種類の動作を確認するサンプルもダウンロードしましょう。MathJaxの多くの機能を網羅するようなSampleってのはありがたいものです。以下のアドレスから取得できます。
同じフォルダ(例C:\JavaScript\MathJax)にDownloadしましょう。結果的にC:\JavaScript\MathJax\MathJax-Example\MathJax-base\index.htmlのようなものにアクセスできるようになります。このindex.htmlには2次方程式の解の公式がサンプル表示されるようになっています。Mathjaxのプログラムの参照部分が ../MathJax.js になっているので、 ../../es5/tex-mml-chtml.js のように書き換える必要があります。全部のファイルで書き換えないとだめなのでちょっと辛いですけど、やってみましょう。ファイルによっては違うプログラムを参照した方がいいようなものもあります。これで関連記事のようなVSCodeを設定をするとデバッグができるようになります。
軽く追いかけてみる
序盤その1
自動整形で変換したtex-mml-chtml.jsの1行目の (function(){ にブレークポイントを設定して、プライマリサイドバーの上から4つめ実行とデバッグを選択して、launch.jsonを作ったら、Open index.html の横にある 蛍光色黄緑 ▷ のボタンを押します。ブラウザが起動して、VisualStudioCodeのtex-mml-chtml.jsの1行目で止まります。
最初の行に"use strict";とあるので、このプログラムでは、強制的に構文チェックをするモードになり、変数定義を正式に実施していないとエラーになるモードになります。
次に __webpack_modules__ 代入処理で配列が定義されます。 {351 : … とあるので、351が最初に定義される関数みたいですね。何に使うものなのか、この番号からはあまり想像つきません。ものすごい数の配列になってそう。__webpack_modules__はいわば、関数群ですね。
次に __webpack_module_cache__ = {}; という上記関数群のためのcacheと思われる変数への初期化がされます。プログラムが進むとここにいろいろ設定されていくのでしょう。よく使うやつとかがこんなかにはいるのかな。__webpack_modules__はものスゴイ数の関数がありますので。
次に function __webpack_require__(t){ … } があるので、tという変数の引数を受け取る__webpack_require__関数が定義されています。短いです。
function __webpack_require__(t) {
var e = __webpack_module_cache__[t];
if(void 0 !== e) return e.exports;
var r = __webpack_module_cache__[t] = {
exports: {}
};
return __webpack_modules__[t].call(r.exports, r, r.exports, __webpack_require__), r.exports
}
ペロッと貼っておきました。cacheの中に設定されている連想配列キー変数 t の配列値を e に取得して何もなければ t の配列値にexports : {} という連想配列を代入して、その値を更に r で覚えさせておくみたいだね。そして、最初に登場にした巨大な関数群を格納している配列 t を実行しています。 返却する値として .call は連想配列値を実行ですね。連想配列にプログラムを格納しておくという技なんだな。なるほど。その引数は、たった今、{exports:{}}とした r の exports キーの値 {} と r 全体 と また同じ、r の exports キーの値 {} と この関数全体の値 __webpack_require__ も引数にしていて4つが引数になっています。この関数群の中のひとつの実行結果が返り値の一つ目、そしてもうひとつ何回も登場するr の exports キーの値 {} を返却するみたい。
次に__webpack_require__に.gというメンバ変数をつくって、これに何やら設定する関数が実行されます。
__webpack_require__.g = function() {
if("object" == typeof globalThis) return globalThis;
try {
return this || new Function("return this")()
} catch(t) {
if("object" == typeof window) return window
}
}();
ここは単純だな。globalThisという値を __webpack_require__.g に代入する処理なんだけど、ちゃんとobjectとして存在しているかを確認して代入している慎重さがある。globalThisというオブジェクトが無い場合は例外処理付の次の処理を実施します。tryはエラーが発生する可能性がある処理で、エラーが発生したらcatchの処理をするという感じです。返却される可能性のある値は、this || new Function("return this")() エラーになった場合は、window ですね。とにかく取得できる環境変数は取るみたいな処理ですね。catch(t)のtはエラー情報格納変数です。eを使う人も多い印象だけど、tでもいいんだね。
勉強になるのは this || new Function("return this")() っていう部分が無駄に感じるけど、Debugしてみると単純なthisはundefinedで、new Function("return this")()だとwindowオブジェクトがかえってきたりして、意味が違うんだなって思ったことです。そして、この場合の || の使い方がむずい。なんじゃコレ。論理和演算だからどっちかがtrueになったらtrueが返却され、両方がfalseならfalseがかえるのだろう。それも動かないときはwindowオブジェクトで扱う。
まだわからないのは
- new Function("return this")()の後ろのカッコの有り無しの違い
- 関数構文の中のthisとfunction(){}の中のthisの違い。
また今度だな。
序盤その2
次にvar __webpack_exports__ = {}; で __webpack_exports__ の連想配列変数を初期化。
次はちょっと特殊 !function(){}という関数の実行処理です。戻り値のBool値が反転しそうだ。論理否定付きの関数。こんなこともできるんだな。なるほどなるほど。function xxx = {} や var xxx = { zzz : function(x,x,x) = {} }なら関数定義だけfunction(){ }なら実行する内容になるんだね。定義だけか関数だよといいながらも即実行。
次の部分からは __webpack_require__(t 【←数値】 )で5つの値 t, e, r, n, o を定義。なんの処理の関数を結び付けるのやら、みえもわからんだろうな。9515, 3282, 235, 265, 2388 暗号だな。さっきしらべたとおり __webpack_require__(t) を呼ぶと、__webpack_modules__[t].call(xx,xx,xx,xx)が実行される。__webpack_modules_cache__[t]には{ exports:{ } }が作成されている状態です。
何やってるか、想像がつきそうなのが2個3個あればいいな。
9515: function(t, e, r) {
var n = this && this.__values || function(t) {
var e = "function" == typeof Symbol && Symbol.iterator,
r = e && t[e],
n = 0;
if(r) return r.call(t);
if(t && "number" == typeof t.length) return {
next: function() {
return t && n >= t.length && (t = void 0), {
value: t && t[n++],
done: !t
}
}
};
throw new TypeError(e ? "Object is not iterable." : "Symbol.iterator is not defined.")
};
Object.defineProperty(e, "__esModule", {
value: !0
}), e.MathJax = e.combineWithMathJax = e.combineDefaults = e.combineConfig = e.isObject = void 0;
var o = r(3282);
function i(t) {
return "object" == typeof t && null !== t
}
function s(t, e) {
var r, o;
try {
for(var a = n(Object.keys(e)), l = a.next(); !l.done; l = a.next()) {
var c = l.value;
"__esModule" !== c && (!i(t[c]) || !i(e[c]) || e[c] instanceof Promise ? null !== e[c] && void 0 !== e[c] && (t[c] = e[c]) : s(t[c], e[c]))
}
} catch(t) {
r = {
error: t
}
} finally {
try {
l && !l.done && (o = a.return) && o.call(a)
} finally {
if(r) throw r.error
}
}
return t
}
e.isObject = i, e.combineConfig = s, e.combineDefaults = function t(e, r, o) {
var s, a;
e[r] || (e[r] = {}), e = e[r];
try {
for(var l = n(Object.keys(o)), c = l.next(); !c.done; c = l.next()) {
var u = c.value;
i(e[u]) && i(o[u]) ? t(e, u, o[u]) : null == e[u] && null != o[u] && (e[u] = o[u])
}
} catch(t) {
s = {
error: t
}
} finally {
try {
c && !c.done && (a = l.return) && a.call(l)
} finally {
if(s) throw s.error
}
}
return e
}, e.combineWithMathJax = function(t) {
return s(e.MathJax, t)
}, void 0 === r.g.MathJax && (r.g.MathJax = {}), r.g.MathJax.version || (r.g.MathJax = {
version: o.VERSION,
_: {},
config: r.g.MathJax
}), e.MathJax = r.g.MathJax
},
関連情報