Rails7×Svelte でWebアプリ開発 part1 (環境設定、簡単な表示まで)

# はじめに

Svelte というフロントエンドフレームワークはご存じでしょうか？　
Rails7からはTurboなどHotwireからJavaScriptを描かずともモダンなSPA風なページも作れるようにもなりましたね

個人的にフロントエンドフレームワークとしてReactは好きですし、他にもVue.jsなどは有名ですね！

https://2022.stateofjs.com/ja-JP/libraries/front-end-frameworks/

そんなフロントエンドフレームワークの最近のトレンドを見てみると...
いろいろな分野で上位に来ている「Svelte」　

今回はこちらについて紹介とRailsと組み合わせてWebアプリを開発してみた記録を書かせていただきます

# Svelte についての紹介

https://svelte.jp/

- 読み方は「すべると」　みたいです
- コンパイラで仮想DOMは使用しない
    - Reactなどは仮想DOMを使用している
    - ビルド時にアプリの変更箇所を探す

# Svelteを使ってRailsアプリを開発してみる

Railsアプリを作成してフロントエンドをSvelteに設定してみましょう。
gemをインストールして作成する方法で試してみます。
調べみるといくつかgemは見つかりました

- [vite_rails](https://github.com/ElMassimo/vite_ruby/tree/main)
- [svelte-rails](https://github.com/nning/svelte-rails)

ただ、 svelte-railsについては　2020年ごろで開発が止まっていて2022年にアーカイブされている状態です。
試しにrails7で検証してみましたが、ビルドしてみると以下のようなエラーが起きるので上手くいきませんでした。

```
bundle exec rails svelte:install
rails aborted!
NameError: uninitialized constant Svelte::InstallGenerator::Webpacker (NameError)

      Webpacker.config.source_entry_path.relative_path_from(::Rails.root)
```

rails6等でビルドすれば上手くいくのかもしれないですが、アーカイブされているgemを今から使うのは色々リスクが伴うと思うので今から始めると言う人は vite_rails からviteを下地にsvelteを使ってみるのが良いと思います


今回諸々の検証バージョンは以下の通りです

```
$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-darwin20]
$ rails -v
Rails 7.0.8
$ bundle -v
Bundler version 2.4.18
```

基本的に上記READMEの手順をもとに進めていきます。Railsアプリを１から作るときデフォルトでJavaScriptのコードなどSvelteとの競合が起きる気がして面倒そうだったので `--minimal` オプションつけて最低限のRailsアプリの作成まで実行することに(javaScriptのみ要らなそうであれば `-j` とかでも良いかもです)

## 初期設定


参考) https://way-too-mainstream.vercel.app/articles/using-svelte-typescript-in-rails-7-vite-tutorial

```
$ rails new svelte-sandbox --minimal
$ cd svelte-sandbox
$ echo "gem 'vite_rails'" >> Gemfile
$ bundle install
$ bundle exec vite install
```

## vite周りの設定

```
$ yarn add -D vite-plugin-full-reload
```

`vite.config.ts` に以下の行を追加

```diff_tsx
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/**/*']),
  ],
})
```

## svelteを追加

svelte のマウントするコードは一旦jsで作成しますが、ts対応させておきます

```
$ yarn add -D @sveltejs/vite-plugin-svelte @tsconfig/svelte svelte svelte-check svelte-preprocess tslib typescript
```

`package.json` に以下の行を追加

```diff_tsx
{
+  "type": "module",
  "devDependencies": {
...
```

以下のファイルをRails project rootに作成

```svelte.config.js
import sveltePreprocess from 'svelte-preprocess'

export default {
  preprocess: sveltePreprocess()
}
```

```tsconfig.node.json
{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node"
  },
  "include": ["vite.config.ts"]
}
```

```tsconfig.json
{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "resolveJsonModule": true,
    "baseUrl": ".",
    "allowJs": true,
    "checkJs": true,
    "isolatedModules": true
  },
  "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.svelte"],
  "exclude": ["./public/**/*", "svelte.config.js",],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

`vite.config.ts` に以下の行を追加

```diff_tsx
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import FullReload from 'vite-plugin-full-reload'
+ import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [
    RubyPlugin(),
    FullReload(['config/routes.rb', 'app/views/**/*']),
+    svelte(),
  ],
})

```


## svelte を使う

topページを作成します

```
$ bin/rails generate controller tops index
```

svelte コンポーネント、マウントjsコードを追加

```app/frontend/components/TopsPage.svelte
<script>
</script>

<p>Hello Svelte!!</p>
```

```app/frontend/entrypoints/tops-page.js
import TopsPage from '../components/TopsPage.svelte';

new TopsPage({
  target: document.getElementById('tops-page'),
});
```


`#tops-page` にマウントできるようになったため `app/views/tops/index.html.erb` にコンポーネント を描画するコードを追加します

```diff_erb
<h1>Tops#index</h1>
<p>Find me in app/views/tops/index.html.erb</p>
+ <%= content_tag :div, '', id: 'tops-page' %>
+ <%= vite_javascript_tag 'tops-page' %>
```

## 動作確認

実際にサーバーを立ち上げてtops#index を確認してみましょう

```
$ bin/rails s
$ bin/vite dev
```

![tops-index.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226348/ad63b9d3-9388-e3bb-a390-be0ada6a9b27.png)

erb ファイルで書かれている pタグの下にsvelteコンポーネントが表示されていますね！

# 最後に

　次の記事では svelteにデータを送信したり、細かい設定など変更していきたいと思います