今天,我們很高興宣布在 Remix v2.7.0 中,對 Vite 的支援現已穩定!在 Remix Vite 初次不穩定版本發布之後,在過去幾個月中,我們在所有早期採用者和社群貢獻者的幫助下,一直努力改進和擴展它。
以下是我們一直在做的事情
讓我們來詳細分析自我們最初發布以來最重大的變更。
我們所做的最重大變更非常重要,我們將保留在稍後的文章中討論它對 React 生態系的影響。
簡而言之,Remix 現在支援建構純靜態網站,這些網站在生產環境中不需要 JavaScript 伺服器,同時保留 Remix 基於檔案的路由約定、自動程式碼分割、路由模組預先擷取、標頭標籤管理等優點。
這為 React Router 的使用者開啟了一條全新的遷移路徑,讓他們可以遷移到 Remix,而無需切換到伺服器渲染的架構,這對許多人來說甚至不是一個選項。對於任何希望未來在其 Remix 應用程式中引入伺服器的人來說,遷移路徑現在更加直接。
如需更多資訊,請查閱 SPA 模式文件。
React Router 支援為您的應用程式設定基名,允許您將整個應用程式巢狀在子路徑中,但此功能在 Remix 中明顯缺失。雖然可以透過手動為路由和連結加上前綴來解決此問題,但顯然不如設定單個組態值方便。
隨著轉向 Vite,由於 Vite 公開了自己的「base」選項,因此缺乏基名支援變得更加明顯。許多使用者錯誤地認為這會與 Remix 一起使用,但此選項實際上與 Remix 的「publicPath」選項相同。
為了避免這種混淆,不再有 publicPath
選項(您應該改用 Vite 的 base
選項),而且 Remix Vite 外掛程式現在有一個全新的 basename
選項。
因此,將您的 Remix 應用程式巢狀在您網站的子路徑中從未如此簡單,而無需觸及您的應用程式程式碼。
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
base: "/my-app/public/",
plugins: [
remix({
basename: "/my-app",
}),
],
});
在我們最初發布的 Remix Vite 不穩定版本中,Cloudflare Pages 的支援尚未完全就緒。Cloudflare 的 workerd
執行時間與 Vite 的 Node 環境完全分離,因此我們需要找出彌合此差距的最佳方法。
隨著 Remix Vite 變得穩定,我們現在提供一個內建的 Vite 外掛程式,用於在本地開發期間將 Cloudflare 的工具與 Remix 整合。
為了在 Vite 中模擬 Cloudflare 環境,Wrangler 提供 Node 代理,以連接到本地 workerd
繫結。Remix 的 cloudflareDevProxyVitePlugin
為您設定這些代理。
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 團隊合作,以確保 Remix 使用者獲得最佳體驗。未來,透過利用 Vite 新(仍在實驗階段)的 Runtime API,整合可能會更加順暢,敬請關注後續更新。
有關此功能的更多資訊,請查閱 Remix Vite + Cloudflare 文件。
對於那些一直在 Vercel 上執行 Remix 的人來說,您可能已經注意到 Vercel 允許您將伺服器組建分割成多個套件,其中不同的路由針對 無伺服器函數 和 邊緣函數。
您可能沒有意識到的是,此功能實際上是透過 Vercel 在其 Remix 組建器中使用的 Remix 分支實現的。
隨著轉向 Vite,我們希望確保不需要我們組建系統的另一個分支,因此我們一直與 Vercel 團隊合作,將此功能引入 Remix Vite。現在,任何人(而不僅僅是 Vercel 的使用者)都可以根據自己的喜好將其伺服器組建分割成多個套件。
非常感謝 Vercel,尤其是 Nathan Rajlich,為這項工作提供的幫助。有關此功能的更多資訊,請查閱 伺服器套件文件。
在調查 Vercel 對 Remix Vite 的支援時,我們清楚地意識到,我們需要一種方法,讓其他工具和託管提供者可以自訂 Vite 外掛程式的行為,而無需深入內部或執行自己的分支。為了支援此功能,我們引入了「預設值」的概念。
預設值只能做兩件事
預設值旨在發布到 npm 並在您的 Vite 組態中使用。
Vercel 預設值即將推出,我們很高興看到社群會提出其他哪些預設值,特別是因為預設值可以存取所有 Remix Vite 外掛程式選項,因此不僅僅限於託管提供者支援。
有關此功能的更多資訊(包括如何建立您自己的預設值的指南),請查閱 預設值文件。
Remix 允許您使用 .server.ts
副檔名命名檔案,以確保它們永遠不會意外地出現在用戶端上。然而,事實證明我們之前的實作與 Vite 的 ESM 模型不相容,因此我們不得不重新審視我們的方法。
相反,如果我們在用戶端程式碼路徑中匯入 .server.ts
檔案時,將其設為編譯時錯誤會如何?
我們之前的方法會導致執行階段錯誤,這些錯誤很容易滑入生產環境。在組建期間引發這些錯誤可防止它們影響真實使用者,同時為開發人員提供更快、更全面的回饋。我們很快意識到這好得多。
額外的好處是,由於我們已經在這個領域工作,我們決定新增對 .server
目錄(而不僅僅是檔案)的支援,使其易於將專案的整個部分標記為僅伺服器端。
如果您想更深入地了解此變更背後的原理,請查閱我們關於 在 Vite 中分離用戶端和伺服器程式碼的決策文件。
為了提高速度,Vite 會隔離延遲地編譯每個檔案。Vite 開箱即用地假設用戶端程式碼引用的任何檔案都是完全用戶端安全的。
Remix 會自動處理從路由檔案中移除 loader
、action
和 headers
匯出,確保它們始終對瀏覽器安全。但是,非 Remix 匯出呢?我們如何知道要從瀏覽器組建中移除哪些,而不僅僅是從路由中移除,而是從專案中的任何模組中移除?
例如,如果您想撰寫類似以下的內容會如何?
import { db } from "~/.server/db";
// This export is server-only ❌
export const getPosts = async () => db.posts.findMany();
// This export is client-safe ✅
export const PostPreview = ({ title, description }) => (
<article>
<h2>{title}</h2>
<p>{description}</p>
</article>
);
在此檔案的目前狀態下,Remix 會因在用戶端上使用 .server
模組而擲回編譯時錯誤。這是一件好事!您絕對不希望將僅伺服器端程式碼洩漏到用戶端。您可以透過將僅伺服器端程式碼分割到自己的檔案中來修正此問題,但如果您不想重組您的程式碼,那就太好了,特別是如果您正在遷移現有專案!
此問題並非 Remix 所獨有。它實際上會影響任何全堆疊 Vite 專案,因此我們編寫了一個名為 vite-env-only 的獨立 Vite 外掛程式來解決此問題。此外掛程式可讓您將個別運算式標記為僅伺服器端或僅用戶端。
例如,當使用 serverOnly$
巨集時
import { serverOnly$ } from "vite-env-only";
import { db } from "~/.server/db";
export const getPosts = serverOnly$(async () => db.posts.findMany());
export const PostPreview = ({ title, description }) => (
<article>
<h2>{title}</h2>
<p>{description}</p>
</article>
);
在用戶端上,這會變成
export const getPosts = undefined;
export const PostPreview = ({ title, description }) => (
<article>
<h2>{title}</h2>
<p>{description}</p>
</article>
);
值得重申的是,這是一個單獨的 Vite 外掛程式,而不是 Remix 的功能。是否要使用 vite-env-only
、將僅伺服器端程式碼分割成單獨的檔案,甚至是引入您自己的 Vite 外掛程式完全取決於您。
如需更多資訊,請查閱我們關於 分割用戶端和伺服器程式碼的文件。
.css?url
匯入從一開始,Remix 就提供了一種 管理 CSS 匯入的替代模型。匯入 CSS 檔案時,其 URL 會以字串的形式提供,以便在 link
標籤中呈現
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import styles from "~/styles/dashboard.css";
export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }];
雖然 Vite 長期以來一直支援 將靜態資產匯入為 URL,但如果 CSS 檔案需要任何處理(例如 PostCSS(包括 Tailwind)、CSS 模組、CSS 預處理器等),則此方法不適用於 CSS 檔案。
隨著最近發布的 Vite v5.1.0,現在可以透過 .css?url
匯入語法完全支援 CSS
import styles from "~/styles/dashboard.css?url";
舊版的 Remix 編譯器會將客戶端和伺服器建置到可以獨立設定的不同目錄中。預設情況下,客戶端資源的輸出目錄是 public/build
,而伺服器的輸出目錄是 build
。結果發現這種結構與 Vite 的 public 目錄衝突。
由於 Vite 會將檔案從 public
複製到客戶端建置目錄,而 Remix 的客戶端建置目錄又嵌套在 public 目錄中,因此有些使用者發現他們的 public 目錄被遞迴地複製到自身 🫠
為了修正這個問題,我們必須稍微重新安排我們的建置輸出。Remix Vite 現在有一個單一的頂層 buildDirectory
選項,預設值為 "build"
,產生的目錄結構為 build/client
和 build/server
。
有趣的是,即使我們實作此變更只是為了修正一個錯誤,我們實際上更喜歡這種結構。而且根據我們收到的回饋,我們的早期採用者也這麼認為!
我們最早的採用者直接執行 Vite CLI — vite dev
用於本地開發,而 vite build && vite build --ssr
用於生產環境建置。由於缺乏對 Vite 的自訂包裝器,我們最初的不穩定版本發布時提到 Remix 現在「只是一個 Vite 外掛」。
然而,隨著伺服器套件的引入,我們無法堅持這種方法。當使用 serverBundles
選項時,現在會有動態數量的伺服器建置。我們原以為可以為 Vite 的 ssr
建置定義多個輸入和輸出,但結果並非如此,因此 Remix 需要一種方式來協調整個建置過程。現在,Vite 外掛也提供了一個新的 buildEnd
Hook,讓您可以在 Remix 建置完成後執行自己的自訂邏輯。
我們盡可能保留了舊的架構,最大化了 Vite 外掛中的程式碼量(我們很高興這麼做!),並在 Remix CLI 中新增了 remix vite:dev
和 remix vite:build
命令。在 Remix v3 中,這些命令將會變成預設的 dev
和 build
命令。
因此,雖然我們不再「只是一個 Vite 外掛」,但可以公平地說,我們仍然主要只是一個 Vite 外掛 🙂
既然 Remix Vite 已經穩定,您將會開始看到我們的文件和範本預設移至 Vite。
就像我們最初的不穩定版本發布一樣,我們為那些希望將現有的 Remix 專案移至 Vite 的使用者提供了遷移指南。
請放心,舊版的 Remix 編譯器將會在 Remix v2 中繼續運作。然而,從現在開始,所有需要編譯器整合的新功能和改進都只會以 Vite 為目標。未來,Vite 將會是建置 Remix 應用程式的唯一官方方式,因此我們鼓勵您盡快開始遷移。
如果您在過程中對我們有任何回饋意見,請與我們聯繫。我們很樂意收到您的來信!
感謝 Remix 社群中所有的早期採用者,他們提供了回饋意見、提出了問題並提交了 Pull Request。沒有您們,我們不可能走到這一步。
我們也想特別感謝 Hiroshi Ogawa,一位外部貢獻者,他在 Remix Vite 中完成了驚人的 25 個 Pull Request 🔥
一如既往,感謝 Vite 團隊為我們提供如此出色的工具來在此基礎上建置。我們很期待看到我們能一起將它帶到哪裡。
💿⚡️🚀