MacBook AirでBlocklyの使用
目的
自作のプログラミング言語をビジュアルプログラミング言語にしたい.ひとまず導入や簡単な操作確認を行う.
無料で Web アプリの画面 (フロントエンド) を作れるサービス CodePen と、JavaScript のライブラリーを配信している CDN の JsDelivr を使って、Blockly を動かす.
環境
Apple M1: メモリ8GB
Ventura 13.5
Google Chrome バージョン: 117.0.5938.149(Official Build) (arm64)
導入
この記事を参考にする.2018年の記事だから2023年でも使えるかどうか.
CodePenの導入
Sign upしてwith GitHubで登録したらすぐ使えるようになった.
ただし,有料版じゃないとPrivateモードは出来ない点には注意.
ライブラリをCDNから呼び出す.
Blockyは以下のサイトで配布されているので,そこからいくつかJavaScriptをCodePenのSettings > JavaScriptに追加する.順番も重要.
https://cdn.jsdelivr.net/npm/blockly@1.0.0/blockly_compressed.min.js
https://cdn.jsdelivr.net/npm/blockly@1.0.0/blocks_compressed.min.js
https://cdn.jsdelivr.net/npm/blockly@1.0.0/javascript_compressed.min.js
簡易構築
HTML でワークスペースとソースコード出力画面作成
Blockly を表示する #blocklyDiv 、ソースコードを表示する #code をHTMLのエディタに追加する.flex レイアウトを使って2列に表示する.
<div id="content" style="display:flex;">
<div>
<div id="code" style="width: 400px; padding: 1px;"><pre/></div>
<button onclick="runCode();">Run</button>
</div>
<div id="blocklyDiv" style="height: 480px; width: 800px;"></div>
</div>
表示するブロックを定義するために,以下のXMLもHTMLのエディタに追加する.
<xml id="toolbox" style="display: none">
<block type="controls_if"></block>
<block type="controls_repeat_ext"></block>
<block type="logic_compare"></block>
<block type="math_number"></block>
<block type="math_arithmetic"></block>
<block type="text"></block>
<block type="text_print"></block>
</xml>
ガイダンス的に,ブロックを一つだけ表示するようにして何ができるのかを伝える.これもHTMLのエディタに追加する.
<xml id="startBlocks" style="display: none">
<block type="text_print" id="0@)u5QVs/@*zX^XXw%Lp" x="35" y="21">
<value name="TEXT">
<shadow type="text" id="=[)BkyD!BK*AF%PcBi9;">
<field name="TEXT">Hello, World</field>
</shadow>
</value>
</block>
</xml>
この時点での画面はこのようになっている.Runボタンを押してみると,まだJSなどの記述がないためエラーが出力される.
JavaScriptでワークスペースを初期化する
公式ドキュメント https://developers.google.com/blockly/guides/configure/web/fixed-size を参考にしてワークスペースを初期化する.
https://developers.google.com/blockly/guides/get-started/web#configuration を参考にしてゴミ箱を表示する.
var workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox'),
trashcan: true
});
ガイダンス用のブロック#startBlocksを追加.
window.setTimeout(function () {
Blockly.Xml.domToWorkspace(document.querySelector('#startBlocks'), workspace);
}, 0);
ここまで追加するとこのようになる.
toolbox: document.getElementById('toolbox')
の部分を消してみるとピースが消えるので,自分でカスタマイズしていくときはこの部分をいじるのかもしれない.
JSでソースコードを出力
公式ドキュメント https://developers.google.com/blockly/guides/configure/web/code-generators を参考にして、ブロックが変化すると #code にソースコードを表示させる.
function myUpdateFunction(event) {
var code = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('code').innerHTML = '<pre>' + code + '</pre>';
}
workspace.addChangeListener(myUpdateFunction);
右の画面で置いてあるピースに対応したコードが書かれている.
出力コードをシンタックスハイライト
整形する前は次のような感じ.
CodePen の Settings > JavaScript で以下のライブラリーを呼び出す.
次に,JavaScript を修正し,ソースコードの pre タグに class として prettyprint と lang-js を追加する.さらに PR.prettyPrint() で整形処理を実行
これを行うと,コードのまわりが四角で囲まれてハイライトがつく.
BlocklyをLocalStorageに保存
作ったブロックを Web ブラウザの LocalStorage に保存できるようにする.
CodePen では公式ドキュメント https://developers.google.com/blockly/guides/configure/web/cloud-storage の方法が使えないので,https://cdn.jsdelivr.net/npm/blockly@1.0.0/appengine/storage.js を参考にしながら、以下のコードで実現.
//追加
var KEY = 'BlocklyStorage';
function backupBlocks() {
if (!'localStorage' in window) return;
var xml = Blockly.Xml.workspaceToDom(workspace);
var text = Blockly.Xml.domToText(xml);
//console.log(text);
window.localStorage.setItem(KEY, text);
}
function restoreBlocks() {
var xml = Blockly.Xml.textToDom(window.localStorage[KEY]);
Blockly.Xml.domToWorkspace(xml, workspace);
}
ブロックが変更するたびに保存させる.
先ほど作った myUpdateFunction() の中で backupBlocks() を呼び出す.
function myUpdateFunction(event) {
var code = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('code').innerHTML = '<pre class="prettyprint lang-js" style="margin: 0px"><span style="font-size:1.1em">' + code + '</span></pre>';
PR.prettyPrint();
// 追加
backupBlocks();
}
workspace.addChangeListener(myUpdateFunction);
Web ページを呼び出した後,workspace を作り,restoreBlocks() を呼び出す
var workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox'),
trashcan: true
});
window.setTimeout(function () {
//修正
if ('localStorage' in window && window.localStorage[KEY]) {
restoreBlocks();
} else {
Blockly.Xml.domToWorkspace(document.querySelector('#startBlocks'), workspace);
}
}, 0);
これで完了,見た目的なところは何も変化がない.
出力したソースコードを実行
現状では,Runボタンを押してもエラーが出力されて命令は実行されない
https://developers.google.com/blockly/guides/app-integration/running-javascript を参考にして、無限ループにならないように工夫しつつ実行.
function runCode() {
window.LoopTrap = 1000;
Blockly.JavaScript.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
var code = Blockly.JavaScript.workspaceToCode(workspace);
Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
try {
eval(code);
} catch (e) {
alert('Bad code: ' + e);
}
}
これをJSに加えると,Runによって命令が実行される.
最終的なJSのコード
var KEY = 'BlocklyStorage';
var workspace = Blockly.inject('blocklyDiv', {
toolbox: document.getElementById('toolbox'),
trashcan: true
});
window.setTimeout(function () {
if ('localStorage' in window && window.localStorage[KEY]) {
restoreBlocks();
} else {
Blockly.Xml.domToWorkspace(document.querySelector('#startBlocks'), workspace);
}
}, 0);
function myUpdateFunction(event) {
var code = Blockly.JavaScript.workspaceToCode(workspace);
document.getElementById('code').innerHTML = '<pre class="prettyprint lang-js" style="margin: 0px"><span style="font-size:1.1em">' + code + '</span></pre>';
PR.prettyPrint();
backupBlocks();
}
workspace.addChangeListener(myUpdateFunction);
function backupBlocks() {
if (!'localStorage' in window) return;
var xml = Blockly.Xml.workspaceToDom(workspace);
var text = Blockly.Xml.domToText(xml);
//console.log(text);
window.localStorage.setItem(KEY, text);
}
function restoreBlocks() {
var xml = Blockly.Xml.textToDom(window.localStorage[KEY]);
Blockly.Xml.domToWorkspace(xml, workspace);
}
function runCode() {
window.LoopTrap = 1000;
Blockly.JavaScript.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
var code = Blockly.JavaScript.workspaceToCode(workspace);
Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
try {
eval(code);
} catch (e) {
alert('Bad code: ' + e);
}
}
終わりに
古い記事だが,2023年でも使えるようだ.これを元に自分のものにカスタマイズをしていく.
この記事が気に入ったらサポートをしてみませんか?