codeなにがしのソース表示ウィジェット制作ノウハウ
個人的にいろいろと立て込んでいて時間が経ってしまいましたが、 5 月にcodeなにがしがリニューアルしました。実は私も少しお手伝いさせていただきまして、ソースコード・ノウハウッ!のソースコード表示部分の JavaScript ウィジェットを制作しました。そこで、本日は新版codeなにがしのご紹介とともに、ソースコード表示ウィジェットの制作ノウハウなどを公開します。まあ、高度なことをやっているわけではありませんが、実例として多少は世の中の役には立つかな、と思っています(^^ゞ
なお、これらの情報は OpenType 様の許可をいただいて掲載しています。情報公開を快諾してくださった OpenType 様に感謝いたします。
新版codeなにがしについて
codeなにがしについてご存じない方もおられると思いますので、簡単にご紹介を。codeなにがしはソフトウェア技術者向けのノウハウ共有サービスで、主にユーザー間の FAQ コーナーである「教えて・助けて・コード書いてっ♪」、ソースコードの投稿・共有ができる「ソースコード・ノウハウッ!」、ユーザーが自由にフォーラムを作れる「大部屋・小部屋・隠し部屋」の 3 つのサービスで構成されています。リニューアル前のcodeなにがしについてこちらの記事でレビューしていますので、ご参照ください。
リニューアルの内容はニュースリリースにまとめられていますが、まず画面デザインが一新され、より多くの情報が読みやすく配置されるようになりました。例えばトップページも、以下のように 3 つのコーナーの更新情報が一望できるように変更されています。
また、特筆すべきなのがコミュニティー機能の拡充です。各登録ユーザーには以下のような非公開のホームページが用意され、自分の書き込みや Follow しているユーザーの活動などがこのページだけで確認できます。また、簡単なフィードリーダーやブックマーク、会員同士のメッセージ機能なども用意され、より SNS っぽい雰囲気になってきています。
そして今回私が制作させていただいたのがソースコード表示用のウィジェットです。 google-code-prettify による色分けや行番号の表示などが可能で、「ブログパーツ」のところに表示されているコードを自分の Web ページに埋め込むことで、任意のページにソースを貼付けることも可能になっています。
とても楽しく、使いやすくリニューアルした新codeなにがし、ぜひ使ってみてください!
一般的なコーディング Tips
それでは、ソースコード表示ウィジェットの制作ノウハウをご紹介していきます。最初にソースコード表示に関係のない一般的な Tips をご紹介して、その後にソースコード表示の方法などをご紹介していきます。
名前空間を汚染しないウィジェットの書き方
まずは汎用的なウィジェットやライブラリを書く際の基本、外部環境を汚染しない JavaScript の書き方について。
利用される環境が特定できない状態でコードを書く場合、無節操にグローバル変数等を使用してしまうと、他のライブラリと名前がバッティングして誤動作することがあります。それを避ける方法はいくつかありますが、もっとも簡単なのはコード全体を無名関数の中に入れてしまうことです。
しかし、そのままだと外部のコードからライブラリの機能にアクセスすることができません。これを解消する方法もいろいろありますが、今回は公開メソッドをひとつのオブジェクトにまとめ、それを無名関数の返り値にしてグローバル変数に保存するという方法をとりました。
if(window.CodeNanigaC) { var CodeNanigaC = (function() { // ウィジェットのコード var CodeNanigaC = { // 公開メソッドの定義 }; return CodeNanigaC; })(); }
一番外側の if 文は、 .js ファイルが複数回読み込まれた際に二回目以降のコード実行をスキップするためのものです。おまじないとして付けておくのが良いと思います。
このような書き方にしておけば、外部に公開するメソッドが明確になりますし、開発中は無名関数の部分をコメントアウトすることで内部状態に外からアクセスできますので、デバッグも楽になります。
また、 CSS などの ID / クラス名に関しては名前空間がないので、特定のプリフィクスを付けることで対処します。 JavaScript 中でそれらの名前を記述する場合は、以下のような関数を使うことでプリフィクスの変更がしやすくなり、プリフィクスが長くてもコードはシンプルに保てます。
function className(label) { return 'long_prefix_' + label; }
また、まったく別の考え方として、すべてを IFRAME の中に入れてしまうのもひとつの手です。こうすればページ本体との干渉は気にする必要がなくなります。今回はユーザーがカスタマイズする余地を残したいという意向があって採用しませんでしたが、そういった理由がなければお手軽&確実な方法です。
外部スタイルシートを BODY 内で読み込む
通常の HTML ページで外部スタイルシートを読み込むには LINK タグを使うわけですが、 LINK タグは HEAD 内にしか記述できません(少なくとも規格上は)。今回は BODY に埋め込まれるのが前提なので、この方法はそのままでは使えません。
Web 標準に違反せずに BODY 内で外部スタイルシートを読み込むには、以下の 2 つの方法があります。
- DOM API を利用して HEAD に LINK 要素を挿入する。
- インラインスタイルシート(STYLE タグ)で @import を使う。
codeなにがしウィジェットでは後者の方法を使っています。具体的には document.write を使って以下の HTML コードを挿入しています。
<style type="text/css">@import url(http://path/to.css);</style>
この方法は JavaScript なしでも使えるので、覚えておくとなにかと便利です。例えばブログや Wiki でテンプレートを変更せずに特定のページだけ外部スタイルシートを読み込みたい場合にも使えますね。
簡易テンプレートエンジン
今回のような埋め込み型ウィジェットでは必要な HTML はすべて JavaScript で生成する必要があります。 document.write や innerHTML で HTML を挿入する方法が一般的ですが、そんなときに便利なのがテンプレートエンジンです。別に ERB のようなレベルのものでなくとも、単純に文字列中のプレースホルダを別の文字列で置き換えられるだけでもじゅうぶん使えます。
Prototype フレームワークにはそのような機能があったりもしますが、このためだけに大規模なコードを読み込むのは考えものなので、以下のような簡単な関数を作ってみました。
function tinyTemplate(template, args) { return template.replace(/\$(\d+)/g, function(str, p1) { return args[parseInt(p1, 10)]; }); }
文字列中の $0, $1 といったプレースホルダを対応する引数で置き換えるものです。例えばこんな感じで使います。
var tpl = '<div id="$0" class="$1">$2</div>'; document.open(); document.write(tinyTemplate(tpl, ['id_widget', 'class_widget', 'Hello!'])); document.close();
こんな感じで主な HTML テンプレートをソースの先頭で定義しておき、必要な場所で tinyTemplaate を使って展開しています。こうすることでウィジェットの HTML のカスタマイズがとてもやりやすくなるので、ぜひ試してみてください。
ソースコードの表示
今回のウィジェットの目玉機能のひとつが、ソースコードの色分け表示と行番号表示です。色分けには以前制作した非公式ウィジェットと同様に google-code-prettify を利用しています。他のライブラリも検討したのですが、やはり言語を自動判別して適切に色分けしてくれる点で今回のウィジェットには最適と判断しました。
google-code-prettify ライブラリの基本的な使い方はこちらの記事をご参照ください。ここでは新たに工夫した点をご紹介したいと思います。また、実は現在のcodeなにがしで使用している google-code-prettify はひとつ前のバージョンなのですが、手元で最新版を適用したバージョンもテスト中なので、ここでも最新版を前提とした情報にしたいと思います。
google-code-prettify の修正点
google-code-prettify はオリジナルそのままではなく、若干の変更を加えています。まず、現在の最新版 (14-Jul-2008) では数値文字参照('&#??;' みたいなやつ)が正しく扱えないため、 htmlToText 関数の以下の部分を・・・
var num = html.substring(pos + 3, end);
このように修正しました。3 を 2 に変えただけです(^^;
var num = html.substring(pos + 2, end);
また、行番号表示の実装にあたって span タグが複数行にまたがると都合が悪かったので、行末で span タグを閉じるようにした。修正箇所は以下のとおりです。
var htmlChunk = textToHtml( tabExpander(sourceText.substring(outputIdx, sourceIdx))) .replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1 '); // Keep track of whether we need to escape space at the beginning of the // next chunk. lastWasSpace = trailingSpaceRe.test(htmlChunk); + var br = openDecoration ? '</span><br /><span class="' + openDecoration + '">' : '<br />'; - html.push(htmlChunk.replace(newlineRe, '<br />')); + html.push(htmlChunk.replace(newlineRe, br)); outputIdx = sourceIdx;
いずれもcodeなにがしウィジェット以外でも使える変更かと思いますので、 google-code-prettify を使用する際は、ぜひご活用ください。
行番号の表示
行番号表示の機能は google-code-prettify にはありませんので、独自に実装しました。基本的には、色分け処理が終わった後の HTML テキストをさらに加工することで実現しています。ここはいろいろと試行錯誤しまして、主に以下の方法を試してみました。
- 番号付きリスト
- 定義リスト
- 表 (TABLE)
- 表示テキストに直接行番号を埋め込む
(1) の番号付きリストは一番手軽な方法で、ソースコード全体を UL タグで囲み、各行を LI タグで囲むだけです。置き換えは正規表現で簡単に実現できます。この方法の最大の欠点は、行番号部分のスタイル指定が自由にできない点です。
(2) は定義リストの DT タグとして、 (3) はテーブルのカラムとして、行番号を表示する方法です。これなら行番号部分のスタイル指定が可能ですが、とくに IE での表示の崩れが解消できず、また CSS で行番号を非表示にしてもレイアウトが更新されないなどの不具合もみられたため、採用は見送りました。
(4) は一番原始的な方法で、以下の正規表現置換で各行頭に行番号を埋め込みます。
var line_count = 0; source = source.replace(/^(.*)$/mg, function(str) { line_count += 1; var lineno = line_count.toString(); var padding = ' '.substring(lineno.length*6); return '<span class="lineno">' + padding + lineno + ': </span>' + str; }
いろいろ試したわりには、けっきょくこの方法がもっとも崩れが少なく、表示も高速でした。 Simple Is Best ということですかね(笑)。さらに CSS クラス lineno の display 属性を制御することで、行番号表示の ON/OFF も簡単に実現できました。同様に、色分けの ON/OFF も CSS の変更だけで対応しています。
開発環境
コーディング上のノウハウはこれくらいにして、今度は開発環境周りの工夫をご紹介します。まあ、大掛かりなものではありませんが、 pure javascript な開発でもこういった工夫をしておけば効率よくできるよ、ということで参考にしていただければと思います。
Rails で開発用サーバーを構築
今回は実験的に、 Ruby on Rails で開発用サーバーを構築してみました。 Rails ならデスクトップマシンにも簡単にデプロイできますし、テンプレートエンジンなど必要なものはすべて揃っているので、開発用サーバーを手っ取り早く構築するのには最適です。
開発用サーバーと言ってもたいそうな機能があるわけではなく、テストページの表示と ERB テンプレートで JavaScript / CSS のソースのパラメータ置換などを行っているだけです。例えば JavaScript 用のコントローラは以下のようになっています(実際にはもう少し長いのですが、冗長になるのではしょっています)。
require 'widget_conf.rb' class JsController < ApplicationController def widget() @headers['Content-Type'] = 'text/javascript; charset=utf-8' @conf = WidgetConf.new(:debug) end end
WidgetConf は置換用のパラメータをまとめたクラスで、 lib/widget_conf.rb で以下のように定義しています。
class WidgetConf def initialize(mode = :debug) @release = mode == :release end def self.param(name, debug, release = nil) define_method(name) { @release ? (release || debug) : debug } end param :param_name, 'デバッグ時の値', 'リリース時の値' # ...その他のパラメータ... end
ここでデバッグ・リリースで別の値を指定していますが、これは上述のコントローラで生成するときと後述のリリース用に圧縮したファイルを生成するときで別の値に置換できるようにするためです。
あとは説明するまでもないと思いますが、 view で普通に JavaScript を書いて、 ERB の記法でパラメータ置換などを埋め込んでいるだけです。 CSS もほぼ同様。
また、テストページの表示では以下のように URL のパラメータでデバッグとリリースを切り替えられるようにしています。
require 'widget_conf.rb' class TestPageController < ApplicationController def test_page @mode = (params[:mode]||:debug).to_sym @conf = WidgetConf.new(@mode) end end
JavaScript ファイルや CSS ファイルのパスも WidgetConf の中で定義されていて、コントローラで生成したバージョンとリリース用に圧縮したバージョンの両方を簡単にテストできるようになっています。
とまあ、このようにとても簡易なものですが、最初にこれを作ったおかげでずいぶん楽に開発できました。将来的には、このあたりを汎用的なパッケージとしてまとめたいな、とも思っています。
ファイルサイズ削減
今回のウィジェットはcodeなにがしの各ソースコードノウハウのページで使用され、さらにユーザーのページにエンベッドされる可能性もあるということで、JavaScript / CSS ファイルをできるだけ圧縮しています。使ったツールは YUI Conpressor と JsJuicer (当サイトの紹介記事)です(CSS は YUI Compressor のみ使用)。これらを rake タスクに組み込んで、簡単に圧縮ができるようにしてあります。
JavaScript で 2 つの圧縮ツールを併用している理由は、 YUI Compressor はオブジェクトのメソッド名などが圧縮できないからです。そこで、自分で定義したオブジェクトのメソッド名は $ ではじまるようにしておき、 JsJuicer が圧縮できるようにしています。
さらに圧縮率を高めるために、 window, document などのよく使うグローバル変数、文字列リテラルなどはファイル先頭(正確には上述の無名関数の先頭)でローカル変数に代入しています。よく使う処理を関数にまとめることも効果的ですね。
今回は使いませんでしたが、さらに姑息な技として、 window.addEventListener などの長いメソッド名も以下のようにローカル変数に代入すると、圧縮が効くようになります。
(function() { var _window = window; var addEventListener = 'addEventListener'; // ... _window[addEventListener]('click', callback, false); // ... })();
以前公開した DragResize.js では実験的にこの手法を大活用していますので、興味のある方はご参照ください。もっとも、こんなことをするくらいなら素直に関数を定義したほうが良いかもしれませんが・・・。
codeなにがしウィジェットのカスタマイズ
最後に、codeなにがしウィジェットをより活用していただくための情報をご紹介します。今回は当初からユーザーがカスタマイズしやすいようにというコンセプトがありまして、各種オプションや表示スタイルの変更などが行えます。
CodeNanigaC オブジェクトの使い方
codeなにがしウィジェットの埋め込みは各ソースコードのページにあるブログパーツのコードを使うのが手軽ですが、手動で行うとより細かい制御が可能です。
そのためには、まずウィジェットの JavaScript を読み込んでおきます。
<script type="text/javascript" src="http://code.nanigac.com/javascripts/widget/widget.js" charset="UTF-8"></script>
そして、ソースコードを埋め込みたい場所で CodeNanigaC.write を呼び出します。複数のソースコードを埋め込みたい場合は、それぞれの場所で write メソッドを呼んでください。返り値としてウィジェットの ID が取得できますので、これを保存しておけば後でソースコードを読み替えたりオプションを変更したりできます。
<script>var widgetId = CodeNanigaC.write({ id:1 });</script>
write メソッドには id 以外にも以下のオプションを与えることができます。
属性名 | 機能 |
---|---|
id | 表示するソースコードのID |
parentNode | ウィジェットの親になる DOM 要素 |
width | ウィジェットの横ピクセル数 |
height | ウィジェットの縦ピクセル数 |
bg_title | タイトルの背景色 |
bg_source | ソースの背景色 |
mode_color | ソースの色分け ON/OFF (true or false) |
mode_lineno | 行番号 ON/OFF (true or false) |
font | フォントサイズ ("small", "middle" or "large") |
parentNode オプションを使えば、 onload イベント等で既存の要素に挿入することもできます。
<body onload="CodeNanigaC.write({ id:1, parentNode:document.getElementById('widget') });">
write メソッドの返り値が保存してあれば、動的に別のソースコードに読み替えることも可能です。
CodeNanigaC.load(widgetId, ソースコードID);
setOptions メソッドを使えばオプションを変更できます(id, parentNode を除く)。
CodeNanigaC.setOptions(widgetId, { font:"small" });
一般的に使える機能はだいたいこんなところでしょうか。ぜひご活用ください。
スタイルのカスタマイズ
ウィジェットのスタイルはほとんど CSS で定義されていますので、それを変更することでかなり柔軟にカスタマイズできます。多数のクラスが定義されているのですが、そのなかでよく使いそうなものを以下に挙げておきます。
クラス名 | 定義内容 |
---|---|
div.source_font_small | フォントサイズ(小) |
div.source_font_middle | フォントサイズ(中) |
div.source_font_large | フォントサイズ(大) |
.nanigac_loading | ロード中のアニメーション GIF |
pre.nanigac_source | ソースコードのフォント、行間など |
pre.nanigac_source .lineno | 行番号 |
.nanigac_colorize .str | 文字列の色 |
.nanigac_colorize .kwd | キーワードの色 |
.nanigac_colorize .typ | 型名の色 |
.nanigac_colorize .lit | リテラルの色 |
.nanigac_colorize .pun | 区切り記号の色 |
.nanigac_colorize .pln | その他の部分の色 |
.nanigac_colorize .tag | タグ名の色 |
.nanigac_colorize .atn | 属性名の色 |
.nanigac_colorize .atv | 属性の値の色 |
.nanigac_colorize .dec | DOCTYPE 宣言などの色 |
.nanigac_colorize .src | PHP や ERB の埋め込みソースの色 |
スタイルをカスタマイズする際は、ウィジェットを読み込む SCRIPT タグよりも後でスタイルを定義するか、もしくは以下のように !import 指定することで標準のスタイルを上書きできます。
.nanigac_colorize .str { color: #00f !important }
とくに色分け表示は google-code-prettify のデフォルトそのままになっていますので、好みに合わせてカスタマイズしてみてください。
以上、本日は開発に利用した Tips などを中心にcodeなにがしウィジェットをご紹介しました。同様なウィジェットを制作するときの参考にしていただければと思います。本当はもっと細かい Tips とかも書きたかったのですが、時間もないのでこのへんで。
最後になりましたが、新codeなにがしはとても優れたサービスですので、まだ使っていない方はぜひお試しください!
詳しくはこちらの記事をどうぞ!
この記事にコメントする