Vite

Vite は、JavaScript プロジェクトのための強力で高性能、かつ拡張可能な開発環境です。Remix のバンドル機能を向上させ、拡張するために、代替コンパイラとして Vite をサポートするようになりました。将来的には、Vite が Remix のデフォルトコンパイラになります。

クラシックRemixコンパイラ vs. Remix Vite

remix buildremix dev CLIコマンドでアクセスし、remix.config.js で設定される既存のRemixコンパイラは、現在「クラシック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設定ドキュメントを参照してください。

vite-config: (This needs a URL to be added here. Replace with the actual link to the Vite config documentation.)

Cloudflare

Cloudflareを使い始めるには、cloudflare テンプレートを使用できます。

npx create-remix@latest --template remix-run/remix/templates/cloudflare

Cloudflareアプリをローカルで実行するには、2つの方法があります。

# Vite
remix vite:dev
 
# Wrangler
remix vite:build # アプリをビルドしてからwranglerを実行します
wrangler pages dev ./build/client

Viteはより優れた開発エクスペリエンスを提供しますが、WranglerはNodeではなくCloudflareのworkerdランタイムでサーバーコードを実行することで、Cloudflare環境により近いエミュレーションを提供します。

template-cloudflare: template-cloudflareへのリンク cloudflare-workerd: cloudflare-workerdへのリンク

Cloudflare プロキシ

Vite で Cloudflare 環境をシミュレートするために、Wrangler は ローカル workerd バインディングへの Node プロキシを提供します。 Remix の Cloudflare プロキシプラグインは、これらのプロキシを自動的に設定します。

vite.config.ts
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;
  // ... さらに loader コードをここに記述します...
};

これらのプロキシの詳細については、Cloudflare の getPlatformProxy ドキュメント を参照してください。

バインディング

Cloudflare リソースのバインディングを構成するには:

wrangler.toml ファイルを変更するたびに、バインディングを再生成するために wrangler types を実行する必要があります。

その後、context.cloudflare.env を介してバインディングにアクセスできます。 たとえば、MY_KV としてバインドされたKV 名前空間 の場合:

app/routes/_index.tsx
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のロードコンテキストがすべて同じ方法で拡張されます

load-context.ts
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",
  };
};

アプリの実行方法によっては、ロードコンテキストの拡張に一貫性がなくなってしまうため、getLoadContext両方のCloudflare Proxyプラグインとfunctions/[[path]].tsのリクエストハンドラーに渡す必要があります。

まず、Viteを実行する際のロードコンテキストを拡張するために、Viteの設定でCloudflare ProxyプラグインにgetLoadContextを渡します。

vite.config.ts
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を渡します。

functions/[[path]].ts
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
 
// @ts-ignore - サーバービルドファイルは`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)をクライアントビルドディレクトリとして使用していました。

Viteの動作に合わせてRemixプロジェクトのデフォルト構造を調整するため、ビルド出力パスが変更されました。個別のassetsBuildDirectoryserverBuildDirectoryオプションに代わって、デフォルトで"build"となる単一のbuildDirectoryオプションが追加されました。これは、デフォルトではサーバーがbuild/serverに、クライアントがbuild/clientにコンパイルされることを意味します。

これにより、次の設定のデフォルト値も変更されました。

  • publicPathViteの"base"オプションに置き換えられ、デフォルトは"/"ではなく"/"になりました。
  • serverBuildPathserverBuildFileに置き換えられ、デフォルトは"index.js"になりました。このファイルは、設定されたbuildDirectory内のサーバーディレクトリに書き込まれます。

RemixがViteに移行する理由の1つは、Remixを採用する際に学習する内容を減らすためです。 つまり、使用したい追加のバンドル機能については、Remixのドキュメントではなく、ViteのドキュメントViteプラグインコミュニティを参照する必要があります。

Viteには、既存のRemixコンパイラには組み込まれていない多くの機能プラグインがあります。 このような機能を使用すると、既存のRemixコンパイラではアプリをコンパイルできなくなるため、Viteを今後専ら使用する予定の場合のみ使用してください。

移行

Viteの設定

👉 開発依存関係としてViteをインストールする

npm install -D vite

Remixは現在Viteプラグインなので、Viteに接続する必要があります。

👉 Remixアプリのルートにあるremix.config.jsvite.config.tsに置き換える

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
 
