Skip to content

Commit

Permalink
vite: no livereload, scripts injects hmr runtime instead
Browse files Browse the repository at this point in the history
  • Loading branch information
pcattori committed Jan 30, 2024
1 parent c372825 commit 945f177
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 97 deletions.
40 changes: 40 additions & 0 deletions .changeset/ninety-turtles-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
"@remix-run/dev": patch
---

Vite: Remove interop with `<LiveReload />`, rely on `<Scripts />` instead

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

For HMR and HDR to function, the React Fast Refresh preamble has to execute
before any route modules. Having a separate component for dev scripts
`<LiveReload />` — implicitly relied on browser script execution order.
This opened the door for race conditions that could only be avoided through hacks.

Additionally, Vite comes with its own HMR runtime out-of-the-box, so much of the
setup done within `<LiveReload />` became obsolete.

To fix this, we now rely on `<Scripts />` to programmatically control execution
order between dev preamble and route modules within a single script.

```diff
import {
- LiveReload,
Outlet,
Scripts,
}

export default function App() {
return (
<html>
<head>
</head>
<body>
<Outlet />
<Scripts />
- <LiveReload />
</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 @@ -1156,3 +1193,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
2 changes: 0 additions & 2 deletions integration/helpers/vite-template/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -20,7 +19,6 @@ export default function App() {
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
3 changes: 1 addition & 2 deletions integration/vite-css-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const files = {
});
`,
"app/root.tsx": `
import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react";
import { Links, Meta, Outlet, Scripts } from "@remix-run/react";
export default function Root() {
return (
Expand All @@ -46,7 +46,6 @@ const files = {
<body>
<Outlet />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
6 changes: 1 addition & 5 deletions integration/vite-dev-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ test.describe("Vite dev", () => {
});
`,
"app/root.tsx": js`
import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react";
import { Links, Meta, Outlet, Scripts } from "@remix-run/react";
export default function Root() {
return (
Expand All @@ -55,7 +55,6 @@ test.describe("Vite dev", () => {
<Outlet />
</div>
<Scripts />
<LiveReload nonce="1234" />
</body>
</html>
);
Expand Down Expand Up @@ -330,9 +329,6 @@ test.describe("Vite dev", () => {
await expect(hmrStatus).toHaveText("HMR updated: yes");
await expect(input).toHaveValue("stateful");

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

// Ensure no errors after HMR
expect(pageErrors).toEqual([]);
});
Expand Down
3 changes: 1 addition & 2 deletions integration/vite-server-bundles-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ 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 } from "@remix-run/react";
export default function Root() {
return (
Expand All @@ -86,7 +86,6 @@ const files = {
<body>
<Outlet />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
53 changes: 2 additions & 51 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 @@ -1290,53 +1288,6 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => {
};
},
},
{
name: "remix-remix-react-proxy",
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 @@ -1512,7 +1463,7 @@ const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof
let prevRefreshReg;
let prevRefreshSig;
if (import.meta.hot && !inWebWorker && window.__remixLiveReloadEnabled) {
if (import.meta.hot && !inWebWorker) {
if (!window.__vite_plugin_react_preamble_installed__) {
throw new Error(
"Remix Vite plugin can't detect preamble. Something is wrong."
Expand All @@ -1528,7 +1479,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.$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.

3 changes: 0 additions & 3 deletions templates/spa/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -20,7 +19,6 @@ export default function App() {
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand All @@ -38,7 +36,6 @@ export function HydrateFallback() {
<body>
<p>Loading...</p>
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
2 changes: 0 additions & 2 deletions templates/unstable-vite-cloudflare/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -20,7 +19,6 @@ export default function App() {
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
2 changes: 0 additions & 2 deletions templates/unstable-vite-express/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -20,7 +19,6 @@ export default function App() {
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
2 changes: 0 additions & 2 deletions templates/unstable-vite/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
Expand All @@ -20,7 +19,6 @@ export default function App() {
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down

0 comments on commit 945f177

Please sign in to comment.