11import * as path from "node:path" ;
22import { test , expect } from "@playwright/test" ;
3- import shell from "shelljs" ;
4- import glob from "glob" ;
53
6- import { createProject , viteBuild } from "./helpers/vite.js" ;
4+ import { createProject , grep , viteBuild } from "./helpers/vite.js" ;
75
86let files = {
97 "app/utils.server.ts" : String . raw `
@@ -16,7 +14,7 @@ let files = {
1614 ` ,
1715} ;
1816
19- test ( "Vite / .server file / named import in client fails with expected error" , async ( ) => {
17+ test ( "Vite / .server file / named import in client fails with expected error" , async ( ) => {
2018 let cwd = await createProject ( {
2119 ...files ,
2220 "app/routes/fail-server-file-in-client.tsx" : String . raw `
@@ -35,7 +33,7 @@ test("Vite / .server file / named import in client fails with expected error",
3533 ) ;
3634} ) ;
3735
38- test ( "Vite / .server file / namespace import in client fails with expected error" , async ( ) => {
36+ test ( "Vite / .server file / namespace import in client fails with expected error" , async ( ) => {
3937 let cwd = await createProject ( {
4038 ...files ,
4139 "app/routes/fail-server-file-in-client.tsx" : String . raw `
@@ -126,54 +124,75 @@ test("Vite / .server dir / default import in client fails with expected error",
126124 expect ( stderr ) . toMatch ( `"default" is not exported by "app/.server/utils.ts"` ) ;
127125} ) ;
128126
127+ test ( "Vite / `handle` with dynamic imports as an escape hatch for server-only code" , async ( ) => {
128+ let cwd = await createProject ( {
129+ ...files ,
130+ "app/routes/handle-server-only.tsx" : String . raw `
131+ export const handle = {
132+ // Sharp knife alert: you probably should avoid doing this, but you can!
133+ serverOnlyEscapeHatch: async () => {
134+ let { dotServerFile } = await import("~/utils.server");
135+ let dotServerDir = await import("~/.server/utils");
136+ return { dotServerFile, dotServerDir };
137+ }
138+ }
139+
140+ export default function() {
141+ return <h1>This should work</h1>
142+ }
143+ ` ,
144+ } ) ;
145+ let [ client , server ] = viteBuild ( { cwd } ) ;
146+ expect ( client . status ) . toBe ( 0 ) ;
147+ expect ( server . status ) . toBe ( 0 ) ;
148+
149+ let lines = grep (
150+ path . join ( cwd , "build/client" ) ,
151+ / S E R V E R _ O N L Y _ F I L E | S E R V E R _ O N L Y _ D I R /
152+ ) ;
153+ expect ( lines ) . toHaveLength ( 0 ) ;
154+ } ) ;
155+
129156test ( "Vite / dead-code elimination for server exports" , async ( ) => {
130157 let cwd = await createProject ( {
131158 ...files ,
132159 "app/routes/remove-server-exports-and-dce.tsx" : String . raw `
133- import fs from "node:fs";
134- import { json } from "@remix-run/node";
135- import { useLoaderData } from "@remix-run/react";
160+ import fs from "node:fs";
161+ import { json } from "@remix-run/node";
162+ import { useLoaderData } from "@remix-run/react";
136163
137- import { dotServerFile } from "../utils.server";
138- import { dotServerDir } from "../.server/utils";
164+ import { dotServerFile } from "../utils.server";
165+ import { dotServerDir } from "../.server/utils";
139166
140- export const loader = () => {
141- let contents = fs.readFileSync("blah");
142- let data = dotServerFile + dotServerDir + serverOnly + contents;
143- return json({ data });
144- }
167+ export const loader = () => {
168+ let contents = fs.readFileSync("blah");
169+ let data = dotServerFile + dotServerDir + serverOnly + contents;
170+ return json({ data });
171+ }
145172
146- export const action = () => {
147- console.log(dotServerFile, dotServerDir, serverOnly);
148- return null;
149- }
173+ export const action = () => {
174+ console.log(dotServerFile, dotServerDir, serverOnly);
175+ return null;
176+ }
150177
151- export default function() {
152- let { data } = useLoaderData<typeof loader>();
153- return (
154- <>
155- <h2>Index</h2>
156- <p>{data}</p>
157- </>
158- );
159- }
160- ` ,
178+ export default function() {
179+ let { data } = useLoaderData<typeof loader>();
180+ return (
181+ <>
182+ <h2>Index</h2>
183+ <p>{data}</p>
184+ </>
185+ );
186+ }
187+ ` ,
161188 } ) ;
162- let client = viteBuild ( { cwd } ) [ 0 ] ;
189+ let [ client , server ] = viteBuild ( { cwd } ) ;
163190 expect ( client . status ) . toBe ( 0 ) ;
191+ expect ( server . status ) . toBe ( 0 ) ;
164192
165- // detect client asset files
166- let assetFiles = glob . sync ( "**/*.@(js|jsx|ts|tsx)" , {
167- cwd : path . join ( cwd , "build/client" ) ,
168- absolute : true ,
169- } ) ;
170-
171- // grep for server-only values in client assets
172- let result = shell
173- . grep ( "-l" , / S E R V E R _ O N L Y _ F I L E | S E R V E R _ O N L Y _ D I R | n o d e : f s / , assetFiles )
174- . stdout . trim ( )
175- . split ( "\n" )
176- . filter ( ( line ) => line . length > 0 ) ;
177-
178- expect ( result ) . toHaveLength ( 0 ) ;
193+ let lines = grep (
194+ path . join ( cwd , "build/client" ) ,
195+ / S E R V E R _ O N L Y _ F I L E | S E R V E R _ O N L Y _ D I R | n o d e : f s /
196+ ) ;
197+ expect ( lines ) . toHaveLength ( 0 ) ;
179198} ) ;
0 commit comments