Vite
Vite は、JavaScript プロジェクトのための強力で高性能で拡張性のある開発環境です。Remix のバンドリング機能を改善し拡張するために、私たちは今Viteをもう一つのコンパイラとしてサポートしています。近い将来、Viteがデフォルトのコンパイラになるでしょう。
クラシックなRemixコンパイラvsRemixのVite
既存のRemixコンパイラは、remix build
およびremix dev
CLIコマンドで利用でき、remix.config.js
で設定されています。これは「クラシックなRemixコンパイラ」と呼ばれるようになりました。
Remix Viteプラグインとremix vite:build
およびremix vite:dev
CLIコマンドを総称して「Remix Vite」と呼びます。
今後、特に記載がない限り、ドキュメントはRemix Viteの使用を前提とします。
はじめに
様々なVite ベースのテンプレートをご用意しています。
# 最小構成:
npx create-remix@latest
# Express:
npx create-remix@latest --template remix-run/remix/templates/express
# Cloudflare:
npx create-remix@latest --template remix-run/remix/templates/cloudflare
# Cloudflare Workers:
npx create-remix@latest --template remix-run/remix/templates/cloudflare-workers
これらのテンプレートには、Remix Viteプラグインが設定されたvite.config.ts
ファイルが含まれています。
設定
Remix Viteプラグインは、プロジェクトのルートにあるvite.config.ts
ファイルで設定されます。詳細はVite設定のドキュメントをご覧ください。
Cloudflare
Cloudflareを使い始めるには、cloudflare
テンプレートを使うと良いでしょう:
npx create-remix@latest --template remix-run/remix/templates/cloudflare
Viteでローカルで実行する2つの方法があります:
# Vite
remix vite:dev
# Wrangler
remix vite:build # アプリケーションを先にビルドしてから wrangler を実行
wrangler pages dev ./build/client
Viteはより良い開発体験を提供しますが、Wranglerはクラウドワーカーのworkerd
ランタイムで実行することで、Cloudflare環境をより忠実に模擬できます。
Cloudflareプロキシ
Viteでクラウドワーカー環境をシミュレーションするために、Wranglerはローカルのworkerd
バインディングへのNodeプロキシを提供しています。
Remix のCloudflareプロキシプラグインはこれらのプロキシを設定してくれます:
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remixCloudflareDevProxy(), remix()],
});
これにより、loader
やaction
関数内でcontext.cloudflare
から使えるようになります:
export const loader = ({ context }: LoaderFunctionArgs) => {
const { env, cf, ctx } = context.cloudflare;
// ... more loader code here...
};
Cloudflare のgetPlatformProxy
ドキュメントで、これらのプロキシについてさらに詳しく確認できます。
バインディング
ローカル開発でのViteやWranglerの設定にはwrangler.tomlを、デプロイ時にはCloudflareダッシュボードのCloudflare Pages バインディングを使います。
wrangler.toml
ファイルを変更したら、必ずwrangler types
を実行してバインディングを再生成する必要があります。
その後、context.cloudflare.env
からバインディングにアクセスできます。
例えば、KVネームスペースがMY_KV
としてバインドされている場合:
export async function loader({
context,
}: LoaderFunctionArgs) {
const { MY_KV } = context.cloudflare.env;
const value = await MY_KV.get("my-key");
return json({ value });
}
ロードコンテキストの拡張
ロードコンテキストに追加のプロパティを追加したい場合は、共有モジュールからgetLoadContext
関数をエクスポートする必要があります。これにより、Vite、Wrangler、Cloudflare Pagesでロードコンテキストが一貫して拡張されます:
import { type AppLoadContext } from "@remix-run/cloudflare";
import { type PlatformProxy } from "wrangler";
// `wrangler.toml`を使ってバインディングを設定する場合、
// `wrangler types`がそれらのバインディングの型を
// グローバルな`Env`インターフェイスに生成します。
// `wrangler.toml`が存在しない場合でもタイプチェックが通るよう、
// 空のインターフェイスを定義しています。
interface Env {}
type Cloudflare = Omit<PlatformProxy<Env>, "dispose">;
declare module "@remix-run/cloudflare" {
interface AppLoadContext {
cloudflare: Cloudflare;
extra: string; // 拡張
}
}
type GetLoadContext = (args: {
request: Request;
context: { cloudflare: Cloudflare }; // 拡張前のロードコンテキスト
}) => AppLoadContext;
// Vite、Wrangler、Cloudflare Pagesで互換性のある共有実装
export const getLoadContext: GetLoadContext = ({
context,
}) => {
return {
...context,
extra: "stuff",
};
};
functions/[[path]].ts
の両方でgetLoadContext
を渡す必要があります。そうしないと、アプリの実行方法によってロードコンテキストの拡張が一貫していません。
まず、Vite設定でCloudflareプロキシプラグインにgetLoadContext
を渡して、Viteの実行時にロードコンテキストを拡張します:
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { getLoadContext } from "./load-context";
export default defineConfig({
plugins: [
remixCloudflareDevProxy({ getLoadContext }),
remix(),
],
});
次に、Wranglerや Cloudflare Pagesへのデプロイ時にロードコンテキストを拡張するため、functions/[[path]].ts
のリクエストハンドラにgetLoadContext
を渡します:
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// @ts-ignore - the server build file is generated by `remix vite:build`
import * as build from "../build/server";
import { getLoadContext } from "../load-context";
export const onRequest = createPagesFunctionHandler({
build,
getLoadContext,
});
クライアントとサーバーコードの分離
Viteは、クラシックなRemixコンパイラとは異なる方法でクライアントとサーバーコードを扱います。詳細はクライアントとサーバーコードの分離のドキュメントをご覧ください。
新しいビルド出力パス
Viteがpublic
ディレクトリを扱う方法と、既存のRemixコンパイラとは大きな違いがあります。Viteはpublic
ディレクトリからファイルをクライアントビルドディレクトリにコピーしますが、Remixコンパイラはpublic
ディレクトリを変更せず、別のサブディレクトリ(public/build
)をクライアントビルドディレクトリとして使っていました。
Remix のデフォルトプロジェクト構造をViteの動作に合わせるため、ビルド出力パスが変更されました。assetsBuildDirectory
とserverBuildDirectory
のオプションが廃止され、単一のbuildDirectory
オプションに統一されました。デフォルトでは、サーバーはbuild/server
に、クライアントはbuild/client
にビルドされます。
これに伴い、以下のデフォルト設定も変更されています:
- publicPathはViteの"base"オプションに置き換えられ、デフォルトは
"/"
になりました (以前は"/build/"
でした)。 - serverBuildPathは
serverBuildFile
に置き換えられ、デフォルトは"index.js"
になりました。このファイルは、設定したbuildDirectory
内のサーバーディレクトリに書き込まれます。
RemixがViteに移行する理由の1つは、Remixを採用する際の学習コストを下げることです。 つまり、追加の bundling 機能を使う場合は、Remix のドキュメントではなく ViteのドキュメントとViteプラグインコミュニティを参照してください。
Viteには、既存のRemixコンパイラには含まれていない多くの 機能とプラグインがあります。 これらの機能を使う場合は、以降Viteのみを使うことを意図している必要があります。そうでない場合、既存のRemixコンパイラではアプリをコンパイルできなくなります。
移行
Viteのセットアップ
👉 Viteを開発依存関係としてインストール
npm install -D vite
Remixはもはやスタンドアロンのコンパイラではなく、Viteプラグインに過ぎないので、Viteにフックアップする必要があります。
👉 Remix アプリのルートに vite.config.ts
を作成し、remix.config.js
を削除
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remix()],
});
サポートされているRemix設定オプションのサブセットは、プラグインに直接渡す必要があります:
export default defineConfig({
plugins: [
remix({
ignoredRouteFiles: ["**/*.css"],
}),
],
});
HMR & HDR
Viteは HMR やその他の開発機能のためのロバストなクライアントサイドランタイムを提供するので、<LiveReload />
コンポーネントは不要になりました。Remix Viteプラグインを使って開発する際は、<Scripts />
コンポーネントがViteのクライアントサイドランタイムやその他の開発用スクリプトを自動的に含むようになります。
👉 <LiveReload/>
を削除し、<Scripts />
を残す
import {
- LiveReload,
Outlet,
Scripts,
}
export default function App() {
return (
<html>
<head>
</head>
<body>
<Outlet />
- <LiveReload />
<Scripts />
</body>
</html>
)
}
TypeScript統合
Viteは様々なファイルタイプのインポートを扱いますが、既存のRemixコンパイラとは異なる方法で行う場合があるので、@remix-run/dev
の古い型ではなく、vite/client
の型を参照する必要があります。
vite/client
が提供する型は@remix-run/dev
に暗黙的に含まれる型と互換性がないため、TypeScript設定でskipLibCheck
フラグを有効にする必要があります。将来的にViteプラグインがデフォルトのコンパイラになれば、Remixはこのフラグを必要としなくなるでしょう。
👉 tsconfig.json
を更新
tsconfig.json
のtypes
フィールドを更新し、skipLibCheck
、module
、moduleResolution
が適切に設定されていることを確認してください。
{
"compilerOptions": {
"types": ["@remix-run/node", "vite/client"],
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
👉 remix.env.d.ts
の更新/削除
remix.env.d.ts
から以下の型宣言を削除します
rm remix.env.d.ts
Remix App Serverからの移行
開発時にremix-serve
を使っていた場合 (またはremix dev
に-c
フラグをつけていなかった場合)、新しいミニマルなdevサーバーに切り替える必要があります。
これは Remix Viteプラグインに組み込まれており、remix vite:dev
を実行すると自動的に起動します。
Remix ViteプラグインはグローバルなNode polyfillをインストールしないので、remix-serve
に依存していた場合は自分でインストールする必要があります。これは、Vite設定の冒頭でinstallGlobals
を呼び出すのが一番簡単です。
Viteのdevサーバーのデフォルトポートはremix-serve
とは異なるので、同じポートを維持したい場合は、Viteのserver.port
オプションで設定する必要があります。
また、新しいビルド出力パスに合わせて更新する必要があります。サーバーはbuild/server
に、クライアントアセットはbuild/client
にビルドされます。
👉 dev
、build
、start
スクリプトを更新
{
"scripts": {
"dev": "remix vite:dev",
"build": "remix vite:build",
"start": "remix-serve ./build/server/index.js"
}
}
👉 グローバルなNode polyfillをVite設定に追加
import { vitePlugin as remix } from "@remix-run/dev";
+import { installGlobals } from "@remix-run/node";
import { defineConfig } from "vite";
+installGlobals();
export default defineConfig({
plugins: [remix()],
});
👉 Viteのdevサーバーポートを設定(オプション)
export default defineConfig({
server: {
port: 3000,
},
plugins: [remix()],
});
カスタムサーバーの移行
カスタムサーバーを開発時に使っていた場合、カスタムサーバーを編集して Viteのconnect
ミドルウェアを使う必要があります。
これにより、開発中にアセットリクエストと初期レンダリングリクエストをViteにデリゲートできるので、Viteの優れたDXを活用できます。
その上で、開発時に"virtual:remix/server-build"
という仮想モジュールをロードして、Viteベースのリクエストハンドラを作成できます。
また、サーバーコードを更新して新しいビルド出力パスを参照する必要がああります。サーバービルドはbuild/server
に、クライアントアセットはbuild/client
にビルドされます。
Expressを使っていた場合の例は以下の通りです。
👉 server.mjs
ファイルを更新
import { createRequestHandler } from "@remix-run/express";
import { installGlobals } from "@remix-run/node";
import express from "express";
installGlobals();
const viteDevServer =
process.env.NODE_ENV === "production"
? undefined
: await import("vite").then((vite) =>
vite.createServer({
server: { middlewareMode: true },
})
);
const app = express();
// アセットリクエストを処理
if (viteDevServer) {
app.use(viteDevServer.middlewares);
} else {
app.use(
"/assets",
express.static("build/client/assets", {
immutable: true,
maxAge: "1y",
})
);
}
app.use(express.static("build/client", { maxAge: "1h" }));
// SSRリクエストを処理
app.all(
"*",
createRequestHandler({
build: viteDevServer
? () =>
viteDevServer.ssrLoadModule(
"virtual:remix/server-build"
)
: await import("./build/server/index.js"),
})
);
const port = 3000;
app.listen(port, () =>
console.log("http://localhost:" + port)
);
👉 build
、dev
、start
スクリプトを更新
{
"scripts": {
"dev": "node ./server.mjs",
"build": "remix vite:build",
"start": "cross-env NODE_ENV=production node ./server.mjs"
}
}
TypeScriptでカスタムサーバーを書きたい場合は、tsx
やtsm
などのツールを使ってサーバーを実行できます:
tsx ./server.ts
node --loader tsm ./server.ts
ただし、サーバー起動時の初期遅延が若干目立つ可能性があります。
Cloudflareファンクションの移行
Remix Viteプラグインは、フルスタックアプリケーション向けに設計されたCloudflare Pagesのみをサポートしています。Cloudflare Workers Sitesをご利用の場合は、Cloudflare Pagesへの移行ガイドをご覧ください。
👉 remix
プラグインの前にcloudflareDevProxyVitePlugin
を追加して、Viteのdevサーバーのミドルウェアを適切に上書きする!
import {
vitePlugin as remix,
cloudflareDevProxyVitePlugin,
} from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [cloudflareDevProxyVitePlugin(), remix()],
});
アプリケーションでは、Remix設定のserver
フィールドを使ってCatchallのCloudflareファンクションを生成していた可能性があります。
Viteでは、このような間接的な方法は不要になりました。
代わりに、Express用やその他のカスタムサーバー用と同じように、Cloudflare用の Catchall ルートを直接記述できます。
👉 Remixの Catchall ルートを作成
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
// @ts-ignore - the server build file is generated by `remix vite:build`
import * as build from "../build/server";
export const onRequest = createPagesFunctionHandler({
build,
});
👉 バインディングと環境変数はcontext.cloudflare.env
から、context.env
ではなく
主に開発時にはViteを使いますが、Wranglerでプレビューやデプロイすることもできます。
詳細は、このドキュメントのCloudflareセクションをご覧ください。
👉 package.json
のスクリプトを更新
{
"scripts": {
"dev": "remix vite:dev",
"build": "remix vite:build",
"preview": "wrangler pages dev ./build/client",
"deploy": "wrangler pages deploy ./build/client"
}
}
ビルド出力パスの参照の更新
既存のRemixコンパイラのデフォルトオプションを使っていた場合、サーバーはbuild
に、クライアントはpublic/build
にビルドされていました。Viteがpublic
ディレクトリを扱う方法と、既存のRemixコンパイラとは異なるため、これらの出力パスが変更されました。
👉 ビルド出力パスの参照を更新
- サーバーは現在、デフォルトで
build/server
にビルドされます。 - クライアントは現在、デフォルトで
build/client
にビルドされます。
例えば、Blues StackのDockerfileを更新する場合:
-COPY --from=build /myapp/build /myapp/build
-COPY --from=build /myapp/public /myapp/public
+COPY --from=build /myapp/build/server /myapp/build/server
+COPY --from=build /myapp/build/client /myapp/build/client
パスエイリアスの設定
Remixコンパイラは、tsconfig.json
のpaths
オプションを活用してパスエイリアスを解決していました。Remixコミュニティでは、app
ディレクトリに~
というエイリアスを定義するのが一般的でした。
Viteはデフォルトではパスエイリアスを提供していません。この機能に依存していた場合は、vite-tsconfig-pathsプラグインを使ってRemixコンパイラと同様の動作を実現できます:
👉 vite-tsconfig-paths
をインストール
npm install -D vite-tsconfig-paths
👉 Vite設定にvite-tsconfig-paths
を追加
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [remix(), tsconfigPaths()],
});
@remix-run/css-bundle
の削除
Viteには、CSS Side Effect インポート、PostCSS、CSS Modules など、CSS バンドリングの機能が組み込まれています。Remix Viteプラグインは、自動的にバンドルされたCSSをリレバントなルートに適用します。
@remix-run/css-bundle
cssBundleHref
エクスポートは常にundefined
になるからです。
👉 @remix-run/css-bundle
をアンインストール
npm uninstall @remix-run/css-bundle
👉 cssBundleHref
の参照を削除
- import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
export const links: LinksFunction = () => [
- ...(cssBundleHref
- ? [{ rel: "stylesheet", href: cssBundleHref }]
- : []),
// ...
];
ルートのlinks
関数がcssBundleHref
を設定するためだけに使われている場合は、完全に削除できます。
- import { cssBundleHref } from "@remix-run/css-bundle";
- import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
- export const links: LinksFunction = () => [
- ...(cssBundleHref
- ? [{ rel: "stylesheet", href: cssBundleHref }]
- : []),
- ];
links
で参照されるCSSインポートを修正
links
関数でCSSを参照している場合、対応するCSSインポートをViteの明示的な?url
インポート構文に更新する必要があります。
👉 links
で使われているCSSインポートに?url
を追加
.css?url
インポートには、Vite v5.1以降が必要です
-import styles from "~/styles/dashboard.css";
+import styles from "~/styles/dashboard.css?url";
export const links = () => {
return [
{ rel: "stylesheet", href: styles }
];
}
TailwindにPostCSSを使って有効化
プロジェクトが Tailwind CSS を使っている場合、Remix コンパイラでは tailwind
オプションを有効にするだけで設定が不要でした。
しかし、Viteの場合は明示的にPostCSSの設定ファイルが必要です。
👉 PostCSSの設定ファイルがない場合は追加し、tailwindcss
プラグインを含める
export default {
plugins: {
tailwindcss: {},
},
};
プロジェクトにすでにPostCSSの設定ファイルがある場合は、tailwindcss
プラグインが含まれていない可能性があります。
これは、Remixコンパイラの tailwind
設定オプションが有効だった場合、自動的に含まれていたためです。
👉 PostCSSの設定にTailwindプラグインが含まれていない場合は追加
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
👉 Tailwind CSS インポートの移行
links
関数でTailwind CSSファイルを参照している場合、Tailwind CSSインポートステートメントを移行する必要があります。
Vanilla Extractプラグインの追加
Vanilla Extractを使っている場合は、Viteプラグインを設定する必要があります。
👉 Vanilla Extractの公式Viteプラグインをインストール
npm install -D @vanilla-extract/vite-plugin
👉 Vite設定にVanilla Extractプラグインを追加
import { vitePlugin as remix } from "@remix-run/dev";
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remix(), vanillaExtractPlugin()],
});
MDXプラグインの追加
MDXを使っている場合、ViteのプラグインAPI はRollupのプラグインAPI の拡張なので、公式のMDX Rollupプラグインを使うべきです:
👉 MDX Rollupプラグインをインストール
npm install -D @mdx-js/rollup
RemixプラグインはJavaScriptやTypeScriptファイルを処理することを期待しているので、他の言語 (MDXなど) からの変換は先に行う必要があります。 この場合、MDXプラグインをRemixプラグインの前に配置する必要があります。
👉 Vite設定にMDX Rollupプラグインを追加
import mdx from "@mdx-js/rollup";
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [mdx(), remix()],
});
MDXフロントマターのサポート
Remixコンパイラでは、MDXでフロントマターを定義できました。この機能を使っていた場合、Viteではremark-mdx-frontmatterを使って実現できます。
👉 必要なRemarkフロントマタープラグインをインストール
npm install -D remark-frontmatter remark-mdx-frontmatter
👉 MDX RollupプラグインにRemarkフロントマタープラグインを渡す
import mdx from "@mdx-js/rollup";
import { vitePlugin as remix } from "@remix-run/dev";
import remarkFrontmatter from "remark-frontmatter";
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
mdx({
remarkPlugins: [
remarkFrontmatter,
remarkMdxFrontmatter,
],
}),
remix(),
],
});
Remixコンパイラでは、フロントマターのエクスポート名はattributes
でしたが、フロントマタープラグインのデフォルトのエクスポート名はfrontmatter
です。エクスポート名を設定することは可能ですが、代わりにアプリコードを更新して、デフォルトのエクスポート名を使うことをお勧めします。
👉 MDXファイル内のMDX attributes
エクスポートをfrontmatter
に変更
---
title: Hello, World!
---
- # {attributes.title}
+ # {frontmatter.title}
👉 MDX attributes
エクスポートをfrontmatter
に変更(consumer側)
import Component, {
- attributes,
+ frontmatter,
} from "./posts/first-post.mdx";
MDXファイルの型定義
👉 env.d.ts
にMDX用の型を追加
/// <reference types="@remix-run/node" />
/// <reference types="vite/client" />
declare module "*.mdx" {
let MDXComponent: (props: any) => JSX.Element;
export const frontmatter: any;
export default MDXComponent;
}
MDXのフロントマターをルートエクスポートにマッピング
Remixコンパイラでは、フロントマターからheaders
、meta
、handle
ルートエクスポートを定義できました。
この Remix 固有の機能は、remark-mdx-frontmatter
プラグインではサポートされていません。
この機能を使っていた場合は、手動でフロントマターをルートエクスポートにマッピングする必要があります:
👉 MDXルートでフロントマターをルートエクスポートにマッピング
---
meta:
- title: My First Post
- name: description
content: Isn't this awesome?
headers:
Cache-Control: no-cache
---
export const meta = frontmatter.meta;
export const headers = frontmatter.headers;
# Hello World
注意点として、MDXのルートエクスポートを明示的にマッピングすることで、好きな形式のフロントマターを使うことができるようになります。
---
title: My First Post
description: Isn't this awesome?
---
export const meta = () => {
return [
{ title: frontmatter.title },
{
name: "description",
content: frontmatter.description,
},
];
};
# Hello World
MDXファイル名の使用の更新
Remixコンパイラでは、すべてのMDXファイルからfilename
エクスポートが提供されていました。
これは主に、MDXルートのコレクションにリンクする際に使われていました。
この機能を使っていた場合、Viteではグローブインポートを使うと、ファイル名をモジュールにマッピングする便利なデータ構造が得られます。
これにより、個々のMDXファイルをすべて手動でインポートする必要がなくなります。
例えば、posts
ディレクトリ内のすべてのMDXファイルをインポートするには:
const posts = import.meta.glob("./posts/*.mdx");
これは、以下のように個々にインポートするのと同等です:
const posts = {
"./posts/a.mdx": () => import("./posts/a.mdx"),
"./posts/b.mdx": () => import("./posts/b.mdx"),
"./posts/c.mdx": () => import("./posts/c.mdx"),
// etc.
};
すぐにMDXファイルをインポートしたい場合は、以下のようにできます:
const posts = import.meta.glob("./posts/*.mdx", {
eager: true,
});
デバッグ
NODE_OPTIONS
環境変数を使ってデバッグセッションを開始できます:
NODE_OPTIONS="--inspect-brk" npm run dev
次に、ブラウザからデバッガーを接続できます。
例えば、Chromeでは chrome://inspect
を開いたり、開発者ツールのNodeJSアイコンをクリックするとデバッガーに接続できます。
vite-plugin-inspect
vite-plugin-inspect
は、各Viteプラグインがコードをどのように変換しているか、およびプラグインごとの所要時間を表示してくれます。
パフォーマンス
Remixには--profile
フラグがあり、パフォーマンスプロファイリングが可能です。
remix vite:build --profile
--profile
モードで実行すると、.cpuprofile
ファイルが生成されます。これをspeedscope.appにアップロードして分析できます。
開発中も、p + enter
を押してプロファイリングセッションを開始/停止できます。
開発サーバーの起動時にプロファイリングを行いたい場合は、--profile
フラグを使います:
remix vite:dev --profile
Viteのパフォーマンスドキュメントも参考にしてください!
バンドル分析
バンドルを視覚化、分析するには、rollup-plugin-visualizerプラグインを使えます:
import { vitePlugin as remix } from "@remix-run/dev";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
remix(),
// `emitFile`は Remix が複数のバンドルを構築するため必要
visualizer({ emitFile: true }),
],
});
remix vite:build
を実行すると、各バンドルにstats.html
ファイルが生成されます:
build
├── client
│ ├── assets/
│ ├── favicon.ico
│ └── stats.html 👈
└── server
├── index.js
└── stats.html 👈
stats.html
をブラウザで開いて、バンドルの分析ができます。
トラブルシューティング
デバッグとパフォーマンスのセクションで、一般的なトラブルシューティングのヒントを確認してください。 また、githubのRemix Viteプラグインの既知の問題で、同様の問題が報告されていないかチェックしてください。
HMR
ホットアップデートを期待しているのにフルページリロードが発生する場合は、ホットモジュール置換に関する議論を確認し、一般的な問題への対処方法を学んでください。
ESM / CJS
ViteはESMとCJSの両方のディペンデンシーをサポートしますが、時々ESM/CJSの相互運用性の問題に遭遇することがあります。 通常、これはディペンデンシーがESMをうまくサポートしていないためです。 そしてそれは非常に難しい問題なので、私たちも彼らを非難するつもりはありませんESMとCJSの両立は本当に難しい。
例の不具合を修正する方法の詳細については、🎥 How to Fix CJS/ESM Bugs in Remixをご覧ください。
ディペンデンシーが正しく設定されていないかどうかを診断するには、publintやAre The Types Wrongを使ってください。
さらに、Viteのssr.noExternal
オプションを使って、Remixコンパイラの serverDependenciesToBundle
と同様の動作をEmulateするこ
ともできます。
開発中にブラウザにサーバーコードのエラーが表示される
開発中にブラウザのコンソールにサーバーコードに関するエラーが表示される場合は、明示的にサーバー専用コードを分離する必要があります。 例えば、以下のようなエラーが表示された場合:
Uncaught ReferenceError: process is not defined
process
などのサーバー専用のグローバル変数を使っているモジュールを特定し、別の.server
モジュールやvite-env-only
を使って隔離する必要があります。
Viteはロールアップを使ってコードをツリーシェイクするので、これらのエラーは開発中にのみ発生します。
他のViteベースのツールでのプラグインの使用 (Vitest、Storybookなど)
Remix Viteプラグインは、アプリケーションの開発サーバーと本番ビルドでのみ使用することを意図しています。 一方、Vitest やStorybookなどの他のViteベースのツールもVite設定ファイルを使用しますが、Remix Viteプラグインはこれらのツールと連携するように設計されていません。 現時点では、他のViteベースのツールと併用する際は、プラグインを除外することをお勧めします。
Vitest の場合:
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig, loadEnv } from "vite";
export default defineConfig({
plugins: [!process.env.VITEST && remix()],
test: {
environment: "happy-dom",
// 加えて、vitest実行時に ".env.test" を読み込む
env: loadEnv("test", process.cwd(), ""),
},
});
Storybookの場合:
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
const isStorybook = process.argv[1]?.includes("storybook");
export default defineConfig({
plugins: [!isStorybook && remix()],
});
または、ツールごとに別のVite設定ファイルを使うこともできます。 Remixに特化したVite設定ファイルを使う例:
remix vite:dev --config vite.config.remix.ts
Remix Viteプラグインを提供しない場合は、Vite Plugin Reactを自分で提供する必要がある
ドキュメントがリマウントされるときにスタイルが消える問題
Remix では、ドキュメント全体をReactで描画しています。この際、head
要素に動的に要素が挿入されると問題が発生する可能性があります。ドキュメントが再マウントされると、既存のhead
要素が削除され新しいものに置き換わるため、Viteが開発中に注入したstyle
要素が削除されてしまうのです。
これは Reactの既知の問題で、canary版で修正されています。リスクを理解した上で、特定のReactバージョンにピンポイントでアプリを固定し、パッケージのオーバーライドを使ってプロジェクト全体で同じバージョンのReactを使うことができます。例:
{
"dependencies": {
"react": "18.3.0-canary-...",
"react-dom": "18.3.0-canary-..."
},
"overrides": {
"react": "18.3.0-canary-...",
"react-dom": "18.3.0-canary-..."
}
}
この Viteによって開発中に注入されたスタイルの問題は、本番ビルドでは発生しません。なぜなら、静的なCSSファイルが生成されるからです。
Remixでは、ルートコンポーネントのデフォルトエクスポートと、そのErrorBoundaryやHydrateFallbackエクスポートの間で描画が切り替わると、新しいドキュメントレベルのコンポーネントがマウントされるため、この問題が表面化する可能性があります。
hydrationエラーも同様の問題を引き起こす可能性があります。hydrationエラーはアプリのコードが原因である場合もありますし、ブラウザ拡張機能によってドキュメントが書き換えられることが原因になることもあります。
これがViteに関連するのは、開発時にViteがCSSインポートをJSファイルに変換し、それらを副作用としてドキュメントにインジェクションしているためです。これにより、遅延読み込みやCSSファイルのHMRをサポートしています。
例えば、アプリに以下のようなCSSファイルがあるとします:
* { margin: 0 }
開発時、このCSSファイルは以下のようなJavaScriptコードに変換されます:
import {createHotContext as __vite__createHotContext} from "/@vite/client";
import.meta.hot = __vite__createHotContext("/app/styles.css");
import {updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle} from "/@vite/client";
const __vite__id = "/path/to/app/styles.css";
const __vite__css = "*{margin:0}"
__vite__updateStyle(__vite__id, __vite__css);
import.meta.hot.accept();
import.meta.hot.prune(()=>__vite__removeStyle(__vite__id));
この変換は本番コードには適用されないため、この問題は開発時にのみ発生します。
開発時のWranglerエラー
Cloudflare Pagesを使っている場合、wrangler pages dev
から以下のようなエラーが発生することがあります:
ERROR: Your worker called response.clone(), but did not read the body of both clones.
This is wasteful, as it forces the system to buffer the entire response body
in memory, rather than streaming it through. This may cause your worker to be
unexpectedly terminated for going over the memory limit. If you only meant to
copy the response headers and metadata (e.g. in order to be able to modify
them), use `new Response(response.body, response)` instead.
これはWranglerの既知の問題です。
謝辞
Viteは素晴らしいプロジェクトで、Viteチームの皆様に感謝しています。 特に、Matias Capeletto、Arnaud Barré、Bjorn Luのようなメンバーからの指導に感謝します。
RemixコミュニティもすぐにViteサポートを探求してくれ、その貢献に感謝しています:
最後に、他のフレームワークがViteサポートをどのように実装したかにも触発されました: