Skip to content

Commit

Permalink
feat(vite): strict route exports (#8171)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcattori committed Dec 1, 2023
1 parent 40cf019 commit 3782e4b
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 1 deletion.
70 changes: 70 additions & 0 deletions integration/vite-route-exports-test.ts
@@ -0,0 +1,70 @@
import { test, expect } from "@playwright/test";

import { createProject, viteBuild } from "./helpers/vite.js";

test("Vite / invalid route exports / expected build error", async () => {
let cwd = await createProject({
"app/routes/fail-non-remix-exports.tsx": String.raw`
// Remix exports
export const ErrorBoundary = () => {}
export const action = () => {}
export const handle = () => {}
export const headers = () => {}
export const links = () => {}
export const loader = () => {}
export const meta = () => {}
export const shouldRevalidate = () => {}
export default function() {}
// Non-Remix exports
export const invalid1 = 1;
export const invalid2 = 2;
`,
});
let client = viteBuild({ cwd })[0];
let stderr = client.stderr.toString("utf8");
expect(stderr).toMatch(
"2 invalid route exports in `routes/fail-non-remix-exports.tsx`:\n - `invalid1`\n - `invalid2`"
);
expect(stderr).toMatch(
"See https://remix.run/docs/en/main/future/vite#strict-route-exports"
);
});

test("Vite / invalid route exports / ignore in mdx", async () => {
let cwd = await createProject({
"vite.config.ts": String.raw`
import { defineConfig } from "vite";
import { unstable_vitePlugin as remix } from "@remix-run/dev";
import mdx from "@mdx-js/rollup";
export default defineConfig({
plugins: [
remix(),
mdx(),
],
});
`,
"app/routes/pass-non-remix-exports-in-mdx.mdx": String.raw`
// Remix exports
export const ErrorBoundary = () => {}
export const action = () => {}
export const handle = () => {}
export const headers = () => {}
export const links = () => {}
export const loader = () => {}
export const meta = () => {}
export const shouldRevalidate = () => {}
export default function() {}
// Non-Remix exports
export const invalid1 = 1;
export const invalid2 = 2;
# Hello World
`,
});
let [client, server] = viteBuild({ cwd });
expect(client.status).toBe(0);
expect(server.status).toBe(0);
});
31 changes: 30 additions & 1 deletion packages/remix-dev/vite/plugin.ts
Expand Up @@ -47,6 +47,18 @@ const supportedRemixConfigKeys = [
type SupportedRemixConfigKey = typeof supportedRemixConfigKeys[number];
type SupportedRemixConfig = Pick<RemixUserConfig, SupportedRemixConfigKey>;

const ROUTE_EXPORTS = new Set([
"ErrorBoundary",
"action",
"default", // component
"handle",
"headers",
"links",
"loader",
"meta",
"shouldRevalidate",
]);

// We need to provide different JSDoc comments in some cases due to differences
// between the Remix config and the Vite plugin.
type RemixConfigJsdocOverrides = {
Expand Down Expand Up @@ -940,7 +952,7 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => {
},
},
{
name: "remix-remove-server-exports",
name: "remix-route-exports",
enforce: "post", // Ensure we're operating on the transformed code to support MDX etc.
async transform(code, id, options) {
if (options?.ssr) return;
Expand All @@ -950,6 +962,23 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => {
let route = getRoute(pluginConfig, id);
if (!route) return;

// check the exports, fail if unknown exists, unless id ends with .mdx
let nonRemixExports = esModuleLexer(code)[1]
.map((exp) => exp.n)
.filter((exp) => !ROUTE_EXPORTS.has(exp));
if (nonRemixExports.length > 0 && !id.endsWith(".mdx")) {
let message = [
`${nonRemixExports.length} invalid route export${
nonRemixExports.length > 1 ? "s" : ""
} in \`${route.file}\`:`,
...nonRemixExports.map((exp) => ` - \`${exp}\``),
"",
"See https://remix.run/docs/en/main/future/vite#strict-route-exports",
"",
].join("\n");
throw Error(message);
}

let serverExports = ["loader", "action", "headers"];

return {
Expand Down

0 comments on commit 3782e4b

Please sign in to comment.