💎

RailsからWebpackerを剥がして、Vite RubyでVue3 + TypeScriptを導入する

2022/09/11に公開

はじめに

Vite は Vue.js 開発者の Evan You 氏によって開発されたフロントエンドのビルドツールです。
Vue.js はもちろん、React や Svelte もサポートされています。

既存の Rails プロジェクトに Vue.js3 と TypeScript を導入するにあたって、Rails 自体の脱 Webpacker 化の流れもあり、代替を検討していたところ、Vite が候補に上がりました。
ちょうどVite Rubyというライブラリが良さげだったので、試してみることにしました。

前提

  • Webpacker を使用している Rails6 プロジェクト
  • Rails を API サーバー専用とするのではなく、Rails の view 内に Vue コンポーネントを埋め込む
    • APIサーバーにするのがメジャーだとは思いますが、いきなりこの方式にするのは影響が大きすぎるので、まずは部分的に導入していくことを想定しています
  • Rails6 + Vue.js3 + TypeScript
  • 複数エントリーポイントを持つ

手順

事前準備:Webpacker導入済のRailsプロジェクトを用意する

  • ディレクトリ作成・bundler初期化

    mkdir Vue3-TypeScript-with-Vite-Rails
    cd Vue3-TypeScript-with-Vite-Rails
    bundle init
    
  • Gemfile書き換え

    gem 'rails', '~> 6.1.0' # 追加
    
  • Railsプロジェクト作成

    bundle install
    bundle exec rails _6.1.0_ new . -d mysql --skip-test
    # Gemfileを上書きするか聞かれるので、Yを入力
    
  • Webpackerインストール

    rails install:webpacker
    
  • database.ymlを設定してrails db:createを実行

    設定例
    # MySQL. Versions 5.5.8 and up are supported.
    #
    # Install the MySQL driver
    #   gem install mysql2
    #
    # Ensure the MySQL gem is defined in your Gemfile
    #   gem 'mysql2'
    #
    # And be sure to use new-style password hashing:
    #   https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html
    #
    default: &default
      adapter: mysql2
      encoding: utf8mb4
      pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
      username: root
      password: password
      socket: /tmp/mysql.sock
    
    development:
      <<: *default
      database: vue_with_webpack_development
    
    # Warning: The database defined as "test" will be erased and
    # re-generated from your development database when you run "rake".
    # Do not set this db to the same as development or production.
    test:
      <<: *default
      database: vue_with_webpack_test
    
    # As with config/credentials.yml, you never want to store sensitive information,
    # like your database password, in your source code. If your source code is
    # ever seen by anyone, they now have access to your database.
    #
    # Instead, provide the password or a full connection URL as an environment
    # variable when you boot the app. For example:
    #
    #   DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
    #
    # If the connection URL is provided in the special DATABASE_URL environment
    # variable, Rails will automatically merge its configuration values on top of
    # the values provided in this file. Alternatively, you can specify a connection
    # URL environment variable explicitly:
    #
    #   production:
    #     url: <%= ENV['MY_APP_DATABASE_URL'] %>
    #
    # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
    # for a full overview on how database connection configuration can be specified.
    #
    production:
      <<: *default
      database: vue_with_webpack_production
      username: vue_with_webpack
      password: <%= ENV['VUE_WITH_WEBPACK_DATABASE_PASSWORD'] %>
    

Vite Ruby導入

  • vite_railsインストール

    • Gemfileに下記追加

      gem 'vite_rails'
      
    • 下記を実行

      bundle install
      bundle exec vite install
      
      • bundle exec vite installコマンドにより下記を実行してくれます
        • bin/viteを作成
        • vite, vite-plugin-rubyのパッケージをインストール
        • 設定ファイル(vite.config.ts, config/vite.json)を追加
        • サンプルのエントリーポイントを作成
          • Webpacker導入済(app下にjavascriptディレクトリがある場合)
            • エントリーポイントはapp/javascript/entrypoint/application.js
          • 上記以外の場合
            • エントリーポイントはapp/frontend/entrypoint/application.js
        • application.html.erb に上記エントリーポイントのヘルパータグを挿入
  • Full Reload設定

    • viewが変更された際にホットリロードする設定を行います。

      yarn add -D vite-plugin-full-reload
      
      // vite.config.ts
      import { defineConfig } from "vite";
      import RubyPlugin from "vite-plugin-ruby";
      // これを追加
      import FullReload from "vite-plugin-full-reload";
      
      export default defineConfig({
        plugins: [
          RubyPlugin(),
          // これを追加
          FullReload(["config/routes.rb", "app/views/**/*"], { delay: 100 }),
        ],
      });
      
  • bin/vite devでサーバーを起動できればOK

