[インデックス 12816] ファイルの概要
このコミットは、Go言語の公式ウェブサイトに組み込まれているGo PlaygroundのフロントエンドJavaScriptファイルである doc/play/playground.js
の同期(更新)に関するものです。Go Playgroundは、ユーザーがブラウザ上でGoコードを記述、コンパイル、実行できるインタラクティブな環境を提供します。このファイルは、そのGo Playgroundの機能、特にコードの実行、出力の表示、エラーハンドリング、そして新しい機能としてコードのフォーマット(go fmt
)とサンプルコード("toys")の読み込みを制御するロジックを含んでいます。
コミット
このコミットは、playground.js
ファイルを更新し、Go Playgroundのユーザーエクスペリエンスと機能性を向上させています。主な変更点としては、出力表示の改善、エラーメッセージのハイライト精度の向上、そしてコードの自動フォーマット機能(go fmt
)と、あらかじめ用意されたサンプルコード("toys")を読み込む機能の追加が挙げられます。これにより、ユーザーはより快適にGoコードを試すことができるようになります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f1a39ff003db83dad301a045f4796384a1037e62
元コミット内容
doc: sync playground.js
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5956043
変更の背景
この変更の背景には、Go Playgroundのユーザーエクスペリエンスを向上させ、よりリッチな機能を提供したいという意図があります。具体的には、以下の点が挙げられます。
- ユーザーへのフィードバックの改善: コードの実行中やエラー発生時に、より明確な視覚的フィードバックを提供することで、ユーザーが現在の状態を把握しやすくする。
- コード品質の向上支援:
go fmt
コマンドの機能をGo Playgroundに統合することで、ユーザーが記述したGoコードを簡単にGoの標準的なスタイルに整形できるようにし、コードの可読性と一貫性を高める。これは、Goコミュニティ全体で推奨されるプラクティスであり、Go言語の設計思想の一部でもあります。 - 学習と探索の促進: "toys"(サンプルコード)機能を追加することで、Go言語の様々な機能やイディオムをユーザーが手軽に試せるようにし、学習プロセスを支援する。これにより、Go言語に不慣れなユーザーでも、既存のコードをベースに試行錯誤しやすくなります。
- コードの保守性と拡張性:
playground.js
内のコードをリファクタリングし、共通の処理を関数としてまとめることで、将来的な機能追加やメンテナンスを容易にする。
これらの改善は、Go Playgroundを単なるコード実行環境としてだけでなく、Go言語の学習ツールとしても強化することを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識があると役立ちます。
1. Go Playground
Go Playgroundは、Go言語の公式ウェブサイト (golang.org) 上で提供されているオンラインツールです。ユーザーはブラウザ上でGoコードを記述し、サーバーサイドでコンパイル・実行された結果をブラウザで確認できます。これは、Go言語の学習、簡単なコードのテスト、Goコードのスニペットの共有などに広く利用されています。Go Playgroundのバックエンドは、ユーザーのコードをサンドボックス化された環境で安全に実行し、その結果を返します。
2. go fmt
go fmt
は、Go言語の標準的なコードフォーマッタです。Goのソースコードを自動的に整形し、Goコミュニティで推奨される統一されたスタイルに準拠させます。これにより、コードの可読性が向上し、異なる開発者間でのコードスタイルの不一致による議論を減らすことができます。go fmt
はGoツールチェインの一部として提供されており、多くのGo開発者が日常的に使用しています。
3. JavaScriptとjQuery
playground.js
はJavaScriptで書かれており、DOM操作やAjaxリクエストのためにjQueryライブラリを使用しています。
- JavaScript: ウェブブラウザ上で動作するスクリプト言語で、ウェブページに動的な機能を追加するために使用されます。
- jQuery: JavaScriptライブラリの一つで、HTMLドキュメントのトラバーサル、操作、イベントハンドリング、アニメーション、Ajaxなどの機能を簡潔な構文で提供します。
$
シンボルは通常jQueryオブジェクトを指します。
4. Ajax (Asynchronous JavaScript and XML)
Ajaxは、ウェブページ全体をリロードすることなく、非同期にサーバーとデータをやり取りするための技術です。Go Playgroundでは、ユーザーが入力したコードをサーバーに送信してコンパイル・実行したり、フォーマットしたり、サンプルコードを読み込んだりするためにAjaxリクエストが使用されます。
5. 正規表現 (Regular Expressions)
正規表現は、文字列の中から特定のパターンを検索、置換、抽出するための強力なツールです。このコミットでは、コンパイルエラーメッセージから行番号を抽出するために正規表現が使用されています。
技術的詳細
このコミットにおける playground.js
の変更は、主に以下の技術的な側面に焦点を当てています。
1. エラーメッセージの正規表現の改善
元のコードでは、コンパイルエラーの正規表現 /[a-z]+\.go:([0-9]+): /g
が使用されていました。これはファイル名、行番号、そしてその後にスペースが続くパターンにマッチします。しかし、新しい正規表現は /[a-z]+\.go:([0-9]+):/g
となり、行番号の後のスペースが削除されています。これにより、より厳密なマッチングが可能になり、エラーメッセージの解析精度が向上します。
2. 出力表示の共通化と改善
loading()
関数と setOutput()
関数が新しく導入され、出力エリアの表示管理が共通化されました。
loading()
関数: コードの実行やフォーマット処理中に「Waiting for remote server...」というメッセージを表示し、ユーザーに処理が進行中であることを伝えます。これにより、ユーザーはアプリケーションがフリーズしていると誤解することなく、待機状態を認識できます。setOutput(text, error)
関数: Go Playgroundの出力エリアにテキストを表示するための共通関数です。error
引数がtrue
の場合、出力エリアにerror
クラスを追加し、エラーメッセージを視覚的に区別できるようにします。これにより、エラー表示の一貫性が保たれ、コードの重複が削減されます。
これらの関数は、以前は run()
関数内に直接記述されていた出力関連のロジックを抽象化し、コードの可読性と保守性を向上させています。
3. go fmt
機能の追加
新しい fmtEl
(format button element) にクリックイベントリスナーが追加され、ユーザーがボタンをクリックするとコードのフォーマット処理が実行されるようになりました。
- ユーザーが
fmtEl
をクリックすると、loading()
関数が呼び出され、処理中であることを示します。 - 現在のコードの内容が
/fmt
エンドポイントにPOSTリクエストとして送信されます。 - サーバーからの応答がJSON形式で返され、
Error
フィールドにエラーがあればsetOutput
で表示し、Body
フィールドに整形されたコードがあればsetBody
でエディタに反映します。
この機能は、Go Playgroundが単なる実行環境ではなく、Goコードの記述と整形を支援するツールとしての役割を強化します。
4. "Toys" (サンプルコード) 機能の追加
toysEl
(select element with a list of toys) に change
イベントリスナーが追加され、ユーザーがドロップダウンリストからサンプルコードを選択すると、そのコードがエディタに読み込まれるようになりました。
- ユーザーが
toysEl
の選択を変更すると、loading()
関数が呼び出されます。 - 選択されたサンプルコードのパス(例:
/doc/play/hello.go
)に対してGETリクエストが送信されます。 - サーバーからサンプルコードのテキストが返されると、
setBody
でエディタにそのコードが設定され、setOutput
で出力エリアがクリアされます。
この機能は、Go言語の様々な機能やイディオムをユーザーが手軽に試せるようにすることで、学習体験を向上させます。
5. 不要なコードの削除とリファクタリング
以前の toysEl
に関連するコードブロックが削除され、新しい実装に置き換えられています。また、console.log(xhr.status);
のようなデバッグ用のコードも削除され、プロダクション環境に適したクリーンなコードベースになっています。
これらの変更は、Go Playgroundのフロントエンドロジックをより堅牢で、ユーザーフレンドリーで、拡張性のあるものにすることを目的としています。
コアとなるコードの変更箇所
1. playground
関数のオプションに fmtEl
を追加
@@ -6,13 +6,14 @@
// codeEl - code editor element
// outputEl - program output element
// runEl - run button element
+// fmtEl - fmt button element (optional)
// shareEl - share button element (optional)
// shareURLEl - share URL text input element (optional)
// shareRedirect - base URL to redirect to on share (optional)
-// preCompile - callback to mutate request data before compiling
-// postCompile - callback to read response data after compiling
-// simple - use plain textarea instead of CodeMirror.\n-// toysEl - select element with a list of toys.\n+// preCompile - callback to mutate request data before compiling (optional)
+// postCompile - callback to read response data after compiling (optional)
+// simple - use plain textarea instead of CodeMirror. (optional)
+// toysEl - select element with a list of toys. (optional)
function playground(opts) {
var simple = opts['simple'];
var code = $(opts['codeEl']);
2. エラー正規表現の修正
@@ -97,7 +98,7 @@ function playground(opts) {
if (!editor) {
return;
}
-\t\tvar errorRe = /[a-z]+\.go:([0-9]+): /g;
+\t\tvar errorRe = /[a-z]+\.go:([0-9]+):/g;
var result;
while ((result = errorRe.exec(text)) != null) {
var line = result[1]*1-1;
3. loading()
および setOutput()
関数の追加
@@ -120,13 +121,23 @@ function playground(opts) {
function origin(href) {
return (""+href).split("/").slice(0, 3).join("/");
}
+\tfunction loading() {
+\t\toutput.removeClass("error").html(
+\t\t\t'<div class="loading">Waiting for remote server...</div>'
+\t\t);
+\t}
+\tfunction setOutput(text, error) {
+\t\toutput.empty();
+\t\tif (error) {
+\t\t\toutput.addClass("error");
+\t\t}
+\t\t$("<pre/>").text(text).appendTo(output);
+\t}
4. run()
関数内での loading()
と setOutput()
の利用
@@ -134,8 +145,7 @@ function playground(opts) {
var seq = 0;
function run() {
clearErrors();
-\t\toutput.removeClass("error").html(
-\t\t\t'<div class="loading">Waiting for remote server...</div>'
-\t\t);
+\t\tloading();
seq++;
var cur = seq;
var data = {"body": body()};
@@ -149,8 +159,6 @@ function playground(opts) {
\tif (seq != cur) {
\t\treturn;
\t}
-\t\t\t\tpre = $("<pre/>");
-\t\t\t\toutput.empty().append(pre);
\tif (opts['postCompile']) {
\t\topts['postCompile'](data);
\t}
@@ -158,8 +166,7 @@ function playground(opts) {
\t\treturn;
\t}
\tif (data.compile_errors != "") {
-\t\t\t\t\tpre.text(data.compile_errors);
-\t\t\t\t\toutput.addClass("error");
+\t\t\t\t\tsetOutput(data.compile_errors, true);
\t\thighlightErrors(data.compile_errors);
\t\treturn;
\t}
@@ -172,11 +179,10 @@ function playground(opts) {
\t\toutput.empty().append(img);
\t\treturn;
\t}
-\t\t\t\tpre.text(out);
+\t\t\t\tsetOutput(out, false);
\t},
\terror: function(xhr) {
\t\tvar text = "Error communicating with remote server.";
-\t\t\t\t\tconsole.log(xhr.status);\n \t\t\t\tif (xhr.status == 501) {
\t\ttext = xhr.responseText;
\t}
5. fmtEl
と toysEl
のイベントハンドラ追加
@@ -178,6 +185,41 @@ function playground(opts) {
}\n \t$(opts['runEl']).click(run);\n \n+\t$(opts['fmtEl']).click(function() {
+\t\tloading();
+\t\t$.ajax("/fmt", {
+\t\t\tdata: {"body": body()},
+\t\t\ttype: "POST",
+\t\t\tdataType: "json",
+\t\t\tsuccess: function(data) {
+\t\t\t\tif (data.Error) {
+\t\t\t\t\tsetOutput(data.Error, true);
+\t\t\t\t\thighlightErrors(data.Error);
+\t\t\t\t\treturn;
+\t\t\t\t}
+\t\t\t\tsetBody(data.Body);
+\t\t\t\tsetOutput("", false);
+\t\t\t}
+\t\t});
+\t});
+\n+\t$(opts['toysEl']).bind('change', function() {
+\t\tvar toy = $(this).val();
+\t\tloading();
+\t\t$.ajax("/doc/play/"+toy, {
+\t\t\tprocessData: false,
+\t\t\ttype: "GET",
+\t\t\tcomplete: function(xhr) {
+\t\t\t\tif (xhr.status != 200) {
+\t\t\t\t\tsetOutput("Server error; try again.", true);
+\t\t\t\t\treturn;
+\t\t\t\t}
+\t\t\t\tsetBody(xhr.responseText);
+\t\t\t\tsetOutput("", false);
+\t\t\t}
+\t\t});
+\t});
+\n \tif (opts['shareEl'] != null && (opts['shareURLEl'] != null || opts['shareRedirect'] != null)) {
\t\tvar shareURL;
\t\tif (opts['shareURLEl']) {
@@ -213,22 +255,5 @@ function playground(opts) {
\t\t});
\t}
\n-\tif (opts['toysEl'] != null) {
-\t\t$(opts['toysEl']).bind('change', function() {
-\t\t\tvar toy = $(this).val();
-\t\t\t$.ajax("/doc/play/"+toy, {
-\t\t\t\tprocessData: false,
-\t\t\t\ttype: "GET",
-\t\t\t\tcomplete: function(xhr) {
-\t\t\t\t\tif (xhr.status != 200) {
-\t\t\t\t\t\talert("Server error; try again.")
-\t\t\t\t\t\treturn;
-\t\t\t\t\t}\n-\t\t\t\t\tsetBody(xhr.responseText);
-\t\t\t\t}\n-\t\t\t});
-\t\t});
-\t}\n-\n \treturn editor;\n }\n```
## コアとなるコードの解説
### 1. `playground` 関数のオプション変更
`playground` 関数は、Go Playgroundのインスタンスを初期化するための設定オブジェクト `opts` を引数として取ります。このコミットでは、`opts` に新しいオプション `fmtEl` が追加されました。これは、コードフォーマットボタンのDOM要素を指します。また、既存のオプションの説明もより詳細になり、`optional` の記述が追加されています。これにより、`playground` 関数の柔軟性が向上し、フォーマット機能がGo Playgroundに統合される基盤が作られました。
### 2. エラー正規表現の修正
`var errorRe = /[a-z]+\.go:([0-9]+):/g;` の変更は、コンパイルエラーメッセージから行番号を正確に抽出するためのものです。以前の正規表現 `/[a-z]+\.go:([0-9]+): /g` は行番号の後にスペースを要求していましたが、Goコンパイラが出力するエラーメッセージの形式が変更されたか、より厳密なマッチングが必要になったため、このスペースが削除されました。これにより、Go Playgroundがエラーメッセージ内の行番号を正しく解析し、エディタ内でエラー箇所をハイライトする精度が向上します。
### 3. `loading()` および `setOutput()` 関数の追加
* **`loading()` 関数**:
```javascript
function loading() {
output.removeClass("error").html(
'<div class="loading">Waiting for remote server...</div>'
);
}
```
この関数は、`output` 要素(Go Playgroundの出力表示エリア)から `error` クラスを削除し、"Waiting for remote server..." というメッセージを含む `div` 要素を挿入します。これは、コードの実行やフォーマット処理が開始された際に、ユーザーに視覚的なフィードバックを提供し、処理が進行中であることを示すために使用されます。
* **`setOutput(text, error)` 関数**:
```javascript
function setOutput(text, error) {
output.empty();
if (error) {
output.addClass("error");
}
$("<pre/>").text(text).appendTo(output);
}
```
この関数は、Go Playgroundの出力エリアにテキストを表示するための汎用的なユーティリティです。まず `output` 要素の内容をクリアし、`error` 引数が `true` の場合は `error` クラスを追加してエラー表示を強調します。その後、与えられた `text` を `<pre>` タグで囲んで `output` 要素に追加します。これにより、出力表示のロジックが一元化され、コードの重複が排除され、エラー表示の一貫性が保たれます。
### 4. `run()` 関数内での `loading()` と `setOutput()` の利用
`run()` 関数は、ユーザーが「Run」ボタンをクリックしたときにGoコードを実行する主要なロジックを含んでいます。このコミットでは、以前直接記述されていた出力表示のロジックが、新しく定義された `loading()` と `setOutput()` 関数に置き換えられました。
* `output.removeClass("error").html(...)` の代わりに `loading();` が呼び出されるようになりました。
* コンパイルエラーが発生した場合の `pre.text(data.compile_errors); output.addClass("error");` の代わりに `setOutput(data.compile_errors, true);` が呼び出されるようになりました。
* 正常な出力の場合の `pre.text(out);` の代わりに `setOutput(out, false);` が呼び出されるようになりました。
これにより、`run()` 関数のコードがより簡潔になり、出力表示のロジックが他の機能(フォーマット、サンプルコード読み込み)と共通化されました。
### 5. `fmtEl` と `toysEl` のイベントハンドラ追加
* **`$(opts['fmtEl']).click(function() { ... });`**:
このコードブロックは、`fmtEl` オプションで指定された要素(フォーマットボタン)がクリックされたときに実行される処理を定義しています。
1. `loading()` を呼び出し、処理中であることを表示します。
2. `$.ajax("/fmt", ...)` を使用して、現在のエディタの内容 (`body()`) を `/fmt` エンドポイントにPOSTリクエストとして送信します。
3. サーバーからの応答(JSON形式)を受け取り、`data.Error` が存在すればエラーとして表示し、`data.Body` に整形されたコードがあれば `setBody(data.Body)` でエディタの内容を更新します。
4. 最後に `setOutput("", false)` で出力エリアをクリアします。
これにより、Go Playgroundに `go fmt` の機能が統合され、ユーザーは簡単にコードを整形できるようになりました。
* **`$(opts['toysEl']).bind('change', function() { ... });`**:
このコードブロックは、`toysEl` オプションで指定された要素(サンプルコード選択ドロップダウン)の選択が変更されたときに実行される処理を定義しています。
1. `var toy = $(this).val();` で選択されたサンプルコードのファイル名を取得します。
2. `loading()` を呼び出し、処理中であることを表示します。
3. `$.ajax("/doc/play/"+toy, ...)` を使用して、選択されたサンプルコードのファイル(例: `/doc/play/hello.go`)をGETリクエストで取得します。
4. サーバーからの応答 (`xhr.responseText`) を受け取り、`setBody(xhr.responseText)` でエディタの内容をそのサンプルコードに更新します。
5. 最後に `setOutput("", false)` で出力エリアをクリアします。
これにより、ユーザーはGo Playgroundで様々なサンプルコードを簡単に読み込み、試すことができるようになりました。
これらの変更は、Go Playgroundの機能性を大幅に拡張し、ユーザーエクスペリエンスを向上させるための重要なステップです。
## 関連リンク
* Go Playground: [https://go.dev/play/](https://go.dev/play/)
* `go fmt` のドキュメント: [https://go.dev/blog/go-fmt](https://go.dev/blog/go-fmt)
* jQuery 公式サイト: [https://jquery.com/](https://jquery.com/)
## 参考にした情報源リンク
* Go Playground の仕組み (The Go Playground): [https://go.dev/blog/playground](https://go.dev/blog/playground)
* Go Playground のソースコード (GitHub): [https://github.com/golang/go/tree/master/doc/play](https://github.com/golang/go/tree/master/doc/play)
* Ajax と jQuery.ajax(): [https://api.jquery.com/jquery.ajax/](https://api.jquery.com/jquery.ajax/)
* 正規表現 (MDN Web Docs): [https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_expressions](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_expressions)
* Go言語の公式ブログ: [https://go.dev/blog/](https://go.dev/blog/)
* Go言語のドキュメント: [https://go.dev/doc/](https://go.dev/doc/)