Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vite: replace <LiveReload/> with <DevScripts/> #8609

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .changeset/ninety-turtles-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
"@remix-run/dev": patch
"@remix-run/react": patch
---

Vite: Replace <LiveReload/> with <DevScripts/>

**This is a breaking change for projects using the unstable Vite plugin.**

The `<LiveReload/>` component has a confusing name as it now also supports HMR and HDR.
Additionally, it provides an bespoke client-side runtime that is obsoleted by Vite.
To get our Vite plugin working, we were doing some compiler magic to swap out the
implementation of `<LiveReload/>`.
This was always meant as a temporary measure.

Now we have a better solution in the form of a new `<DevScripts/>` component specifically
designed with Vite's HMR capabilities in mind.

The `<LiveReload />` component will cease to provide HMR and HDR capabilities in Vite,
so you'll need to replace `<LiveReload/>` with `<DevScripts/>` in your app.

The `<DevScripts/>` component should be placed in the `<head/>` of your app so that it
can be loaded before any other scripts as required by React Fast Refresh.

```diff
import {
- LiveReload,
+ DevScripts,
Outlet,
}

export default function App() {
return (
<html>
<head>
+ <DevScripts />
</head>
<body>
- <LiveReload />
<Outlet />
</body>
</html>
)
}
```
38 changes: 38 additions & 0 deletions docs/future/vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,43 @@ export default defineConfig({
});
```

#### HMR & HDR

The new `<DevScripts/>` component enables development-specific features like HMR and HDR.
`<DevScripts/>` automatically removes itself in production, just like the old `<LiveReload/>` component.
But unlike `<LiveReload/>`, it works with Vite's out-of-the-box HMR capabilities.

<docs-info>

The `<DevScripts/>` component should be placed in the `<head/>` of your app so that it
can be loaded before any other scripts as required by [React Fast Refresh][react-fast-refresh].

</docs-info>

👉 **Replace `<LiveReload/>` with `<DevScripts/>`**

```diff
import {
- LiveReload,
+ DevScripts,
Outlet,
}

export default function App() {
return (
<html>
<head>
+ <DevScripts />
</head>
<body>
- <LiveReload />
<Outlet />
</body>
</html>
)
}
```

#### TypeScript integration

Vite handles imports for all sorts of different file types, sometimes in ways that differ from the existing Remix compiler, so let's reference Vite's types from `vite/client` instead of the obsolete types from `@remix-run/dev`.
Expand Down Expand Up @@ -1149,3 +1186,4 @@ We're definitely late to the Vite party, but we're excited to be here now!
[wrangler-getbindingsproxy]: https://github.com/cloudflare/workers-sdk/pull/4523
[remix-config-server]: https://remix.run/docs/en/main/file-conventions/remix-config#server
[cloudflare-vite-and-wrangler]: #vite--wrangler
[react-fast-refresh]: https://github.com/facebook/react/issues/16604#issuecomment-528663101
4 changes: 2 additions & 2 deletions integration/helpers/vite-template/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
DevScripts,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -15,12 +15,12 @@ export default function App() {
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<DevScripts />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
4 changes: 2 additions & 2 deletions integration/vite-css-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ const files = {
});
`,
"app/root.tsx": `
import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react";
import { Links, Meta, Outlet, Scripts, DevScripts } from "@remix-run/react";

export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
<DevScripts />
</head>
<body>
<Outlet />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
6 changes: 3 additions & 3 deletions integration/vite-dev-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,22 @@ test.describe("Vite dev", () => {
});
`,
"app/root.tsx": js`
import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react";
import { Links, Meta, Outlet, Scripts, DevScripts } from "@remix-run/react";

export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
<DevScripts nonce="1234" />
</head>
<body>
<div id="content">
<h1>Root</h1>
<Outlet />
</div>
<Scripts />
<LiveReload nonce="1234" />
</body>
</html>
);
Expand Down Expand Up @@ -330,7 +330,7 @@ test.describe("Vite dev", () => {
await expect(hmrStatus).toHaveText("HMR updated: yes");
await expect(input).toHaveValue("stateful");

// check LiveReload script has nonce
// check DevScripts script has nonce
await expect(page.locator(`script[nonce="1234"]`)).toBeAttached();

// Ensure no errors after HMR
Expand Down
4 changes: 2 additions & 2 deletions integration/vite-server-bundles-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,19 @@ const TEST_ROUTES = [
const files = {
"app/root.tsx": `
${ROUTE_FILE_COMMENT}
import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react";
import { Links, Meta, Outlet, Scripts, DevScripts } from "@remix-run/react";

