「Scrapboxにコードを書いて実行する」を説明してみる

# はじめに
今回の投稿は[ScrapboxのUserScriptテンプレートまとめ][1]に引き続き、[Scrapbox](https://scrapbox.io/features)ネタです。（よっぽど好きだということです）

まずは以下のリンク先ページをご覧ください。
[Scrapboxにコードを書いて実行する][4]

なんとっ Scrapbox上に書いたJavaScriptのコードが実行されてるじゃないですか！　すごい。UserScriptととはまた違ったことやってる！
自分もやりたいぃってことで、どうやって実現しているのか調べてみました。

※ `Vue.js`タグにさせてもらいましたが、`Vue.js`自体は実演で使わせてもらっただけで、チュートリアルの域を出ていないです。ご注意ください。

# 知れること・知れないこと
知れること

- Scrapbox上に[コード記法][2]で記載したJavaScriptのコードを実行して表示させる方法

知れないこと

- Scrapbox上に書いたJavaScript以外（コンパイルが必要になるような言語）を実行させる方法
- Scrapbox上に実行結果を埋め込む方法

# 仕組み
どうやって実現させているか紐解く鍵は、`下のコードを実行`のリンクの中身にあります。
遷移した後のURLを見るのでも構いませんが、
`https://[アカウント名].github.io/[リポジトリ名]/?code=[ほにゃらら]/[ほにゃらら].js`となっているはずです。

### Tip1 : CDN
（他にもやりようはあるかとは思いますが）各種プログラムを実行するために、お馴染みのCDN(Contents Delivery Network)を利用します。

### Tip2 : GitHub Pages
urlを見てもらったら分かるように、`github.io`の静的ホスティングサービスである[GitHub Pages][3]を利用します。
（実際は静的ホスティングサービスであれば、何であっても構わないと思うのですが、今回は参考元のやり方の[GitHub Pages][3]で説明させてもらいます）

### Tip3 : ScrapboxのAPI
公式で大々的に説明があるAPIではありませんが、[コード記法][2]で書いた内容にアクセスできるAPIがあります。
形式 : `https://scrapbox.io/api/code/[プロジェクト名]/[ページ名]/[付けたファイル名]`
<img width="600" height="300" alt="画面収録 2020-03-13 22.15.03.mov.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/eb5e618e-6232-090a-0b9a-4f97377d6181.gif">
普段は上記のように`コード記法`で書いたコードをコピーするために利用されます。
(APIのURLにアクセスすればブラウザ上にコードの中身が表示されます)

「Scrapboxにコードを書いて実行する」の仕組みを、一言で（ざっくり）言うなら
**静的ホスティングしたサイトにScrapboxで書いたコードを返却するAPIを渡して、コード記法で書かれた該当のコードを書き込み、CDNを利用して実行する**
となります。

# 実装方法〜〜ユースケースとともに〜〜
本家[Scrapboxにコードを書いて実行する](4)のように、動きのあるコードの方が、面白味があるとは思いますが、今回は`Vue.js`のチュートリアルのコードを実行してみます。

## GitHub Pagesの準備(簡易説明)

- Github上にリポジトリを作成する。
- `index.html`ファイルを作成する。
- branch : `gh-pages`を作成する
- 後はrepositoryのURLにアクセスで`index.html`の内容が表示される！

## コードの全容
私が今回説明で使っているソースは[こちら](https://github.com/nekotokurasu11/page-for-scrapbox-vue-code/tree/gh-pages)です。
フォルダ構成

```
├── index.html
└── index.js
```
Github上に置くファイルはGithub Pagesで準備した`index.html`と`index.js`だけ！！（簡単）
※`README.md`ファイルは省略して記載しています。

## index.js (ScrapboxのAPIのURLをGetパラメータから取得して、画面に読み込ませる)
ここが一番肝要なところですが、
本家様のコードをほぼほぼ流用しています。基本、本家様のコードを参照でお願いします。:bow:

[本家様コード](https://github.com/masui/runp5/blob/gh-pages/index.js)
ちなみに`gh-pages`ブランチと`master`ブランチで内容が違っていたので、見比べて勉強させてもらいました。（jsだけでなくstyle読み込んだりなどなど）

今回`Vue.js`のコードを動かしたい関係で、jQueryありきの部分については少し変更を加えています。
[今回のindex.js](https://github.com/nekotokurasu11/page-for-scrapbox-vue-code/blob/gh-pages/index.js)

```index.js
// jQueryお馴染みの「$」の部分
$(function(){
↓
// 純粋なJavaScriptに変更
window.addEventListener('DOMContentLoaded', function(){
```
コードを見てもらったらわかりますが、JavaScriptでアクセスしたURLを解析して、
`?code=`の=以降の値（Getパラメータの値）を読み取って、その値をscriptタグとして追加する実装になっています。
（実行する際は`?code=[ScrapboxのAPIのURL]`としてアクセスしてください）


## index.html (必要なCDNの読み込みや簡易のレイアウト記載)
今回はこんなかんじ。

```index.html
<!DOCTYPE html>
<html>

<head>
    <title>Scrapboxでプログラミングの勉強をする</title>
    <!-- bulma -->
    <link href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css" rel="stylesheet">
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>

<div id="app">
    <app-template></app-template>
</div>

<!-- vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="./index.js"></script>

</html>
```
基本`CDN`と先ほどの`index.js`を読み込んでいるだけ！
`CDN`はここで自分の利用したいものをカスタマイズしましょう。
今回はVue.jsと表示の調整で[Bulma](https://bulma.io/documentation/overview/start/)を書きました。

ここまでで下準備は完了！

## Scrapboxにコードを書いて実行
では、Scrapboxでコードを書いて実際に実行してみましょう。
コードは[Vue.jsのチュートリアル](https://jp.vuejs.org/v2/guide/index.html)を参考に書いています。
Githubに挙げているコードは変更しない方向で、`<app-template></app-template>`をカスタム要素として利用した書き方に調整しています。

[コード記法][2]は拡張子「js」でファイル名書くようにしてください。

- Hello World

```js
 const Messages = {
     template:
      `<div class="hero">` +
      '<div class="hero-body has-text-centered">{{ message }}</div>' +
      '</div>',
     data: function() {
       return {
         message: 'Hello World!!',
       }
     }
 }
 
 new Vue({
   el: '#app',
   components: {
     'app-template': Messages
   }
 })
```
実行の様子。上部は切り取っていますが、実行すると新規タブが開きます。
<img width="600" height="300" alt="画面収録 2020-03-14 21.33.44.mov.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/8fb2fd56-ba60-0c52-1f2a-aa3f8bfd0ae7.gif">

※押しているボタンについては[おまけ](#おまけ)で後述します。やってることは`https://[アカウント名].github.io/[リポジトリ名]/?code=[ScrapboxのAPIのURL]`にアクセスするだけです。

Scrapboxのコードを実行しているだけなので、Scrapboxのコードを書き換えると、当然結果も変わります。
<img width="600" height="300" alt="画面収録 2020-03-14 21.55.59.mov.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/436d4242-9a03-b573-ce58-8384ab0b9681.gif">

他の例も用意したので、載せておきます。

- v-for

```js
const BlogPost = {
    template:
        `<div class="hero">` +
        '<div class="hero-body has-text-centered">' +
        '<div v-for="post in posts" v-bind:key="post.id">' +
        '{{post.title}}' +
        '</div>' +
        '</div>' +
        '</div>',
    data: function () {
        return {
            posts: [
                { id: 1, title: 'My journey with Vue' },
                { id: 2, title: 'Blogging with Vue' },
                { id: 3, title: 'Why Vue is so fun' }
            ]
        }
    }
}

new Vue({
    el: '#app',
    components: {
        'app-template': BlogPost
    }
})
```
<img width="600" height="300" alt="画面収録 2020-03-14 22.26.39.mov.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/ade91245-1265-f89d-1fe9-532fcecf8b9b.gif">

- ボタン

```js
 Vue.component('app-template',{
 	data: function(){
 		return {
 			count1: 0,
 			count2: 0
 		}
 	},
 	template:
 		'<div class="hero">'
 		+ '<div class="hero-body has-text-centered">'
 		+ '<button v-on:click="count1++">You clicked me {{count1}} times.</button>'
 		+ '<br>'
 		+ '<button v-on:click="count2++">You clicked me {{count2}} times.</button>'
 		+ '</div>'
 		+ '</div>'
 })
 
 new Vue({ el: '#app' })

```
<img width="600" height="300" alt="画面収録 2020-03-14 22.28.26.mov.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/687a0239-eeb8-fcfc-2fc8-1fa5b2051f35.gif">

**アクセスするたびに同じ画面だけど違う結果が出てくる**
というのはなかなかに面白いものです。
上記の3つのコードを続け様で実行した場合↓
<img width="600" height="400" alt="画面収録 2020-03-15 0.32.54.mov.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/768442ec-c77f-33d4-fc3f-397cfa4377dd.gif">

### どういった時に使える？
本家にも書いてありますが「プログラミング体験」として抜群です。
自分で勉強するにしても人に教えるにしても、文面と実行できるコードを共存して管理できるというのは、利点が多いのではないでしょうか。（いろいろな使い方ができそうです）

## 注意
言わずもがな、自分のコードは**自分のGitHub Pages**（あるいは別の静的ホスティングサービス）にアクセス・実行するようお願いします。

といっても他人のページだと`CDN`のカスタマイズもできないですし、自分で用意した方がいろいろ可能性が広がって楽しいと思います。

# おまけ 
開いているページ(前回はカードと書いていた)上の[コード記法][2]にアクセスするAPIを抽出して、`https://[アカウント名].github.io/[リポジトリ名]/?code=[ScrapboxのAPIのURL]`を開くUserScript作っているので、参考で載せておきます。
（今までのgif画像でも使っていました）

ScrapboxのUserScriptの詳しい説明については前回記事[ScrapboxのUserScriptテンプレートまとめ][1]や[Scrapboxのヘルプ](https://scrapbox.io/help-jp/UserScript)参照ください。

## 下準備
前回記事の[番外テンプレート(1)](https://qiita.com/beeeyan/items/0d04b1859943a9a1e92c#%E7%95%AA%E5%A4%96%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%EF%BC%91--%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%82%92%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AB%E3%81%99%E3%82%8B)を作成するために、画像を用意して1つページを作ります。
![スクリーンショット 2020-03-15 1.20.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277737/09cea2fa-a767-57eb-9931-5fb7d375e750.png)

## UserScript
UserScriptとはこんな感じ。

```script.js
const GITHUB_REPOSITORY = 'https://[githubユーザ名].github.io/[リポジトリ名]';
const SCRAPBOX_URL = 'https://scrapbox.io';
const $appRoot = $('#app-container');
$appRoot.on('click', 'img[class="icon"]', e => {
    const $icon = $(e.target).closest('img[class="icon"]');
    const iconName = $icon.attr('title');
    // 「programming」アイコンの場合に処理を実行
    if (iconName === 'programming') {
        let codeUrl;
        $('a').each(function (index, atag) {
            let hrefStr = String($(atag).attr('href'));
            // getパラメータの作成
            if (hrefStr.startsWith('/api/code') && hrefStr.endsWith('js')) {
                codeUrl = (codeUrl == null) ?
                    'code=' + SCRAPBOX_URL + hrefStr
                    : codeUrl + '&code=' + SCRAPBOX_URL + hrefStr;
            }
        })
        console.log('呼び出し先コードのURL :' + codeUrl);
        window.open(GITHUB_REPOSITORY + '?' + codeUrl);
        return false;
    }
});

```

これで、Scrapboxの`[programming.icon]`（[アイコン記法](https://scrapbox.io/help-jp/%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E8%A8%98%E6%B3%95)）と書かれた場所をクリックすれば、プログラムの実行（条件を満たした状態でGitHub Pagesにアクセス）できます!!

※ScrapboxのUserScriptは自分にしか効かない（[Scrapboxのヘルプ](https://scrapbox.io/help-jp/UserScript)参照）ので、Publicで公開しているプロジェクトにおいて、閲覧者に実行してもらいたい場合等は、リンクをScrapbox上に書くようにしてください。

# おわりの余談
前回のブログを書いていた頃含め、1年以上Scrapboxを書いてきたわけですが、[アドベントカレンダー](https://qiita.com/beeeyan/items/342d51fa301bcf81e9c0)やろう!　ってなったときも、内容考えるのすごく楽でした。
Scrapboxは項目と項目をつなげることで新たな発想が生まれたり、思考がクリアになったりと、使うほどにいろんな効果があるような気がしています。（個人の感想）

ちなみに……
Scrapboxでアウトプットするというのも一つですが、私個人として「Scrapboxの内容をまとめてブログに書く」使い方はこれからもしていくと思います。（私にとってScrapboxはネタ帳の色合いが濃いです）

以上です！

[1]: https://qiita.com/beeeyan/items/0d04b1859943a9a1e92c
[2]: https://scrapbox.io/help-jp/%E3%82%B3%E3%83%BC%E3%83%89%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF
[3]: https://help.github.com/ja/github/working-with-github-pages/about-github-pages
[4]: https://scrapbox.io/ProgrammingExperience/Scrapbox%E3%81%AB%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B