JavaScript最小化プログラムを作ってみる|PageSpeedInsights対策

勘違いしながらドはまりしながら作り上げていくジジイの『JavaScript奮闘記』です。3択クイズプログラムが出来上がったので次なるターゲット「JavaScript の最小化」です。

ポク太郎です。

ふと気付いたのがサイト運営者にとって悩ましいのがページの表示スピード。GoogleさんのPageSpeed Insightsにてよく叱られる「JavaScript の最小化」警告。

JavaScript ファイルを最小化すると、ペイロード サイズとスクリプトの解析時間を抑えることができます。

JavaScriptを最小化してくれるサービスは多数ありますが、どれも動かなくなるものバッカリ。

なので、仕様を明記した自分専用のJavaScriptを最小化機能をJavaScriptで作ってみます。

すぐにJavaScript最小化フォームへ飛ぶ
“ド素人作成の怪しいツール”と念頭に置いてどうぞ。その方が動かなかった場合に要因が特定しやすいと思います。


“最小化”しないといけない事情持つJavaScript

石器時代の話。大昔の主流はインタープリタ言語。“コンパイル”の概念がなく、人間が書いたプログラムを、実行時にその都度機械語翻訳しながら処理していく言語。

通常、ソースコードには人間が見やすいようコメント、空白等によるインデント、改行。

でも、毎回それ認識しながら実行すると処理速度遅→コンピュータにとっての無駄情報を最初から省くことで見づらさと引き換えに高速化を図る手法が存在しました。

JavaScriptにもそれと似た事情が。

JavaScriptは閲覧者のデバイスにダウンロードされ使われるもの←容量小さい方がダウンロード速。

現在の高性能スマホはインタープリタ故の無駄部分の実行時間は無視できるものの、貧弱な無線通信故にわずか数KBの微小情報量がページの表示速度に影響します。

そこで、無駄な空白やコメント、改行を削除し、見づらい1行に潰してしまうのが「JavaScriptの最小化」。

出来上がったJavaScript最小化機能

このJavaScript最小化プログラムの仕様・ソースは後述することにして、まず出来上がった最小化機能を先に。【動作条件】に注意。

使い方~JavaScript最小化機能

【使い方】

  1. (WordPressの場合)まずAutoptimize等の最適化プラグインの無効化設定を実施
  2. 必ず下項【動作条件】に目を通し、ご自身のソースコード内にサポート外の記述が無いかを確認。
  3. 使用前にご自身のソースコードをワーニングも含めて修正→JavaScript文法チェック
  4. 下のフォームにご自身のソースコード貼り付けて「最小化」ボタン押す。
  5. 結果がクリップボードにコピー&文法チェックサイトが別窓で開くのでワーニングすら存在しないこと再度確認。
  6. 最小化したものをページに導入し動作テスト。
  7. 動作確認が出来たらプラグインの無効化設定を解除し再度動作確認。

動作条件~JavaScript最小化機能

【動作条件】ソースコードの記述は以下を徹底して下さい。

  • JavaScriptの文字列、id名を指し示すクォーテーションはシングル’、HTMLでのをダブル”とすること。
  • document.getElementById('debug').innerHTML += '<div class="hoge">' + a + '</div>';
    ×document.getElementById("debug").innerHTML += "<div class='hoge'>" + a + "</div>";

    どうとでも書けちゃうのは欠陥仕様。人間は欠陥を利用せず一意のルールを徹底。

  • 変数名、関数名の1文字化は危険と判断し行っておりません。できる限り簡素なものをご自身で心掛けて下さい。
  • 自分のソースに以下のプロトコル以外が書かれてないか確認。以下以外はサポート外。
  • http://https://ftp://ftps://

  • 自分のソースに以下の「半角スペース必須な予約語」以外が使われてないか確認。以下以外はサポート外。
    1. “static “
    2. “private “
    3. “public “
    4. “function “
    5. “new “
    6. “var “
    7. “void “
    8. “let “
    9. “return “
    10. “else if”
    11. “case “
  • 自分のソースに「正規表現を表すクォーテーション無し関数」が使われてないか確認。以下以外はサポート外。
  • 正規表現は1行につき1つまでしか対応できていません。

プロトコル、予約語、クォーテーション無し関数は全部網羅したつもりですが、コメント欄にでも足りないよと連絡頂ければ修正して対応致します。

動かなくなった!~WordPressの最適化プラグインは注意

本ページの最小化プログラムの方が怪しいので、最初からAutoptimizeで除外&動作確認した後、プラグイン有効化環境で再度動作確認する方が効率的です。

要注意はWordPressの最適化プラグイン。Autoptimizeなどは複雑な処理をしようとして既に最適化されたものを動かなくしてしまう場合があります。

そんな場合には除外設定。以下はAutoptimizeでの例です。

JavaScript最小化プログラム作成|PageSpeedInsights対策

JavaScriptオプション」中の「Autoptimizeからスクリプトを除外」に該当のJavaScriptファイル名を指定します。

記入例)public_html直下のjsフォルダ内にあるhoge.jsを指定したいなら、js/hoge.jsと記入。複数ある場合はカンマで区切ります。

実践~JavaScript最小化機能

最小化」ボタンを押したら文法チェックのページが開くのでそこに貼り付けてエラーが無いことをすぐに確認下さい。
上項の【動作条件】は必ず読んでね。

JavaScript最小化機能の仕様

HTMLにはフォーム最小化ボタンを一つづつ配置。フォーム内に閲覧者が作った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って作りにくいやねぇ。下らんことでのトラブルが非常に多いです。

コメント

タイトルとURLをコピーしました