WebページでJavaScriptを編集・実行できるページを作る

# はじめに
自作のJavaScript数値計算ライブラリの確認用デモページのために作る。
同様なサービスなどは大量にあると思うがよりシンプルに...というか個人開発で賄える範囲のものに留める(ようにする)。

こんな感じで
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2567691/a483c173-02c4-27e6-5126-a6cf93b0697e.png)


## 使ったライブラリ
### [Ace](https://ace.c9.io/)
ブラウザ上でリアルタイムに編集できてシンタックスハイライトが付けられるコードエディタ。
HTMLで`<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ace.js"></script>`のように読み込む
私は以下のようにユーティリティ化している
```js:aceUtil.js
let editor=null;

export default {
    set: (id, lang=void 0, theme=void 0)=>{
        if( typeof(id)==='object' ){
            if( id.lang !=null ) lang=id.lang;
            if( id.theme!=null ) theme=id.theme;
        }
        editor=ace.edit(id);
        editor.setTheme(theme);
        editor.session.setMode(lang);
        console.log(editor);
    },

    setValue: (text, point=-1)=>{ editor.setValue(text, point); },
    getValue: ()=>{ return editor.getValue(); },

    loadText: async (url)=>{ return fetch(url, { cache: "no-store" } ).then(res=> res.text()).then(text=> editor.setValue(text, -1)); }
}
```
HTMLのID(多分、エレメント自体でもOK)でエレメントを指定するとその下に`<texterea>`と色々作られる。
(textareaが本体だがそれにラッパーで色付けとか付けられた透過する`div`が作られる)

編集エリアからの入出力は`setValue`と`getValue`で行われる。
`setValue`のpositionは位置を表すが負の値を与えると0になるようである。

## コードの実行
```js:index.js
function run(){
//    const { code }=Babel.transform(aceUtil.getValue(), { presets: ['env'], plugins: ['operator_overload'] });
//    const f=Function(code);
    const f=Function(aceUtil.getValue());

    if( document.getElementById('auto-clear').checked ) clearResult();
    f();
}
```
これだけす。Functionを作って実行するだけです。
後々は少しトランスパイルを通しますがその場合は上のようなコードになります。
これには別途、色々設定が必要です。

## モジュールのロード
ただ上のコードではグローバルにあるものしか使えません。
そこで以下のようなloadMdule関数を使って自作ライブラリをロードしています。

```js:loadModule.js
export default async (filepath)=>{
    return await import(filepath).then(module=>{
        for( const [key, val] of Object.entries(module) ){
            if( window[key]==null ){
                window[key]=val;
                console.log('Set Global Property '+key);                   
            }
            else{
                console.error('! Global.'+key+' already defined');
            }
        }
    });
}
```
exportされたオブジェクトのプロパティをブラウザのグローバルであるwindowにセットしているだけです。
もちろん、名前がかぶると衝突してエラーメッセージを吐きます。

export側はこんな感じになってます。

```js:modules/calc.js
...

export {add, sub, mul, div, // 四則演算 
        sqrt, pow, factorial, exp, sin, cos, tan, sinh, cosh, tanh, // 初等関数 
        specialFunc,
        diff, integral, // 微分、積分
        Complex, // 複素数クラス
        Vector, Matrix, // ベクトルクラス、行列クラス 
        linearAlgebra,
        solver,
       };
```
`...`の部分で本体を定義`import`する形になっています。
JS標準のようにMathクラスのような、MyMathとかでラップしても良かったですがユーザーが編集するにはめんどくさいと思ったので裸でexportしました。

## アウトプット
アウトプットはconsoleのように`output.log(...)`とすると下の`id="result"`に出るようにしてあります。
下のように`div`要素を作ってそこに入れているだけです。

また、`output.math(...)`を使うと[Mathjax](https://www.mathjax.org/)を使った、形式で出してくれるようにしてあります。

`function resetMathJax(...)`の部分は追加があった場合の再描写です。
```js:output.js
const output={
    log: (...args)=>{
        const elem=document.getElementById('result');
        const div=document.createElement('div');
        const text=args.reverse().reduce((a, sum)=> sum+' '+a, '');

        div.innerHTML=text;
        elem.appendChild(div);
    },

    math: (...args)=>{
        const elem=document.getElementById('result');
        const div=document.createElement('div');
        div.innerHTML+='\\[ ';
        args.forEach(a=>{ div.innerHTML+=typeof a.toTex ==='function' ? a.toTex(): a });
        div.innerHTML+=' \\]';
        elem.appendChild(div);
        restartMathJax();
    }
};
export { output };

function restartMathJax(){ MathJax.Hub.Queue(["Typeset", MathJax.Hub, "dynamicElement"]); }
```


# 成果物
[gitpubリポジトリ](https://github.com/T-Yasusi/t-yasusi.github.io/commit/36a1d57a1f48039f5f675ff97edf8fdefd85f38f)、色々付け加えてしかも途中ですが...orz
[githubページ](https://t-yasusi.github.io/js-test.html)