ポク太郎です。
ふと気付いたのがサイト運営者にとって悩ましいのがページの表示スピード。GoogleさんのPageSpeed Insightsにてよく叱られる「JavaScript の最小化」警告。
JavaScriptを最小化してくれるサービスは多数ありますが、どれも動かなくなるものバッカリ。
なので、仕様を明記した自分専用のJavaScriptを最小化機能をJavaScriptで作ってみます。
“最小化”しないといけない事情持つJavaScript
通常、ソースコードには人間が見やすいようコメント、空白等によるインデント、改行。
でも、毎回それ認識しながら実行すると処理速度遅→コンピュータにとっての無駄情報を最初から省くことで見づらさと引き換えに高速化を図る手法が存在しました。
JavaScriptにもそれと似た事情が。
JavaScriptは閲覧者のデバイスにダウンロードされ使われるもの←容量小さい方がダウンロード速。
現在の高性能スマホはインタープリタ故の無駄部分の実行時間は無視できるものの、貧弱な無線通信故にわずか数KBの微小情報量がページの表示速度に影響します。
そこで、無駄な空白やコメント、改行を削除し、見づらい1行に潰してしまうのが「JavaScriptの最小化」。
出来上がったJavaScript最小化機能
使い方~JavaScript最小化機能
- (WordPressの場合)まずAutoptimize等の最適化プラグインの無効化設定を実施。
- 必ず下項 に目を通し、ご自身のソースコード内にサポート外の記述が無いかを確認。
- 使用前にご自身のソースコードをワーニングも含めて修正→JavaScript文法チェック。
- 下のフォームにご自身のソースコード貼り付けて「最小化」ボタン押す。
- 結果がクリップボードにコピー&文法チェックサイトが別窓で開くのでワーニングすら存在しないこと再度確認。
- 最小化したものをページに導入し動作テスト。
- 動作確認が出来たらプラグインの無効化設定を解除し再度動作確認。
動作条件~JavaScript最小化機能
ソースコードの記述は以下を徹底して下さい。
…- JavaScriptの文字列、id名を指し示すクォーテーションはシングル’、HTMLでのをダブル”とすること。
〇document.getElementById('debug').innerHTML += '<div class="hoge">' + a + '</div>'; ×document.getElementById("debug").innerHTML += "<div class='hoge'>" + a + "</div>";
どうとでも書けちゃうのは欠陥仕様。人間は欠陥を利用せず一意のルールを徹底。
http://、https://、ftp://、ftps://。
- “static “
- “private “
- “public “
- “function “
- “new “
- “var “
- “void “
- “let “
- “return “
- “else if”
- “case “
正規表現は
しか対応できていません。動かなくなった!~WordPressの最適化プラグインは注意
要注意はWordPressの最適化プラグイン。Autoptimizeなどは複雑な処理をしようとして既に最適化されたものを動かなくしてしまう場合があります。
そんな場合には除外設定。以下はAutoptimizeでの例です。
「JavaScriptオプション」中の「Autoptimizeからスクリプトを除外」に該当のJavaScriptファイル名を指定します。
実践~JavaScript最小化機能
JavaScript最小化機能の仕様
ページ内のJavaScriptの仕事は、
1 | フォーム内に貼り付けられたソースコードを1行ごとに配列に格納。 |
2 | コメントを削除。 |
3 | 正規表現使う関数の引数部分()をあり得ない文字列に置換。 |
4 | クォーテーション外のタブ、全角空白を半角スペースに置換。 |
6 | クォーテーション外の予約語をあり得ない文字列に変換。 |
6 | 全空白と改行を削除し1行化。 |
7 | 正規表現と予約語を元に戻す。 |
8 | 複数行コメントを削除。 |
9 | 出来上がったコードをクリップボードへ。 |
諦める仕様は変数名、関数名の1文字化。変数aの方が変数ahochaimannenpa-dennenより当然情報量を抑えられます。
が、これはJavaScriptのソースコードを理解するロジックが無いと危険と判断し、触らない仕様とします。
なので、できる限り最初からシンプルで短い名前を付けるよう自分で心がけます。
JavaScript最小化ソースコード
//ソース内に出現するreplaceall(dt,a,b)は引数dt内の文字列aを全部文字列bに置換する自作関数です。 //ソース内に出現するnthc(dt,spr),nthf(dt,spr,n)はカンマなどで区切られたフィールドデータを扱う自作関数です。 var jsource='';//貼り付けられたJSソース var js_src=[];//貼り付けられたJSソース(行ごと) var prot=[];//プロトコル prot[0]='http://'; prot[1]='https://'; prot[2]='ftp://'; prot[3]='ftps://'; var yoyaku=[];//空白必須の予約語 yoyaku[0]='static '; yoyaku[1]='private '; yoyaku[2]='public '; yoyaku[3]='function '; yoyaku[4]='new '; yoyaku[5]='var '; yoyaku[6]='void '; yoyaku[7]='let '; yoyaku[8]='return '; yoyaku[9]='else if'; yoyaku[10]='case '; var noqua=[];//クォーテション無し関数 noqua[0]='.match'; noqua[1]='.replace'; var ariezu=[];//あり得ない文字列 var eldbg = document.getElementById('jres'); var enter=String.fromCharCode(10);//textarea内は改行をlfにするらしい document.querySelector("#b_start").addEventListener('click',start); function start() {//HTMLのフォーム内容を受け取る var elm = document.getElementById('jsmin'); //変数初期化 js_src=[]; ariezu=[]; jsource=elm.value; make_ariezu(); for (var i=0;i<ariezu.length;i++){ // eldbg.value += 'make_ariezu後ariezu['+i.toString() +']: '+ariezu[i]+enter; } load_js(jsource); for (i=0;i<js_src.length;i++){ // eldbg.value += 'load_js後js_src['+i.toString() +']: '+js_src[i]+enter; } del_comment1(); for (i=0;i<js_src.length;i++){ eldbg.value += 'del_comment1後js_src['+i.toString() +']: '+js_src[i]+enter; } del_space(); eldbg.value += 'del_space後jsource: '+jsource+enter; del_comment2(); if(navigator.clipboard){ navigator.clipboard.writeText(jsource); eldbg.value = '最小化されたJSをコピーしました!'+enter; eldbg.value += jsource+enter; } window.open('https://jshint.com/', '_blank'); } function make_ariezu() { //あり得ない文字列作成 for (var i=0;i<yoyaku.length;i++){ ariezu[i]='≪―)●予皿約●(―≫'+i.toString(); } } function load_js(dt) { //フォームの内容を読み取りグローバルに代入 // var enter=String.fromCharCode(10)+String.fromCharCode(13); // var enter=String.fromCharCode(13); dt=dt+enter;//最後に改行なくコピーされる場合も for (var i=1;i<=nthc(dt,enter);i++){ js_src[i-1]=nthf(dt,enter,i); // eldbg.value += 'load_js内js_src['+(i-1).toString() +']: '+js_src[i-1]+enter; } } function del_comment1() { //コメントの削除1 var i,j; for (i=0;i<js_src.length;i++){ //プロトコルの記述をあり得ない文字列に置換して準備 for (j=0;j<prot.length;j++){ js_src[i]=js_src[i].replace(prot[j],ariezu[j]); } js_src[i]=nthf(js_src[i],'//',1); //あり得ない文字列を最大要素数側から戻す for (j=prot.length-1;j>-1;j--){ js_src[i]=js_src[i].replace(ariezu[j],prot[j]); } }} function del_space() { var find,fin; var st,en; var seiki=[];//正規表現を記憶する変数 var n=0; var i,j,k; var tmp,tmp2; var lf=String.fromCharCode(10); //全部一行に for (i=0;i<js_src.length;i++){ find=false; fin=false; for (j=0;j<noqua.length;j++){ if (js_src[i].indexOf(noqua[j]) == -1){ } else { for (k=0;k<js_src[i].length;k++){ if (js_src[i].substring(k,k+noqua[j].length) == noqua[j] ){ find=true; } else if (find==true){ if (js_src[i].substring(k,k+1) == '(' ){ st=k; } else if (js_src[i].substring(k,k+1) == ')'){ en=k; fin=true; break; } } } if (fin==true){ seiki[n]=js_src[i].substring(st,en+1); tmp='≪―)●正皿規●(―≫'+n.toString(); js_src[i]=js_src[i].replace(seiki[n],tmp); n=n+1; fin=false; } } } } jsource=''; for (i=0;i<js_src.length;i++){ jsource=jsource+js_src[i]; } jsource=replaceall(jsource,lf,''); for (i=0;i<js_src.length;i++){ // eldbg.value += 'del_space中js_src['+i.toString() +']: '+js_src[i]+enter; } eldbg.value += 'del_space中jsource一行化: '+jsource+enter; //クォーテーション外の予約語、空白を置換する var qua=String.fromCharCode(39); var tab=String.fromCharCode(9); if (nthc(jsource,qua) >= 1 ){ //クォーテーション有りの場合 tmp2=''; for (i=1;i<=nthc(jsource,qua);i++){ tmp=nthf(jsource,qua,i); if (i%2 == 1 ){ //タブ文字、全角空白を半角空白に置換 tmp=replaceall(tmp,tab,' '); tmp=replaceall(tmp,' ',' '); for (j=0;j<yoyaku.length;j++){ //予約語をあり得ない文字に置換 tmp=replaceall(tmp,yoyaku[j],ariezu[j]); } //全空白削除 tmp=replaceall(tmp,' ',''); } eldbg.value += 'del_space中tmp: '+i.toString()+' '+tmp+enter; if (tmp2==''){ tmp2=tmp; } else { tmp2=tmp2+qua+tmp; } } jsource=tmp2; } else { //クォーテーション無しの場合 tmp = jsource; tmp=replaceall(tmp,tab,' '); tmp=replaceall(tmp,' ',' '); for (j=0;j<yoyaku.length;j++){ //予約語をあり得ない文字に置換 tmp=replaceall(tmp,yoyaku[j],ariezu[j]); } tmp=replaceall(tmp,' ',''); jsource = tmp; } eldbg.value += 'del_space最後jsource置換後: '+jsource+enter; //正規表現を元に戻す for (i=seiki.length-1;i>-1;i--){ jsource=replaceall(jsource,'≪―)●正皿規●(―≫'+i.toString(),seiki[i]); } //予約語を元に戻す for (i=yoyaku.length-1;i>-1;i--){ jsource=replaceall(jsource,ariezu[i],yoyaku[i]); } } function del_comment2() { //複数行コメントを削除 var st='/*'; var ed='*/'; var tmp,tmp2; tmp2=''; if (nthc(jsource,ed)>0) { for (var i=1;i<=nthc(jsource,ed);i++){ tmp=nthf(jsource,ed,i); tmp2=tmp2+nthf(tmp,st,1); } jsource=tmp2; } else { } } //文字列中の引数aで指定された部分をすべて引数bに置換する自作関数です。 function replaceall(dt,a,b) { var tmp; var r=''; if (dt.indexOf(a) == -1){ //引数aが存在しない時は引数dtをそのまま返す。 r=dt; } else { tmp=dt.split(a); for (var i=0;i<tmp.length;i++){ if (i==0){ r=tmp[i]; } else { r=r+b+tmp[i]; } } } return r; } //カンマなどで区切られたフィールドデータを扱う自作関数です。 function nthc(dt,spr) { var tmp; var r=0; if (dt.indexOf(spr) == -1){ r=0; } else { tmp=dt.split(spr); r=tmp.length; } return r; } //カンマなどで区切られたフィールドデータを扱う自作関数です。 function nthf(dt,spr,n) { var tmp; var rt=''; if (dt.indexOf(spr) == -1){ rt=dt; } else { tmp=dt.split(spr); if (n<1||n>tmp.length){ rt=''; } else { rt=tmp[n-1]; } } return rt; }
手作業でも最小化できる程度の十数行のものなら瞬時に最適化完了。でも、正規表現使ったものに関しては特に完璧でないので手作業の方が確実だし、巨大なものは高確率で動かなく。
結局、使えもしないシロモノを苦労して作ってた感アリです。まぁ、今後改善していくとしてとりあえず投稿してみます。
シッカシ、JavaScriptって作りにくいやねぇ。下らんことでのトラブルが非常に多いです。
コメント