Scratch 3.0でオリジナルブロックをつくろう

# Scratch 3.0が正式版となって登場！

#### 更新

- 2019/1/16 [Scratchで楽しく学ぶ アート&サイエンス](https://www.amazon.co.jp/exec/obidos/ASIN/4822292339/junyasdiary-22/ref=nosim/)の著者の石原さん@champierre 直々にコメントをいただいて、しかも修正箇所もご指摘いただきました。ありがとうございました!びっくりしたー
- 2019/3/31 @mkisono から最新版に合わせたコメントいただき`src/extension-support/extension-manager.js`修正！ありがとうござました！
- 2019/8/19 公開方法を追記しました。
- 2019/9/7 作れるブロックのタイプ４種類を[こちら](https://qiita.com/Hiroyuki_OSAKI/items/60a53e2eeedc22151524)にまとめました。

---
2019/1/3、ついにScratch 3.0がでました。

Scratch
https://scratch.mit.edu/

かっこいいですね～！

![2019-01-04 (5).png](https://qiita-image-store.s3.amazonaws.com/0/96277/847a2e87-57c7-1f78-6b65-17c77c0446bd.png)

2020年からの小学校プログラミング必修化に向けて、**タブレット対応**も完了したことになります。
これで導入の拍車がかかるScratch 3.0。
実装もかなり洗練されていて、もう**こどもの勉強道具の領域を超えています**。

Scratch 3.0の重要ポイントと、オリジナルブロック追加方法について説明します。

## 3.0の重要ポイント (使う人向け)

- ブラウザだけで動作 (これは従来通り)
- **Flashなしで動作可能に** :thumbsup:<br/>したがって**iPad、タブレット、iPhoneなどどこでも使用可能**。
- チュートリアルが豊富に。<br/>マニュアルなしでもとにかく触りながら覚えられる。
- デザインが段違いに洗練された。

## 3.0の重要ポイント (Scratch3.0内実装)

- 内部はNode.js。TypeScriptではない生のJavaScript。
- 画面は**Reactで実装**されていて、各パーツでちゃんとコンポーネント化されている。すばらしい。
- ES2015(constなど)には対応。しかしES2017(asyncなど)は未対応。webpackでコンパイルしている。
- 拡張機能の仕様のたたき台が公開されている。2から少し変わったらしい。
- **npmパッケージを導入可能**。拡張機能にも利用できるらしい(公式には書いてないが私はできた)

# オリジナルブロックをつくってみよう

わたしは、２つの方法でブロックをつくりました。

- 方法１．感触を試すだけ
- 方法２．ちゃんとしたオリジナルブロックを作る
    - 自分の開発環境を作る
    - ブロックを実装
    - GUI側を更新

## 方法1．お試しサイトで感触を試す

### この方法の特徴

- 雰囲気はつかめるので、入りとしてはよい
- 単一JavaScriptファイルのみ読み込み可能
- ブラウザリロードで消えてしまう
- npmパッケージ利用不可

### やりかた

以下の記事の通りに実施します。
すると、正式版ではないサイトで自作JavaScriptを読み込ませることで、オリジナルブロック追加の感触が試せます。
うまくいけば10分ほどでできます。

http://blog.champierre.com/1133

利用する正式版ではないサイト

https://sheeptester.github.io/scratch-gui/

### 試した結果

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/9ad0fab2-4bdc-0849-3807-66a00a2d2b33.png)

できた！！

## 方法2. ちゃんとしたオリジナルブロックをつくる

教えていただいたのは公式Wikiさんのツイート。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Japanese Scratch-Wikiには、もっと複雑な例や、拡張機能ライブラリへの追加方法も載っています! 参考にしてください。<a href="https://t.co/nNoGUF7IPd">https://t.co/nNoGUF7IPd</a></p>&mdash; Japanese Scratch-Wiki (@ScratchWiki_ja) <a href="https://twitter.com/ScratchWiki_ja/status/1080485731812798464?ref_src=twsrc%5Etfw">2019年1月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


### この方法の特徴

- 開発環境が手に入る
- npmなどを使い、自由にNode.jsの拡張機能を作れる
- まだ開発済みブロックを共有・公開するまでには至らない <br/>公開はScratch開発チームにて実施。共有はこれからやり方が提供されるそう。[公式Wiki](https://ja.scratch-wiki.info/wiki/Scratch_3.0_%E3%83%99%E3%83%BC%E3%82%BFFAQ#Scratch_3.0.E7.94.A8.E3.81.AE.E6.8B.A1.E5.BC.B5.E6.A9.9F.E8.83.BD.E3.81.AF.E3.81.A9.E3.81.86.E3.82.84.E3.81.A3.E3.81.A6.E4.BD.9C.E6.88.90.E3.81.97.E3.81.BE.E3.81.99.E3.81.8B.3F)


### 前提 [2019/09追記]

私が確認した環境は以下2つで、いずれも成功。

|| pattern 1| pattern 2|
|---|---|---|
|OS|Windows 10|MacOS Mojave 10.14.6|
|npm|6.4.1|6.9.0|
|node|11.2.0|12.1.0|
|備考|WSL(ubuntu 18.04)上です||

### やりかた

[公式Wikiの記事](https://ja.scratch-wiki.info/wiki/Scratch_3.0%E3%81%AE%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86)に記載の通りに実施しますが、途中から私のアレンジです。

今回は、**ただ入力の文字列をログに吐き出すブロック**を作ります。

#### 1) 開発環境を整えます
2つのgitプロジェクト([scratch-vm](https://github.com/LLK/scratch-vm/), [scratch-gui](https://github.com/LLK/scratch-gui/))をクローンします。
両方とも開発する部分があるので、同時にコンパイルできるように、scratch-guiを親として、scratch-vmを子供としてリンクして使用します。

```sh
## 以下yarn はnpmでも可能
$ git clone --depth 1 https://github.com/llk/scratch-vm.git
$ git clone --depth 1 https://github.com/llk/scratch-gui.git
$ cd scratch-vm && yarn install && yarn link
## 上記でscratch-vmの依存パッケージがダウンロードされる
## 最後にnpmのリンクに追加される。
$ cd scratch-gui && yarn link scratch-vm && yarn install
## 上記で、scratch-vmを子供としてリンクし、ほかのパッケージをダウンロードする。
```

起動を確認します。

```
$ cd scratch-gui
$ yarn start
```

コンパイルが完了したら、

http://localhost:8601

にアクセスして画面を見てみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/29307bfc-c7c9-6368-1dbe-5d5a9f0da01f.png)

いけましたね。

#### 2) ブロックを実装します

今回はWikiに従い「newblocks」という新しい拡張機能を追加する。
拡張機能は複数のブロックを含むことができるが、**今回のnewblocksは1つのブロックのみ**を持つ。

追加（★）および更新（★★）するファイルは以下。(`scratch-vm`のソースツリー)

```
.
├── LICENSE
├── node_modules
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── blocks
│   ├── dispatch
│   ├── engine
│   ├── extensions
│   │   ├── scratch3_ev3
│   │   ├── scratch3_newblocks 【★これを追加する】
│   │   │   └── index.js【★これを追加する → ファイル1】
│   │   ├── 【その他いくつか】
│   │   └── scratch3_wedo2
│   ├── extension-support
│   │   ├── argument-type.js
│   │   ├── extension-manager.js　【★★これを更新する → ファイル2】
│   │   └── 【その他いくつか】
│   ├── import
│   ├── index.js
│   ├── io
│   ├── playground
│   ├── serialization
│   ├── sprites
│   ├── util
│   └── virtual-machine.js
├── test
├── TRADEMARK
├── webpack.config.js
├── yarn-error.log
└── yarn.lock
```

さて、1の手順でダウンロードしてきた`scratch-vm`内に、ソースコードを追加・更新します。

ファイル1`src/extensions/scratch3_newblocks/index.js`は、ブロックの処理を記載します。

```src/extensions/scratch3_newblocks/index.js
const ArgumentType = require('../../extension-support/argument-type');
const BlockType = require('../../extension-support/block-type');
const Cast = require('../../util/cast');
const log = require('../../util/log');

class Scratch3NewBlocks {
    constructor (runtime) {
        this.runtime = runtime;
    }

    getInfo () {
        return {
            id: 'newblocks',
            name: 'New Blocks',
            blocks: [
                {
                    opcode: 'writeLog',
                    blockType: BlockType.COMMAND,
                    text: 'log [TEXT]',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "hello"
                        }
                    }
                }
            ],
            menus: {
            }
        };
    }

    writeLog (args) {
        const text = Cast.toString(args.TEXT);
        log.log(text);
    }
}

module.exports = Scratch3NewBlocks;
```

ファイル2`src/extension-support/extension-manager.js`は、ブロックの一覧です。
そこに先ほどの`scratch3_newblocks`を追加します。

```src/extension-support/extension-manager.js

const builtinExtensions = {
    coreExample: () => require('../blocks/scratch3_core_example'),
    pen: () => require('../extensions/scratch3_pen'),
    wedo2: () => require('../extensions/scratch3_wedo2'),
    music: () => require('../extensions/scratch3_music'),
    microbit: () => require('../extensions/scratch3_microbit'),
    text2speech: () => require('../extensions/scratch3_text2speech'),
    translate: () => require('../extensions/scratch3_translate'),
    videoSensing: () => require('../extensions/scratch3_video_sensing'),
    speech2text: () => require('../extensions/scratch3_speech2text'),
    ev3: () => require('../extensions/scratch3_ev3'),
    makeymakey: () => require('../extensions/scratch3_makeymakey'),
    gdxfor: () => require('../extensions/scratch3_gdx_for'), 【★最後のコンマを追加】
    newblocks: () => require('../extensions/scratch3_newblocks')【★　この行を追加】
};
```

これで、ブロックの実装は完了です。おめでとうございます。

#### 3) GUIを更新します

ユーザがブロックを追加できるようにGUIを変更しましょう。

GUIの左下の「＋」ボタンを押すと、メニューが出てきます。これをライブラリといいます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/5aef1b7d-e9aa-e455-c275-1119637c0926.png)

ここに2)でつくったブロック処理を追加します。
`scratch-gui`のフォルダ内の`src/lib/libraries/extensions/index.jsx`の最後に以下を追加します。

```src/lib/libraries/extensions/index.jsx

【略】

import wedoMenuImage from './peripheral-connection/wedo/wedo-small.svg';
import wedoButtonImage from './peripheral-connection/wedo/wedo-button-illustration.svg';

// 冒頭importが続く部分に以下の2行を追加
import newBlockImage from './newblocks/newblocks.png';
import newBlockButtonImage from './newblocks/newblocks-small.png';

export default [
    {
        【中略】
        connectingMessage: (
            <FormattedMessage
                defaultMessage="Connecting"
                description="Message to help people connect to their WeDo."
                id="gui.extension.wedo2.connectingMessage"
            />
        ),
        helpLink: 'https://scratch.mit.edu/wedo'

    }
// ここから下を追加
,
    {
        name: 'NewBlocks',
        extensionId: 'newblocks',
        collaborator: 'Me',
        iconURL: newBlockImage,
        insetIconURL: newBlockButtonImage,
        description: (
            <FormattedMessage
                defaultMessage="New blocks."
                description="my block"
                id="gui.extension.newblocks.description"
            />
        ),
        featured: true,
        disabled: false,
        internetConnectionRequired: true,
        bluetoothRequired: false,
        helpLink: 'https://scratch.mit.edu/wedo'

    }
// ここまでを追加
];

```

メニュー用の画像を追加します。
追加する場所として先程の`src/lib/libraries/extensions/`の下に`newblocks`というフォルダを追加します。
メニューの背景用の画像として、同じフォルダに600 x 372のサイズのnewblocks.pngファイルを追加します。

![newblocks.png](https://qiita-image-store.s3.amazonaws.com/0/96277/f75c2054-e370-f83b-b175-87982803574d.png)

メニューのアイコン用画像として、同じフォルダに180 x 180のサイズのnewblocks-small.pngファイルを追加します。

![newblocks-small.png](https://qiita-image-store.s3.amazonaws.com/0/96277/dc20830f-19ef-38dc-77f3-0695ae417019.png)

こんなフォルダ構成になっていればOK.

```
.
├── LICENSE
├── package.json
├── prune-gh-pages.sh
├── README.md
├── src
│   ├── components
│   ├── containers
│   ├── css
│   ├── examples
│   ├── index.js
│   ├── lib
│   │   ├── alerts
│   │   ├── libraries
│   │   │   └── extensions/
│   │   │   │   ├── index.jsx　【★　更新された】
│   │   │   │   ├── newblocks【★追加された】
│   │   │   │   │   ├── newblocks.png 【★　追加された】
│   │   │   │   │   └── newblocks-small.png　【★　追加された】
│   │   │   │   └── 【その他】
│   │   ├── log.js
│   ├── playground
│   ├── reducers
│   └── test.js
├── static
├── test
├── TRADEMARK
├── translations
├── webpack.config.js
├── yarn-error.log
└── yarn.lock
```

ここまできたら最後。1)の最後と同じように、GUIを起動します。
これでコンパイルが走り、うまく2)のブロックと3)のGUIがコンパイルされれば成功。

```
$ cd scratch-gui
$ yarn start
```

#### 結果：

メニューを開くと、自分で作った拡張機能「NewBlocks」が表示されるので、クリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/5b09c47f-f6ef-d096-9b85-9faa60dc3afa.png)

そうすると、元の画面のアイコンの一番下にNewBlocksが追加されていて、その中に、`log(hello)`があります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/c6a02f91-2ee5-464b-71d0-a67225c92d57.png)

