Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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の公式ドキュメントサイトに統合する第一歩となりました。

前提知識の解説

このコミットの変更内容を理解するためには、以下の技術的な前提知識があると役立ちます。

  1. Go言語: Go PlaygroundはGo言語のコードを実行するための環境であるため、Go言語の基本的な構文や実行モデルを理解していると、コードの意図がより深く理解できます。
  2. 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コードをサーバーに送信し、結果を受け取って表示するロジックを実装しています。
  3. AJAX (Asynchronous JavaScript and XML): ウェブページ全体をリロードすることなく、サーバーと非同期でデータをやり取りする技術です。Go Playgroundでは、ユーザーが「Run」ボタンをクリックした際に、入力されたGoコードをAJAXリクエストとしてサーバーに送信し、実行結果を非同期で受け取って表示するために利用されています。これにより、スムーズなユーザー体験が実現されます。
  4. jQuery: JavaScriptライブラリの一つで、HTMLドキュメントの操作、イベント処理、アニメーション、AJAXなどを簡素化します。このコミットのJavaScriptコードでは、jQueryが広く利用されており、DOM要素の選択やイベントハンドリング、AJAXリクエストの送信などがjQueryのAPIを通じて行われています。
  5. CodeMirror: ブラウザ上で動作する多機能なテキストエディタです。特にコードエディタとしての機能が豊富で、シンタックスハイライト、行番号表示、インデント補助などの機能を提供します。playground.js は、simple オプションが false の場合にCodeMirrorを統合し、よりリッチなコード編集体験を提供します。
  6. Google Feeds API: Googleが提供していたRSS/AtomフィードをJavaScriptで簡単に読み込むためのAPIです。このコミットでは、Goブログのフィードを読み込んで表示するために利用されています。
  7. Go Playgroundのアーキテクチャ (概念): 一般的なウェブベースのコード実行環境は、クライアントサイド(ブラウザ)でコードを入力し、それをサーバーサイドに送信してコンパイル・実行し、その結果をクライアントサイドに返すというアーキテクチャを取ります。このコミットはクライアントサイドの実装に焦点を当てていますが、サーバーサイドの /compile および /share エンドポイントとの連携を前提としています。

技術的詳細

このコミットは、Go Playgroundのクライアントサイド機能を実装する playground.js の導入を中心に展開されています。

playground.js の機能と構造

playground.jsplayground(opts) というグローバル関数を定義しており、この関数がGo Playgroundの主要なロジックをカプセル化しています。opts オブジェクトを通じて、UI要素の指定やコールバック関数の設定が行われます。

  • オプション (opts):

    • codeEl: コード入力エリアの要素(jQueryセレクタまたはDOM要素)。
    • outputEl: プログラム出力エリアの要素。
    • runEl: 実行ボタンの要素。
    • shareEl: 共有ボタンの要素(オプション)。
    • shareURLEl: 共有URL表示用のテキスト入力要素(オプション)。
    • preCompile: コンパイルリクエスト送信前にデータを変更するためのコールバック関数。
    • postCompile: コンパイルレスポンス受信後にデータを処理するためのコールバック関数。
    • simple: true の場合、CodeMirrorではなく通常の textarea を使用します。
  • コードエディタの選択:

    • simple: true の場合、通常の textarea 要素がコードエディタとして使用されます。このモードでは、insertTabsautoindent といった基本的なインデント補助機能が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.」というエラーメッセージを表示します。
  • コード共有機能:

    • shareElshareURLEl オプションが指定されている場合に有効になります。
    • 共有ボタンがクリックされると、/share エンドポイントに対してAJAX POSTリクエストを送信します。リクエストボディには現在のコード内容が含まれます。
    • 成功時の処理:
      • サーバーからのレスポンス(共有されたコードのIDなど)を受け取ります。
      • 現在のウィンドウのオリジンとレスポンスを組み合わせて、共有可能なURL(例: https://golang.org/p/xxxxxxxx)を生成します。
      • 生成されたURLを shareURLEl で指定されたテキスト入力要素に表示し、ユーザーがコピーしやすいようにフォーカスして選択状態にします。

root.html の変更点

  • UI要素のID追加:
    • コード入力用の textareaid="code" を追加。
    • 出力表示用の divid="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 関数です。

  1. playground 関数の初期化: playground(opts) 関数は、Go PlaygroundのUI要素(コード入力、出力、実行ボタン、共有ボタンなど)を引数 opts で受け取り、それらの要素に対してイベントリスナーを設定し、インタラクティブな機能を提供します。 doc/root.html では、google.setOnLoadCallback(init) を通じてページロード時に init 関数が実行され、その中で playground 関数が呼び出されます。この際、"simple": true が渡されているため、初期状態ではCodeMirrorではなく、より軽量な通常の textarea がコードエディタとして使用されます。

  2. コードの取得と実行 (run 関数): ユーザーが「Run」ボタンをクリックすると、run 関数が実行されます。

    • body() 関数を通じて、現在のコードエディタ(textarea または CodeMirror)からGoコードの文字列を取得します。
    • 取得したコードは、{"body": "..."} という形式のJSONデータとして、/compile エンドポイントにAJAX POSTリクエストで送信されます。
    • サーバーサイドの /compile エンドポイントは、このGoコードを受け取り、コンパイル・実行します。
    • サーバーからのレスポンスには、compile_errors(コンパイルエラーメッセージ)と output(プログラムの標準出力)が含まれます。
    • クライアントサイドでは、これらのレスポンスを解析し、エラーがあれば該当行をハイライト表示し、そうでなければプログラムの出力を表示します。特に、outputIMAGE: で始まる場合は、Base64エンコードされた画像データとして解釈し、<img> タグとして表示する機能も備わっています。
  3. コードの共有: ユーザーが「Share」ボタンをクリックすると、現在のGoコードが /share エンドポイントにAJAX POSTリクエストで送信されます。

    • サーバーサイドの /share エンドポイントは、受け取ったコードを永続化し、一意のIDを生成してクライアントに返します。
    • クライアントサイドでは、このIDを使用して https://golang.org/p/ に続く共有可能なURLを生成し、ユーザーがコピーできるように表示します。これにより、ユーザーは自分の書いたGoコードを簡単に他の人と共有できます。
  4. キーイベントとインデント: keyHandler 関数は、Tab キーや Enter キーが押された際の挙動を制御します。

    • Tab キーでは、textarea 内にタブ文字を挿入し、ブラウザのデフォルトのタブ挙動(フォーカス移動)を抑制します。
    • Enter キーでは、Shift との組み合わせでコード実行をトリガーしたり、simple モードでは前の行のインデントレベルを自動的に継承する autoindent 機能を提供したりします。

これらの機能が連携することで、ユーザーはGo言語のコードをブラウザ上で手軽に試すことができ、学習や実験の効率が大幅に向上します。

関連リンク

参考にした情報源リンク

  • Go Playgroundの公式ドキュメントや関連ブログ記事 (当時の情報源は特定が困難なため、一般的なGo Playgroundの解説を参照)
  • jQuery API Documentation
  • CodeMirror Documentation
  • Google Feeds API Documentation (現在は非推奨またはサービス終了)
  • Go言語の公式リポジトリのコミット履歴
  • Go Playgroundのアーキテクチャに関する一般的なウェブ記事や解説
  • Go Playgroundのサーバーサイド実装に関する情報 (例: go.dev/play のソースコード)