まず前提として、
現時点で基本的には Vue を TSX で書けるようになっておりまだまだ苦労を伴う。
そんな TSX を Composition API と合わせて書いた時に感じた苦労話などを先日投稿、v-okinawa #3 でもリモートながら喋らせていただいてます。
v-okinawa #3 https://blog.nekohack.me/posts/enter-the-final-v-okinawa-in-2019
そして今日はその TSX を使って Vue を書くためのスタートアップについて書きました。
Vue を TS で書くとは、
これまで Vue を TS で書くためには素で書くか API を大きく分けると二択存在。
さらに細かく分けると以下 3 種類存在する。
- Vue.extend
- Class Component
- vue-property-decorator
当然これらを使っても TSX で書けるのですが、来たる Vue3 に向け Composition API 採用によって第一に書きやすいこと。
より迅速にスタートアップできること、そして TSX による厳密な型管理を実現できる。
あとは React と Vue の双方で TSX を書けるようになることは、互いに往き来しやすいというメリットも生まれそうです。
Webpack と Vue、そして TSX
まず基本から、Webpack をモジュールバンドラにした場合どんな感じで書けるのか。
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'ts-loader',
            options: {
              appendTsSuffixTo: [/\.vue$/],
            },
          },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.vue$/,
        use: 'vue-loader',
      },
    ],
  },
}
こんな感じで各種ローダを読み込むと最低限 Vue を TS で書けるようになる。
ここまではさくっと流した上で、TSX で書くための準備として Babel を設定する必要がある。
今回は babel-plugin-transform-vue-jsx を使いたいので。
plugins: ['transform-vue-jsx]
ルートコンポーネントである App.vue を App.tsx に置き換えます。
import Vue, { CreateElement, VNode } from 'vue'
export default Vue.extend({
  render(h: CreateElement): VNode {
    return (
      <div class="container">
        <transition name="fade">
          <router-view />
        </transition>
      </div>
    )
  },
})
これで Vue を TSX で書くための準備が整いますね。
<a class="link-preview" href="https://github.com/jiyuujin/vue-tsx-boilerplate">jiyuujin/vue-tsx-boilerplate</a>
詳しくはこちらのリポジトリを参照ください。
Vue CLI で TSX を使う
構築できたとはいえ実際のプロジェクト導入にあたって、Webpack を直接メンテすることは極力避けたいですね。ということで Vue CLI の中で TSX を使いましょう。
vue create vue-tsx-boilerplate
このように vue create すると @vue/cli-plugin-babel を確認できる。
JSX を JavaScript に変換してくれる babel-plugin-transform-vue-jsx が既に含まれている。
ではこれで TSX を書いて動くのか、残念ながら動きません。
その理由は createComponent を h にマッピングできていないから。これを解決するため、自動で createComponent を h にマッピングしてくれる babel-preset-vca-jsx を利用します。
presets: ['vca-jsx']
TSX のトランスパイル設定を忘れずに。
{
  "jsx": "preserve"
}
React 以外でも TSX を使えるようになります。
あとは型定義を拡張すること。
import Vue, { VNode } from 'vue';
import { ComponentRenderProxy } from '@vue/composition-api';
declare global {
  namespace JSX {
    // tslint:disable no-empty-interface
    interface Element extends VNode {}
    // tslint:disable no-empty-interface
    interface ElementClass extends ComponentRenderProxy {}
    interface ElementAttributesProperty {
      $props: any;
    }
    interface IntrinsicElements {
      [elem: string]: any;
    }
  }
}
ルートコンポーネントである App.vue を App.tsx に置き換えます。
import { createComponent, SetupContext } from '@vue/composition-api'
export default createComponent({
  setup(props: {}, ctx: SetupContext) {
    return () => (
      <div class="container">
        <transition name="fade">
          <router-view />
        </transition>
      </div>
    )
  },
})
これで Vue CLI でも TSX を使って書けるようになります。
<a class="link-preview" href="https://github.com/jiyuujin/vue-cli-tsx-boilerplate">jiyuujin/vue-cli-tsx-boilerplate</a>
詳しくはこちらのリポジトリを参照ください。
スタイルの適用
ここまで来ると次はスタイルですね。
実際に私自身が書いているお供に vue-styled-components の存在があります。
使い方こそ特筆すべき事項も無くて README を見て構築できるが react 同様 CSS in JS で書けるのですんなりと入っていけました。
<a class ="link-preview" href="https://www.npmjs.com/package/vue-styled-components">vue-styled-components</a>
ただし CSS in JS 自体向き不向きがあると思っているので必ずしもオススメはしません。
以下のようにスタイルをテンプレートに直接書く方法もあります。
{
  ;`
    <style jsx>
        .title {
            height: 100%;
        }
    </style>
`
}
この辺りは何が王道で、というのが自分自身でも分からないので教えて欲しいといった次第。
最後に、
色々とスタートアップについて書いてきました。
<script lang="tsx">
import { createComponent, SetupContext } from '@vue/composition-api'
export default createComponent({
    setup(props: {}, ctx: SetupContext) {
        return () => (
            //
        )
    }
})
</script>
無理に拡張子を TSX にする必要はなく、Vue ならではの書き方を踏襲することがとりあえず良さそうです。
来る 2020 年、早いうちにも Vue3 が正式に登場することになります (るでしょう)
Vue を TSX で書く元年になるとも思っているので、個人的にはこの年末年始にでも慣らしておこうかな。