export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
<DevScripts />
</head>
<body>
<Outlet />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
54 changes: 2 additions & 52 deletions packages/remix-dev/vite/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { getStylesForUrl, isCssModulesFile } from "./styles";
import * as VirtualModule from "./vmod";
import { resolveFileUrl } from "./resolve-file-url";
import { removeExports } from "./remove-exports";
import { replaceImportSpecifier } from "./replace-import-specifier";
import { importViteEsmSync, preloadViteEsm } from "./import-vite-esm-sync";

const supportedRemixEsbuildConfigKeys = [
Expand Down Expand Up @@ -227,7 +226,6 @@ export type RemixPluginContext = RemixPluginSsrBuildContext & {
let serverBuildId = VirtualModule.id("server-build");
let serverManifestId = VirtualModule.id("server-manifest");
let browserManifestId = VirtualModule.id("browser-manifest");
let remixReactProxyId = VirtualModule.id("remix-react-proxy");
let hmrRuntimeId = VirtualModule.id("hmr-runtime");
let injectHmrRuntimeId = VirtualModule.id("inject-hmr-runtime");

Expand Down Expand Up @@ -1280,54 +1278,6 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => {
};
},
},
{
name: "remix-remix-react-proxy",
enforce: "post", // Ensure we're operating on the transformed code to support MDX etc.
resolveId(id) {
if (id === remixReactProxyId) {
return VirtualModule.resolve(remixReactProxyId);
}
},
transform(code, id) {
// Don't transform the proxy itself, otherwise it will import itself
if (id === VirtualModule.resolve(remixReactProxyId)) {
return;
}

let hasLiveReloadHints =
code.includes("LiveReload") && code.includes("@remix-run/react");

// Don't transform files that don't need the proxy
if (!hasLiveReloadHints) {
return;
}

// Rewrite imports to use the proxy
return replaceImportSpecifier({
code,
specifier: "@remix-run/react",
replaceWith: remixReactProxyId,
});
},
load(id) {
if (id === VirtualModule.resolve(remixReactProxyId)) {
// TODO: ensure react refresh is initialized before `<Scripts />`
return [
'import { createElement } from "react";',
'export * from "@remix-run/react";',
`export const LiveReload = ${
viteCommand !== "serve"
} ? () => null : `,
'({ nonce = undefined }) => createElement("script", {',
" nonce,",
" dangerouslySetInnerHTML: { ",
" __html: `window.__remixLiveReloadEnabled = true`",
" }",
"});",
].join("\n");
}
},
},
{
name: "remix-inject-hmr-runtime",
enforce: "pre",
Expand Down Expand Up @@ -1504,7 +1454,7 @@ const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof
let prevRefreshReg;
let prevRefreshSig;

if (import.meta.hot && !inWebWorker && window.__remixLiveReloadEnabled) {
if (import.meta.hot && !inWebWorker && window.__remixHmrEnabled) {
if (!window.__vite_plugin_react_preamble_installed__) {
throw new Error(
"Remix Vite plugin can't detect preamble. Something is wrong."
Expand All @@ -1520,7 +1470,7 @@ if (import.meta.hot && !inWebWorker && window.__remixLiveReloadEnabled) {
}`.replace(/\n+/g, "");

const REACT_REFRESH_FOOTER = `
if (import.meta.hot && !inWebWorker && window.__remixLiveReloadEnabled) {
if (import.meta.hot && !inWebWorker && window.__remixHmrEnabled) {
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;
RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
Expand Down
26 changes: 0 additions & 26 deletions packages/remix-dev/vite/replace-import-specifier.ts

This file was deleted.

12 changes: 12 additions & 0 deletions packages/remix-react/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,18 @@ export function useFetcher<TData = AppData>(
return useFetcherRR(opts);
}

export const DevScripts =
process.env.NODE_ENV === "production"
pcattori marked this conversation as resolved.
Show resolved Hide resolved
? () => null
: ({ nonce }: { nonce?: undefined }) => (
<script
nonce={nonce}
dangerouslySetInnerHTML={{
__html: `window.__remixHmrEnabled = true`,
}}
/>
);

/**
* This component connects your app to the Remix asset server and
* automatically reloads the page when files change in development.
Expand Down
1 change: 1 addition & 0 deletions packages/remix-react/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export {
Await,
Meta,
Links,
DevScripts,
Scripts,
Link,
NavLink,
Expand Down
6 changes: 3 additions & 3 deletions templates/spa/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
DevScripts,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -15,12 +15,12 @@ export default function App() {
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<DevScripts />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand All @@ -34,11 +34,11 @@ export function HydrateFallback() {
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<DevScripts />
</head>
<body>
<p>Loading...</p>
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
4 changes: 2 additions & 2 deletions templates/unstable-vite-cloudflare/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
DevScripts,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -15,12 +15,12 @@ export default function App() {
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<DevScripts />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
Loading
Loading