[インデックス 11916] ファイルの概要
このコミットは、Go言語の公式ドキュメントサイトにインタラクティブなGo Playground機能を追加するものです。具体的には、ユーザーがブラウザ上でGoコードを記述し、実行し、その結果を確認できる機能を提供するためのJavaScriptファイル playground.js
の新規追加と、関連するHTMLおよびCSSファイルの修正が含まれています。これにより、Go言語の学習者がコードを試すための手軽な環境が提供されます。
コミット
commit 3509687d6a98ca492d4e64a0cd093c5644266740
Author: Andrew Gerrand <adg@golang.org>
Date: Wed Feb 15 12:59:50 2012 +1100
doc: add playground.js
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5649087
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3509687d6a98ca492d4e64a0cd093c5644266740
元コミット内容
このコミットの元の内容は、doc: add playground.js
という簡潔なメッセージが示す通り、Go言語の公式ドキュメントサイトに playground.js
というJavaScriptファイルを追加することです。このファイルは、Go Playgroundのクライアントサイドのロジックを実装しており、ユーザーがブラウザ上でGoコードを実行できるインタラクティブな環境を提供します。
変更の背景
Go言語は、そのシンプルさと効率性から急速に人気を集めていました。しかし、新しい言語を学ぶ上で、実際にコードを書いて実行し、その挙動を即座に確認できる環境は非常に重要です。従来の学習方法では、ローカルにGo環境をセットアップする必要があり、これは初心者にとって障壁となる可能性がありました。
このコミットの背景には、Go言語の学習体験を向上させ、より多くの人々がGoに触れる機会を増やすという目的があります。ブラウザ上で直接コードを実行できるGo Playgroundは、環境構築の手間を省き、Goコードの試行錯誤を容易にします。これにより、Go言語の普及とコミュニティの拡大が促進されることが期待されました。
また、既存の doc/play/playground.js
はダミーファイルであり、実質的な機能を持っていませんでした。このコミットは、そのダミーファイルを実際のGo Playgroundのクライアントサイド実装に置き換えることで、Go Playgroundの機能をGoの公式ドキュメントサイトに統合する第一歩となりました。
前提知識の解説
このコミットの変更内容を理解するためには、以下の技術的な前提知識があると役立ちます。
- Go言語: Go PlaygroundはGo言語のコードを実行するための環境であるため、Go言語の基本的な構文や実行モデルを理解していると、コードの意図がより深く理解できます。
- Web技術の基礎:
- HTML (HyperText Markup Language): ウェブページの構造を定義するためのマークアップ言語です。このコミットでは、
doc/root.html
が変更され、Go PlaygroundのUI要素(コード入力エリア、出力エリア、実行ボタンなど)が追加されています。 - CSS (Cascading Style Sheets): ウェブページの見た目(スタイル)を定義するためのスタイルシート言語です。
doc/style.css
の変更は、Go PlaygroundのUI要素の表示を調整するためのものです。 - JavaScript: ウェブページに動的な機能を追加するためのプログラミング言語です。
playground.js
はJavaScriptで書かれており、ユーザーの操作(コード入力、ボタンクリック)に応じてGoコードをサーバーに送信し、結果を受け取って表示するロジックを実装しています。
- HTML (HyperText Markup Language): ウェブページの構造を定義するためのマークアップ言語です。このコミットでは、
- AJAX (Asynchronous JavaScript and XML): ウェブページ全体をリロードすることなく、サーバーと非同期でデータをやり取りする技術です。Go Playgroundでは、ユーザーが「Run」ボタンをクリックした際に、入力されたGoコードをAJAXリクエストとしてサーバーに送信し、実行結果を非同期で受け取って表示するために利用されています。これにより、スムーズなユーザー体験が実現されます。
- jQuery: JavaScriptライブラリの一つで、HTMLドキュメントの操作、イベント処理、アニメーション、AJAXなどを簡素化します。このコミットのJavaScriptコードでは、jQueryが広く利用されており、DOM要素の選択やイベントハンドリング、AJAXリクエストの送信などがjQueryのAPIを通じて行われています。
- CodeMirror: ブラウザ上で動作する多機能なテキストエディタです。特にコードエディタとしての機能が豊富で、シンタックスハイライト、行番号表示、インデント補助などの機能を提供します。
playground.js
は、simple
オプションがfalse
の場合にCodeMirrorを統合し、よりリッチなコード編集体験を提供します。 - Google Feeds API: Googleが提供していたRSS/AtomフィードをJavaScriptで簡単に読み込むためのAPIです。このコミットでは、Goブログのフィードを読み込んで表示するために利用されています。
- Go Playgroundのアーキテクチャ (概念): 一般的なウェブベースのコード実行環境は、クライアントサイド(ブラウザ)でコードを入力し、それをサーバーサイドに送信してコンパイル・実行し、その結果をクライアントサイドに返すというアーキテクチャを取ります。このコミットはクライアントサイドの実装に焦点を当てていますが、サーバーサイドの
/compile
および/share
エンドポイントとの連携を前提としています。
技術的詳細
このコミットは、Go Playgroundのクライアントサイド機能を実装する playground.js
の導入を中心に展開されています。
playground.js
の機能と構造
playground.js
は playground(opts)
というグローバル関数を定義しており、この関数がGo Playgroundの主要なロジックをカプセル化しています。opts
オブジェクトを通じて、UI要素の指定やコールバック関数の設定が行われます。
-
オプション (
opts
):codeEl
: コード入力エリアの要素(jQueryセレクタまたはDOM要素)。outputEl
: プログラム出力エリアの要素。runEl
: 実行ボタンの要素。shareEl
: 共有ボタンの要素(オプション)。shareURLEl
: 共有URL表示用のテキスト入力要素(オプション)。preCompile
: コンパイルリクエスト送信前にデータを変更するためのコールバック関数。postCompile
: コンパイルレスポンス受信後にデータを処理するためのコールバック関数。simple
:true
の場合、CodeMirrorではなく通常のtextarea
を使用します。
-
コードエディタの選択:
simple: true
の場合、通常のtextarea
要素がコードエディタとして使用されます。このモードでは、insertTabs
やautoindent
といった基本的なインデント補助機能がJavaScriptで実装されています。simple: false
の場合、CodeMirrorライブラリが動的に初期化され、より高度なコード編集機能(行番号、シンタックスハイライトなど)が提供されます。onKeyEvent
コールバックを通じて、CodeMirror上でのキーイベントもkeyHandler
で処理されます。
-
キーイベントハンドリング (
keyHandler
):Tab
キー (keyCode == 9
):simple
モードの場合、タブ文字を挿入し、デフォルトのタブ挙動(フォーカス移動)を防止します。Enter
キー (keyCode == 13
):Shift + Enter
: コードの実行をトリガーします。Enter
のみ (simple
モード): 自動インデント機能 (autoindent
) を呼び出し、前の行のインデントレベルを継承します。
-
エラー表示機能:
clearErrors()
: CodeMirrorエディタ上の既存のエラーハイライトをクリアします。highlightErrors(text)
: コンパイルエラーメッセージ(例:main.go:10: some error
)を解析し、エラーが発生した行にCSSクラス (errLine
) を適用してハイライト表示します。
-
コード実行機能 (
run
):clearErrors()
を呼び出して、以前のエラー表示をリセットします。- 出力エリアに「Waiting for remote server...」というローディングメッセージを表示します。
- 現在のコード内容を
body()
関数(CodeMirrorまたはtextareaから取得)で取得し、JSONデータとして準備します。 preCompile
コールバックが設定されていれば、リクエストデータを変更します。/compile
エンドポイントに対してAJAX POSTリクエストを送信します。- 成功時の処理:
- サーバーからのレスポンスデータ (
data
) を処理します。 data.compile_errors
が空でない場合、コンパイルエラーを出力エリアに表示し、highlightErrors
で該当行をハイライトします。data.output
が「IMAGE:」で始まる場合、Base64エンコードされた画像データとして解釈し、<img>
タグを生成して出力エリアに表示します。これは、Go Playgroundが画像出力もサポートしていることを示唆しています。- それ以外の場合、
data.output
をそのままテキストとして出力エリアに表示します。 postCompile
コールバックが設定されていれば、レスポンスデータを処理します。
- サーバーからのレスポンスデータ (
- 失敗時の処理:
- 出力エリアに「Error communicating with remote server.」というエラーメッセージを表示します。
-
コード共有機能:
shareEl
とshareURLEl
オプションが指定されている場合に有効になります。- 共有ボタンがクリックされると、
/share
エンドポイントに対してAJAX POSTリクエストを送信します。リクエストボディには現在のコード内容が含まれます。 - 成功時の処理:
- サーバーからのレスポンス(共有されたコードのIDなど)を受け取ります。
- 現在のウィンドウのオリジンとレスポンスを組み合わせて、共有可能なURL(例:
https://golang.org/p/xxxxxxxx
)を生成します。 - 生成されたURLを
shareURLEl
で指定されたテキスト入力要素に表示し、ユーザーがコピーしやすいようにフォーカスして選択状態にします。
root.html
の変更点
- UI要素のID追加:
- コード入力用の
textarea
にid="code"
を追加。 - 出力表示用の
div
にid="output"
を追加。 - 「Run」ボタンの
a
タグにid="run"
を追加。 - 「Share」ボタンの
a
タグにid="share"
を追加。 これらのIDは、playground.js
がDOM要素を特定し、操作するために使用されます。
- コード入力用の
- スクリプトの読み込み順序の変更と追加:
google.load("jquery", "1.7.1");
を追加し、jQueryライブラリを読み込みます。これはplayground.js
がjQueryに依存しているためです。playground.js
を読み込む<script type="text/javascript" src="/doc/play/playground.js"></script>
タグを追加します。
init
関数の導入:- 以前は
fetchFeeds()
が直接google.setOnLoadCallback
で呼び出されていましたが、このコミットではinit()
関数が導入され、その中でフィードの読み込みとGo Playgroundの初期化が行われるようになりました。 playground()
関数は、新しく追加されたIDを持つ要素を引数として初期化されます。"simple": true
が渡されていることから、初期状態ではCodeMirrorではなくシンプルなtextareaが使用される設定であることがわかります。
- 以前は
style.css
の変更点
#learn pre, #learn textarea
セレクタに対してpadding: 0;
を追加。これは、Go Playgroundのコード入力エリアと出力エリアのパディングをリセットし、よりタイトなレイアウトを実現するための微調整です。
サーバーサイドとの連携
このコミットはクライアントサイドの変更ですが、Go Playgroundが機能するためには、以下のサーバーサイドのエンドポイントが必要です。
/compile
: クライアントから送信されたGoコードを受け取り、コンパイル・実行し、その結果(標準出力、標準エラー出力、コンパイルエラーなど)をJSON形式でクライアントに返すエンドポイント。/share
: クライアントから送信されたGoコードを受け取り、それを永続化して一意のIDを割り当て、そのIDをクライアントに返すエンドポイント。クライアントはこのIDを使って共有可能なURLを生成します。
これらのエンドポイントは、このコミットの範囲外で既に存在するか、あるいはこのコミットと並行して実装されることが前提となっています。
コアとなるコードの変更箇所
doc/play/playground.js
このファイルは、以前はダミーファイルでしたが、このコミットでGo Playgroundのクライアントサイドロジックが完全に実装されました。
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// opts is an object with these keys
// codeEl - code editor element
// outputEl - program output element
// runEl - run button element
// shareEl - share button element (optional)
// shareURLEl - share URL text input element (optional)
// preCompile - callback to mutate request data before compiling
// postCompile - callback to read response data after compiling
// simple - use plain textarea instead of CodeMirror.
function playground(opts) {
var simple = opts['simple'];
var code = $(opts['codeEl']);
var editor;
// autoindent helpers for simple mode.
function insertTabs(n) { /* ... */ }
function autoindent(el) { /* ... */ }
function keyHandler(e) { /* ... */ }
if (simple) {
code.unbind('keydown').bind('keydown', keyHandler);
} else {
editor = CodeMirror.fromTextArea(
code[0],
{
lineNumbers: true,
indentUnit: 8,
indentWithTabs: true,
onKeyEvent: function(editor, e) { keyHandler(e); }
}
);
}
var output = $(opts['outputEl']);
function clearErrors() { /* ... */ }
function highlightErrors(text) { /* ... */ }
function body() {
if (editor) {
return editor.getValue();
}
return $(opts['codeEl']).val();
}
var seq = 0;
function run() {
clearErrors();
output.removeClass("error").html(
'<div class="loading">Waiting for remote server..."></div>'
);
seq++;
var cur = seq;
var data = {"body": body()};
if (opts['preCompile']) {
opts['preCompile'](data);
}
$.ajax("/compile", {
data: data,
type: "POST",
dataType: "json",
success: function(data) {
if (seq != cur) {
return;
}
pre = $("<pre/>");
output.empty().append(pre);
if (opts['postCompile']) {
opts['postCompile'](data);
}
if (!data) {
return;
}
if (data.compile_errors != "") {
pre.text(data.compile_errors);
output.addClass("error");
highlightErrors(data.compile_errors);
return;
}
var out = ""+data.output;
if (out.indexOf("IMAGE:") == 0) {
var img = $("<img/>");
var url = "data:image/png;base64,";
url += out.substr(6)
img.attr("src", url);
output.empty().append(img);
return;
}
pre.text(out);
},
error: function() {
output.addClass("error").text(
"Error communicating with remote server."
);
}
});
}
$(opts['runEl']).click(run);
if (opts['shareEl'] == null || opts['shareURLEl'] == null) {
return editor;
}
function origin(href) {
return (""+href).split("/").slice(0, 3).join("/");
}
var shareURL = $(opts['shareURLEl']).hide();
var sharing = false;
$(opts['shareEl']).click(function() {
if (sharing) return;
sharing = true;
$.ajax("/share", {
processData: false,
data: body(),
type: "POST",
complete: function(xhr) {
sharing = false;
if (xhr.status != 200) {
alert("Server error; try again.");
return
}
var url = origin(window.location) + "/p/" +
xhr.responseText;
shareURL.show().val(url).focus().select();
}
});
});
return editor;
}
doc/root.html
Go PlaygroundのUI要素にIDが追加され、playground.js
とjQueryが読み込まれ、init
関数でGo Playgroundが初期化されるようになりました。
<div id="learn">
<div class="rootHeading">Try Go</div>
<div class="input">
<textarea spellcheck="false" id="code">// You can edit this code!
// Click here and start typing.
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}</textarea>
</div>
<div class="output" id="output">
<pre>
Hello, 世界
</pre>
</div>
<div class="buttons">
<a id="run" href="#">Run</a>
<a id="share" href="#">Share</a>
</div>
</div>
<!-- ... 略 ... -->
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="/doc/play/playground.js"></script>
<script type="text/javascript">
google.load("feeds", "1");
google.load("jquery", "1.7.1"); // jQueryの読み込み
function feedLoaded(result) { /* ... */ }
function init() {
// Load blog feed.
var feed = new google.feeds.Feed("http://blog.golang.org/feeds/posts/default");
feed.load(feedLoaded);
// Set up playground.
playground({
"simple": true, // シンプルモードで初期化
"codeEl": "#code",
"outputEl": "#output",
"runEl": "#run",
"shareEl": "#share",
"shareURLEl": "#shareURL" // 共有URL表示要素の指定
});
}
google.setOnLoadCallback(init); // init関数をコールバックに設定
</script>
doc/style.css
Go PlaygroundのUI要素のパディングが調整されました。
#learn pre, #learn textarea {
padding: 0; /* 追加 */
margin: 0;
font-family: Menlo, monospace;
font-size: 14px;
}
コアとなるコードの解説
このコミットの核となるのは、playground.js
ファイルに実装された playground
関数です。
-
playground
関数の初期化:playground(opts)
関数は、Go PlaygroundのUI要素(コード入力、出力、実行ボタン、共有ボタンなど)を引数opts
で受け取り、それらの要素に対してイベントリスナーを設定し、インタラクティブな機能を提供します。doc/root.html
では、google.setOnLoadCallback(init)
を通じてページロード時にinit
関数が実行され、その中でplayground
関数が呼び出されます。この際、"simple": true
が渡されているため、初期状態ではCodeMirrorではなく、より軽量な通常のtextarea
がコードエディタとして使用されます。 -
コードの取得と実行 (
run
関数): ユーザーが「Run」ボタンをクリックすると、run
関数が実行されます。body()
関数を通じて、現在のコードエディタ(textarea
または CodeMirror)からGoコードの文字列を取得します。- 取得したコードは、
{"body": "..."}
という形式のJSONデータとして、/compile
エンドポイントにAJAX POSTリクエストで送信されます。 - サーバーサイドの
/compile
エンドポイントは、このGoコードを受け取り、コンパイル・実行します。 - サーバーからのレスポンスには、
compile_errors
(コンパイルエラーメッセージ)とoutput
(プログラムの標準出力)が含まれます。 - クライアントサイドでは、これらのレスポンスを解析し、エラーがあれば該当行をハイライト表示し、そうでなければプログラムの出力を表示します。特に、
output
がIMAGE:
で始まる場合は、Base64エンコードされた画像データとして解釈し、<img>
タグとして表示する機能も備わっています。
-
コードの共有: ユーザーが「Share」ボタンをクリックすると、現在のGoコードが
/share
エンドポイントにAJAX POSTリクエストで送信されます。- サーバーサイドの
/share
エンドポイントは、受け取ったコードを永続化し、一意のIDを生成してクライアントに返します。 - クライアントサイドでは、このIDを使用して
https://golang.org/p/
に続く共有可能なURLを生成し、ユーザーがコピーできるように表示します。これにより、ユーザーは自分の書いたGoコードを簡単に他の人と共有できます。
- サーバーサイドの
-
キーイベントとインデント:
keyHandler
関数は、Tab
キーやEnter
キーが押された際の挙動を制御します。Tab
キーでは、textarea
内にタブ文字を挿入し、ブラウザのデフォルトのタブ挙動(フォーカス移動)を抑制します。Enter
キーでは、Shift
との組み合わせでコード実行をトリガーしたり、simple
モードでは前の行のインデントレベルを自動的に継承するautoindent
機能を提供したりします。
これらの機能が連携することで、ユーザーはGo言語のコードをブラウザ上で手軽に試すことができ、学習や実験の効率が大幅に向上します。
関連リンク
- Go Playground: https://play.golang.org/
- CodeMirror: https://codemirror.net/
- jQuery: https://jquery.com/
参考にした情報源リンク
- Go Playgroundの公式ドキュメントや関連ブログ記事 (当時の情報源は特定が困難なため、一般的なGo Playgroundの解説を参照)
- jQuery API Documentation
- CodeMirror Documentation
- Google Feeds API Documentation (現在は非推奨またはサービス終了)
- Go言語の公式リポジトリのコミット履歴
- Go Playgroundのアーキテクチャに関する一般的なウェブ記事や解説
- Go Playgroundのサーバーサイド実装に関する情報 (例:
go.dev/play
のソースコード)