Railsプロジェクトでvue.js + typescriptを使うためのts-loaderの設定

Webpacker gemを導入したRailsプロジェクトにtypescript+vue.jsを導入した時に調べたことのメモ記載しておきます。

TypeScript/vueのインストール/設定

既にプロジェクトにはWebpackerは導入済だったので、WebpackerのREADME通り

$ bin/rails webpacker:install:typescript
$ bin/rails webpacker:install:vue

と実行し、typescriptとvueをインストール/設定しました。その際、出力される ts-loader の設定ファイルは、

# /config/webpack/loaders/typescripts.js

const PnpWebpackPlugin = require('pnp-webpack-plugin')

module.exports = {
  test: /\.tsx?(\.erb)?$/,
  use: [
    {
      loader: 'ts-loader',
      options: PnpWebpackPlugin.tsLoaderOptions()
    }
  ]
}

のようになってましたが、このままではVueファイルを以下のようなSFC(単一ファイルコンポーネント)にした場合にうまくTypeScriptとして認識してくれずに、ブラウザで開くとJSエラーが発生していました。

# app/javascript/app.vue

<template>
  <div id="app">
    <p>{{ message }}</p>
  </div>
</template>

<script lang="ts">
export default {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

[エラー内容]

Property or method "message" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property

ts-loaderのドキュメントを参照し、オプションを指定するとうまく動くようになってくれました。

# /config/webpack/loaders/typescripts.js

const PnpWebpackPlugin = require('pnp-webpack-plugin')

module.exports = {
  test: /\.tsx?(\.erb)?$/,
  use: [
    {
      loader: 'ts-loader',
      options: PnpWebpackPlugin.tsLoaderOptions({
        appendTsSuffixTo: [/\.vue$/]
      })
    }
  ]
}

Vue-loaderの動き

Vue-loaderのドキュメントを読む

vue-loader はファイルを解析し、それぞれの言語ブロックを必要に応じて他の loader を通し、最終的に module.exports が Vue.js のコンポーネントオプションオブジェクトの CommonJS モジュールに変換します。

と記載されていて、 vue-loader を介して ts-loaderコンパイルしてくれるらしいことが分かります。

PnpWebpackPlugin.tsLoaderOptionsについて

上記の ts-loaderの設定ファイル( /config/webpack/loaders/typescripts.js )で、 PnpWebpackPlugin.tsLoaderOptions()のようにオプション指定されていて、これが何かも気になったので、調べてみました。

Pnpとは?

そもそも、先頭3文字の Pnp というのは何かというと、Yarn 2.0から導入された node_modulesに変わるパッケージの依存関係を解決するAPIであるPlug'n'Playの頭文字を取ったもの。 Plug'n'Play を導入すると、でかい node_modules の変わりに .pnp.jsという単一のファイルで済むようになり70%ほど速度が早くなるようです(参考

ただし、Webpackerのissueを見ると、2020/5/16時点ではまだ使える状態にはなさそうなので、恩恵を受けられる日が来るのを待ちましょう。

PnpWebpackPlugin.tsLoaderOptionsはなぜ要るのか?

Plug'n'Playが導入されると、Typescriptimport 文やRefarenceディレクティブが参照する箇所を node_modules から変えないといけないので、Plug'n'Playが有効になっている場合のみ、ts-loaderのオプションに

  • resolveModuleName
  • resolveTypeReferenceDirective

というオプションを渡して、TypeScriptのデフォルトの実装(import 文やRefarenceディレクティブ)と動きが変わるようにしてあげる必要があるようです。

PnpWebpackPlugin.tsLoaderOptionsの実装は以下のコードを確認しました。 https://github.com/arcanis/pnp-webpack-plugin/blob/659125cee0625f1a543bc1645e3917ad90857052/index.js#L157

参考までに、PnpWebpackPlugin.tsLoaderOptionsの実装の中の

の参照リンクも貼っておきます。

Webpacker Gem/ Loader Packageバージョン

  • Webpacker gem
    • 4.2.2
  • ts-loader
    • 7.0.4
  • vue-loader
    • 15.9.2

その他参考資料