export default defineConfig({
  plugins: [remix()],
});

サポートされているRemixの設定オプション のサブセットは、プラグインに直接渡す必要があります。

vite.config.ts
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の型を参照しましょう。

vite/clientによって提供されるモジュール型は、@remix-run/devに暗黙的に含まれるモジュール型と互換性がないため、TypeScriptの設定でskipLibCheckフラグを有効にする必要があります。Viteプラグインがデフォルトのコンパイラになれば、Remixはこのフラグを必要としなくなります。

👉 tsconfig.jsonの更新

tsconfig.jsontypesフィールドを更新し、skipLibCheckmodulemoduleResolutionが正しく設定されていることを確認してください。

tsconfig.json
{
  "compilerOptions": {
    "types": ["@remix-run/node", "vite/client"],
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "Bundler"
  }
}

👉 remix.env.d.tsの更新/削除

remix.env.d.ts内の以下の型宣言を削除してください。

remix.env.d.ts
- /// <reference types="@remix-run/dev" />
- /// <reference types="@remix-run/node" />

remix.env.d.tsが空になった場合は、削除してください。

rm remix.env.d.ts

Remix App Serverからの移行

開発環境でremix-serve(または-cフラグなしのremix dev)を使用していた場合は、新しい最小限の開発サーバーに切り替える必要があります。 これはRemix Viteプラグインに組み込まれており、remix vite:devを実行すると動作します。

Remix Viteプラグインは、グローバルNodeポリフィルをインストールしないため、remix-serveに依存していた場合は、自分でインストールする必要があります。これを行う最も簡単な方法は、Vite設定の先頭でinstallGlobalsを呼び出すことです。

Vite開発サーバーのデフォルトポートはremix-serveとは異なるため、同じポートを維持したい場合は、Viteのserver.portオプションで設定する必要があります。

また、サーバーにはbuild/server、クライアントアセットにはbuild/clientという新しいビルド出力パスに更新する必要があります。

👉 devbuildstartスクリプトを更新する

package.json
{
  "scripts": {
    "dev": "remix vite:dev",
    "build": "remix vite:build",
    "start": "remix-serve ./build/server/index.js"
  }
}

👉 Vite設定でグローバルNodeポリフィルをインストールする

vite.config.ts
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開発サーバーポートを設定する(オプション)

vite.config.ts
export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [remix()],
});

カスタムサーバーの移行

開発でカスタムサーバーを使用していた場合、Viteのconnectミドルウェアを使用するようにカスタムサーバーを編集する必要があります。これにより、開発中にアセットリクエストと最初のレンダリングリクエストがViteに委任され、カスタムサーバーを使用する場合でも、Viteの優れた開発者エクスペリエンスを活用できます。

その後、開発中に仮想モジュール"virtual:remix/server-build"をロードして、Viteベースのリクエストハンドラーを作成できます。

また、サーバーコードを更新して、新しいビルド出力パスを参照する必要があります。サーバービルドの場合はbuild/server、クライアントアセットの場合はbuild/clientです。

たとえば、Expressを使用している場合、次のように実行できます。

👉 server.mjsファイルの更新

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)
);

👉 builddevstartスクリプトの更新

package.json
{
  "scripts": {
    "dev": "node ./server.mjs",
    "build": "remix vite:build",
    "start": "cross-env NODE_ENV=production node ./server.mjs"
  }
}

必要に応じて、TypeScriptでカスタムサーバーを作成することもできます。その後、tsxtsmなどのツールを使用してカスタムサーバーを実行できます。

tsx ./server.ts
node --loader tsm ./server.ts

ただし、これを行うと、サーバーの初回起動時に顕著な遅延が発生する可能性があることに注意してください。

Cloudflare Functions の移行

Remix Vite プラグインは、フルスタックアプリケーション向けに特別に設計されたCloudflare Pagesのみを正式にサポートしています。Cloudflare Workers Sitesとは異なります。現在 Cloudflare Workers Sites を使用している場合は、Cloudflare Pages 移行ガイドを参照してください。

👉 Vite開発サーバーのミドルウェアを正しく上書きするには、remixプラグインのcloudflareDevProxyVitePluginを追加してください!

vite.config.ts
import {
  vitePlugin as remix,
  cloudflareDevProxyVitePlugin,
} from "@remix-run/dev";
import { defineConfig } from "vite";
 
