まえおき

Vue2.xでは仮想DOMが使われるようになりました。それに伴い、Vue用のテンプレート仮想DOMをレンダリングするための関数に変換するという処理をコンパイル時に行ってくれる仕組みが組み込まれています。

そして、お手軽にVueを試したい人、プロダクトにVueを使いたい人、コンポーネント志向でアトミックに実装したい人など、様々な用途に応じたテンプレートの書き方(&コンパイルの方法)を用意してくれています。

そのためテンプレートの書き方とコンパイルの種類については仕様がやや複雑で、公式ドキュメントだけでは理解が難しくけっこうハマりどころだと感じている(事実ずっぽりハマりました)ので、知見を残しておこうと思います。

※この記事を書いている時点でのVueのバージョンは2.2.1です。

おさらい Vue.jsの利用方法3パターン

まずVueの利用方法の確認から。

  1. <script>埋め込み
  2. vue-cli
  3. npmモジュール(webpack, browserify, rollup等の利用)

の3つがありますという単純なお話なので、知ってる場合は読み飛ばしてください。

1. <script>埋め込み

<script src="https://unpkg.com/vue"></script>

これでグローバルにVueが用意されます。よくあるHTMLにscript直読込させる方法です。プロダクト開発には向かないですが、軽くVueを試すのには十分ですね。

2. vue-cli

Vueを使ったSPAをつくる環境を用意してくれるcliです。ビルド用のスクリプトやエントリーポイントになるHTMLなどが一括で生成されます。Reactで言うcreate-react-appのようなもの…というとかえって分かりにくい?

以下のコマンドを叩きます。

$ npm install -g vue-cli               # まずはcliのグローバルインストール
$ vue init webpack <project_name>      # webpackを利用したプロジェクトを生成する
$ cd <project_name>                    # プロジェクトのディレクトリに移動して…
$ npm install                          # 依存関係のあるファイルを落としてきて…
$ npm run dev                          # ビルド

公式に「Node.js ベースのツールについて精通していない場合、初心者が vue-cli で始めることは推奨しません」と明記された手法です。たしかにビルド周りの前提知識なしに使うのは難しそうですし、前提知識ある人の多くは自前でビルド環境用意しそうなもので、非推奨であり不人気な気がします😳

3. npmモジュール

SPAをつくろうと思ったらやっぱりこの方法に行き着きますよね。行き着きました。

$ npm install -S vue
$ npm install -D vue-loader         # webpackでビルドする場合
$ npm install -D vueify             # browserifyでビルドする場合
$ npm install -D rollup-plugin-vue  # rollupでビルドする場合

ユーザー各々のビルド環境にvueを組み込めるよう、webpackbrowserifyrollupとモジュールバンドラごとにvueのコンパイラが用意されています。

以降、実用例が多いと勝手に思っているwebpackを使い自前でビルド環境を用意する前提で、テンプレートの実装方法とコンパイル方法について話を進めていきます。

Vueテンプレートの実装方法

ここから本題。
Vueのテンプレートの実装方法は大きく3種類あります。

1. templateオプションを使う

Vue1.xからあった記法で、コンポーネントオブジェクト内にtemplateをキーに定義します。

Vue.component(hello-world, {
  template: <p>Hello! </p>’  // x-template等でテンプレートを分割する場合も含む
})

2. render関数を使う

Reactライクな書き方…と言っていいんですかね、微妙なとこですね。個人的には可読性が低くてあまり実用的とは思えない方法です。

Vue.component(hello-world, {
  render: function (createElement) {
    return createElement('p', this.name);
  }
})

3. 単一ファイルコンポーネントの<template></template>を使う

.vueの拡張子で表現される単一ファイルコンポーネント。テンプレートとスタイルとスクリプトをひとまとめのコンポーネントとしてカプセル化したものです。
参考: 単一ファイルコンポーネント - Vue.js

<template>
    <p>Hello! </p>
</template>

<script>
    // 省略
</script>

この3つの書き方には、「render関数に変換する(コンパイルする)タイミング」の違いがあります。

  render関数への変換
