SyntaxHighlighterの読込みを最適化する
SyntaxHighlighterの読込みを最小限にします。概要は次の通りです。
<pre>
なしならば読み込まない- 必要なブラシは、最低限読み込む
- 遅延読込みも合わせて更に高速化する
元ネタは、下記の記事です。不必要な強い同期処理を簡易なものに置き換えてます。その他、自分用のチューニングをいろいろ盛り込んでます。
コード
SyntaxHighlighterSimpleLoader.js/**
* SyntaxHighlighterSimpleLoader.js
* 最小ファイル読込み
* JSとCSSの一本化(ファイル数削減)
* SyntaxHighlighterの問題動作修正
*
* [参考]
* Dr.ウーパのコンピュータ備忘録
* ページ表示速度改善:SyntaxHighlighter使用箇所があれば読み込む(完成スクリプトの配布)
* see http://upa-pc.blogspot.com/2014/04/syntaxhighlighter28.html
*/
(function(document) {
// --- 同期非同期関連 ----------------------------------------
// すべての登録が完了後、実行開始を想定(以降、新規追加なし)
const _syncfuncs = []; // 実行待ち配列
// 次の登録関数を実行する
const nextSyncChain = function() {
if (_syncfuncs.length != 0) {
_syncfuncs.shift()();
}
};
// 同期実行の関数を登録する
const addSyncFunc = function(func) {
_syncfuncs.push(function() {
func();
nextSyncChain();
});
};
// 同期非同期実行の関数を登録する
// 呼び出し関数内で nextSyncChain を実行すること
const addSyncAsyncFunc = function(func) {
_syncfuncs.push(func);
};
// chain >
// sync ----> chain
// syncasync -> -> chain
// sync ----> chain
// syncasync -> -> chain
// sync ----> chain(end)
// chain の呼び出しによって次関数を実行する。最後まで行ったら終わり。
// --- js/css関連 --------------------------------------------
// ローカルスタイル挿入(末尾追加:優先)
const addLocalStyle = function(text) {
const style = document.createElement('style');
style.type = 'text/css';
const rule = document.createTextNode(text);
style.appendChild(rule);
document.head.appendChild(style);
};
// css動的挿入(先頭追加:非優先)
const addStyles = function(urls) {
for (let i=0; i<urls.length; i++) {
const style = document.createElement('link');
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = urls[i];
document.head.appendChild(style);
}
};
// js動的同期挿入
const addScripts = function(urls) {
const scripts = [];
for (let i=0; i<urls.length; i++) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = urls[i];
scripts.push(script);
}
// 非同期処理を同期実行(ファイルを読み込む後、次処理を実行する)
addSyncAsyncFunc(function() {
const sc = document.getElementsByTagName('script')[0];
let count = 0;
for (let i=0; i<scripts.length; i++) {
const file = scripts[i];
file.onload = file.onreadystatechange = function() {
if (!file.readyState || /loaded|complete/.test(file.readyState)) {
file.onload = file.onreadystatechange = null;
count++;
if (count == scripts.length) {
nextSyncChain();
}
}
};
sc.parentNode.insertBefore(file, sc);
//console.log('lazy: '+(file.href || file.src));
}
});
};
// --- main --------------------------------------------------
// 最低限の SyntaxHighlighter を読み込む
function main() {
//console.log('lazy: main');
// ※要初期設定
const commonURL = 'https://cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/';
const scriptURL = commonURL + 'scripts/';
const styleURL = commonURL + 'styles/';
// ページ内のブラシの確認
// ※要初期設定
const scripts = [];
const brushs = [
// {names:['applescript '], file:'shBrushAppleScript.min.js'},
// {names:['as3', 'actionscript3'], file:'shBrushAS3.min.js'},
// {names:['bash', 'shell'], file:'shBrushBash.min.js'},
// {names:['cf', 'coldfusion'], file:'shBrushColdFusion.min.js'},
// {names:['cpp', 'c'], file:'shBrushCpp.min.js'},
// {names:['c-sharp', 'csharp'], file:'shBrushCSharp.min.js'},
{names:['css'], file:'shBrushCss.min.js'},
// {names:['delphi', 'pas', 'pascal'], file:'shBrushDelphi.min.js'},
// {names:['diff', 'patch'], file:'shBrushDiff.min.js'},
// {names:['erl', 'erlang'], file:'shBrushErlang.min.js'},
// {names:['groovy'], file:'shBrushGroovy.min.js'},
// {names:['java'], file:'shBrushJava.min.js'},
// {names:['jfx', 'javafx'], file:'shBrushJavaFX.min.js'},
{names:['js', 'jscript', 'javascript'], file:'shBrushJScript.min.js'},
// {names:['perl', 'pl'], file:'shBrushPerl.min.js'},
// {names:['php'], file:'shBrushPhp.min.js'},
{names:['plain', 'text'], file:'shBrushPlain.min.js'},
// {names:['ps', 'powershell'], file:'shBrushPowerShell.min.js'},
// {names:['py', 'python'], file:'shBrushPython.min.js'},
// {names:['rails', 'ror', 'ruby'], file:'shBrushRuby.min.js'},
// {names:['sass '], file:'shBrushSass.min.js'},
// {names:['scala'], file:'shBrushScala.min.js'},
// {names:['sql'], file:'shBrushSql.min.js'},
// {names:['vb', 'vbnet'], file:'shBrushVb.min.js'},
{names:['xml', 'xhtml', 'xslt', 'html', 'xhtml'], file:'shBrushXml.min.js'}
];
// <pre> を検索し、SyntaxHighlighter のブラシを検索する
const tags = document.getElementsByTagName('pre');
const re = /brush:\s*([^\s;]+)\s*;?/;
for (let i=0; i<tags.length; i++) {
// 不具合:単一の<pre>で複数ブラシを指定していると、2個目以降を読み込まない
const found = tags[i].className.match(re);
if (found != null) {
// ブラシの追加
for (let n=0; n<brushs.length; n++) {
if (brushs[n].names.indexOf(found[1]) != -1) {
//console.log('lazy: '+found[1]);
scripts.push(scriptURL + brushs[n].file);
brushs.splice(n, 1); // 再実行防止
break;
}
}
}
}
// ページ内にブラシが存在したら、SyntaxHighlighterの使用準備を実行
if (scripts.length > 0) {
//console.log('lazy: load');
// ※要初期設定
addStyles([styleURL + 'shCoreFadeToGrey.min.css']);
addLocalStyle("_{font-size:1.4rem!;border:1px solid #aaa!}_ *{cursor:auto}_ a,_ code,_ div,_ table,_ table caption,_ table tbody,_ table td,_ table thead,_ table tr,_ textarea{line-height:1!;min-height:1.6rem!}_ table caption{background-color:#666!;color:#ffe!;line-height:1.4!;padding:.25em 0 .125em 1em!}_ table td.code{padding:.25em 0!}_ .line.alt1,_ .line.alt2{background-color:#2c2c2c!}_ .plain,_ .plain a{color:#e6e6e6!}_ .comments,_ .comments a,_ .html.color2,_ .preprocessor{color:#87d46f!}_ .string,_ .string a{color:#ff91a7!}_ .keyword{color:#e9a7ff!}_ .value{color:#6cd0dc!}_.nogutter td.code .container textarea,_.nogutter td.code .line{padding-left:1em!}_ .line.highlighted.alt1,_ .line.highlighted.alt2{background-color:#146!}_ .gutter .line.highlighted{background-color:#3185b9!;color:#121212!}_ code{font-family:'Ricty Diminished','Noto Sans Mono CJK JP Regular','Source Han Code JP N',SFMono-Regular,Consolas,'Courier New',monospace!}".replace(/!/g, '!important').replace(/_/g, '.syntaxhighlighter'));
// shCore.jsは、読込みが最初でない場合、
// 「SyntaxHighlighter Cant find brush for: css」エラーが発生する。
addScripts([scriptURL + 'shCore.min.js']);
addScripts(scripts);
addSyncFunc(function() {
// ※要初期設定
SyntaxHighlighter.config.bloggerMode = true; // Bloggerで使用する
SyntaxHighlighter.defaults['toolbar'] = false; // ツールバー非表示
SyntaxHighlighter.defaults['auto-links'] = false; // URLに自動でリンクを設定しない
SyntaxHighlighter.defaults['tab-size'] = 2; // タブ数
SyntaxHighlighter.defaults['quick-code'] = false; // ダブルクリックでの全選択しない
// ハイライト
// 本スクリプトをページ読み込み後(ページ末尾)ロードすることで、
// loadイベントで実行する`highlight()`を直接呼び出す。
// 直接呼び出すことで高速化と同期処理を実現する。
//SyntaxHighlighter.all();
SyntaxHighlighter.highlight();
});
addSyncFunc(function() {
// 空白行(文字のない行、スペースのみを含む)
// 空白行の行末にスペースを追加する(SyntaxHighlighterの仕様?)を除去する。
// 空白行にスペースすらない場合、HTMLを選択コピーすると、
// 空白行(改行)が消える問題を回避するため、改行文字に置き換える。
const space = ' ';
const enter = '
';
const code = '</code>';
const line = document.querySelectorAll('.syntaxhighlighter .code .container .line');
for (let i=0; i<line.length; i++) {
const text = line[i].innerHTML;
//if (text.endsWith(' ')) {
if (text.substring(text.length-space.length, text.length) === space) {
const idx = text.lastIndexOf(code);
if (idx != -1) {
// 行末スペースを除去
line[i].innerHTML = text.substring(0, idx+code.length);
} else if (text.length == space.length) {
line[i].innerHTML = enter;
}
}
}
});
// スターター
nextSyncChain();
}
};
//console.log('lazy: init');
main();
//window.addEventListener('lazy', main);
})(document);
※「※要初期設定」を設定する
※遅延読込みは、関連参照
変更履歴
日付 | 備考 |
---|---|
2019/05/21 | 初版 |
2019/12/13 | CSSをJS内部に保持するように修正 |
2020/02/08 | CSSの読込み順を修正(ローカルを後ろで読み込む) |
備考
SyntaxHighlighter
は、shCore.js
より先に他のスクリプトを読み込んではならない。しかし、他のスクリプトの読込み順は特に意識しない。
SyntaxHighlighterのスクリプトローダーは、shAutoloader.js
が標準で準備されているが、引数で指定したブラシを読み込んでいるだけです。動的に引数を作成すれば最小の読込みにできるが、自作すればshAutoloader.js
分読込みを減らせる。自作ならば不必要な場合、shCore.js
と標準CSS分の読込みも減らせる。ただし、コード量で比べると自作しても大差はない。