export default defineConfig({
  plugins: [cloudflareDevProxyVitePlugin(), remix()],
});

Cloudflare アプリケーションでは、Remix 設定の server フィールドを設定して、キャッチオール Cloudflare Function を生成している可能性があります。 Vite では、この間接参照は不要になりました。 代わりに、Express やその他のカスタムサーバーと同様に、Cloudflare 用のキャッチオールルートを直接作成できます。

👉 Remix のキャッチオールルートを作成する

functions/[[page]].ts
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.env ではなく context.cloudflare.env を使用して、バインディングと環境変数にアクセスする

開発中は主に Vite を使用しますが、Wrangler を使用してアプリのプレビューとデプロイを行うこともできます。

詳細については、このドキュメントのCloudflareセクションを参照してください。

👉 package.json スクリプトを更新する

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を更新するには、以下のようにします。

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

blues-stack: (Blues Stackへのリンクをここに挿入)

パスエイリアスの設定

Remixコンパイラは、パスエイリアスの解決にtsconfig.jsonpathsオプションを利用します。Remixコミュニティでは一般的に、~appディレクトリのエイリアスとして定義するために使用されます。

Viteはデフォルトではパスエイリアスを提供しません。この機能に依存していた場合、vite-tsconfig-pathsプラグインをインストールして、Remixコンパイラの動作に合わせて、tsconfig.jsonからのパスエイリアスをViteで自動的に解決することができます。

👉 vite-tsconfig-pathsのインストール

npm install -D vite-tsconfig-paths

👉 Vite設定へのvite-tsconfig-pathsの追加

vite.config.ts
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の副作用インポート、PostCSS、CSS Modulesなどを含む、CSSバンドル機能を組み込みでサポートしています。Remix Viteプラグインは、バンドルされたCSSを関連するルートに自動的にアタッチします。

@remix-run/css-bundleパッケージは、Viteを使用する場合、そのcssBundleHrefエクスポートは常にundefinedになるため、冗長です。

👉 @remix-run/css-bundleをアンインストールする

npm uninstall @remix-run/css-bundle

👉 cssBundleHrefへの参照を削除する

app/root.tsx
- 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を接続するためだけに使用されている場合、完全に削除できます。

app/root.tsx
- 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 }]
-     : []),
- ];

これは、CSS バンドリング の他の形式(例:CSS Modules、CSS サイドエフェクトインポート、Vanilla Extract など)には必要ありません。

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 }
  ];
}

css-bundling: (CSS バンドリングへのリンク) regular-css: (通常のCSS参照へのリンク) vite-url-imports: (Viteの?urlインポートへのリンク)

PostCSS を介して Tailwind を有効化

プロジェクトで Tailwind CSS を使用している場合、まず、Vite によって自動的に検出される PostCSS 設定ファイルがあることを確認する必要があります。 これは、Remix の tailwind オプションが有効になっている場合、Remix コンパイラでは PostCSS 設定ファイルが不要だったためです。

👉 PostCSS 設定ファイルが存在しない場合は追加し、tailwindcss プラグインを含めます

postcss.config.mjs
export default {
  plugins: {
    tailwindcss: {},
  },
};

プロジェクトに既に PostCSS 設定ファイルがある場合は、tailwindcss プラグインがまだ存在しない場合は追加する必要があります。 これは、Remix コンパイラが Remix の tailwind 設定オプション が有効になっている場合、このプラグインを自動的に含めていたためです。

👉 tailwindcss プラグインが不足している場合は、PostCSS 設定ファイルに追加します

postcss.config.mjs
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

👉 Tailwind CSS のインポートを移行します

links 関数で Tailwind CSS ファイルを参照している場合Tailwind CSS のインポート文を移行する必要があります。

tailwind: (Tailwind CSSへのリンク) postcss: (PostCSSへのリンク) tailwind-config-option: (Remixのtailwind設定オプションへのリンク) regular-css: (通常のCSS参照方法へのリンク) fix-up-css-imports-referenced-in-links: (CSSインポート文の修正方法へのリンク)

Vanilla Extract プラグインの追加

Vanilla Extract を使用している場合は、Viteプラグインを設定する必要があります。

👉 公式の Vanilla Extract Viteプラグイン をインストールします

npm install -D @vanilla-extract/vite-plugin