(コンパイル)
変換のタイミング
1.templateオプション 必要 JITコンパイル時
2.render関数 不要 -
3.単一ファイルコンポーネント 必要 プリコンパイル時

②はそもそもrender関数を利用しているので変換の必要がないのですが、上述のとおり実用性が低いので無視。となると、実用手段は①と③でしょうか。つまりテンプレートの実装方法は、単一ファイルコンポーネントを使うかどうかとどのタイミングでコンパイルするかの2つのポイントから選ぶということになりそうです。

プリコンパイルとJITコンパイル

コンパイルのタイミングには、Webpackなどで事前にされるプリコンパイルと、実行時にコンパイルするJustInTimeコンパイルの2種類があります。上述のとおり、テンプレートの実装方法によりどのタイミングでコンパイル(render関数への変換)を行うかが異なります。

単一ファイルコンポーネントをつかっている場合

Webpack等によりプリコンパイルされる時にrender関数への変換がなされます。そのためbundleされたファイルにはrender関数に変換済みの自前ロジックとvue本体(vue.js)が含まれることになります。   実行時にコンパイルする必要がないので高速で、vue本体にも余計なものが含まれないので軽量です。

templateオプションをつかっている場合

プリコンパイルでは変換されず、ブラウザにてJITコンパイルされる時render関数へ変換されます。そのためWebpack等でビルドしていたとしても、ブラウザで実行するときに(内部的に)コンパイルが走り変換が行われます。   そのため、実行速度がやや遅くなります。Vue公式ではこう言っています。

これは十分高速ですが、アプリケーションのパフォーマンスが重要な場合は避けるのが最善です。

また、bundleされたファイルにはvue本体 + コンパイラというコンパイラを内包するvue.jsが含まれることになります(そりゃそうですね、実行時にコンパイルしてもらうんですから)。そのためvue.jsの容量がやや大きくなります。

比較

  render関数への変換 読み込むVue
単一ファイルコンポーネント プリコンパイル時 vueロジック本体
(ランタイム限定ビルド されたVue.js)
templateオプション JITコンパイル時 vueロジック本体 + コンパイラ
(完全ビルド されたVue.js)

完全ビルドとランタイム限定ビルドの読み込み方

templateの書き方によってコンパイルのタイミングが違い、それにより読み込むVue.jsにも差が出ることがわかりました。次はそれぞれをどうやって読み込むかです。

vueはデフォルトでランタイム限定ビルド(されたVue.js)を読み込みます[^*1]
完全ビルド(されたVue.js)を読み込むように変更するには以下の修正が必要です。

webpackの場合はwebpack.config.jsに追記

module.exports = {
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
}

browserifyの場合はpackage.jsonに追記

{
  "browser": {
    "vue": "vue/dist/vue.common.js"
  }
}

rollup.jsの場合はrollup.config.jsに追記

const alias = require('rollup-plugin-alias')
rollup({
  plugins: [
    alias({
      'vue': 'vue/dist/vue.esm.js'
    })
  ]
})

まとめ

テンプレートの実装 render関数への変換 必要なvueモジュール
単一ファイルコンポーネント プリコンパイル時 ランタイム限定
templateオプション JITコンパイル時 完全

軽量&高速なランタイム限定をつかいたいけど単一ファイルコンポーネントはいやだ

そんな人はいませんか、私です。
Riot.jsなんかも単一ファイル形式ですけど流行りなんですかね。個人的にはテンプレートは別ファイルに切り出したいのですが…。しかも速度とファイル容量を良くしたいからランタイム限定ビルドを使いたい。そんな場合は分離できるそうです。
参考:単一ファイルコンポーネント 関心の分離について

ちなみに私はテンプレートだけ別ファイルにしました。

軽量&高速なランタイム限定をつかいたいし1系の書き方を踏襲させろ

そんな人いませんか、欲張りさんめ。
templateオプションは使いたいつつプリコンパイルも使う。できるみたいですがやったことありません。
参考: GitHub - ktsn/vue-template-loader: Vue.js 2.0 template loader for webpack

おしまい

長くなりました。
分かりにくい部分や間違っている部分があれば是非@aloerina_までご連絡ください。