Repository: remix-run/remix (ts)

Issue:
Remix 1.16 `unstable_dev` - problems with assets
### What version of Remix are you using?

1.16

### Are all your remix dependencies & dev-dependencies using the same version?

- [X] Yes

### Steps to Reproduce

1. `git clone git@github.com:ArekBartnik/remix-116-dev-assets.git`
2. `npm i`
3. `npm run dev`
4. go to `http://localhost:3000`

### Expected Behavior

It should show a page using Inter font and an icon after "Welcome to Remix".

| With `unstable_dev: true;` | Without `unstable_dev: true`|
|-|-|
|<img width="367" alt="image" src="https://user-images.githubusercontent.com/1569446/235552660-5c15b7d8-7f1f-437d-9607-194e90fdb1cf.png">| <img width="338" alt="image" src="https://user-images.githubusercontent.com/1569446/235552713-c59feb2b-2bad-4dc6-90cd-8135087be80f.png">|

Without new devserver paths to assets are relative but after turning this flag they change to absolute.


### Actual Behavior

There're 2 errors:
- for the fonts -> 404 `http://localhost:3001/fonts/InterV.var.woff2 net::ERR_ABORTED 404 (Not Found)`
- for the svg icon -> `Unsafe attempt to load URL http://localhost:3001/build/_assets/document-QP6N4QKU.svg from frame with URL http://localhost:3000/. Domains, protocols and ports must match.

<img width="1167" alt="image" src="https://user-images.githubusercontent.com/1569446/235552816-668a7c90-5461-41e5-983a-6ce2a11f233d.png">


Pull request description:
fix(dev): serve any non-Remix assets from `public/`
Fixes #6271

Patch:
diff --git a/.changeset/ninety-flies-allow.md b/.changeset/ninety-flies-allow.md
new file mode 100644
index 000000000..c7787534a
--- /dev/null
+++ b/.changeset/ninety-flies-allow.md
@@ -0,0 +1,7 @@
+---
+"@remix-run/dev": patch
+---
+
+- Fix public asset serving for non-Remix assets in the new dev server
+- Add `--public-directory` / `unstable_dev.publicDirectory` option for configuring local directory for serving non-Remix assets like fonts
+- Fix `svg` serving for new dev server
diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts
index 17dec4514..ece1033fc 100644
--- a/packages/remix-dev/cli/commands.ts
+++ b/packages/remix-dev/cli/commands.ts
@@ -224,6 +224,7 @@ export async function dev(
     httpHost?: string;
     httpPort?: number;
     restart?: boolean;
+    publicDirectory?: string;
     websocketPort?: number;
   } = {}
 ) {
@@ -515,6 +516,7 @@ let resolveDevBuild = async (
 
 type DevServeFlags = DevBuildFlags & {
   command: string;
+  publicDirectory: string;
   restart: boolean;
 };
 let resolveDevServe = async (
@@ -552,6 +554,12 @@ let resolveDevServe = async (
       process.exit(1);
     }
   }
+
+  let publicDirectory =
+    flags.publicDirectory ??
+    (dev === true ? undefined : dev.publicDirectory) ??
+    "public";
+
   let restart =
     flags.restart ?? (dev === true ? undefined : dev.restart) ?? true;
 
@@ -560,6 +568,7 @@ let resolveDevServe = async (
     httpScheme,
     httpHost,
     httpPort,
+    publicDirectory,
     websocketPort,
     restart,
   };
diff --git a/packages/remix-dev/cli/run.ts b/packages/remix-dev/cli/run.ts
index c4448f122..ff5000c28 100644
--- a/packages/remix-dev/cli/run.ts
+++ b/packages/remix-dev/cli/run.ts
@@ -48,6 +48,7 @@ ${colors.logoBlue("R")} ${colors.logoGreen("E")} ${colors.logoYellow(
     --http-host         HTTP(S) host for the dev server. Default: localhost
     --http-port         HTTP(S) port for the dev server. Default: any open port
     --no-restart        Do not restart the app server when rebuilds occur.
+    --public-directory  Path to public assets directory relative to Remix project root. Default: public
     --websocket-port    Websocket port for the dev server. Default: any open port
   \`init\` Options:
     --no-delete         Skip deleting the \`remix.init\` script
@@ -188,6 +189,7 @@ export async function run(argv: string[] = process.argv.slice(2)) {
       "--http-host": String,
       "--http-port": Number,
       "--no-restart": Boolean,
+      "--public-directory": String,
       "--websocket-port": Number,
     },
     {
@@ -225,6 +227,10 @@ export async function run(argv: string[] = process.argv.slice(2)) {
     flags.httpPort = flags["http-port"];
     delete flags["http-port"];
   }
+  if (flags["public-directory"]) {
+    flags.publicDirectory = flags["public-directory"];
+    delete flags["public-directory"];
+  }
   if (flags["websocket-port"]) {
     flags.websocketPort = flags["websocket-port"];
     delete flags["websocket-port"];
diff --git a/packages/remix-dev/compiler/js/compiler.ts b/packages/remix-dev/compiler/js/compiler.ts
index e2bed448f..cf96fc0d4 100644
--- a/packages/remix-dev/compiler/js/compiler.ts
+++ b/packages/remix-dev/compiler/js/compiler.ts
@@ -202,7 +202,11 @@ const createEsbuildConfig = (
     platform: "browser",
     format: "esm",
     external: getExternals(ctx.config),
-    loader: loaders,
+    loader: {
+      ...loaders,
+      // in development, use dataurls for svgs so that `<use href={mysvg} />` works across different origins
+      ".svg": ctx.options.mode === "development" ? "dataurl" : loaders[".svg"],
+    },
     bundle: true,
     logLevel: "silent",
     splitting: true,
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index e5c7a3924..8f427f9dc 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -45,6 +45,7 @@ type Dev = {
   httpPort?: number;
   websocketPort?: number;
   restart?: boolean;
+  publicDirectory?: string;
 };
 
 interface FutureConfig {
diff --git a/packages/remix-dev/devServer_unstable/index.ts b/packages/remix-dev/devServer_unstable/index.ts
index 183263a96..89accea88 100644
--- a/packages/remix-dev/devServer_unstable/index.ts
+++ b/packages/remix-dev/devServer_unstable/index.ts
@@ -54,6 +54,7 @@ export let serve = async (
     httpScheme: string;
     httpHost: string;
     httpPort: number;
+    publicDirectory: string;
     websocketPort: number;
     restart: boolean;
   }
@@ -196,6 +197,7 @@ export let serve = async (
         maxAge: "1y",
       })
     )
+    .use(express.static(options.publicDirectory, { maxAge: "1h" }))
 
     // handle `broadcastDevReady` messages
     .use(express.json())


Test patch:
diff --git a/integration/hmr-log-test.ts b/integration/hmr-log-test.ts
index ad3bd4093..b873ab525 100644
--- a/integration/hmr-log-test.ts
+++ b/integration/hmr-log-test.ts
@@ -230,7 +230,7 @@ test("HMR", async ({ page }) => {
     }
   });
 
-  let portRange = makeRange(3080, 3099);
+  let portRange = makeRange(4080, 4099);
   let appServerPort = await getPort({ port: portRange });
   let httpPort = await getPort({ port: portRange });
   let webSocketPort = await getPort({ port: portRange });
diff --git a/packages/remix-dev/__tests__/cli-test.ts b/packages/remix-dev/__tests__/cli-test.ts
index ba57c1006..59e5dcb93 100644
--- a/packages/remix-dev/__tests__/cli-test.ts
+++ b/packages/remix-dev/__tests__/cli-test.ts
@@ -122,6 +122,7 @@ describe("remix CLI", () => {
             --http-host         HTTP(S) host for the dev server. Default: localhost
             --http-port         HTTP(S) port for the dev server. Default: any open port
             --no-restart        Do not restart the app server when rebuilds occur.
+            --public-directory  Path to public assets directory relative to Remix project root. Default: public
             --websocket-port    Websocket port for the dev server. Default: any open port
           \`init\` Options:
             --no-delete         Skip deleting the \`remix.init\` script