Vue.js3, TypeScript導入

  • 参考

  • Vue.js3, TypeScript関連のパッケージと、ViteのVue.js用プラグインをインストール

    yarn add -D @vitejs/plugin-vue @vue/compiler-sfc typescript vue@next
    
  • vite.config.jsにVue.jsプラグインの設定を追加

    import { defineConfig } from "vite";
    import RubyPlugin from "vite-plugin-ruby";
    import FullReload from "vite-plugin-full-reload";
    // これを追加
    import vue from "@vitejs/plugin-vue";
    
    export default defineConfig({
      plugins: [
        RubyPlugin(),
        FullReload(["config/routes.rb", "app/views/**/*"], { delay: 100 }),
        // これを追加
        vue(),
      ],
    });
    
  • tsconfig.jsonを追加する

    {
      "compilerOptions": {
        "target": "ESNext",
        "useDefineForClassFields": true,
        "module": "ESNext",
        "moduleResolution": "Node",
        "strict": true,
        "jsx": "preserve",
        "sourceMap": true,
        "resolveJsonModule": true,
        "isolatedModules": true,
        "esModuleInterop": true,
        "lib": ["ESNext", "DOM"],
        "skipLibCheck": true,
        "allowSyntheticDefaultImports": true
      },
      "include": ["**/*.ts", "**/*.d.ts", "**/*.tsx", "**/*.vue"],
      "exclude": [
        "node_modules"
      ]
    }
    
    
  • Vueコンポーネントを作成

    <!-- app/javascript/components/Test.vue -->
    <template>
      <p>{{ state.message }}</p>
    </template>
    
    <script lang="ts">
    import { defineComponent } from "vue";
    export default defineComponent({
      name: "Test",
      setup() {
        return {
          state: {
            message: "Hello Vue3 + TypeScript!",
          },
        };
      },
    });
    </script>
    
    <style scoped>
    p {
      font-size: 2em;
      text-align: center;
    }
    </style>
    
  • エントリーポイントを作成して、コンポーネントをマウント

    • Webpackerでapp/javascript/packs以下に既にエントリーポイントを作成している場合は、app/javascript/entrypoints以下に移動します。
      • このとき、必ずentrypoints直下にjs or tsファイルを配置してください。
        • entrypoints以下のディレクトリ構成がネストされていても、バンドルの出力先ではそのネスト構造が保持されず、一律でpublic/vite/assets(developの場合はpublic/vite-dev/assets)直下に出力されます。
        • そのため、ネスト構造でファイル名に重複がある場合、viewから正しく呼び出せません。この問題を回避するため、entrypoints直下に一意のファイル名となるよう配置しましょう。
    // app/javascript/entrypoints/test.ts
    import { createApp } from "vue";
    import Test from "../components/Test.vue";
    
    document.addEventListener("DOMContentLoaded", () => {
      createApp(Test).mount("#test");
    });
    
  • エントリーポイントをviewで呼び出し

    • 今回は複数エントリーポイントを持つ前提としているので、使用したいVueコンポーネントを表示するerbファイル内で呼び出しています
    • 単一エントリーポイントの場合は、app/view/layouts/application.html.erbで呼び出します。
    <%= vite_client_tag %>
    <%= vite_javascript_tag 'test' %>
    
    <div id="test"></div>
    
  • サーバーを起動し、コンポーネントが表示されていればOKです。

    bin/vite dev & rails s
    

    スクリーンショット 2022-09-11 23 09 38

参考

今回使用したリポジトリです。
記事内容よりも作業を進めて、APIをコンポーネントを実装して簡易的なつぶやきリストを作っています。
https://github.com/shu-illy/Vue3-TypeScript-with-Vite-Rails

なお、コンポーネントのUIに関しては、Vue.js3の学習に使ったこちらのUdemy講座から流用させて頂いています。
https://www.udemy.com/course/vue3-typescript/

最後に

最初はWebpackerを剥がしてWebpackでVue.js3とTypeScriptを導入しようとしていました。
が、フロントエンド経験が浅い僕にとってはwebpack.config.js周りの設定が煩雑でかなりハマってしまいました。
そこでViteを使うことにしたのですが、Vite Rubyのおかげで驚くほどあっさり導入できました。

Webpackerのように設定ファイルを抽象化されているわけでもないですし、Webpackerほど諸々の依存関係をRailsに支配されているわけでもなさそう(?)なので、ひとまずRailsでViteを使う時にはVite Rubyでいいのではないかと思っています。

参考になれば幸いです。

Discussion