diff --git a/.changeset/ninety-turtles-explode.md b/.changeset/ninety-turtles-explode.md new file mode 100644 index 00000000000..1887323fe93 --- /dev/null +++ b/.changeset/ninety-turtles-explode.md @@ -0,0 +1,39 @@ +--- +"@remix-run/dev": patch +--- + +Vite: Remove interop with ``, rely on `` instead + +**This is a breaking change for projects using the unstable Vite plugin.** + +Vite provides a robust client-side runtime for development features like HMR, +making the `` component obsolete. + +In fact, having a separate dev scripts component was causing issues with script execution order. +To work around this, the Remix Vite plugin used to override `` into a bespoke +implementation that was compatible with Vite. + +Instead of all this indirection, now the Remix Vite plugin instructs the `` component +to automatically include Vite's client-side runtime and other dev-only scripts. + +```diff + import { +- LiveReload, + Outlet, + Scripts, + } + + export default function App() { + return ( + + + + + + +- + + + ) + } +``` diff --git a/docs/future/vite.md b/docs/future/vite.md index d47ac62d428..19a740ad3f9 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -243,6 +243,36 @@ export default defineConfig({ }); ``` +#### HMR & HDR + +Vite provides a robust client-side runtime for development features like HMR, +making the `` component obsolete. When using the Remix Vite plugin in development, +the `` component will automatically include Vite's client-side runtime and other dev-only scripts. + +👉 **Remove ``, keep ``** + +```diff + import { +- LiveReload, + Outlet, + Scripts, + } + + export default function App() { + return ( + + + + + +- + + + + ) + } +``` + #### 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`. diff --git a/integration/helpers/vite-template/app/root.tsx b/integration/helpers/vite-template/app/root.tsx index 1d6916394c3..e31409ca31d 100644 --- a/integration/helpers/vite-template/app/root.tsx +++ b/integration/helpers/vite-template/app/root.tsx @@ -1,6 +1,5 @@ import { Links, - LiveReload, Meta, Outlet, Scripts, @@ -20,7 +19,6 @@ export default function App() { - ); diff --git a/integration/vite-css-test.ts b/integration/vite-css-test.ts index 1a5c21a082c..c63ddf93ee2 100644 --- a/integration/vite-css-test.ts +++ b/integration/vite-css-test.ts @@ -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 ( @@ -46,7 +46,6 @@ const files = { - ); diff --git a/integration/vite-dev-test.ts b/integration/vite-dev-test.ts index 611ec002267..ce864a58760 100644 --- a/integration/vite-dev-test.ts +++ b/integration/vite-dev-test.ts @@ -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 ( @@ -55,7 +55,6 @@ test.describe("Vite dev", () => { - ); @@ -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([]); }); diff --git a/integration/vite-server-bundles-test.ts b/integration/vite-server-bundles-test.ts index 316ec571be7..4ea42ba59e1 100644 --- a/integration/vite-server-bundles-test.ts +++ b/integration/vite-server-bundles-test.ts @@ -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 ( @@ -86,7 +86,6 @@ const files = { - ); diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts index b8a64818168..181871d43a6 100644 --- a/packages/remix-dev/vite/plugin.ts +++ b/packages/remix-dev/vite/plugin.ts @@ -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 = [ @@ -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"); @@ -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 `` - 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", @@ -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." @@ -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) => { diff --git a/packages/remix-dev/vite/replace-import-specifier.ts b/packages/remix-dev/vite/replace-import-specifier.ts deleted file mode 100644 index 8f1fd59c07c..00000000000 --- a/packages/remix-dev/vite/replace-import-specifier.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { parse, traverse, generate } from "./babel"; - -export const replaceImportSpecifier = ({ - code, - specifier, - replaceWith, -}: { - code: string; - specifier: string; - replaceWith: string; -}) => { - let ast = parse(code, { sourceType: "module" }); - - traverse(ast, { - ImportDeclaration(path) { - if (path.node.source.value === specifier) { - path.node.source.value = replaceWith; - } - }, - }); - - return { - code: generate(ast, { retainLines: true }).code, - map: null, - }; -}; diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index f9604f7655e..aec1ba2dde6 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -1073,7 +1073,7 @@ export const LiveReload = process.env.NODE_ENV !== "development" ? () => null : function LiveReload({ - origin = process.env.REMIX_DEV_ORIGIN, + origin, port, timeoutMs = 1000, nonce = undefined, @@ -1083,6 +1083,20 @@ export const LiveReload = timeoutMs?: number; nonce?: string; }) { + // @ts-expect-error + let isViteClient = import.meta && import.meta.env !== undefined; + if (isViteClient) { + console.warn( + [ + "`` is obsolete when using Vite and can conflict with Vite's built-in HMR runtime.", + "", + "Remove `` from your code and instead only use ``.", + "Then refresh the page to remove lingering scripts from ``.", + ].join("\n") + ); + return null; + } + origin ??= process.env.REMIX_DEV_ORIGIN; let js = String.raw; return (