最近個人のサービスのフロントエンド周りをタイトル通りwebpack + SimpackerからVite + Vite Railsに移行したので対応したこととかをメモ
前提事項
- Vite v.3.2系のHMRは使わない構成になります
- フロントエンド系のライブラリはVue.js v3系、TypeScriptを利用しています。
- Railsは v7.0系、Vite Railsはv3.0系を利用しています
- Viteで既存をファイルをbuildしてpublic/pack配下に吐き出すようにする(webpack -> Vite)
- ViteでbuildしたファイルをRailsで読み込む(Simpacker -> Vite Rails)
- おわりに
- 参考
Viteで既存をファイルをbuildしてpublic/pack
配下に吐き出すようにする(webpack -> Vite)
Viteの導入方法や各種設定の詳しい説明は省きますが、私は以下のようなvite.config.ts
を作成してapp/javascript/packs
配下のエントリーをViteでbuildし、webpack + simpacker時代と同じようにpublic/packs
or public/packs-test
に出力するようにしました。(buildの設定は既存のwebpackの構成によって異なるので自身の環境に合わせて変更してください)
import { defineConfig } from "vitest/config"; import vue from "@vitejs/plugin-vue"; import EnvironmentPlugin from "vite-plugin-environment"; import * as glob from "glob"; import * as path from "path"; /** * 指定したentryのルートディレクトリ配下のjsまたはtsファイルのファイル名とパスのobjectを取得 * 例) * - /entries/foo.ts => { foo: "/entries/foo.ts" } * - /entries/bars/bar.ts => { "bars/bar": "/entries/bars/bar.ts" } * @param {string} entryRoot entryのルートディレクトリ */ const getEntries = (entryRoot) => { const result = []; const filePaths = glob.sync(`${entryRoot}/**/*.{js,ts,html}`); filePaths.forEach((filePath) => { const dirName = filePath.replace(entryRoot, "").replace(path.basename(filePath), ""); result[`${dirName}${path.basename(filePath, path.extname(filePath))}`] = filePath; }); return result; }; const JAVASCRIPT_ENTRY_PATH = "./app/javascript/packs/"; const outDirPath = (mode: string) => (mode === "test" ? "public/packs-test" : "public/packs"); export default defineConfig(({ command, mode }) => { return { build: { manifest: "manifest.json", // vite_rubyはmanifest.jsonを固定で参照するので名称を固定 rollupOptions: { input: { ...getEntries(JAVASCRIPT_ENTRY_PATH) }, }, copyPublicDir: false, // public配下の既存ファイルがpublic/packs配下にコピーされないようにした。 outDir: outDirPath(mode), // modeでテスト環境ではpublic/packs-testにbuildするようにした assetsDir: "", // public/packs/assets配下にbuildされないようにassetsDirには空白を設定 }, css: { postcss: "postcss.config.js", }, plugins: [ vue(), EnvironmentPlugin({ NODE_ENV: "" }), ], resolve: { alias: { vue: "vue/dist/vue.esm-bundler.js", "@js": `${__dirname}/app/javascript`, "@css": `${__dirname}/app/javascript/stylesheets`, }, }, }; });
webpackからViteに移行する際に発生したエラー等
requireが使えずにbuild時にエラーになる
requireを使って画像等を読み込んでいる部分がエラーなるのでimportする方針に変更し、
画像のpathの解決が明示的にしとかないのルート/
で参照してしまうのでmoduleを作ってpacks配下を明示するようにしました。
多分この辺が影響してそう(?)
https://ja.vitejs.dev/guide/assets.html#new-url-url-import-meta-url
++ import noImage from "@js/assets/noimage.png"; ++ import { imageUrl } from "@js/utils/common/ImageUrl"; ++ return imageUrl(noImage as string); -- return require("@js/assets/noimage.png") as string
export const imageUrl = (image: string) => { const nodeENV = process.env.NODE_ENV; const envPrefix = nodeENV == "test" ? "packs-test" : "packs"; return `/${envPrefix}${image}`; };
ViteでbuildしたファイルをRailsで読み込む(Simpacker -> Vite Rails)
Viteで吐き出したファイルを読み込むにはVite Railsを使うと諸々いい感じに実装できるので便利です💎✨(導入方法等は公式ガイドを参照いただければと思います)
※当初以下の参考にSimpacker経由でViteでbuildしたファイルを読み込もうとしましたが、cssのchunk周りがViteでは調整が難しく利用が複雑になってしまいそうだったのでVite Railsを素直に使う方針としました。
HMRを使わないのであれば設定はシンプルでconfig/simpacker.yml
をconfig/vite.json
に置き換えて、
{ "all": { "sourceCodeDir": "app/javascript", "watchAdditionalPaths": [] }, "production": { "publicOutputDir": "packs" }, "development": { "publicOutputDir": "packs" }, "test": { "publicOutputDir": "packs-test" } }
既存のjavascript_pack_tag
等を置換するのが大変そうに思ったので以下のようなapp/helpers/vite_pack_helper.rb
を用意してApplicationHelper
にincludeすることでViewへの変更は行わずにVite RailsのHelperに移譲するようにしました。
Vite RailsはHMRの利用が前提のためかmanifestの読み込みが初回起動時に行われなかったので、開発環境では明示的にViteRuby.instance.manifest.refresh
を呼び出しmanifestを再読込するようにしました。
# frozen_string_literal: true module VitePackHelper include ViteRails::TagHelpers def javascript_pack_tag(*names, typescript: true, **options) refresh_manifest unless cache_manifest? paths = names.map { |name| entry_path(name) } return vite_typescript_tag(*paths, **options) if typescript vite_javascript_tag(*paths, **options) end def stylesheet_pack_tag(*names, **options) refresh_manifest unless cache_manifest? paths = names.map { |name| entry_path(name) } vite_stylesheet_tag(*paths, **options) end def image_pack_tag(name, **options) refresh_manifest unless cache_manifest? vite_image_tag(assets_path(name), **options) end private def javascript_root_path 'app/javascript/' end def entry_path(name, entry_dir: 'packs') File.join(javascript_root_path, entry_dir, name) end def assets_path(name, assets_dir: 'assets') File.join(javascript_root_path, assets_dir, name) end def refresh_manifest ViteRuby.instance.manifest.refresh end def cache_manifest? # NOTE: vite_rubyはHRM前提からかリクエスト時にmanifestのrefreshは行なわれなので開発環境では明示的に行う。 !Rails.env.development? end end
おわりに
esbuild-loader
を使っていたこともあり、そこまでbuild速度とかは変わらなかったのですが、webpackかViteに乗り換えると依存パッケージが私の個人サービスでも10個程度減らせたのと、VitestやHistoireといった他ライブラリと設定を共有できるのが、非常に管理しやすくて良いですね⚡️
Vite Railsのおかげで、意外と既存との差分も少なくwebpack + Simpackerから乗り換えられるのも体験が良かったです💎✨