[インデックス 16707] ファイルの概要
このコミットは、Go言語の公式ウェブサイトに組み込まれているGo Playgroundのフロントエンドロジックを司るJavaScriptファイル doc/play/playground.js
と、その出力表示に関連するスタイルシート doc/style.css
の更新を含んでいます。主な変更は playground.js
の大規模なリファクタリングであり、Go Playgroundの機能性、特にコードの実行、フォーマット、共有、そして出力の表示方法に影響を与えます。
コミット
このコミットは、Go PlaygroundのJavaScriptコードを大幅に更新し、よりモジュール化された設計と改善されたエラーハンドリングを導入しています。特に、コードの実行と出力表示のメカニズムが抽象化され、HTTPベースとWebSocketベースの2種類の通信トランスポートが導入されました。これにより、Go Playgroundの基盤がより堅牢で拡張性の高いものになっています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1856286fc23ec4332b7ccc46d9838fbb96fd4ce0
元コミット内容
doc: update playground.js
R=dsymonds
CC=golang-dev
https://golang.org/cl/10933044
このコミットメッセージは簡潔に「playground.jsを更新する」と述べており、具体的な変更内容については触れていません。しかし、コードの変更差分を見ると、単なる軽微な更新ではなく、大規模なリファクタリングと機能強化が行われていることがわかります。R=dsymonds
はレビュー担当者を示し、CC=golang-dev
はGo開発者メーリングリストに通知されたことを意味します。https://golang.org/cl/10933044
は、Goプロジェクトがコードレビューに利用しているGerritシステムにおける変更リスト(Change-ID)へのリンクです。
変更の背景
このコミットの最も重要な背景は、doc/play/playground.js
の冒頭に追加されたコメントに示されています。
// This is a copy of present/js/playground.js from the repository at
// https://code.google.com/p/go.talks
// Please make changes to that repository.
このコメントは、golang/go
リポジトリ内の playground.js
が、go.talks
という別のリポジトリ(Google CodeでホストされていたGo関連のプレゼンテーションやツールのリポジトリ)の present/js/playground.js
のコピーであることを明確にしています。
この変更の背景には、以下の目的があったと考えられます。
- コードの再利用と一元化:
go.talks
リポジトリは、Go言語に関するプレゼンテーションやデモンストレーションでGo Playgroundの機能を利用していました。これらの異なる場所で同じようなPlayground機能が必要とされるため、共通のコードベースを持つことで、開発とメンテナンスの効率化を図ったと考えられます。 - 機能の改善と標準化:
go.talks
リポジトリで開発されたPlaygroundの機能(例えば、WebSocketサポートやより洗練された出力処理)を、公式のGoウェブサイトにも取り込むことで、ユーザー体験の向上と機能の標準化を目指した可能性があります。 - アーキテクチャの改善: 以前の
playground.js
は、通信ロジックとUIロジックが密結合していました。このコミットでは、通信部分を「トランスポート」として抽象化し、出力表示をPlaygroundOutput
関数に分離することで、よりモジュール化された、保守しやすいアーキテクチャへの移行を図っています。これにより、将来的な機能追加や異なる通信プロトコルへの対応が容易になります。 - エラー表示の強化: コンパイルエラーや実行時エラーの表示を改善し、ユーザーが問題箇所を特定しやすくすることも目的の一つです。
前提知識の解説
このコミットの変更内容を理解するためには、以下の技術的知識が役立ちます。
- Go Playground: Go言語のコードをブラウザ上で記述、実行、共有できるオンラインツールです。Go言語の公式ウェブサイトに組み込まれており、Go言語の学習やデモンストレーションに広く利用されています。バックエンドでGoコードをコンパイル・実行し、その結果をフロントエンドに返します。
- JavaScript: ウェブページの動的な挙動を実装するためのプログラミング言語です。Go PlaygroundのフロントエンドはJavaScriptで記述されています。
- jQuery: JavaScriptライブラリの一つで、DOM操作、イベントハンドリング、アニメーション、Ajax通信などを簡潔に記述できます。この
playground.js
はjQueryに依存しています。 - Ajax (Asynchronous JavaScript and XML): ウェブページ全体をリロードすることなく、非同期でサーバーとデータをやり取りする技術です。Go Playgroundでは、コードのコンパイルやフォーマットのためにHTTPベースのAjax通信が利用されます。
- WebSocket: クライアントとサーバー間で永続的な双方向通信チャネルを確立するためのプロトコルです。HTTPとは異なり、一度接続が確立されると、サーバーとクライアントは互いに自由にデータを送受信できます。リアルタイムな出力表示やインタラクティブな機能に適しています。このコミットでは、WebSocketを介した通信をサポートする
SocketTransport
が導入されています。 - HTML5 History API: ブラウザの履歴をJavaScriptで操作するためのAPIです。これにより、URLを変更してもページ全体をリロードせずにコンテンツを更新したり、ブラウザの「戻る」「進む」ボタンの挙動を制御したりできます。Go Playgroundでは、共有されたコードのURLを履歴にプッシュするために利用されています。
- コードのリファクタリング: ソフトウェアの外部的な振る舞いを変更せずに、内部構造を改善するプロセスです。可読性、保守性、拡張性を向上させることを目的とします。このコミットは、まさに
playground.js
の大規模なリファクタリングに当たります。 - モジュール化と抽象化: ソフトウェア設計の原則で、プログラムを独立した小さな部品(モジュール)に分割し、それぞれの部品が特定の機能に責任を持つようにすることです。また、詳細な実装を隠蔽し、インターフェースを通じてのみアクセスできるようにすることを抽象化と呼びます。このコミットでは、通信ロジックをトランスポートとして抽象化し、出力表示ロジックを
PlaygroundOutput
にモジュール化しています。
技術的詳細
このコミットの技術的詳細は、主に doc/play/playground.js
の大幅な変更に集約されます。
-
トランスポート層の導入:
- 以前の
playground
関数は、直接Ajaxリクエストを送信し、出力の再生ロジックを持っていました。 - 新しいバージョンでは、
HTTPTransport
とSocketTransport
という2つの「トランスポート」が導入されました。これらは、Go Playgroundのバックエンドとの通信方法を抽象化する役割を担います。 playground
関数は、初期化時にopts.transport
オプションを通じて、使用するトランスポートインスタンスを受け取るようになりました。デフォルトはHTTPTransport
です。HTTPTransport
:- 従来のAjaxベースの通信をカプセル化します。
/compile
エンドポイントにPOSTリクエストを送信し、Goコードのコンパイルと実行を要求します。- サーバーからの応答(
data.Events
)を受け取り、playback
関数を使って出力イベントを順次再生します。 - エラーが発生した場合は、
error
関数を通じてstderr
出力として表示します。 playback
関数は、各出力イベント(e.Message
)をoutput
コールバックに渡し、e.Delay
に応じて遅延を設けています。
SocketTransport
:- WebSocket (
ws://
+window.location.host
+/socket
) を介したリアルタイム通信をサポートします。 onmessage
イベントハンドラでサーバーからのメッセージ(JSON形式)を受け取り、output
コールバックに渡します。send
メソッドを通じて、run
(コード実行) やkill
(実行停止) などのコマンドをサーバーに送信します。- これにより、よりインタラクティブで低遅延なGo Playgroundの体験が可能になります。
- WebSocket (
- 以前の
-
出力表示の標準化 (
PlaygroundOutput
):PlaygroundOutput
関数は、Go Playgroundの出力エリア (el
) にメッセージを表示するための統一されたインターフェースを提供します。- この関数は、
write
オブジェクト(Kind
とBody
を持つ)を受け取ります。 Kind
には'start'
,'stdout'
,'stderr'
,'end'
があり、それぞれ出力の開始、標準出力、標準エラー出力、終了を示します。'start'
の場合は出力エリアをクリアします。'stdout'
や'stderr'
の場合は、メッセージをHTMLエスケープし、適切なCSSクラス(stdout
またはstderr
)を付けて表示します。'end'
の場合は、「Program exited」というメッセージと、オプションで終了ステータスを表示します。- 特殊なケースとして、
IMAGE:
で始まるメッセージはBase64エンコードされた画像データとして解釈され、<img>
タグとして出力エリアに挿入されます。 \x0c
(フォームフィード) 文字は、画面クリアの指示として解釈され、出力エリアの内容をリセットします。- 出力エリアの自動スクロールも管理されます。
-
エラー行のハイライト機能:
lineHighlight
関数が導入され、コンパイルエラーメッセージ(例:prog.go:10: ...
)から行番号を抽出し、対応するコードエディタの行にlineerror
クラスを追加してハイライト表示するようになりました。highlightOutput
ラッパー関数は、PlaygroundOutput
を呼び出す前にlineHighlight
を実行し、エラーメッセージに基づいて行をハイライトします。lineClear
関数は、以前のハイライトをクリアします。
-
playground
関数のリファクタリング:playground
関数は、UI要素(コードエディタ、出力エリア、実行ボタンなど)の初期化とイベントハンドラの設定に専念するようになりました。- コードの実行 (
run
) やフォーマット (fmt
) のロジックは、それぞれtransport.Run
やAjaxリクエストに委譲されます。 loading()
関数は、実行開始時に出力エリアに「Waiting for remote server...」と表示し、以前のエラーハイライトをクリアします。setError()
関数は、エラーメッセージを表示し、必要に応じて実行中のプロセスを停止させます。
-
CSSの変更 (
doc/style.css
):div.play .output .exit
セレクタが削除され、代わりに.output .stderr
と.output .system
という新しいセレクタが追加されました。.output .stderr
はエラーメッセージを赤みがかった色 (#933
) で表示し、.output .system
はシステムメッセージ(例: 終了メッセージ)を灰色 (#999
) で表示します。これにより、出力の種類に応じた視覚的な区別が可能になりました。
これらの変更により、Go Playgroundのフロントエンドは、よりクリーンなコードベース、改善されたエラー報告、そして将来の機能拡張のための柔軟なアーキテクチャを獲得しました。
コアとなるコードの変更箇所
doc/play/playground.js
このファイルは、全体的に大幅な変更が加えられています。
- ファイルの冒頭:
go.talks
リポジトリからのコピーである旨のコメントが追加されました。 playground
関数のシグネチャと初期化:opts
オブジェクトの構造が変更され、transport
オプションが追加されました。var transport = opts['transport'] || new HTTPTransport();
の行で、使用するトランスポートが決定されます。
HTTPTransport
関数の新規追加:- Ajax (
/compile
) を使用したコード実行ロジックと、playback
関数がこの中にカプセル化されました。
- Ajax (
SocketTransport
関数の新規追加:- WebSocket (
/socket
) を使用したリアルタイム通信ロジックが追加されました。
- WebSocket (
PlaygroundOutput
関数の新規追加:- 出力表示のロジックがこの関数に集約されました。
Kind
(start, stdout, stderr, end) に応じた処理、画像出力、画面クリア (\x0c
)、HTMLエスケープ、自動スクロールなどが実装されています。
- 出力表示のロジックがこの関数に集約されました。
lineHighlight
、highlightOutput
、lineClear
関数の新規追加:- エラー行のハイライト表示とクリアのロジックが追加されました。
run
関数とfmt
関数の変更:- これらの関数は、直接Ajaxリクエストを送信する代わりに、
transport.Run
や$.ajax
を呼び出すようになりました。 - 出力処理は
highlightOutput(PlaygroundOutput(output[0]))
に委譲されます。
- これらの関数は、直接Ajaxリクエストを送信する代わりに、
setError
およびloading
関数の新規追加:- エラー表示とロード中の状態表示を統一的に扱うための関数です。
- 旧バージョンのコードの削除:
- 以前の
playback
関数、setOutput
関数、および直接的なAjax呼び出しの多くが削除されました。
- 以前の
doc/style.css
.output .stderr
の追加:.output .stderr { color: #933; }
.output .system
の追加:.output .system { color: #999; }
.output .exit
の削除:-div.play .output .exit { +.output .stderr { color: #933; } +.output .system { color: #999; }
コアとなるコードの解説
playground.js
におけるトランスポートの抽象化
新しい playground
関数は、opts.transport
を介して通信メカニズムを注入できるようになりました。
function playground(opts) {
var code = $(opts.codeEl);
var transport = opts['transport'] || new HTTPTransport(); // ここでトランスポートが選択される
var running;
// ... (省略) ...
function run() {
loading();
// 実行ロジックはトランスポートに委譲される
running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0])));
}
// ... (省略) ...
}
HTTPTransport
の実装(抜粋)
HTTPTransportは、Ajaxリクエストを介してコードを実行します。
function HTTPTransport() {
'use strict';
function playback(output, events) {
var timeout;
output({Kind: 'start'}); // 出力開始を通知
function next() {
if (events.length === 0) {
output({Kind: 'end'}); // 出力終了を通知
return;
}
var e = events.shift();
if (e.Delay === 0) {
output({Kind: 'stdout', Body: e.Message}); // 標準出力
next();
return;
}
timeout = setTimeout(function() {
output({Kind: 'stdout', Body: e.Message});
next();
}, e.Delay / 1000000); // 遅延を考慮して出力
}
next();
return {
Stop: function() {
clearTimeout(timeout);
}
}
}
function error(output, msg) {
output({Kind: 'start'});
output({Kind: 'stderr', Body: msg}); // 標準エラー出力
output({Kind: 'end'});
}
var seq = 0;
return {
Run: function(body, output, options) {
seq++;
var cur = seq;
var playing;
$.ajax('/compile', { // /compile エンドポイントに Ajax リクエスト
type: 'POST',
data: {'version': 2, 'body': body},
dataType: 'json',
success: function(data) {
if (seq != cur) return;
if (!data) return;
if (playing != null) playing.Stop();
if (data.Errors) {
error(output, data.Errors); // エラー処理
return;
}
playing = playback(output, data.Events); // 成功時は playback で出力
},
error: function() {
error(output, 'Error communicating with remote server.');
}
});
return {
Kill: function() {
if (playing != null) playing.Stop();
output({Kind: 'end', Body: 'killed'});
}
};
}
};
}
PlaygroundOutput
の実装(抜粋)
PlaygroundOutput
は、出力の種類に応じてHTML要素を生成し、出力エリアに挿入します。
function PlaygroundOutput(el) {
'use strict';
return function(write) {
if (write.Kind == 'start') {
el.innerHTML = ''; // 出力開始時はクリア
return;
}
var cl = 'system';
if (write.Kind == 'stdout' || write.Kind == 'stderr')
cl = write.Kind; // stdout または stderr クラスを設定
var m = write.Body;
if (write.Kind == 'end')
m = '\nProgram exited' + (m?(': '+m):'.'); // 終了メッセージ
if (m.indexOf('IMAGE:') === 0) {
var url = 'data:image/png;base64,' + m.substr(6);
var img = document.createElement('img');
img.src = url;
el.appendChild(img); // 画像表示
return;
}
// ^L clears the screen.
var s = m.split('\x0c');
if (s.length > 1) {
el.innerHTML = ''; // 画面クリア
m = s.pop();
}
m = m.replace(/&/g, '&'); // HTMLエスケープ
m = m.replace(/</g, '<');
m = m.replace(/>/g, '>');
var needScroll = (el.scrollTop + el.offsetHeight) == el.scrollHeight;
var span = document.createElement('span');
span.className = cl; // クラスを適用
span.innerHTML = m;
el.appendChild(span); // 出力要素を追加
if (needScroll)
el.scrollTop = el.scrollHeight - el.offsetHeight; // 自動スクロール
}
}
style.css
の変更
出力の種類に応じたスタイルが定義されました。
.output .stderr {
color: #933; /* 赤みがかった色 */
}
.output .system {
color: #999; /* 灰色 */
}
これらの変更により、Go Playgroundのフロントエンドは、より堅牢で、拡張性が高く、ユーザーフレンドリーな出力表示機能を持つようになりました。
関連リンク
- Go Playground: https://play.golang.org/
- Go言語公式ウェブサイト: https://golang.org/
- golang/go GitHubリポジトリ: https://github.com/golang/go
参考にした情報源リンク
go.talks
リポジトリ (Google Code): https://code.google.com/p/go.talks (現在はアーカイブされており、GitHubに移行している可能性がありますが、コミット当時の参照元です。)- Go Playgroundのソースコード (
doc/play/playground.js
): https://github.com/golang/go/blob/master/doc/play/playground.js (現在の最新版) - Go Playgroundのバックエンドに関する情報 (例:
gopls
やgo/play
パッケージなど、現在の実装): 必要に応じてGoのドキュメントや関連リポジトリを参照。 - Gerrit Change-ID: https://golang.org/cl/10933044
- WebSocket - MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/API/WebSockets_API
- XMLHttpRequest (Ajax) - MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest
- History API - MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/API/History_API