👉 Vite設定にVanilla Extractプラグインを追加します

vite.config.ts
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プラグインの追加

vite.config.ts
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: https://mdxjs.com/ rollup: https://rollupjs.org/ mdx-rollup-plugin: https://github.com/mdx-js/mdx/tree/main/packages/rollup

MDXフロントマターサポートの追加

Remixコンパイラでは、MDXのフロントマターを定義できました。この機能を使用していた場合、Viteではremark-mdx-frontmatterを使用して実現できます。

👉 必要なRemarkフロントマタープラグインをインストールする

npm install -D remark-frontmatter remark-mdx-frontmatter

👉 RemarkフロントマタープラグインをMDX Rollupプラグインに渡す

vite.config.ts
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に名前変更する

app/posts/first-post.mdx
  ---
  title: Hello, World!
  ---
 
- # {attributes.title}
+ # {frontmatter.title}

👉 コンシューマー向けにMDXのattributesエクスポート名をfrontmatterに名前変更する

app/routes/posts/first-post.tsx
  import Component, {
-   attributes,
+   frontmatter,
  } from "./posts/first-post.mdx";
MDXファイルの型定義

👉 env.d.ts*.mdxファイルの型を追加する

env.d.ts
/// <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;
}
Map MDX frontmatter to route exports

Remixコンパイラでは、frontmatterにheadersmetahandleルートエクスポートを定義することができました。このRemix固有の機能は、remark-mdx-frontmatterプラグインでは明らかにサポートされていません。この機能を使用していた場合は、frontmatterをルートエクスポートに手動でマッピングする必要があります。

👉 MDXルートのfrontmatterをルートエクスポートにマッピングする

---
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ルートエクスポートを明示的にマッピングしているので、好きなfrontmatter構造を使用できます。

---
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 ルートのコレクションへのリンクを有効にするために設計されていました。この機能を使用していた場合は、glob インポート を介して 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,
});

glob-imports: (glob インポートへのリンクをここに挿入してください)

デバッグ

NODE_OPTIONS 環境変数 を使用してデバッグセッションを開始できます。

NODE_OPTIONS="--inspect-brk" npm run dev

その後、ブラウザからデバッガを接続できます。 たとえば、Chromeではchrome://inspectを開くか、開発ツールのNodeJSアイコンをクリックしてデバッガを接続できます。

