「ブックマークレットで jQuery を使う魔法の 210 文字」という記事を ブログ に書いたのですが、ここ Qiita ではその技術的な解説をしてみようと思います。
やりたい事はそのものずばり、ブックマークレットの中で jQuery を使うという事です。
ちょっとした処理の自動化や簡単なツールなど、よくブックマークレットを書いて活用しているのですが、ブックマークレットだと素の JavaScript を書かなければならず、DOM 操作が含まれる事をやろうとすると、jQuery に慣れた身には面倒くささが先に立ってしまいます。
ならば、**ブックマークレットで jQuery を使えるようにすればいい!**というのが今回の趣旨になります。
まず方針として、どんなページでブックマークを起動してもちゃんと jQuery が使えるようにしたいです。ですので、jQuery 本体は自前で調達しなければなりません。ただ、いくらミニマイズしたところで、jQuery 本体のサイズは 84KB / 8万文字超。さすがにこれをブックマークに押し込めるには無理があります。
そして、どんなページでもちゃんと動かせるようにするためには、元々のページに影響を与えてはいけません。元々 jQuery がロード済みのページであってもバージョンが古いかも知れないし、そもそも jQuery がロードされていないページだっていっぱいあります。
ですので戦略のポイントは 3 つです。
- CDN 上の jQuery をロードしてその jQuery を使う
- 元のページに jQuery が含まれていてもそれは上書きしない
- グローバルに何も残さない
それをコードで表現するとこんな感じになります。
(function(func) {
var scr = document.createElement("script");
scr.src = "//ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js";
scr.onload = function() {
func(jQuery.noConflict(true));
};
document.body.appendChild(scr);
})(function($) {
console.log($().jquery);
});
変数 scr
は、CDN 上のスクリプトをロードするための script
タグです。CDN としては Google のものを使う事にしました。本家の CDN は https
対応していないので、https ページで警告が出るのを避けられません。src
の記述からこのようにプロトコルを省くことで、ページ自体のプロトコルに合わせたソースからロードすることが出来ます。
そして、scr.onload
で CDN ロード完了時のアクションを指定してから、document.body
に scr
を追加します。
この辺り、本来的な正しい JavaScript としては
scr.setAttribute
や、scr.addEventListener
を使うべきところです。
ただ今回はブックマークレットということでなるべく記述を短くしたいので、あえてレガシーな (しかしまだ有効な) 書き方をしています。
CDN からの jQuery のロードが完了すると、onload イベントが発生します。
そのタイミングで本来実行したかった func
の引数に jQuery オブジェクトを渡して実行します。
ここでポイントになるのが jQuery.noConflict
です。
デフォルトの jQuery.noConflict()
ではグローバルの $
ショートカットをロード以前に戻し、jQuery.noConflict(true)
とすることで、グローバルの jQuery
もロード以前に戻します。返り値はロードした新しい jQuery
オブジェクトそのものです。
こうすることで、jQuery
以外のライブラリが $
を使っていた場合でも、他のバージョンの jQuery
がロード済みだった場合でも競合することが無くなります。
ついでに、グローバルの汚染も一切無くすことが出来ます。
さあ、ここまでで挙動自体は OK です。次にブックマークレットとして使いやすくするよう、コードをある程度短くします。
(function(f, s){
s = document.createElement("script");
s.src = "//ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js";
s.onload = function() {
f(jQuery.noConflict(true))
};
document.body.appendChild(s)
})(function($){
console.log($().jquery);
});
…といっても、変数名を 1 文字にしたのと、function
スコープの変数を var
を使わずに定義するため、引数に入れたくらいです。細かいところだと、無くても動く function
内最後の ;
は取っています。
document
の 1 文字化も試しましたが、使われる回数が少なすぎてかえって文字数が増えてしまいました。createElement
や appendChild
が長いので、繰り返し使うようだったらこれも 1 文字化するところですが、これらも 1 回ずつしか使われないのでやりません。
あとは余計なホワイトスペースを取り除くとこうなります。
(function(f,s){s=document.createElement("script");s.src="//ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js";s.onload=function(){f(jQuery.noConflict(true))};document.body.appendChild(s)})(function($){})
これで 210 文字。これ以上短くするのは、このロジックのままだと、出来ても数文字程度かな?という気がするので、いったん満足してます。
個人的には
%20
を含まないブックマークレットに美学を感じています。
CDN の URL を https
固定にして bit.ly
などで短縮してしまうという大技でだいぶ減らせるので、どうしてもというときは検討してみてもいいですね。
ちなみにこのコードは、jQuery 以外のライブラリでもちょっと改造するだけで汎用的に使えそうです。
追記 (最短を目指したバージョン)
はてなブックマークコメントでヒントをもらった、document
の引数化でマイナス 3 文字。それに加えて URL の短縮を使って、163 文字まで減らしてみました。
bit.ly
のエイリアス j.mp
を使う点と、bit.ly は https
もサポートしてるので URL からプロトコルを省略できる点も重要ですね。
(function(d,f,s){s=d.createElement("script");s.src="//j.mp/1bPoAXq";s.onload=function(){f(jQuery.noConflict(true))};d.body.appendChild(s)})(document,function($){})
これ以上はもう短くならない…かな?
追記2 (最短を目指したバージョン2)
true を !0 に置換できることに気づいたので、161 文字!
(function(d,f,s){s=d.createElement("script");s.src="//j.mp/1bPoAXq";s.onload=function(){f(jQuery.noConflict(!0))};d.body.appendChild(s)})(document,function($){})
追記3 (最短を目指したバージョン3)
コメントのヒントのおかげで、ついに 159 文字を達成。
!function(d,f,s){s=d.createElement("script");s.src="//j.mp/1bPoAXq";s.onload=function(){f(jQuery.noConflict(1))};d.body.appendChild(s)}(document,function($){})