この新しいブロックをプログラムに追加して、旗(スタート)ボタンをおすと、右下のデバッグ画面に

```
vm hello
```
と出ています。

デバッグ画面は、ブラウザごとに違いますので、出し方は[こちらw3school](https://www.w3schools.com/js/js_debugging.asp)を参考にしてください。Chromeなら`F12`キーで出てきます。

目的の「ログに書き出す」を達成したことがわかります。

ブロックの中の文字を変えてみてスタートを押してもちゃんと値が変わることが確認できますよね。
ちゃんとブロックが動いていますね。

# 公開する

すでに`scratch`のソースコードには公開するための仕掛けがついています。
やり方は[こちらのWiki](https://github.com/LLK/scratch-gui/wiki/Publishing-to-GitHub-Pages)に書いてあります。

私の場合は、GitHub上でもとのレポジトリ(`LKK/scratch-gui`)をフォークして自分のレポジトリ(`onelittlenightmusic/scratch-gui`)を作成します。
上記で作成した開発環境の`origin`を自分のレポジトリに変更します。

```
git remote add origin2 https://github.com/onelittlenightmusic/scratch-gui.git
git remote remove origin
git remote rename origin2 origin
```

```
yarn run build
yarn run deploy
```

すると、自分のレポジトリのなかで作成されたgithub pagesに公開されます。
(私の場合は`https://onelittlenightmusic.github.io/scratch-gui/`)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/95115e77-12b7-a7e2-6e3f-d8dc1d2f731e.png)



# まとめ

できました。これまで私いろんな記事書きましたが、これまでになかった興奮です。

Scratch 3.0のブロックが作れてしまいました。しかもこんなにかんたんに。

さて、私のほうでは、バックエンドにGraphQLサーバを置いておき、apollo-linkを使って天気を取得するブロックも作ってみたりしてます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/f0a51724-ea4f-e877-5443-f431f627d507.png)

Scratch猫くんがリアルタイムにNew yorkの天気を教えてくれますよ。

このやり方はまた別の機会に。

## 残課題

- せっかく作ったブロックはみなリリースしたいはず。GUIとVMにまたがるこの修正箇所をいかにリリースできるしかけにするのか。Scratch teamが鋭意検討中とのこと。期待しましょう。私自身は、node-REDのようにnpmパッケージとして公開すれば、動的にロードできる、というふうになっているとベストと思います。

## 備考

Scratch自体は今8歳の娘が昔から結構触ってたのですが、それでもこれまでベータ版で公開されていたScratch 3.0を触らず嫌い。。これからは積極的に触ってもらおうと思います。