+ }
+ `,
+ },
+ })
+ );
+ let app = new PlaywrightFixture(appFixture, page);
+
+ await app.goto("/parent/child");
+ await page.waitForSelector("#child-data");
+ let html = await app.getHtml();
+ expect(html).toMatch("Child Client Loader Data");
+ app.clickElement("button");
+ await page.waitForSelector(':has-text("Child Server Loader Data (2+)")');
+ html = await app.getHtml("main");
+ expect(html).toMatch("Child Server Loader Data (2+) (mutated by client)");
+ });
});
test.describe("clientLoader - lazy route module", () => {
@@ -534,6 +811,50 @@ test.describe("Client Data", () => {
expect(html).toMatch("Parent Server Loader (mutated by client)");
expect(html).toMatch("Child Server Loader (mutated by client");
});
+
+ test("throws a 400 if you call serverLoader without a server loader", async ({
+ page,
+ }) => {
+ appFixture = await createAppFixture(
+ await createFixture({
+ files: {
+ ...getFiles({
+ parentClientLoader: false,
+ parentClientLoaderHydrate: false,
+ childClientLoader: false,
+ childClientLoaderHydrate: false,
+ }),
+ "app/routes/parent.child.tsx": js`
+ import * as React from 'react';
+ import { useLoaderData, useRouteError } from '@remix-run/react';
+ export async function clientLoader({ serverLoader }) {
+ return await serverLoader();
+ }
+ export default function Component() {
+ return
Child
;
+ }
+ export function HydrateFallback() {
+ return
Loading...
;
+ }
+ export function ErrorBoundary() {
+ let error = useRouteError();
+ return
{error.status} {error.data}
;
+ }
+ `,
+ },
+ })
+ );
+ let app = new PlaywrightFixture(appFixture, page);
+
+ await app.goto("/");
+ await app.clickLink("/parent/child");
+ await page.waitForSelector("#child-error");
+ let html = await app.getHtml("#child-error");
+ expect(html.replace(/\n/g, " ").replace(/ +/g, " ")).toMatch(
+ "400 Error: You are trying to call serverLoader() on a route that does " +
+ 'not have a server loader (routeId: "routes/parent.child")'
+ );
+ });
});
test.describe("clientAction - critical route module", () => {
@@ -698,6 +1019,51 @@ test.describe("Client Data", () => {
expect(html).toMatch("Child Server Loader (mutated by client)");
expect(html).toMatch("Child Server Action (mutated by client)");
});
+
+ test("throws a 400 if you call serverAction without a server action", async ({
+ page,
+ }) => {
+ appFixture = await createAppFixture(
+ await createFixture({
+ files: {
+ ...getFiles({
+ parentClientLoader: false,
+ parentClientLoaderHydrate: false,
+ childClientLoader: false,
+ childClientLoaderHydrate: false,
+ }),
+ "app/routes/parent.child.tsx": js`
+ import * as React from 'react';
+ import { json } from '@remix-run/node';
+ import { Form, useRouteError } from '@remix-run/react';
+ export async function clientAction({ serverAction }) {
+ return await serverAction();
+ }
+ export default function Component() {
+ return (
+
+ );
+ }
+ export function ErrorBoundary() {
+ let error = useRouteError();
+ return
{error.status} {error.data}
;
+ }
+ `,
+ },
+ })
+ );
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/parent/child");
+ app.clickSubmitButton("/parent/child");
+ await page.waitForSelector("#child-error");
+ let html = await app.getHtml("#child-error");
+ expect(html.replace(/\n/g, " ").replace(/ +/g, " ")).toMatch(
+ "400 Error: You are trying to call serverAction() on a route that does " +
+ 'not have a server action (routeId: "routes/parent.child")'
+ );
+ });
});
test.describe("clientAction - lazy route module", () => {
@@ -870,5 +1236,52 @@ test.describe("Client Data", () => {
expect(html).toMatch("Child Server Loader (mutated by client)");
expect(html).toMatch("Child Server Action (mutated by client)");
});
+
+ test("throws a 400 if you call serverAction without a server action", async ({
+ page,
+ }) => {
+ appFixture = await createAppFixture(
+ await createFixture({
+ files: {
+ ...getFiles({
+ parentClientLoader: false,
+ parentClientLoaderHydrate: false,
+ childClientLoader: false,
+ childClientLoaderHydrate: false,
+ }),
+ "app/routes/parent.child.tsx": js`
+ import * as React from 'react';
+ import { json } from '@remix-run/node';
+ import { Form, useRouteError } from '@remix-run/react';
+ export async function clientAction({ serverAction }) {
+ return await serverAction();
+ }
+ export default function Component() {
+ return (
+
+ );
+ }
+ export function ErrorBoundary() {
+ let error = useRouteError();
+ return
{error.status} {error.data}
;
+ }
+ `,
+ },
+ })
+ );
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/");
+ await app.goto("/parent/child");
+ await page.waitForSelector("form");
+ app.clickSubmitButton("/parent/child");
+ await page.waitForSelector("#child-error");
+ let html = await app.getHtml("#child-error");
+ expect(html.replace(/\n/g, " ").replace(/ +/g, " ")).toMatch(
+ "400 Error: You are trying to call serverAction() on a route that does " +
+ 'not have a server action (routeId: "routes/parent.child")'
+ );
+ });
});
});
diff --git a/packages/remix-dev/CHANGELOG.md b/packages/remix-dev/CHANGELOG.md
index d40c18eaf8..c0d1981a41 100644
--- a/packages/remix-dev/CHANGELOG.md
+++ b/packages/remix-dev/CHANGELOG.md
@@ -1,5 +1,158 @@
# `@remix-run/dev`
+## 2.4.0
+
+### Minor Changes
+
+- Vite: exclude modules within `.server` directories from client build ([#8154](https://github.com/remix-run/remix/pull/8154))
+
+- Add support for `clientLoader`/`clientAction`/`HydrateFallback` route exports ([RFC](https://github.com/remix-run/remix/discussions/7634)) ([#8173](https://github.com/remix-run/remix/pull/8173))
+
+ Remix now supports loaders/actions that run on the client (in addition to, or instead of the loader/action that runs on the server). While we still recommend server loaders/actions for the majority of your data needs in a Remix app - these provide some levers you can pull for more advanced use-cases such as:
+
+ - Leveraging a data source local to the browser (i.e., `localStorage`)
+ - Managing a client-side cache of server data (like `IndexedDB`)
+ - Bypassing the Remix server in a BFF setup and hitting your API directly from the browser
+ - Migrating a React Router SPA to a Remix application
+
+ By default, `clientLoader` will not run on hydration, and will only run on subsequent client side navigations.
+
+ If you wish to run your client loader on hydration, you can set `clientLoader.hydrate=true` to force Remix to execute it on initial page load. Keep in mind that Remix will still SSR your route component so you should ensure that there is no new _required_ data being added by your `clientLoader`.
+
+ If your `clientLoader` needs to run on hydration and adds data you require to render the route component, you can export a `HydrateFallback` component that will render during SSR, and then your route component will not render until the `clientLoader` has executed on hydration.
+
+ `clientAction` is simpler than `clientLoader` because it has no hydration use-cases. `clientAction` will only run on client-side navigations.
+
+ For more information, please refer to the [`clientLoader`](https://remix.run/route/client-loader) and [`clientAction`](https://remix.run/route/client-action) documentation.
+
+- Vite: Strict route exports ([#8171](https://github.com/remix-run/remix/pull/8171))
+
+ With Vite, Remix gets stricter about which exports are allowed from your route modules.
+ Previously, the Remix compiler would allow any export from routes.
+ While this was convenient, it was also a common source of bugs that were hard to track down because they only surfaced at runtime.
+
+ For more, see
+
+- Add a new `future.v3_relativeSplatPath` flag to implement a breaking bug fix to relative routing when inside a splat route. For more information, please see the React Router [`6.21.0` Release Notes](https://github.com/remix-run/react-router/blob/release-next/CHANGELOG.md#futurev7_relativesplatpath) and the [`useResolvedPath` docs](https://remix.run/hooks/use-resolved-path#splat-paths). ([#8216](https://github.com/remix-run/remix/pull/8216))
+
+### Patch Changes
+
+- Upgrade Vite peer dependency range to v5 ([#8172](https://github.com/remix-run/remix/pull/8172))
+
+- Support HMR for routes with `handle` export in Vite dev ([#8022](https://github.com/remix-run/remix/pull/8022))
+
+- Fix flash of unstyled content for non-Express custom servers in Vite dev ([#8076](https://github.com/remix-run/remix/pull/8076))
+
+- Bundle CSS imported in client entry file in Vite plugin ([#8143](https://github.com/remix-run/remix/pull/8143))
+
+- Change Vite build output paths to fix a conflict between how Vite and the Remix compiler each manage the `public` directory. ([#8077](https://github.com/remix-run/remix/pull/8077))
+
+ **This is a breaking change for projects using the unstable Vite plugin.**
+
+ The server is now compiled into `build/server` rather than `build`, and the client is now compiled into `build/client` rather than `public`.
+
+ For more information on the changes and guidance on how to migrate your project, refer to the updated [Remix Vite documentation](https://remix.run/docs/en/main/future/vite).
+
+- Remove undocumented `legacyCssImports` option from Vite plugin due to issues with `?url` imports of CSS files not being processed correctly in Vite ([#8096](https://github.com/remix-run/remix/pull/8096))
+
+- Vite: fix access to default `entry.{client,server}.tsx` within pnpm workspace on Windows ([#8057](https://github.com/remix-run/remix/pull/8057))
+
+- Remove `unstable_createViteServer` and `unstable_loadViteServerBuild` which were only minimal wrappers around Vite's `createServer` and `ssrLoadModule` functions when using a custom server. ([#8120](https://github.com/remix-run/remix/pull/8120))
+
+ **This is a breaking change for projects using the unstable Vite plugin with a custom server.**
+
+ Instead, we now provide `unstable_viteServerBuildModuleId` so that custom servers interact with Vite directly rather than via Remix APIs, for example:
+
+ ```diff
+ -import {
+ - unstable_createViteServer,
+ - unstable_loadViteServerBuild,
+ -} from "@remix-run/dev";
+ +import { unstable_viteServerBuildModuleId } from "@remix-run/dev";
+ ```
+
+ Creating the Vite server in middleware mode:
+
+ ```diff
+ const vite =
+ process.env.NODE_ENV === "production"
+ ? undefined
+ - : await unstable_createViteServer();
+ + : await import("vite").then(({ createServer }) =>
+ + createServer({
+ + server: {
+ + middlewareMode: true,
+ + },
+ + })
+ + );
+ ```
+
+ Loading the Vite server build in the request handler:
+
+ ```diff
+ app.all(
+ "*",
+ createRequestHandler({
+ build: vite
+ - ? () => unstable_loadViteServerBuild(vite)
+ + ? () => vite.ssrLoadModule(unstable_viteServerBuildModuleId)
+ : await import("./build/server/index.js"),
+ })
+ );
+ ```
+
+- Pass request handler errors to `vite.ssrFixStacktrace` in Vite dev to ensure stack traces correctly map to the original source code ([#8066](https://github.com/remix-run/remix/pull/8066))
+
+- Vite: Preserve names for exports from .client imports ([#8200](https://github.com/remix-run/remix/pull/8200))
+
+ Unlike `.server` modules, the main idea is not to prevent code from leaking into the server build
+ since the client build is already public. Rather, the goal is to isolate the SSR render from client-only code.
+ Routes need to import code from `.client` modules without compilation failing and then rely on runtime checks
+ to determine if the code is running on the server or client.
+
+ Replacing `.client` modules with empty modules would cause the build to fail as ESM named imports are statically analyzed.
+ So instead, we preserve the named export but replace each exported value with an empty object.
+ That way, the import is valid at build time and the standard runtime checks can be used to determine if then
+ code is running on the server or client.
+
+- Add `@remix-run/node` to Vite's `optimizeDeps.include` array ([#8177](https://github.com/remix-run/remix/pull/8177))
+
+- Improve Vite plugin performance ([#8121](https://github.com/remix-run/remix/pull/8121))
+
+ - Parallelize detection of route module exports
+ - Disable `server.preTransformRequests` in Vite child compiler since it's only used to process route modules
+
+- Remove automatic global Node polyfill installation from the built-in Vite dev server and instead allow explicit opt-in. ([#8119](https://github.com/remix-run/remix/pull/8119))
+
+ **This is a breaking change for projects using the unstable Vite plugin without a custom server.**
+
+ If you're not using a custom server, you should call `installGlobals` in your Vite config instead.
+
+ ```diff
+ import { unstable_vitePlugin as remix } from "@remix-run/dev";
+ +import { installGlobals } from "@remix-run/node";
+ import { defineConfig } from "vite";
+
+ +installGlobals();
+
+ export default defineConfig({
+ plugins: [remix()],
+ });
+ ```
+
+- Vite: Errors at build-time when client imports .server default export ([#8184](https://github.com/remix-run/remix/pull/8184))
+
+ Remix already stripped .server file code before ensuring that server code never makes it into the client.
+ That results in errors when client code tries to import server code, which is exactly what we want!
+ But those errors were happening at runtime for default imports.
+ A better experience is to have those errors happen at build-time so that you guarantee that your users won't hit them.
+
+- Fix `request instanceof Request` checks when using Vite dev server ([#8062](https://github.com/remix-run/remix/pull/8062))
+
+- Updated dependencies:
+ - `@remix-run/server-runtime@2.4.0`
+ - `@remix-run/node@2.4.0`
+
## 2.3.1
### Patch Changes
diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json
index fb4e336f36..36a7a19f83 100644
--- a/packages/remix-dev/package.json
+++ b/packages/remix-dev/package.json
@@ -1,6 +1,6 @@
{
"name": "@remix-run/dev",
- "version": "2.3.1",
+ "version": "2.4.0",
"description": "Dev tools and CLI for Remix",
"homepage": "https://remix.run",
"bugs": {
@@ -28,9 +28,9 @@
"@babel/types": "^7.22.5",
"@mdx-js/mdx": "^2.3.0",
"@npmcli/package-json": "^4.0.1",
- "@remix-run/node": "2.3.1",
- "@remix-run/router": "1.14.0-pre.0",
- "@remix-run/server-runtime": "2.3.1",
+ "@remix-run/node": "2.4.0",
+ "@remix-run/router": "1.14.0",
+ "@remix-run/server-runtime": "2.4.0",
"@types/mdx": "^2.0.5",
"@vanilla-extract/integration": "^6.2.0",
"arg": "^5.0.1",
@@ -73,7 +73,7 @@
"ws": "^7.4.5"
},
"devDependencies": {
- "@remix-run/serve": "2.3.1",
+ "@remix-run/serve": "2.4.0",
"@types/cacache": "^17.0.0",
"@types/cross-spawn": "^6.0.2",
"@types/gunzip-maybe": "^1.4.0",
@@ -94,7 +94,7 @@
"vite": "^5.0.0"
},
"peerDependencies": {
- "@remix-run/serve": "^2.3.1",
+ "@remix-run/serve": "^2.4.0",
"typescript": "^5.1.0",
"vite": "^5.0.0"
},
diff --git a/packages/remix-express/CHANGELOG.md b/packages/remix-express/CHANGELOG.md
index 195b6d2541..dbe3546206 100644
--- a/packages/remix-express/CHANGELOG.md
+++ b/packages/remix-express/CHANGELOG.md
@@ -1,5 +1,12 @@
# `@remix-run/express`
+## 2.4.0
+
+### Patch Changes
+
+- Updated dependencies:
+ - `@remix-run/node@2.4.0`
+
## 2.3.1
### Patch Changes
diff --git a/packages/remix-express/package.json b/packages/remix-express/package.json
index e6bb777908..4c34569673 100644
--- a/packages/remix-express/package.json
+++ b/packages/remix-express/package.json
@@ -1,6 +1,6 @@
{
"name": "@remix-run/express",
- "version": "2.3.1",
+ "version": "2.4.0",
"description": "Express server request handler for Remix",
"bugs": {
"url": "https://github.com/remix-run/remix/issues"
@@ -14,7 +14,7 @@
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"dependencies": {
- "@remix-run/node": "2.3.1"
+ "@remix-run/node": "2.4.0"
},
"devDependencies": {
"@types/express": "^4.17.9",
diff --git a/packages/remix-node/CHANGELOG.md b/packages/remix-node/CHANGELOG.md
index c1b5452727..483e5e34d7 100644
--- a/packages/remix-node/CHANGELOG.md
+++ b/packages/remix-node/CHANGELOG.md
@@ -1,5 +1,17 @@
# `@remix-run/node`
+## 2.4.0
+
+### Minor Changes
+
+- Deprecate `DataFunctionArgs` in favor of `LoaderFunctionArgs`/`ActionFunctionArgs`. This is aimed at keeping the types aligned across server/client loaders/actions now that `clientLoader`/`clientActon` functions have `serverLoader`/`serverAction` parameters which differentiate `ClientLoaderFunctionArgs`/`ClientActionFunctionArgs`. ([#8173](https://github.com/remix-run/remix/pull/8173))
+
+### Patch Changes
+
+- Update to `@remix-run/web-fetch@4.4.2` ([#8231](https://github.com/remix-run/remix/pull/8231))
+- Updated dependencies:
+ - `@remix-run/server-runtime@2.4.0`
+
## 2.3.1
### Patch Changes
diff --git a/packages/remix-node/package.json b/packages/remix-node/package.json
index 5b21c1218f..732f57685d 100644
--- a/packages/remix-node/package.json
+++ b/packages/remix-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@remix-run/node",
- "version": "2.3.1",
+ "version": "2.4.0",
"description": "Node.js platform abstractions for Remix",
"bugs": {
"url": "https://github.com/remix-run/remix/issues"
@@ -17,8 +17,8 @@
"./install.js"
],
"dependencies": {
- "@remix-run/server-runtime": "2.3.1",
- "@remix-run/web-fetch": "^4.4.1",
+ "@remix-run/server-runtime": "2.4.0",
+ "@remix-run/web-fetch": "^4.4.2",
"@remix-run/web-file": "^3.1.0",
"@remix-run/web-stream": "^1.1.0",
"@web3-storage/multipart-parser": "^1.0.0",
diff --git a/packages/remix-serve/CHANGELOG.md b/packages/remix-serve/CHANGELOG.md
index 797d9a889a..cd77d1ee01 100644
--- a/packages/remix-serve/CHANGELOG.md
+++ b/packages/remix-serve/CHANGELOG.md
@@ -1,5 +1,14 @@
# `@remix-run/serve`
+## 2.4.0
+
+### Patch Changes
+
+- Fix source map loading when file has `?t=timestamp` suffix (rebuilds) ([#8174](https://github.com/remix-run/remix/pull/8174))
+- Updated dependencies:
+ - `@remix-run/node@2.4.0`
+ - `@remix-run/express@2.4.0`
+
## 2.3.1
### Patch Changes
diff --git a/packages/remix-serve/package.json b/packages/remix-serve/package.json
index b38a185b72..132af85573 100644
--- a/packages/remix-serve/package.json
+++ b/packages/remix-serve/package.json
@@ -1,6 +1,6 @@
{
"name": "@remix-run/serve",
- "version": "2.3.1",
+ "version": "2.4.0",
"description": "Production application server for Remix",
"bugs": {
"url": "https://github.com/remix-run/remix/issues"
@@ -15,8 +15,8 @@
"remix-serve": "dist/cli.js"
},
"dependencies": {
- "@remix-run/express": "2.3.1",
- "@remix-run/node": "2.3.1",
+ "@remix-run/express": "2.4.0",
+ "@remix-run/node": "2.4.0",
"chokidar": "^3.5.3",
"compression": "^1.7.4",
"express": "^4.17.1",
diff --git a/packages/remix-server-runtime/CHANGELOG.md b/packages/remix-server-runtime/CHANGELOG.md
index 404cc68872..908c1660bc 100644
--- a/packages/remix-server-runtime/CHANGELOG.md
+++ b/packages/remix-server-runtime/CHANGELOG.md
@@ -1,5 +1,37 @@
# `@remix-run/server-runtime`
+## 2.4.0
+
+### Minor Changes
+
+- Add support for `clientLoader`/`clientAction`/`HydrateFallback` route exports ([RFC](https://github.com/remix-run/remix/discussions/7634)). ([#8173](https://github.com/remix-run/remix/pull/8173))
+
+ Remix now supports loaders/actions that run on the client (in addition to, or instead of the loader/action that runs on the server). While we still recommend server loaders/actions for the majority of your data needs in a Remix app - these provide some levers you can pull for more advanced use-cases such as:
+
+ - Leveraging a data source local to the browser (i.e., `localStorage`)
+ - Managing a client-side cache of server data (like `IndexedDB`)
+ - Bypassing the Remix server in a BFF setup and hitting your API directly from the browser
+ - Migrating a React Router SPA to a Remix application
+
+ By default, `clientLoader` will not run on hydration, and will only run on subsequent client side navigations.
+
+ If you wish to run your client loader on hydration, you can set `clientLoader.hydrate=true` to force Remix to execute it on initial page load. Keep in mind that Remix will still SSR your route component so you should ensure that there is no new _required_ data being added by your `clientLoader`.
+
+ If your `clientLoader` needs to run on hydration and adds data you require to render the route component, you can export a `HydrateFallback` component that will render during SSR, and then your route component will not render until the `clientLoader` has executed on hydration.
+
+ `clientAction` is simpler than `clientLoader` because it has no hydration use-cases. `clientAction` will only run on client-side navigations.
+
+ For more information, please refer to the [`clientLoader`](https://remix.run/route/client-loader) and [`clientAction`](https://remix.run/route/client-action) documentation.
+
+- Deprecate `DataFunctionArgs` in favor of `LoaderFunctionArgs`/`ActionFunctionArgs`. This is aimed at keeping the types aligned across server/client loaders/actions now that `clientLoader`/`clientActon` functions have `serverLoader`/`serverAction` parameters which differentiate `ClientLoaderFunctionArgs`/`ClientActionFunctionArgs`. ([#8173](https://github.com/remix-run/remix/pull/8173))
+
+- Add a new `future.v3_relativeSplatPath` flag to implement a breaking bug fix to relative routing when inside a splat route. For more information, please see the React Router [`6.21.0` Release Notes](https://github.com/remix-run/react-router/blob/release-next/CHANGELOG.md#futurev7_relativesplatpath) and the [`useResolvedPath` docs](https://remix.run/hooks/use-resolved-path#splat-paths). ([#8216](https://github.com/remix-run/remix/pull/8216))
+
+### Patch Changes
+
+- Fix flash of unstyled content for non-Express custom servers in Vite dev ([#8076](https://github.com/remix-run/remix/pull/8076))
+- Pass request handler errors to `vite.ssrFixStacktrace` in Vite dev to ensure stack traces correctly map to the original source code ([#8066](https://github.com/remix-run/remix/pull/8066))
+
## 2.3.1
No significant changes to this package were made in this release. [See the repo `CHANGELOG.md`](https://github.com/remix-run/remix/blob/main/CHANGELOG.md) for an overview of all changes in v2.3.1.
diff --git a/packages/remix-server-runtime/jsonify.ts b/packages/remix-server-runtime/jsonify.ts
index 80a6c2b5af..80e524ff57 100644
--- a/packages/remix-server-runtime/jsonify.ts
+++ b/packages/remix-server-runtime/jsonify.ts
@@ -21,6 +21,9 @@ export type Jsonify =
T extends Number ? number :
T extends Boolean ? boolean :
+ // Promises JSON.stringify to an empty object
+ T extends Promise ? EmptyObject :
+
// Map & Set
T extends Map ? EmptyObject :
T extends Set ? EmptyObject :
@@ -119,6 +122,7 @@ type _tests = [
Expect, string>>,
Expect, number>>,
Expect, boolean>>,
+ Expect>, EmptyObject>>,
// Map & Set
Expect>, EmptyObject>>,
@@ -251,7 +255,7 @@ type NeverToNull = [T] extends [never] ? null : T;
// adapted from https://github.com/sindresorhus/type-fest/blob/main/source/empty-object.d.ts
declare const emptyObjectSymbol: unique symbol;
-type EmptyObject = { [emptyObjectSymbol]?: never };
+export type EmptyObject = { [emptyObjectSymbol]?: never };
// adapted from https://github.com/type-challenges/type-challenges/blob/main/utils/index.d.ts
type IsAny = 0 extends 1 & T ? true : false;
diff --git a/packages/remix-server-runtime/package.json b/packages/remix-server-runtime/package.json
index 380565f5bc..7801ae9809 100644
--- a/packages/remix-server-runtime/package.json
+++ b/packages/remix-server-runtime/package.json
@@ -1,6 +1,6 @@
{
"name": "@remix-run/server-runtime",
- "version": "2.3.1",
+ "version": "2.4.0",
"description": "Server runtime for Remix",
"bugs": {
"url": "https://github.com/remix-run/remix/issues"
@@ -16,7 +16,7 @@
"typings": "dist/index.d.ts",
"module": "dist/esm/index.js",
"dependencies": {
- "@remix-run/router": "1.14.0-pre.0",
+ "@remix-run/router": "1.14.0",
"@types/cookie": "^0.5.3",
"@web3-storage/multipart-parser": "^1.0.0",
"cookie": "^0.5.0",
diff --git a/packages/remix-server-runtime/routeModules.ts b/packages/remix-server-runtime/routeModules.ts
index f778335dc3..71c838d397 100644
--- a/packages/remix-server-runtime/routeModules.ts
+++ b/packages/remix-server-runtime/routeModules.ts
@@ -54,7 +54,7 @@ type ClientActionFunction = (
* Arguments passed to a route `clientAction` function
* @private Public API is exported from @remix-run/react
*/
-type ClientActionFunctionArgs = RRActionFunctionArgs & {
+export type ClientActionFunctionArgs = RRActionFunctionArgs & {
serverAction: () => Promise>;
};
@@ -87,7 +87,7 @@ type ClientLoaderFunction = ((
* Arguments passed to a route `clientLoader` function
* @private Public API is exported from @remix-run/react
*/
-type ClientLoaderFunctionArgs = RRLoaderFunctionArgs & {
+export type ClientLoaderFunctionArgs = RRLoaderFunctionArgs & {
serverLoader: () => Promise>;
};
diff --git a/packages/remix-server-runtime/routes.ts b/packages/remix-server-runtime/routes.ts
index 4acbcb469c..d25c5098de 100644
--- a/packages/remix-server-runtime/routes.ts
+++ b/packages/remix-server-runtime/routes.ts
@@ -27,6 +27,8 @@ export interface Route {
export interface EntryRoute extends Route {
hasAction: boolean;
hasLoader: boolean;
+ hasClientAction: boolean;
+ hasClientLoader: boolean;
hasErrorBoundary: boolean;
imports?: string[];
css?: string[];
diff --git a/packages/remix-server-runtime/serialize.ts b/packages/remix-server-runtime/serialize.ts
index 4a2a8a3a79..c3d4822f49 100644
--- a/packages/remix-server-runtime/serialize.ts
+++ b/packages/remix-server-runtime/serialize.ts
@@ -1,21 +1,60 @@
-import type { Jsonify } from "./jsonify";
+import type { EmptyObject, Jsonify } from "./jsonify";
import type { TypedDeferredData, TypedResponse } from "./responses";
+import type {
+ ClientActionFunctionArgs,
+ ClientLoaderFunctionArgs,
+} from "./routeModules";
import { expectType } from "./typecheck";
import { type Expect, type Equal } from "./typecheck";
// prettier-ignore
/**
- * Infer JSON serialized data type returned by a loader or action.
+ * Infer JSON serialized data type returned by a loader or action, while
+ * avoiding deserialization if the input type if it's a clientLoader or
+ * clientAction that returns a non-Response
*
* For example:
* `type LoaderData = SerializeFrom`
*/
export type SerializeFrom =
- T extends (...args: any[]) => infer Output ? Serialize> :
+ T extends (...args: any[]) => infer Output ?
+ Parameters extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] ?
+ // Client data functions may not serialize
+ SerializeClient>
+ :
+ // Serialize responses
+ Serialize>
+ :
// Back compat: manually defined data type, not inferred from loader nor action
Jsonify>
;
+// note: cannot be inlined as logic requires union distribution
+// prettier-ignore
+type SerializeClient