Skip to content

Commit 8292199

Browse files
Vite: exclude modules within .server directories from client build (#8154)
Co-authored-by: Mark Dalgleish <[email protected]>
1 parent f0f675c commit 8292199

File tree

5 files changed

+136
-6
lines changed

5 files changed

+136
-6
lines changed

integration/helpers/vite-template/tsconfig.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
"paths": {
1717
"~/*": ["./app/*"]
1818
},
19-
20-
// Remix takes care of building everything in `remix build`.
2119
"noEmit": true
2220
}
2321
}

integration/helpers/vite.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { spawn, type ChildProcess } from "node:child_process";
1+
import { spawn, spawnSync, type ChildProcess } from "node:child_process";
22
import path from "node:path";
33
import type { Readable } from "node:stream";
44
import url from "node:url";
@@ -119,6 +119,24 @@ const createDev =
119119
return async () => await kill(proc.pid!);
120120
};
121121

122+
export const viteBuild = (args: { cwd: string }) => {
123+
let vite = resolveBin.sync("vite");
124+
let commands = [
125+
[vite, "build"],
126+
[vite, "build", "--ssr"],
127+
];
128+
let results = [];
129+
for (let command of commands) {
130+
let result = spawnSync("node", command, {
131+
cwd: args.cwd,
132+
env: {
133+
...process.env,
134+
},
135+
});
136+
results.push(result);
137+
}
138+
return results;
139+
};
122140
export const viteDev = createDev([resolveBin.sync("vite"), "dev"]);
123141
export const customDev = createDev(["./server.mjs"]);
124142

integration/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"strip-indent": "^3.0.0",
3535
"tailwindcss": "^3.3.0",
3636
"type-fest": "^4.0.0",
37-
"typescript": "^5.1.0"
37+
"typescript": "^5.1.0",
38+
"vite-tsconfig-paths": "^4.2.1"
3839
}
3940
}

integration/vite-dot-server-test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import * as path from "node:path";
2+
import { test, expect } from "@playwright/test";
3+
import shell from "shelljs";
4+
import glob from "glob";
5+
6+
import { createProject, viteBuild } from "./helpers/vite.js";
7+
8+
let files = {
9+
"app/utils.server.ts": String.raw`
10+
export const dotServerFile = "SERVER_ONLY_FILE";
11+
`,
12+
"app/.server/utils.ts": String.raw`
13+
export const dotServerDir = "SERVER_ONLY_DIR";
14+
`,
15+
};
16+
17+
test("Vite / build / .server file in client fails with expected error", async () => {
18+
let cwd = await createProject({
19+
...files,
20+
"app/routes/fail-server-file-in-client.tsx": String.raw`
21+
import { dotServerFile } from "~/utils.server";
22+
23+
export default function() {
24+
console.log(dotServerFile);
25+
return <h1>Fail: Server file included in client</h1>
26+
}
27+
`,
28+
});
29+
let client = viteBuild({ cwd })[0];
30+
let stderr = client.stderr.toString("utf8");
31+
expect(stderr).toMatch(
32+
`"dotServerFile" is not exported by "app/utils.server.ts"`
33+
);
34+
});
35+
36+
test("Vite / build / .server dir in client fails with expected error", async () => {
37+
let cwd = await createProject({
38+
...files,
39+
"app/routes/fail-server-dir-in-client.tsx": String.raw`
40+
import { dotServerDir } from "~/.server/utils";
41+
42+
export default function() {
43+
console.log(dotServerDir);
44+
return <h1>Fail: Server directory included in client</h1>
45+
}
46+
`,
47+
});
48+
let client = viteBuild({ cwd })[0];
49+
let stderr = client.stderr.toString("utf8");
50+
expect(stderr).toMatch(
51+
`"dotServerDir" is not exported by "app/.server/utils.ts"`
52+
);
53+
});
54+
55+
test("Vite / build / dead-code elimination for server exports", async () => {
56+
let cwd = await createProject({
57+
...files,
58+
"app/routes/remove-server-exports-and-dce.tsx": String.raw`
59+
import fs from "node:fs";
60+
import { json } from "@remix-run/node";
61+
import { useLoaderData } from "@remix-run/react";
62+
63+
import { dotServerFile } from "../utils.server";
64+
import { dotServerDir } from "../.server/utils";
65+
66+
export const loader = () => {
67+
let contents = fs.readFileSync("blah");
68+
let data = dotServerFile + dotServerDir + serverOnly + contents;
69+
return json({ data });
70+
}
71+
72+
export const action = () => {
73+
console.log(dotServerFile, dotServerDir, serverOnly);
74+
return null;
75+
}
76+
77+
export default function() {
78+
let { data } = useLoaderData<typeof loader>();
79+
return (
80+
<>
81+
<h2>Index</h2>
82+
<p>{data}</p>
83+
</>
84+
);
85+
}
86+
`,
87+
});
88+
let client = viteBuild({ cwd })[0];
89+
expect(client.status).toBe(0);
90+
91+
// detect client asset files
92+
let assetFiles = glob.sync("**/*.@(js|jsx|ts|tsx)", {
93+
cwd: path.join(cwd, "build/client"),
94+
absolute: true,
95+
});
96+
97+
// grep for server-only values in client assets
98+
let result = shell
99+
.grep("-l", /SERVER_ONLY_FILE|SERVER_ONLY_DIR|node:fs/, assetFiles)
100+
.stdout.trim()
101+
.split("\n")
102+
.filter((line) => line.length > 0);
103+
104+
expect(result).toHaveLength(0);
105+
});

packages/remix-dev/vite/plugin.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,22 +889,30 @@ export const remixVitePlugin: RemixVitePlugin = (options = {}) => {
889889
name: "remix-empty-server-modules",
890890
enforce: "pre",
891891
async transform(_code, id, options) {
892-
if (!options?.ssr && /\.server(\.[cm]?[jt]sx?)?$/.test(id))
892+
if (options?.ssr) return;
893+
let serverFileRE = /\.server(\.[cm]?[jt]sx?)?$/;
894+
let serverDirRE = /\/\.server\//;
895+
if (serverFileRE.test(id) || serverDirRE.test(id)) {
893896
return {
894897
code: "export default {}",
895898
map: null,
896899
};
900+
}
897901
},
898902
},
899903
{
900904
name: "remix-empty-client-modules",
901905
enforce: "pre",
902906
async transform(_code, id, options) {
903-
if (options?.ssr && /\.client(\.[cm]?[jt]sx?)?$/.test(id))
907+
if (!options?.ssr) return;
908+
let clientFileRE = /\.client(\.[cm]?[jt]sx?)?$/;
909+
let clientDirRE = /\/\.client\//;
910+
if (clientFileRE.test(id) || clientDirRE.test(id)) {
904911
return {
905912
code: "export default {}",
906913
map: null,
907914
};
915+
}
908916
},
909917
},
910918
{

0 commit comments

Comments
 (0)