node-options: (This needs a URL to be added if available. For example: https://nodejs.org/api/cli.html#cli_node_options)

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 パフォーマンスドキュメントでさらにヒントを確認することもできます!

vite-perf: (Vite パフォーマンスドキュメントへのリンクをここに挿入してください)

バンドル分析

バンドルを視覚化および分析するには、rollup-plugin-visualizer プラグインを使用できます。

vite.config.ts
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 プラグインの既知の問題 を調べて、他に同じ問題を抱えている人がいないか確認してください。

issues-vite: (GitHub の issues ページへのリンクをここに挿入)

HMR

ホットアップデートを期待しているのに、ページ全体が再読み込みされる場合は、ホットモジュール置換に関する議論 を参照して、React Fast Refreshの制限と一般的な問題の回避策について詳しく学んでください。

ESM / CJS

ViteはESMとCJSの両方の依存関係をサポートしていますが、ESM/CJSの相互運用で問題が発生することがあります。 通常、これは依存関係がESMを適切にサポートするように構成されていないことが原因です。 そして、彼らを責めるわけではありません。ESMとCJSの両方を適切にサポートするのは非常に難しいです

例のバグを修正する手順については、🎥 How to Fix CJS/ESM Bugs in Remixをご覧ください。

依存関係のいずれかが誤って構成されているかどうかを診断するには、publintまたはAre The Types Wrongを確認してください。 さらに、vite-plugin-cjs-interopプラグインを使用して、外部CJS依存関係のdefaultエクスポートに関する問題を解決できます。

最後に、Viteのssr.noExternalオプションを使用して、サーバーバンドルにバンドルする依存関係を明示的に構成し、RemixコンパイラのserverDependenciesToBundle(Remix Viteプラグインを使用)をエミュレートすることもできます。

modernizing-packages-to-esm: ここにリンクを挿入 how-fix-cjs-esm: ここにリンクを挿入 publint: ここにリンクを挿入 arethetypeswrong: ここにリンクを挿入 vite-plugin-cjs-interop: ここにリンクを挿入 ssr-no-external: ここにリンクを挿入 server-dependencies-to-bundle: ここにリンクを挿入

開発中のブラウザにおけるサーバーコードエラー

開発中にブラウザコンソールにサーバーコードを指し示すエラーが表示される場合、サーバー専用コードを明示的に分離する必要がある可能性があります。

例えば、次のようなものが見られる場合:

Uncaught ReferenceError: process is not defined

processのようなサーバー専用グローバル変数を期待する依存関係を取り込んでいるモジュールを突き止め、別の.serverモジュール内、またはvite-env-onlyを使用してコードを分離する必要があります。Viteは本番環境でコードをツリーシェイクするためにRollupを使用するため、これらのエラーは開発時のみ発生します。

その他のViteベースツール(例:Vitest、Storybook)でのプラグインの使用

Remix Viteプラグインは、アプリケーションの開発サーバーと本番ビルドでの使用のみを目的としています。VitestやStorybookなど、Viteの設定ファイルを使用する他のViteベースのツールもありますが、Remix Viteプラグインはこれらのツールでの使用を想定して設計されていません。現在、他のViteベースのツールで使用する場合には、プラグインを除外することをお勧めします。

Vitestの場合:

vite.config.ts
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の場合:

vite.config.ts
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を提供する必要がある場合もあります。たとえば、Vitestを使用する場合:

vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import react from "@vitejs/plugin-react";
import { defineConfig, loadEnv } from "vite";
 
export default defineConfig({
  plugins: [!process.env.VITEST ? remix() : react()],
  test: {
    environment: "happy-dom",
    // さらに、これはvitest実行中に".env.test"を読み込むためです
    env: loadEnv("test", process.cwd(), ""),
  },
});

開発中にドキュメントが再マウントされるとスタイルが消える

Reactがドキュメント全体をレンダリングする場合(Remixが行うように)、head要素に動的に要素が挿入されると問題が発生する可能性があります。ドキュメントが再マウントされると、既存のhead要素は削除され、完全に新しい要素に置き換えられ、Viteが開発中に挿入したstyle要素が削除されます。

これは既知のReactの問題であり、カナリアリリースチャンネルで修正されています。リスクを理解している場合は、アプリを特定のReactバージョンに固定し、パッケージオーバーライドを使用して、プロジェクト全体でこれが唯一のReactバージョンであることを確認できます。例:

package.json
{
  "dependencies": {
    "react": "18.3.0-canary-...",
    "react-dom": "18.3.0-canary-..."
  },
  "overrides": {
    "react": "18.3.0-canary-...",
    "react-dom": "18.3.0-canary-..."
  }
}

参考までに、これはNext.jsが内部的にReactのバージョン管理を処理する方法です。そのため、Remixがデフォルトで提供していないにもかかわらず、このアプローチは予想以上に広く使用されています。

Viteによって挿入されたスタイルに関するこの問題は、開発中のみ発生することに注意することが重要です。本番ビルドでは、静的CSSファイルが生成されるため、この問題は発生しません

Remixでは、この問題は、ルートルートのデフォルトコンポーネントエクスポートとそのErrorBoundaryおよび/またはHydrateFallbackエクスポート間でレンダリングが切り替わる場合に発生する可能性があります。これは、新しいドキュメントレベルのコンポーネントがマウントされるためです。

ハイドレーションエラーが原因で発生することもあります。これは、Reactがページ全体を最初から再レンダリングするためです。ハイドレーションエラーはアプリコードによって発生する可能性がありますが、ドキュメントを操作するブラウザ拡張機能によって発生する可能性もあります。

これはViteにとって重要です。なぜなら、開発中はViteがCSSインポートをJSファイルに変換し、副作用としてドキュメントにスタイルを挿入するためです。Viteは、静的CSSファイルの遅延読み込みとHMRをサポートするためにこれを行います。

たとえば、アプリに次のCSSファイルがあるとします。

app/styles.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チームの尽力に感謝しています。 ViteチームのMatias Capeletto、Arnaud Barré、Bjorn Luには特に感謝しています。

RemixコミュニティはViteサポートの調査を迅速に進めてくれ、その貢献に感謝しています。

最後に、他のフレームワークがViteサポートを実装した方法からインスピレーションを受けました。