Grimoire.js

GrimoireJS v1.0(beta)の新機能の紹介

GrimoireJS-core v1.0(beta)がようやくリリースされようとしています。ようやく!
v1.0(beta)は今後のしっかりした後方互換性を保つべく、機能の整理と実装のリファクタリングを含むそこそこ大きな(互換性のない)変更になっています。

ほとんどの変更はプラグインを作成する場合くらいにしか影響しないマイナーなものですが、今回はその全変更点を黙々と紹介していきたいと思います。

ディレクトリ構成変更

期せずして過去最大の変更となったのはディレクトリ構成の大幅な改変です。(いままで適当すぎてごめんなさい)
ちゃんと意味的にあるべき位置にモジュールを再配置しました。
この変更は、typescriptでcoreのモジュールをimportする際のpathが変わることを意味します。

// before
import Component from "grimoirejs/ref/Node/Component";

// after
import Component from "grimoirejs/ref/Core/Component";

また、grに露出するインターフェースのパスも変化します。

// before
gr.Node.GomlLoader

// after
gr.Core.GomlLoader

と、このように移動しています。
ざっくりどこに何があるかというと、以下のように配置されるようになりました。

Core

GomlNode, Component,Attributeなど主要ロジックに関わるクラス。
GomlLoader, Identity, AttributeManagerなどもここにあります。

Tool

IdResolver,Utilityなどの割と便利な汎用ツール群があります。
イチオシ関数はUtility.isCamelCaseです。

gr.Tool.Utility.isCamelCase("CamelCase");// true

Converter/Component

名の通り標準のコンバーターとコンポーネントが入ってます。
とはいえクラスを直接触る機会は少ないでしょう。

他にはインタフェースが定義されるInterface,基底クラスのBaseがありますが普通使わないと思います。
ちなみに命名規則が地味に統一されすべてのディレクトリが 単数形になっています。

いくつかのインタフェースのリネーム

完全な差分は公式のドキュメントを参照してください。(まだありませんけど!
GomlNode#nodeDeclaration=>GomlNode#declaration
Attribute#BoundTo=>Attribute#bindTo
などです。

GomlTreeがスクリプトタグに依存しなくなった

小さな変更ですが重要です。
今までGrimoireJSが扱うすべてのツリーはページ上の何処かのスクリプトタグに結びついている必要がありました。
これからはその制限がなくなり、完全にhtmlから独立したツリーを作成することもできるようになります。
動的なツリー構築がやりやすくなり、実装の自由度が高まります。

動的なツリー構築スニペット(js)
var source = "<goml></goml>"
var xml = gr.Tool.XMLReader.parseXML(source);
var tree = gr.Core.GomlParser.parse(xml);
gr.addRootNode(null, tree); // 第1引数(scriptTag)を省略可能に!

rootがgomlじゃなくても良くなった

これも自由度のための変更です。初期の実装上の都合で制限されていたrootノードに関する制約がなくなり、完全に任意のgomlを読み込むことができます。

Gomlの中間形式を導入した

gomlはいままで文字列表現しかなかったので、

var node = gr("*")("#container").first()
node.append('<mesh geometry="cube"/>')

みたいなコードを書いていました。
中間形式はGrimoireObjectModelと呼ばる構造で、構造化されたgomlのデータ構造です。

interface IGrimoireNodeModel {
  name: string;
  attributes?: { [key: string]: string; };
  optionalComponents?: IGrimoireComponentModel[];
  children?: IGrimoireNodeModel[];
}

interface IGrimoireComponentModel {
  name: string;
  attributes?: { [key: string]: string; };
}

これによって、先程のコードは

node.append({
  name:"mesh",
  attributes:{
    geometry:"cube"
  }
})

と書けるようになります。

Attributeがgenericsになった

AttributeクラスとComponent#getAttributeメソッドがジェネリックになりました。
たとえば、以下のようなコンポーネントの場合、

class HogeComponent extends Component {
  public static componentName = "Hoge";
  public static attributes = {
    a: {
      converter: StringConverter,
      default: null,
    },
    b: {
      converter: BooleanConverter,
      default: true,
    },
  };

属性取得は以下のように書けます。

const c = node.getComponent(HogeComponent)
const v = c.getAttribute<string>("a");

しかし、これでは型指定をわざわざしなくてはならないので今までと大して変わりません。
実はコンバータもジェネリッククラスになったことと、getAttributeの引数にIAttributeDeclaration<T>のオーバーロードが追加されたので、上のコードは以下のように書けます。

const c = node.getComponent(HogeComponent)
const v = c.getAttribute(HogeComponent.attributes.a); // v is string!

型推論によって、属性取得がタイプセーフに書けるようになりました!
この型推論は、属性のコンバータの型に依存することに注意してください。

gr.registerNodeでデフォルトコンポーネントにコンストラクタを指定できるようになった

シンプルにオーバーロードが増えました。

// before
GrimoireInterface.registerNode("hoge", ["HogeComponent"]);

// acter
GrimoireInterface.registerNode("hoge", [HogeComponent]);

より安全にregisterできます。

TemplateNode/Component追加

ノードの構造を再利用したりコンポーネントとして利用したいときに便利なノードが追加されました。

<template src="template.goml"/>

このノードは自身を指定されたgomlで置き換えたのとほとんど同じように振る舞います。

その他

特に知る必要もないけど知っておくと役に立つかもしれないちょっとした変更。

メッセージレシーバのアクセス修飾子

コンポーネントのメッセージレシーバはprotectedに統一されました。(今まではpublicだった)

GrimoireInterface.getRootNodeが存在しない場合nullを返す

今までは例外を投げていました。

MetaInfo

パッケージのメタ情報が参照できるようになりました。
index.tsと同じ場所(root)にmetaInfo.tsが生成されるようになります。
たとえばgrimoirejs-fundamentalの場合

import { 
  __NAMESPACE__, // "fundamental"
  __NAME__, // "grimoirejs-fundamental"
  __VERSION__ // "0.x.x"
} from "../metaInfo";

みたいな情報がとれます。