JavaScript
Aurelia

aureliaのdocsページのGetting startを単一ファイルにまとめた

More than 1 year has passed since last update.

始まり

jQueryとかReactとかVueとかriotとかえとせとら、htmlファイルにcdnからjsファイルを読み込んで実行することのできるフレームワークがある中、世界的にまだまだマイナーなaureliaというフレームワークは「ビルドしてね」と公式でrecommentされているため、どうしても気楽ではない。
だがcdnとか使えてライブラリ読んでデモ作ったらもっとアピールできないか? というissueがaureliaのGitHubリポジトリに投げ込まれているのも確認した。
なので。

単一ファイルでaureliaを実装できるかやってみた。

準備

必要なファイルはだいたいCDNにある。

aurelia-core
https://cdnjs.cloudflare.com/ajax/libs/aurelia/1.0.2/aurelia-core.min.js

aureliaのフレームワークファイル。minなら300kbをちょっと下回るくらい。
軽量フレームワークは実装APIの量を抑えることで容量を絞っているのだが、aureliaは大量にAPIを積んでいるので仕方ない。

require.js
https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.min.js

aureliaのモジュールを発動させるためのrequireライブラリ。
SystemJSでも似たようなことができるが、今回はこちらを選択。

text
https://cdnjs.cloudflare.com/ajax/libs/require-text/2.0.12/text.min.js

aurelia-cliで標準的に組み込まれている、require.jsでテキストファイルを扱うためのモジュール。
これがないとうまくフレームワークが動作しない。

以上3点のライブラリに頼りながら、aureliaにあるGetting startの実装を目指す。

それより先にHello Worldする。

一応動作することを確かめるため、まずHello Worldを表示させてみる。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Aurelia</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/aurelia/1.0.2/aurelia-core.min.js"></script>
</head>
<body>
  <div id="main">
    <h1>${message}</h1>
  </div>

  <script>
    require.config({
      paths: {
        'text': 'https://cdnjs.cloudflare.com/ajax/libs/require-text/2.0.12/text.min'
      }
    });
    require(['aurelia-bootstrapper'],function(bootstrapper) {
      bootstrapper.bootstrap(function(aurelia) {
        aurelia.use
        .basicConfiguration();

        var App = {
          message: 'Hello world!'
        };

        aurelia.start().then(function() { aurelia.enhance(App, document.getElementById('main')); });
      });
    });

  </script>
</body>
</html>

ポイント

  • ES5で問題ない記述にしているので、IE11とかのレガシーブラウザでもとりあえず動く。
  • textはaurelia側で使うのでrequire.jsのpathsに含めている。
  • 通常使うaurelia.setRootではなく、aurelia.enhance を使うことによって、objectをエントリーポイントにしている。Appオブジェクトをスタートアップ関数内に定義しているが、外側でももちろん大丈夫。
  • エントリーポイントにしているオブジェクトのプロパティは、enhanceによって流し込み先を指定したelementオブジェクトのノード内にバインドできる。バインド変数はもちろん、valueやtextcontentやinnerhtmlなどの要素にバインドすれば、内容をviewに表示できる。
  • 本ファイルはFireFoxでなければ、httpサーバーごしに見る必要がある。

本題、Todoリストの作成。

だいたいどういうコードになるかはaureliaのページに書いてあるので、それを単に落とし込むだけ。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/aurelia/1.0.2/aurelia-core.min.js"></script>
</head>
<body>
  <div id="main">
    <h1>${heading}</h1>

    <form submit.trigger="addTodo()">
      <input type="text" value.bind="todoDescription">
      <button type="submit">Add Todo</button>
    </form>

    <ul>
      <li repeat.for='todo of todos'>
        <input type="checkbox" checked.bind="todo.done">
        <span css="text-decoration: ${todo.done ? 'line-through' : 'none'}">
          ${todo.description}
        </span>
        <button click.trigger="removeTodo(todo)">Remove</button>
      </li>
    </ul>
  </div>

  <script>
    require.config({
      paths: {
        'text': 'https://cdnjs.cloudflare.com/ajax/libs/require-text/2.0.12/text.min'
      }
    });
    require(['aurelia-bootstrapper'],function(bootstrapper) {
      bootstrapper.bootstrap(function(aurelia) {
        aurelia.use
        .basicConfiguration();

        var Todo = function(description) {
          this.description = description;
          this.done = false;
        }

        var App = function(){
          this.heading = 'Todos';
          this.todos = [];
          this.todoDescription = '';
          App.prototype.addTodo = function(){
            if (this.todoDescription) {
              this.todos.push(new Todo(this.todoDescription));
              this.todoDescription = '';
            }
          }
          App.prototype.removeTodo = function(todo) {
            var index = this.todos.indexOf(todo);
            if (index !== -1) {
              this.todos.splice(index, 1);
            }
          }
        }
        app = new App;

        aurelia.start().then(function() { aurelia.enhance(app, document.getElementById('main')); });
      });
    });

  </script>
</body>
</html>

ポイント

  • ES5記述で。エントリーポイントは生objectならそのまま投げ込めるが、prototypeチェーンを持つコンストラクタ関数やモダンブラウザで使えるES6のclass式であれば、newで作ったインスタンスを投げ込む。
  • Getting startにあるTodoリストがそのまま実現できている。
  • aureliaは通常のモジュール分けでは、viewを<template>タグで囲ったhtmlファイルで、htmlと同じファイル名をUpperCamelCaseした名前のclassを定義したjsファイル(app.htmlならAppクラスを定義したapp.js)で書く。(例外を作ることもできるが)このtemplate内に書く予定のhtmlをずらずらと、流し込み先のタグ内ノードに記述することができる。
  • じつはこれはすごいこと。他のフレームワークの場合、javascriptのObjectのプロパティにtemplateなりを定義し、その中に文字列としてhtml形式のようなものを記述したりなど、ややHTMLそのものからかけ離れがちだが、aureliaはマークアップをそのままにviewを記述できる。何がすごいといえば、デザイナーさんとの連携がやりやすいということ。
  • AppとTodoはbootstrapのcallback関数内で定義している。もちろん、このcallback関数がアクセス可能な位置であればどこで定義しても良い。

終わりに

全然気楽な実装ではない。

そもそもまずbootstrapしてからstartでaurelia自体のインスタンスを作らないといけない。
当たり前だが、単純にライブラリを読めばaurelia化するわけではないのだ。

よりモダンに書きたければbabelに頼る必要がある。firefoxやchromeだとES2017で実装されたObject.valuesなんかの記述をしても通してくれるが、IEはもちろんスルーするので、これをどうにかする必要がある。
一応routerは、pathsに指定してやればaurelia内で認識してくれるが、そもそもrouterは遷移のためのモジュールなので、それなら単一ファイルにする必要性が薄い。
WebCompornents的な独自タグ・独自要素なんかはモジュール分けしてやるものなので本題から外れる。
いろいろaurelia-cliでプロジェクトを作れば自動的にやってくれることを、自分で気を使って実装してやらなければならないところがたくさん出てくるが、逆に言えばよりコンパクトな環境作りができそう。aurelia-cliで作るプロジェクトは、実際かなり重たいので。