Repository: remix-run/remix
Pull request #5831 (2023-03-16 20:09:43)

ci: release commenting overhaul
we now only hit github for the following things:
- asking if a commit is a PR (this will return the PR so we get the number, title, and body which we use)
- getting any linked issues
- commenting on issues and PRs

also took now as an opportunity to break this out to a proper re-usable GitHub Action https://github.com/mcansh/release-comment-action

an example run can be found in the test repo: https://github.com/mcansh/nightly-release-test/pull/180

React Router PR for this -> https://github.com/remix-run/react-router/pull/10265

<!--

👋 Hey, thanks for your interest in contributing to Remix!

If this is a simple docs change, go ahead and delete all this.

**Please ask first before starting work on any significant new features.**

It's never a fun experience to have your pull request declined after investing a
lot of time and effort into a new feature. To avoid this from happening, we
request that contributors create a
[Feature Request discussion](https://github.com/remix-run/remix/discussions/new?category=ideas)
to first discuss any significant new features.

https://github.com/remix-run/remix/blob/main/CONTRIBUTING.md

Please fill in or delete each item below:

-->

Closes: #

- [ ] Docs
- [ ] Tests

Testing Strategy:

<!--
Please explain how you tested this. For example:

> This test covers this code: <link to test>

Or

> I opened up my windows machine and ran this script:
>
> ```
> npx create-remix@0.0.0-experimental-7e420ee3 --template remix my-test
> cd my-test
> npm run dev
> ```
-->


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: ead12242c81899d75698a29bace78051e8780e2b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/remix-run/remix/new/logan/use-git-and-gh-for-comments?filename=.changeset/twenty-cherries-flash.md&value=---%0A%0A---%0A%0Aci%3A%20use%20%60git%60%20and%20%60gh%60%20for%20commenting%20on%20issues%20and%20prs%20about%20releases%0A)



Patch:
diff --git a/.changeset/mean-deers-wink.md b/.changeset/mean-deers-wink.md
new file mode 100644
index 000000000..4387970f1
--- /dev/null
+++ b/.changeset/mean-deers-wink.md
@@ -0,0 +1,6 @@
+---
+"remix": patch
+"@remix-run/react": patch
+---
+
+don't warn about runtime deprecation warnings in production
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 4b77873d0..38a105379 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -95,10 +95,6 @@ jobs:
     name: 📝 Comment on related issues and pull requests
     if: github.repository == 'remix-run/remix' && needs.nightly.outputs.NEXT_VERSION
     uses: ./.github/workflows/release-comments.yml
-    with:
-      ref: "refs/tags/v${{ needs.nightly.outputs.NEXT_VERSION }}"
-      package_version_to_follow: "remix"
-      release_branch: "dev"
 
   deployments:
     needs: [nightly]
diff --git a/.github/workflows/release-comments.yml b/.github/workflows/release-comments.yml
index 39cf05874..30db113aa 100644
--- a/.github/workflows/release-comments.yml
+++ b/.github/workflows/release-comments.yml
@@ -2,44 +2,20 @@ name: 📝 Comment on Release
 
 on:
   workflow_call:
-    inputs:
-      ref:
-        required: true
-        type: string
-      package_version_to_follow:
-        required: true
-        type: string
-      release_branch:
-        required: true
-        type: string
 
 jobs:
   comment:
-    name: Comment on Release
+    name: 📝 Comment on related issues and pull requests
     if: github.repository == 'remix-run/remix'
     runs-on: ubuntu-latest
     steps:
       - name: ⬇️ Checkout repo
         uses: actions/checkout@v3
-
-      - name: ⎔ Setup node
-        uses: actions/setup-node@v3
         with:
-          node-version-file: ".nvmrc"
-          cache: "npm"
-          cache-dependency-path: scripts/release/package-lock.json
-
-      - name: 📥 Install deps
-        run: npm ci
-        working-directory: ./scripts/release
+          fetch-depth: 0
 
-      - name: 📝 Comment on issues
-        working-directory: ./scripts/release
-        run: node -r esbuild-register ./comment.ts
-        env:
-          GITHUB_REPOSITORY: ${{ github.repository }}
-          GITHUB_TOKEN: ${{ github.token }}
-          VERSION: ${{ inputs.ref }}
-          DEFAULT_BRANCH: "main"
-          RELEASE_BRANCH: ${{ inputs.release_branch }}
-          PACKAGE_VERSION_TO_FOLLOW: ${{ inputs.package_version_to_follow }}
+      - name: 📝 Comment on related issues and pull requests
+        uses: mcansh/release-comment-action@0.2.0
+        with:
+          DIRECTORY_TO_CHECK: "./packages"
+          PACKAGE_VERSION_TO_FOLLOW: "remix"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8be64fe08..4942ce359 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -31,7 +31,6 @@ jobs:
         uses: actions/checkout@v3
         with:
           fetch-depth: 0
-          token: ${{ secrets.NIGHTLY_PAT }}
 
       - name: ⎔ Setup node
         uses: actions/setup-node@v3
@@ -62,7 +61,7 @@ jobs:
           publish: yarn run changeset:release
           createGithubReleases: false
         env:
-          GITHUB_TOKEN: ${{ secrets.NIGHTLY_PAT }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
 
   find_package_version:
@@ -87,7 +86,7 @@ jobs:
 
       - id: find_package_version
         run: |
-          package_version=$(node ./scripts/release/find-release-from-changeset.js)
+          package_version=$(node ./scripts/find-release-from-changeset.js)
           echo "package_version=${package_version}" >> $GITHUB_OUTPUT
         env:
           PACKAGE_VERSION_TO_FOLLOW: "remix"
@@ -98,10 +97,6 @@ jobs:
     if: github.repository == 'remix-run/remix' && needs.find_package_version.outputs.package_version != ''
     needs: [release, find_package_version]
     uses: ./.github/workflows/release-comments.yml
-    with:
-      ref: refs/tags/remix@${{ needs.find_package_version.outputs.package_version }}
-      package_version_to_follow: "remix"
-      release_branch: ${{ github.ref_name }}
 
   deployments:
     name: 🚀 Deployment Tests
diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts
index 5e006a11f..a244405ed 100644
--- a/packages/remix-dev/cli/commands.ts
+++ b/packages/remix-dev/cli/commands.ts
@@ -10,7 +10,7 @@ import { coerce } from "semver";
 import * as colors from "../colors";
 import * as compiler from "../compiler";
 import * as devServer from "../devServer";
-import * as devServer2 from "../devServer2";
+import * as devServer_unstable from "../devServer_unstable";
 import type { RemixConfig } from "../config";
 import { readConfig } from "../config";
 import { formatRoutes, RoutesFormat, isRoutesFormat } from "../config/format";
@@ -209,7 +209,7 @@ export async function dev(
   let mode = compiler.parseMode(modeArg ?? "", "development");
 
   if (config.future.unstable_dev !== false) {
-    await devServer2.serve(config, flags);
+    await devServer_unstable.serve(config, flags);
     return await new Promise(() => {});
   }
 
diff --git a/packages/remix-dev/compiler/compileBrowser.ts b/packages/remix-dev/compiler/browserjs/compiler.ts
similarity index 51%
rename from packages/remix-dev/compiler/compileBrowser.ts
rename to packages/remix-dev/compiler/browserjs/compiler.ts
index a0d9df206..d84004bf0 100644
--- a/packages/remix-dev/compiler/compileBrowser.ts
+++ b/packages/remix-dev/compiler/browserjs/compiler.ts
@@ -1,47 +1,57 @@
 import * as path from "path";
-import * as fse from "fs-extra";
 import { builtinModules as nodeBuiltins } from "module";
 import * as esbuild from "esbuild";
 import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill";
-import postcss from "postcss";
-import postcssDiscardDuplicates from "postcss-discard-duplicates";
 
-import type { Channel, WriteChannel } from "../channel";
-import { createChannel } from "../channel";
-import type { RemixConfig } from "../config";
-import type { AssetsManifest } from "./assets";
-import { createAssetsManifest } from "./assets";
-import { getAppDependencies } from "./dependencies";
-import { loaders } from "./loaders";
-import type { CompileOptions } from "./options";
-import { browserRouteModulesPlugin } from "./plugins/browserRouteModulesPlugin";
-import { browserRouteModulesPlugin as browserRouteModulesPlugin_v2 } from "./plugins/browserRouteModulesPlugin_v2";
-import { cssFilePlugin } from "./plugins/cssFilePlugin";
-import { deprecatedRemixPackagePlugin } from "./plugins/deprecatedRemixPackagePlugin";
-import { emptyModulesPlugin } from "./plugins/emptyModulesPlugin";
-import { mdxPlugin } from "./plugins/mdx";
-import { urlImportsPlugin } from "./plugins/urlImportsPlugin";
-import { cssBundleUpdatePlugin } from "./plugins/cssBundleUpdatePlugin";
-import { cssModulesPlugin } from "./plugins/cssModulesPlugin";
-import { cssSideEffectImportsPlugin } from "./plugins/cssSideEffectImportsPlugin";
-import { vanillaExtractPlugin } from "./plugins/vanillaExtractPlugin";
-import {
-  cssBundleEntryModulePlugin,
-  cssBundleEntryModuleId,
-} from "./plugins/cssBundleEntryModulePlugin";
-import { writeFileSafe } from "./utils/fs";
-import invariant from "../invariant";
-import { hmrPlugin } from "./plugins/hmrPlugin";
-import { NodeProtocolExternalPlugin } from "./plugins/nodeProtocolExternalPlugin";
-
-export type BrowserCompiler = {
+import type { RemixConfig } from "../../config";
+import type * as Manifest from "../manifest";
+import { getAppDependencies } from "../../dependencies";
+import { loaders } from "../loaders";
+import type { CompileOptions } from "../options";
+import { browserRouteModulesPlugin } from "./plugins/routes";
+import { browserRouteModulesPlugin as browserRouteModulesPlugin_v2 } from "./plugins/routes_unstable";
+import { cssFilePlugin } from "../plugins/cssImports";
+import { deprecatedRemixPackagePlugin } from "../plugins/deprecatedRemixPackage";
+import { emptyModulesPlugin } from "../plugins/emptyModules";
+import { mdxPlugin } from "../plugins/mdx";
+import { externalPlugin } from "../plugins/external";
+import { cssBundleUpdatePlugin } from "./plugins/cssBundleUpdate";
+import { cssModulesPlugin } from "../plugins/cssModuleImports";
+import { cssSideEffectImportsPlugin } from "../plugins/cssSideEffectImports";
+import { vanillaExtractPlugin } from "../plugins/vanillaExtract";
+// import {
+//   cssBundleEntryModulePlugin,
+//   cssBundleEntryModuleId,
+// } from "../plugins/cssBundleEntryModulePlugin";
+import invariant from "../../invariant";
+import { hmrPlugin } from "./plugins/hmr";
+import { createMatchPath } from "../utils/tsconfig";
+import { getPreferredPackageManager } from "../../cli/getPreferredPackageManager";
+
+type Compiler = {
   // produce ./public/build/
-  compile: (
-    manifestChannel: WriteChannel<AssetsManifest>
-  ) => Promise<esbuild.Metafile>;
+  compile: () => Promise<{
+    metafile: esbuild.Metafile;
+    hmr?: Manifest.Type["hmr"];
+  }>;
   dispose: () => void;
 };
 
+function getNpmPackageName(id: string): string {
+  let split = id.split("/");
+  let packageName = split[0];
+  if (packageName.startsWith("@")) packageName += `/${split[1]}`;
+  return packageName;
+}
+
+function isBareModuleId(id: string): boolean {
+  return !id.startsWith("node:") && !id.startsWith(".") && !path.isAbsolute(id);
+}
+
+function isNodeBuiltIn(packageName: string) {
+  return nodeBuiltins.includes(packageName);
+}
+
 const getExternals = (remixConfig: RemixConfig): string[] => {
   // For the browser build, exclude node built-ins that don't have a
   // browser-safe alternative installed in node_modules. Nothing should
@@ -61,20 +71,6 @@ const getExternals = (remixConfig: RemixConfig): string[] => {
   return nodeBuiltins.filter((mod) => !dependencies.includes(mod));
 };
 
-const writeAssetsManifest = async (
-  config: RemixConfig,
-  assetsManifest: AssetsManifest
-) => {
-  let filename = `manifest-${assetsManifest.version.toUpperCase()}.js`;
-
-  assetsManifest.url = config.publicPath + filename;
-
-  await writeFileSafe(
-    path.join(config.assetsBuildDirectory, filename),
-    `window.__remixManifest=${JSON.stringify(assetsManifest)};`
-  );
-};
-
 const isCssBundlingEnabled = (config: RemixConfig): boolean =>
   Boolean(
     config.future.unstable_cssModules ||
@@ -82,23 +78,19 @@ const isCssBundlingEnabled = (config: RemixConfig): boolean =>
       config.future.unstable_vanillaExtract
   );
 
-let cssBundleHrefChannel: Channel<string | undefined>;
-
-// This function gives esbuild access to the latest channel value on rebuilds
-let getCssBundleHref = () => cssBundleHrefChannel.read();
-
 const createEsbuildConfig = (
   build: "app" | "css",
   config: RemixConfig,
   options: CompileOptions,
-  onLoader: (filename: string, code: string) => void
+  onLoader: (filename: string, code: string) => void,
+  readCssBundleHref: () => Promise<string | undefined>
 ): esbuild.BuildOptions | esbuild.BuildIncremental => {
   let isCssBuild = build === "css";
   let entryPoints: Record<string, string>;
 
   if (isCssBuild) {
     entryPoints = {
-      "css-bundle": cssBundleEntryModuleId,
+      // "css-bundle": cssBundleEntryModuleId,
     };
   } else {
     entryPoints = {
@@ -116,11 +108,23 @@ const createEsbuildConfig = (
   let { mode } = options;
   let outputCss = isCssBuild;
 
+  let matchPath = config.tsconfigPath
+    ? createMatchPath(config.tsconfigPath)
+    : undefined;
+  function resolvePath(id: string) {
+    if (!matchPath) {
+      return id;
+    }
+    return (
+      matchPath(id, undefined, undefined, [".ts", ".tsx", ".js", ".jsx"]) || id
+    );
+  }
+
   let plugins: esbuild.Plugin[] = [
     deprecatedRemixPackagePlugin(options.onWarning),
-    isCssBundlingEnabled(config) && isCssBuild
-      ? cssBundleEntryModulePlugin(config)
-      : null,
+    // isCssBundlingEnabled(config) && isCssBuild
+    //   ? cssBundleEntryModulePlugin(config)
+    //   : null,
     config.future.unstable_cssModules
       ? cssModulesPlugin({ config, mode, outputCss })
       : null,
@@ -131,14 +135,55 @@ const createEsbuildConfig = (
       ? cssSideEffectImportsPlugin({ config, options })
       : null,
     cssFilePlugin({ config, options }),
-    urlImportsPlugin(),
+    externalPlugin(/^https?:\/\//, { sideEffects: false }),
     mdxPlugin(config),
     config.future.unstable_dev
       ? browserRouteModulesPlugin_v2(config, /\?browser$/, onLoader, mode)
       : browserRouteModulesPlugin(config, /\?browser$/),
     emptyModulesPlugin(config, /\.server(\.[jt]sx?)?$/),
     NodeModulesPolyfillPlugin(),
-    NodeProtocolExternalPlugin(),
+    externalPlugin(/^node:.*/, { sideEffects: false }),
+    {
+      // TODO: should be removed when error handling for compiler is improved
+      name: "warn-on-unresolved-imports",
+      setup: (build) => {
+        build.onResolve({ filter: /.*/ }, (args) => {
+          if (!isBareModuleId(resolvePath(args.path))) {
+            return undefined;
+          }
+
+          if (args.path === "remix:hmr") {
+            return undefined;
+          }
+
+          let packageName = getNpmPackageName(args.path);
+          let pkgManager = getPreferredPackageManager();
+          if (
+            options.onWarning &&
+            !isNodeBuiltIn(packageName) &&
+            !/\bnode_modules\b/.test(args.importer) &&
+            // Silence spurious warnings when using Yarn PnP. Yarn PnP doesn’t use
+            // a `node_modules` folder to keep its dependencies, so the above check
+            // will always fail.
+            (pkgManager === "npm" ||
+              (pkgManager === "yarn" && process.versions.pnp == null))
+          ) {
+            try {
+              require.resolve(args.path);
+            } catch (error: unknown) {
+              options.onWarning(
+                `The path "${args.path}" is imported in ` +
+                  `${path.relative(process.cwd(), args.importer)} but ` +
+                  `"${args.path}" was not found in your node_modules. ` +
+                  `Did you forget to install it?`,
+                args.path
+              );
+            }
+          }
+          return undefined;
+        });
+      },
+    } as esbuild.Plugin,
   ].filter(isNotNull);
 
   if (build === "app" && mode === "development" && config.future.unstable_dev) {
@@ -161,7 +206,9 @@ const createEsbuildConfig = (
     plugins.push(hmrPlugin({ remixConfig: config }));
 
     if (isCssBundlingEnabled(config)) {
-      plugins.push(cssBundleUpdatePlugin({ getCssBundleHref }));
+      plugins.push(
+        cssBundleUpdatePlugin({ getCssBundleHref: readCssBundleHref })
+      );
     }
   }
 
@@ -212,12 +259,12 @@ const createEsbuildConfig = (
   };
 };
 
-export const createBrowserCompiler = (
+export const create = (
   remixConfig: RemixConfig,
-  options: CompileOptions
-): BrowserCompiler => {
+  options: CompileOptions,
+  readCssBundleHref: () => Promise<string | undefined>
+): Compiler => {
   let appCompiler: esbuild.BuildIncremental;
-  let cssCompiler: esbuild.BuildIncremental;
 
   let hmrRoutes: Record<string, { loaderHash: string }> = {};
   let onLoader = (filename: string, code: string) => {
@@ -225,12 +272,18 @@ export const createBrowserCompiler = (
     hmrRoutes[key] = { loaderHash: code };
   };
 
-  let compile = async (manifestChannel: WriteChannel<AssetsManifest>) => {
+  let compile = async () => {
     hmrRoutes = {};
     let appBuildTask = async () => {
       appCompiler = await (!appCompiler
         ? esbuild.build({
-            ...createEsbuildConfig("app", remixConfig, options, onLoader),
+            ...createEsbuildConfig(
+              "app",
+              remixConfig,
+              options,
+              onLoader,
+              readCssBundleHref
+            ),
             metafile: true,
             incremental: true,
           })
@@ -243,112 +296,9 @@ export const createBrowserCompiler = (
       return appCompiler.metafile;
     };
 
-    let cssBuildTask = async () => {
-      if (!isCssBundlingEnabled(remixConfig)) {
-        return;
-      }
-
-      try {
-        // The types aren't great when combining write: false and incremental: true
-        //  so we need to assert that it's an incremental build
-        cssCompiler = (await (!cssCompiler
-          ? esbuild.build({
-              ...createEsbuildConfig("css", remixConfig, options, onLoader),
-              metafile: true,
-              incremental: true,
-              write: false,
-            })
-          : cssCompiler.rebuild())) as esbuild.BuildIncremental;
-
-        invariant(
-          cssCompiler.metafile,
-          "Expected CSS compiler metafile to be defined. This is likely a bug in Remix. Please open an issue at https://github.com/remix-run/remix/issues/new"
-        );
-
-        let outputFiles = cssCompiler.outputFiles || [];
-
-        let isCssBundleFile = (
-          outputFile: esbuild.OutputFile,
-          extension: ".css" | ".css.map"
-        ): boolean => {
-          return (
-            path.dirname(outputFile.path) ===
-              remixConfig.assetsBuildDirectory &&
-            path.basename(outputFile.path).startsWith("css-bundle") &&
-            outputFile.path.endsWith(extension)
-          );
-        };
-
-        let cssBundleFile = outputFiles.find((outputFile) =>
-          isCssBundleFile(outputFile, ".css")
-        );
-
-        if (!cssBundleFile) {
-          cssBundleHrefChannel.write(undefined);
-          return;
-        }
-
-        let cssBundlePath = cssBundleFile.path;
-
-        let cssBundleHref =
-          remixConfig.publicPath +
-          path.relative(
-            remixConfig.assetsBuildDirectory,
-            path.resolve(cssBundlePath)
-          );
-
-        cssBundleHrefChannel.write(cssBundleHref);
-
-        let { css, map } = await postcss([
-          // We need to discard duplicate rules since "composes"
-          // in CSS Modules can result in duplicate styles
-          postcssDiscardDuplicates(),
-        ]).process(cssBundleFile.text, {
-          from: cssBundlePath,
-          to: cssBundlePath,
-          map: options.sourcemap && {
-            prev: outputFiles.find((outputFile) =>
-              isCssBundleFile(outputFile, ".css.map")
-            )?.text,
-            inline: false,
-            annotation: false,
-            sourcesContent: true,
-          },
-        });
-
-        await fse.ensureDir(path.dirname(cssBundlePath));
-
-        await Promise.all([
-          fse.writeFile(cssBundlePath, css),
-          options.mode !== "production" && map
-            ? fse.writeFile(`${cssBundlePath}.map`, map.toString()) // Write our updated source map rather than esbuild's
-            : null,
-          ...outputFiles
-            .filter((outputFile) => !/\.(css|js|map)$/.test(outputFile.path))
-            .map(async (asset) => {
-              await fse.ensureDir(path.dirname(asset.path));
-              await fse.writeFile(asset.path, asset.contents);
-            }),
-        ]);
-
-        return cssBundleHref;
-      } catch (error) {
-        cssBundleHrefChannel.write(undefined);
-        throw error;
-      }
-    };
-
-    // Reset the channel to co-ordinate the CSS and app builds
-    if (isCssBundlingEnabled(remixConfig)) {
-      cssBundleHrefChannel = createChannel();
-    }
+    let metafile = await appBuildTask();
 
-    let [cssBundleHref, metafile] = await Promise.all([
-      cssBuildTask(),
-      appBuildTask(),
-    ]);
-
-    let hmr: AssetsManifest["hmr"] | undefined = undefined;
+    let hmr: Manifest.Type["hmr"] | undefined = undefined;
     if (options.mode === "development" && remixConfig.future.unstable_dev) {
       let hmrRuntimeOutput = Object.entries(metafile.outputs).find(
         ([_, output]) => output.inputs["hmr-runtime:remix:hmr"]
@@ -367,23 +317,12 @@ export const createBrowserCompiler = (
       };
     }
 
-    let manifest = await createAssetsManifest({
-      config: remixConfig,
-      metafile: appCompiler.metafile!,
-      cssBundleHref,
-      hmr,
-    });
-    await writeAssetsManifest(remixConfig, manifest);
-    manifestChannel.write(manifest);
-    return metafile;
+    return { metafile, hmr };
   };
 
   return {
     compile,
-    dispose: () => {
-      appCompiler?.rebuild.dispose();
-      cssCompiler?.rebuild.dispose();
-    },
+    dispose: () => appCompiler?.rebuild.dispose(),
   };
 };
 
diff --git a/packages/remix-dev/compiler/browserjs/index.ts b/packages/remix-dev/compiler/browserjs/index.ts
new file mode 100644
index 000000000..6b608d454
--- /dev/null
+++ b/packages/remix-dev/compiler/browserjs/index.ts
@@ -0,0 +1,1 @@
+export * as compiler from "./compiler";
diff --git a/packages/remix-dev/compiler/plugins/cssBundleUpdatePlugin.ts b/packages/remix-dev/compiler/browserjs/plugins/cssBundleUpdate.ts
similarity index 100%
rename from packages/remix-dev/compiler/plugins/cssBundleUpdatePlugin.ts
rename to packages/remix-dev/compiler/browserjs/plugins/cssBundleUpdate.ts
diff --git a/packages/remix-dev/compiler/plugins/hmrPlugin.ts b/packages/remix-dev/compiler/browserjs/plugins/hmr.ts
similarity index 99%
rename from packages/remix-dev/compiler/plugins/hmrPlugin.ts
rename to packages/remix-dev/compiler/browserjs/plugins/hmr.ts
index b3c383881..35bef40fc 100644
--- a/packages/remix-dev/compiler/plugins/hmrPlugin.ts
+++ b/packages/remix-dev/compiler/browserjs/plugins/hmr.ts
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
 import * as path from "node:path";
 import * as esbuild from "esbuild";
 
-import type { RemixConfig } from "../../config";
+import type { RemixConfig } from "../../../config";
 
 export let hmrPlugin = ({
   remixConfig,
diff --git a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts b/packages/remix-dev/compiler/browserjs/plugins/routes.ts
similarity index 93%
rename from packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts
rename to packages/remix-dev/compiler/browserjs/plugins/routes.ts
index b8fdd1c88..ad8af5532 100644
--- a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts
+++ b/packages/remix-dev/compiler/browserjs/plugins/routes.ts
@@ -1,8 +1,8 @@
 import type esbuild from "esbuild";
 
-import type { RemixConfig } from "../../config";
-import { getRouteModuleExports } from "../routeExports";
-import invariant from "../../invariant";
+import type { RemixConfig } from "../../../config";
+import { getRouteModuleExports } from "../../routeExports";
+import invariant from "../../../invariant";
 
 type Route = RemixConfig["routes"][string];
 
diff --git a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin_v2.ts b/packages/remix-dev/compiler/browserjs/plugins/routes_unstable.ts
similarity index 93%
rename from packages/remix-dev/compiler/plugins/browserRouteModulesPlugin_v2.ts
rename to packages/remix-dev/compiler/browserjs/plugins/routes_unstable.ts
index 5dd00f519..5d595ded8 100644
--- a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin_v2.ts
+++ b/packages/remix-dev/compiler/browserjs/plugins/routes_unstable.ts
@@ -3,13 +3,13 @@ import * as path from "node:path";
 import type esbuild from "esbuild";
 import generate from "@babel/generator";
 
-import type { RemixConfig } from "../../config";
-import invariant from "../../invariant";
-import * as Transform from "../../transform";
-import type { CompileOptions } from "../options";
-import { getLoaderForFile } from "../loaders";
-import { getRouteModuleExports } from "../routeExports";
-import { applyHMR } from "./hmrPlugin";
+import type { RemixConfig } from "../../../config";
+import invariant from "../../../invariant";
+import * as Transform from "../../../transform";
+import type { CompileOptions } from "../../options";
+import { getLoaderForFile } from "../../loaders";
+import { getRouteModuleExports } from "../../routeExports";
+import { applyHMR } from "./hmr";
 
 const serverOnlyExports = new Set(["action", "loader"]);
 
diff --git a/packages/remix-dev/compiler/build.ts b/packages/remix-dev/compiler/build.ts
index 225d56aa5..71f773683 100644
--- a/packages/remix-dev/compiler/build.ts
+++ b/packages/remix-dev/compiler/build.ts
@@ -1,8 +1,8 @@
 import type { RemixConfig } from "../config";
-import { warnOnce } from "./warnings";
+import { warnOnce } from "../warnOnce";
 import { logCompileFailure } from "./onCompileFailure";
 import type { CompileOptions } from "./options";
-import { compile, createRemixCompiler } from "./remixCompiler";
+import * as Compiler from "./compiler";
 
 export async function build(
   config: RemixConfig,
@@ -14,12 +14,12 @@ export async function build(
     onCompileFailure = logCompileFailure,
   }: Partial<CompileOptions> = {}
 ): Promise<void> {
-  let compiler = createRemixCompiler(config, {
+  let compiler = Compiler.create(config, {
     mode,
     target,
     sourcemap,
     onWarning,
     onCompileFailure,
   });
-  await compile(compiler, { onCompileFailure });
+  await compiler.compile();
 }
diff --git a/packages/remix-dev/compiler/compiler.ts b/packages/remix-dev/compiler/compiler.ts
new file mode 100644
index 000000000..8d353760b
--- /dev/null
+++ b/packages/remix-dev/compiler/compiler.ts
@@ -0,0 +1,97 @@
+import * as path from "path";
+import type esbuild from "esbuild";
+import { promises as fsp } from "fs";
+
+import type { RemixConfig } from "../config";
+import * as Manifest from "./manifest";
+import * as BrowserJS from "./browserjs";
+import * as ServerJS from "./serverjs";
+import type { CompileOptions } from "./options";
+import type { Channel } from "../channel";
+import { createChannel } from "../channel";
+import * as CSS from "./css";
+
+export type CompileResult = {
+  assetsManifest: Manifest.Type;
+  metafile: {
+    browser: esbuild.Metafile;
+    server: esbuild.Metafile;
+  };
+};
+
+type Compiler = {
+  compile: () => Promise<CompileResult | undefined>;
+  dispose: () => void;
+};
+
+export let create = (
+  config: RemixConfig,
+  options: CompileOptions
+): Compiler => {
+  let cssBundleHrefChannel: Channel<string | undefined>;
+  let writeCssBundleHref = (cssBundleHref?: string) =>
+    cssBundleHrefChannel.write(cssBundleHref);
+  let readCssBundleHref = () => cssBundleHrefChannel.read();
+  let css = CSS.compiler.create(config, options, writeCssBundleHref);
+  let browser = BrowserJS.compiler.create(config, options, readCssBundleHref);
+  let server = ServerJS.compiler.create(config, options);
+  return {
+    compile: async () => {
+      // TODO: only reset cssBundleHrefChannel if css bundling is enabled?
+      // otherwise we could just write `undefined` to the channel immediately
+      cssBundleHrefChannel = createChannel();
+      try {
+        let [cssBundleHref, { metafile, hmr }] = await Promise.all([
+          css.compile(),
+          browser.compile(),
+        ]);
+        let manifest = await Manifest.create({
+          config,
+          metafile: metafile,
+          cssBundleHref,
+          hmr,
+        });
+        let [serverMetafile] = await Promise.all([
+          server.compile(manifest),
+          writeAssetsManifest(config, manifest),
+        ]);
+
+        return {
+          assetsManifest: manifest,
+          metafile: {
+            browser: metafile,
+            server: serverMetafile,
+          },
+        };
+      } catch (error: unknown) {
+        options.onCompileFailure?.(error as Error);
+        return undefined;
+      }
+    },
+    dispose: () => {
+      css.dispose();
+      browser.dispose();
+      server.dispose();
+    },
+  };
+};
+
+const writeAssetsManifest = async (
+  config: RemixConfig,
+  assetsManifest: Manifest.Type
+) => {
+  let filename = `manifest-${assetsManifest.version.toUpperCase()}.js`;
+
+  assetsManifest.url = config.publicPath + filename;
+
+  await writeFileSafe(
+    path.join(config.assetsBuildDirectory, filename),
+    `window.__remixManifest=${JSON.stringify(assetsManifest)};`
+  );
+};
+
+async function writeFileSafe(file: string, contents: string): Promise<string> {
+  await fsp.mkdir(path.dirname(file), { recursive: true });
+  await fsp.writeFile(file, contents);
+  return file;
+}
diff --git a/packages/remix-dev/compiler/css/compiler.ts b/packages/remix-dev/compiler/css/compiler.ts
new file mode 100644
index 000000000..8f07a9744
--- /dev/null
+++ b/packages/remix-dev/compiler/css/compiler.ts
@@ -0,0 +1,290 @@
+import * as path from "path";
+import * as fse from "fs-extra";
+import { builtinModules as nodeBuiltins } from "module";
+import * as esbuild from "esbuild";
+import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill";
+import postcss from "postcss";
+import postcssDiscardDuplicates from "postcss-discard-duplicates";
+
+import type { RemixConfig } from "../../config";
+import { getAppDependencies } from "../../dependencies";
+import { loaders } from "../loaders";
+import type { CompileOptions } from "../options";
+// import { browserRouteModulesPlugin } from "../plugins/browserRouteModulesPlugin";
+// import { browserRouteModulesPlugin as browserRouteModulesPlugin_v2 } from "../plugins/browserRouteModulesPlugin_v2";
+import { cssFilePlugin } from "../plugins/cssImports";
+import { deprecatedRemixPackagePlugin } from "../plugins/deprecatedRemixPackage";
+import { emptyModulesPlugin } from "../plugins/emptyModules";
+import { mdxPlugin } from "../plugins/mdx";
+import { externalPlugin } from "../plugins/external";
+// import { cssBundleUpdatePlugin } from "../plugins/cssBundleUpdatePlugin";
+import { cssModulesPlugin } from "../plugins/cssModuleImports";
+import { cssSideEffectImportsPlugin } from "../plugins/cssSideEffectImports";
+import { vanillaExtractPlugin } from "../plugins/vanillaExtract";
+import {
+  cssBundleEntryModulePlugin,
+  cssBundleEntryModuleId,
+} from "./plugins/bundleEntry";
+import invariant from "../../invariant";
+// import { hmrPlugin } from "../plugins/hmrPlugin";
+
+function isNotNull<Value>(value: Value): value is Exclude<Value, null> {
+  return value !== null;
+}
+
+const isCssBundlingEnabled = (config: RemixConfig): boolean =>
+  Boolean(
+    config.future.unstable_cssModules ||
+      config.future.unstable_cssSideEffectImports ||
+      config.future.unstable_vanillaExtract
+  );
+
+const getExternals = (remixConfig: RemixConfig): string[] => {
+  // For the browser build, exclude node built-ins that don't have a
+  // browser-safe alternative installed in node_modules. Nothing should
+  // *actually* be external in the browser build (we want to bundle all deps) so
+  // this is really just making sure we don't accidentally have any dependencies
+  // on node built-ins in browser bundles.
+  let dependencies = Object.keys(getAppDependencies(remixConfig));
+  let fakeBuiltins = nodeBuiltins.filter((mod) => dependencies.includes(mod));
+
+  if (fakeBuiltins.length > 0) {
+    throw new Error(
+      `It appears you're using a module that is built in to node, but you installed it as a dependency which could cause problems. Please remove ${fakeBuiltins.join(
+        ", "
+      )} before continuing.`
+    );
+  }
+  return nodeBuiltins.filter((mod) => !dependencies.includes(mod));
+};
+
+const createEsbuildConfig = (
+  build: "app" | "css",
+  config: RemixConfig,
+  options: CompileOptions
+  // onLoader: (filename: string, code: string) => void
+): esbuild.BuildOptions | esbuild.BuildIncremental => {
+  let isCssBuild = build === "css";
+  let entryPoints: Record<string, string>;
+
+  if (isCssBuild) {
+    entryPoints = {
+      "css-bundle": cssBundleEntryModuleId,
+    };
+  } else {
+    entryPoints = {
+      "entry.client": config.entryClientFilePath,
+    };
+
+    for (let id of Object.keys(config.routes)) {
+      // All route entry points are virtual modules that will be loaded by the
+      // browserEntryPointsPlugin. This allows us to tree-shake server-only code
+      // that we don't want to run in the browser (i.e. action & loader).
+      entryPoints[id] = config.routes[id].file + "?browser";
+    }
+  }
+
+  let { mode } = options;
+  let outputCss = isCssBuild;
+
+  let plugins: esbuild.Plugin[] = [
+    deprecatedRemixPackagePlugin(options.onWarning),
+    isCssBundlingEnabled(config) && isCssBuild
+      ? cssBundleEntryModulePlugin(config)
+      : null,
+    config.future.unstable_cssModules
+      ? cssModulesPlugin({ config, mode, outputCss })
+      : null,
+    config.future.unstable_vanillaExtract
+      ? vanillaExtractPlugin({ config, mode, outputCss })
+      : null,
+    config.future.unstable_cssSideEffectImports
+      ? cssSideEffectImportsPlugin({ config, options })
+      : null,
+    cssFilePlugin({ config, options }),
+    externalPlugin(/^https?:\/\//, { sideEffects: false }),
+    mdxPlugin(config),
+    // config.future.unstable_dev
+    //   ? browserRouteModulesPlugin_v2(config, /\?browser$/, onLoader, mode)
+    //   : browserRouteModulesPlugin(config, /\?browser$/),
+    emptyModulesPlugin(config, /\.server(\.[jt]sx?)?$/),
+    NodeModulesPolyfillPlugin(),
+    externalPlugin(/^node:.*/, { sideEffects: false }),
+  ].filter(isNotNull);
+
+  if (build === "app" && mode === "development" && config.future.unstable_dev) {
+    // TODO prebundle deps instead of chunking just these ones
+    let isolateChunks = [
+      require.resolve("react"),
+      require.resolve("react/jsx-dev-runtime"),
+      require.resolve("react/jsx-runtime"),
+      require.resolve("react-dom"),
+      require.resolve("react-dom/client"),
+      require.resolve("react-refresh/runtime"),
+      require.resolve("@remix-run/react"),
+      "remix:hmr",
+    ];
+    entryPoints = {
+      ...entryPoints,
+      ...Object.fromEntries(isolateChunks.map((imprt) => [imprt, imprt])),
+    };
+
+    // plugins.push(hmrPlugin({ remixConfig: config }));
+
+    if (isCssBundlingEnabled(config)) {
+      // plugins.push(cssBundleUpdatePlugin({ getCssBundleHref }));
+    }
+  }
+
+  return {
+    entryPoints,
+    outdir: config.assetsBuildDirectory,
+    platform: "browser",
+    format: "esm",
+    external: [
+      // This allows Vanilla Extract to bundle asset imports, e.g. `import href
+      // from './image.svg'` resolves to a string like "/build/_assets/XXXX.svg"
+      // which will then appear in the compiled CSS, e.g. `background:
+      // url("/build/_assets/XXXX.svg")`. If we don't mark this path as
+      // external, esbuild will try to bundle it again but won't find it.
+      config.future.unstable_vanillaExtract
+        ? `${config.publicPath}_assets/*`
+        : null,
+      ...getExternals(config),
+    ].filter(isNotNull),
+    loader: loaders,
+    bundle: true,
+    logLevel: "silent",
+    splitting: !isCssBuild,
+    sourcemap: options.sourcemap,
+    // As pointed out by https://github.com/evanw/esbuild/issues/2440, when tsconfig is set to
+    // `undefined`, esbuild will keep looking for a tsconfig.json recursively up. This unwanted
+    // behavior can only be avoided by creating an empty tsconfig file in the root directory.
+    tsconfig: config.tsconfigPath,
+    mainFields: ["browser", "module", "main"],
+    treeShaking: true,
+    minify: options.mode === "production",
+    entryNames: "[dir]/[name]-[hash]",
+    chunkNames: "_shared/[name]-[hash]",
+    assetNames: "_assets/[name]-[hash]",
+    publicPath: config.publicPath,
+    define: {
+      "process.env.NODE_ENV": JSON.stringify(options.mode),
+      "process.env.REMIX_DEV_SERVER_WS_PORT": JSON.stringify(
+        config.devServerPort
+      ),
+    },
+    jsx: "automatic",
+    jsxDev: options.mode !== "production",
+    plugins,
+    supported: {
+      "import-meta": true,
+    },
+  };
+};
+
+export let create = (
+  remixConfig: RemixConfig,
+  options: CompileOptions,
+  writeCssBundleHref: (cssBundleHref?: string) => void
+) => {
+  let cssCompiler: esbuild.BuildIncremental;
+  // let onLoader = () => {};
+  let cssBuildTask = async () => {
+    if (!isCssBundlingEnabled(remixConfig)) {
+      return;
+    }
+
+    try {
+      // The types aren't great when combining write: false and incremental: true
+      //  so we need to assert that it's an incremental build
+      cssCompiler = (await (!cssCompiler
+        ? esbuild.build({
+            ...createEsbuildConfig("css", remixConfig, options /* onLoader */),
+            metafile: true,
+            incremental: true,
+            write: false,
+          })
+        : cssCompiler.rebuild())) as esbuild.BuildIncremental;
+
+      invariant(
+        cssCompiler.metafile,
+        "Expected CSS compiler metafile to be defined. This is likely a bug in Remix. Please open an issue at https://github.com/remix-run/remix/issues/new"
+      );
+
+      let outputFiles = cssCompiler.outputFiles || [];
+
+      let isCssBundleFile = (
+        outputFile: esbuild.OutputFile,
+        extension: ".css" | ".css.map"
+      ): boolean => {
+        return (
+          path.dirname(outputFile.path) === remixConfig.assetsBuildDirectory &&
+          path.basename(outputFile.path).startsWith("css-bundle") &&
+          outputFile.path.endsWith(extension)
+        );
+      };
+
+      let cssBundleFile = outputFiles.find((outputFile) =>
+        isCssBundleFile(outputFile, ".css")
+      );
+
+      if (!cssBundleFile) {
+        writeCssBundleHref(undefined);
+        return;
+      }
+
+      let cssBundlePath = cssBundleFile.path;
+
+      let cssBundleHref =
+        remixConfig.publicPath +
+        path.relative(
+          remixConfig.assetsBuildDirectory,
+          path.resolve(cssBundlePath)
+        );
+
+      writeCssBundleHref(cssBundleHref);
+
+      let { css, map } = await postcss([
+        // We need to discard duplicate rules since "composes"
+        // in CSS Modules can result in duplicate styles
+        postcssDiscardDuplicates(),
+      ]).process(cssBundleFile.text, {
+        from: cssBundlePath,
+        to: cssBundlePath,
+        map: options.sourcemap && {
+          prev: outputFiles.find((outputFile) =>
+            isCssBundleFile(outputFile, ".css.map")
+          )?.text,
+          inline: false,
+          annotation: false,
+          sourcesContent: true,
+        },
+      });
+
+      await fse.ensureDir(path.dirname(cssBundlePath));
+
+      await Promise.all([
+        fse.writeFile(cssBundlePath, css),
+        options.mode !== "production" && map
+          ? fse.writeFile(`${cssBundlePath}.map`, map.toString()) // Write our updated source map rather than esbuild's
+          : null,
+        ...outputFiles
+          .filter((outputFile) => !/\.(css|js|map)$/.test(outputFile.path))
+          .map(async (asset) => {
+            await fse.ensureDir(path.dirname(asset.path));
+            await fse.writeFile(asset.path, asset.contents);
+          }),
+      ]);
+
+      return cssBundleHref;
+    } catch (error) {
+      writeCssBundleHref(undefined);
+      throw error;
+    }
+  };
+  return {
+    compile: cssBuildTask,
+    dispose: () => cssCompiler.rebuild.dispose(),
+  };
+};
diff --git a/packages/remix-dev/compiler/css/index.ts b/packages/remix-dev/compiler/css/index.ts
new file mode 100644
index 000000000..6b608d454
--- /dev/null
+++ b/packages/remix-dev/compiler/css/index.ts
@@ -0,0 +1,1 @@
+export * as compiler from "./compiler";
diff --git a/packages/remix-dev/compiler/plugins/cssBundleEntryModulePlugin.ts b/packages/remix-dev/compiler/css/plugins/bundleEntry.ts
similarity index 96%
rename from packages/remix-dev/compiler/plugins/cssBundleEntryModulePlugin.ts
rename to packages/remix-dev/compiler/css/plugins/bundleEntry.ts
index eaa62a021..93c8e3c0b 100644
--- a/packages/remix-dev/compiler/plugins/cssBundleEntryModulePlugin.ts
+++ b/packages/remix-dev/compiler/css/plugins/bundleEntry.ts
@@ -1,7 +1,7 @@
 import path from "node:path";
 import type { Plugin } from "esbuild";
 
-import type { RemixConfig } from "../../config";
+import type { RemixConfig } from "../../../config";
 
 export const cssBundleEntryModuleId = "__remix_cssBundleEntryModule__";
 const filter = new RegExp(`^${cssBundleEntryModuleId}$`);
diff --git a/packages/remix-dev/compiler/index.ts b/packages/remix-dev/compiler/index.ts
index 75da2f2c6..36a06a9e8 100644
--- a/packages/remix-dev/compiler/index.ts
+++ b/packages/remix-dev/compiler/index.ts
@@ -3,4 +3,4 @@ export { type WatchOptions, watch } from "./watch";
 
 export { type CompileOptions, parseMode } from "./options";
 export { logCompileFailure } from "./onCompileFailure";
-export { CompileResult } from "./remixCompiler";
+export { CompileResult } from "./compiler";
diff --git a/packages/remix-dev/compiler/assets.ts b/packages/remix-dev/compiler/manifest.ts
similarity index 92%
rename from packages/remix-dev/compiler/assets.ts
rename to packages/remix-dev/compiler/manifest.ts
index fad890502..67b87cbd4 100644
--- a/packages/remix-dev/compiler/assets.ts
+++ b/packages/remix-dev/compiler/manifest.ts
@@ -5,11 +5,10 @@ import type { RemixConfig } from "../config";
 import invariant from "../invariant";
 import { getRouteModuleExports } from "./routeExports";
 import { getHash } from "./utils/crypto";
-import { createUrl } from "./utils/url";
 
 type Route = RemixConfig["routes"][string];
 
-export interface AssetsManifest {
+type Manifest = {
   version: string;
   url?: string;
   entry: {
@@ -37,9 +36,10 @@ export interface AssetsManifest {
     runtime: string;
     routes: Record<string, { loaderHash: string }>;
   };
-}
+};
+export type Type = Manifest;
 
-export async function createAssetsManifest({
+export async function create({
   config,
   metafile,
   cssBundleHref,
@@ -48,8 +48,8 @@ export async function createAssetsManifest({
   config: RemixConfig;
   metafile: esbuild.Metafile;
   cssBundleHref?: string;
-  hmr?: AssetsManifest["hmr"];
-}): Promise<AssetsManifest> {
+  hmr?: Manifest["hmr"];
+}): Promise<Manifest> {
   function resolveUrl(outputPath: string): string {
     return createUrl(
       config.publicPath,
@@ -77,8 +77,8 @@ export async function createAssetsManifest({
     new Map()
   );
 
-  let entry: AssetsManifest["entry"] | undefined;
-  let routes: AssetsManifest["routes"] = {};
+  let entry: Manifest["entry"] | undefined;
+  let routes: Manifest["routes"] = {};
 
   for (let key of Object.keys(metafile.outputs).sort()) {
     let output = metafile.outputs[key];
@@ -129,10 +129,14 @@ export async function createAssetsManifest({
   return { version, entry, routes, cssBundleHref, hmr };
 }
 
+function createUrl(publicPath: string, file: string): string {
+  return publicPath + file.split(path.win32.sep).join("/");
+}
+
 type ImportsCache = { [routeId: string]: string[] };
 
 function optimizeRoutes(
-  routes: AssetsManifest["routes"],
+  routes: Manifest["routes"],
   entryImports: string[]
 ): void {
   // This cache is an optimization that allows us to avoid pruning the same
@@ -146,7 +150,7 @@ function optimizeRoutes(
 
 function optimizeRouteImports(
   routeId: string,
-  routes: AssetsManifest["routes"],
+  routes: Manifest["routes"],
   parentImports: string[],
   importsCache: ImportsCache
 ): string[] {
diff --git a/packages/remix-dev/compiler/plugins/cssFilePlugin.ts b/packages/remix-dev/compiler/plugins/cssImports.ts
similarity index 100%
rename from packages/remix-dev/compiler/plugins/cssFilePlugin.ts
rename to packages/remix-dev/compiler/plugins/cssImports.ts
diff --git a/packages/remix-dev/compiler/plugins/cssModulesPlugin.ts b/packages/remix-dev/compiler/plugins/cssModuleImports.ts
similarity index 100%
rename from packages/remix-dev/compiler/plugins/cssModulesPlugin.ts
rename to packages/remix-dev/compiler/plugins/cssModuleImports.ts
diff --git a/packages/remix-dev/compiler/plugins/cssSideEffectImportsPlugin.ts b/packages/remix-dev/compiler/plugins/cssSideEffectImports.ts
similarity index 100%
rename from packages/remix-dev/compiler/plugins/cssSideEffectImportsPlugin.ts
rename to packages/remix-dev/compiler/plugins/cssSideEffectImports.ts
diff --git a/packages/remix-dev/compiler/plugins/deprecatedRemixPackagePlugin.ts b/packages/remix-dev/compiler/plugins/deprecatedRemixPackage.ts
similarity index 100%
rename from packages/remix-dev/compiler/plugins/deprecatedRemixPackagePlugin.ts
rename to packages/remix-dev/compiler/plugins/deprecatedRemixPackage.ts
diff --git a/packages/remix-dev/compiler/plugins/emptyModulesPlugin.ts b/packages/remix-dev/compiler/plugins/emptyModules.ts
similarity index 100%
rename from packages/remix-dev/compiler/plugins/emptyModulesPlugin.ts
rename to packages/remix-dev/compiler/plugins/emptyModules.ts
diff --git a/packages/remix-dev/compiler/plugins/external.ts b/packages/remix-dev/compiler/plugins/external.ts
new file mode 100644
index 000000000..763ce2a3b
--- /dev/null
+++ b/packages/remix-dev/compiler/plugins/external.ts
@@ -0,0 +1,20 @@
+import type { Plugin } from "esbuild";
+
+/**
+ * Mark all URL imports as external so that each URL import is preserved in the build output.
+ */
+export const externalPlugin = (
+  filter: RegExp,
+  options: {
+    sideEffects?: boolean;
+  } = {}
+): Plugin => {
+  return {
+    name: "external",
+    setup(build) {
+      build.onResolve({ filter }, () => {
+        return { external: true, sideEffects: options.sideEffects };
+      });
+    },
+  };
+};
diff --git a/packages/remix-dev/compiler/plugins/nodeProtocolExternalPlugin.ts b/packages/remix-dev/compiler/plugins/nodeProtocolExternalPlugin.ts
deleted file mode 100644
index dab0168c8..000000000
--- a/packages/remix-dev/compiler/plugins/nodeProtocolExternalPlugin.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type esbuild from "esbuild";
-
-/**
- * Node supports importing modules using the node: protocol. However, this causes resolution
- * to fail in the compiler. This plugin properly marks all `node:` imports as external.
- */
-export function NodeProtocolExternalPlugin(): esbuild.Plugin {
-  return {
-    name: "node-protocol-external",
-    setup(pluginBuild) {
-      pluginBuild.onResolve({ filter: /node:.*/ }, () => {
-        return { external: true, sideEffects: false };
-      });
-    },
-  };
-}
diff --git a/packages/remix-dev/compiler/plugins/urlImportsPlugin.ts b/packages/remix-dev/compiler/plugins/urlImportsPlugin.ts
deleted file mode 100644
index 6732f02a3..000000000
--- a/packages/remix-dev/compiler/plugins/urlImportsPlugin.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { Plugin } from "esbuild";
-
-/**
- * Mark all URL imports as external so that each URL import is preserved in the build output.
- */
-export const urlImportsPlugin = (): Plugin => {
-  return {
-    name: "url-imports",
-    setup(build) {
-      build.onResolve({ filter: /^https?:\/\// }, ({ path }) => {
-        /*
-        The vast majority of packages are side-effect free,
-        and URL imports don't have a mechanism for specifying that they are side-effect free.
-        
-        Mark all url imports as side-effect free so that they can be treeshaken by esbuild.
-        */
-        return { path, external: true, sideEffects: false };
-      });
-    },
-  };
-};
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtractPluginCached.ts b/packages/remix-dev/compiler/plugins/vanillaExtract/cached.ts
similarity index 92%
rename from packages/remix-dev/compiler/plugins/vanillaExtractPluginCached.ts
rename to packages/remix-dev/compiler/plugins/vanillaExtract/cached.ts
index 068c44d86..b349b8c63 100644
--- a/packages/remix-dev/compiler/plugins/vanillaExtractPluginCached.ts
+++ b/packages/remix-dev/compiler/plugins/vanillaExtract/cached.ts
@@ -3,11 +3,11 @@ import type { Compiler } from "@vanilla-extract/integration";
 import { cssFileFilter, createCompiler } from "@vanilla-extract/integration";
 import type { Plugin } from "esbuild";
 
-import type { RemixConfig } from "../../config";
-import type { CompileOptions } from "../options";
-import { loaders } from "../loaders";
-import { getPostcssProcessor } from "../utils/postcss";
-import { vanillaExtractSideEffectsPlugin } from "./vanillaExtractSideEffectsPlugin";
+import type { RemixConfig } from "../../../config";
+import type { CompileOptions } from "../../options";
+import { loaders } from "../../loaders";
+import { getPostcssProcessor } from "../../utils/postcss";
+import { vanillaExtractSideEffectsPlugin } from "./sideEffects";
 
 const pluginName = "vanilla-extract-plugin-cached";
 const namespace = `${pluginName}-ns`;
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtractPlugin.ts b/packages/remix-dev/compiler/plugins/vanillaExtract/index.ts
similarity index 69%
rename from packages/remix-dev/compiler/plugins/vanillaExtractPlugin.ts
rename to packages/remix-dev/compiler/plugins/vanillaExtract/index.ts
index b5a2479f0..9607fbf0b 100644
--- a/packages/remix-dev/compiler/plugins/vanillaExtractPlugin.ts
+++ b/packages/remix-dev/compiler/plugins/vanillaExtract/index.ts
@@ -1,9 +1,9 @@
 import type { Plugin } from "esbuild";
 
-import type { RemixConfig, VanillaExtractOptions } from "../../config";
-import type { CompileOptions } from "../options";
-import { vanillaExtractPluginCached } from "./vanillaExtractPluginCached";
-import { vanillaExtractPluginUncached } from "./vanillaExtractPluginUncached";
+import type { RemixConfig, VanillaExtractOptions } from "../../../config";
+import type { CompileOptions } from "../../options";
+import { vanillaExtractPluginCached } from "./cached";
+import { vanillaExtractPluginUncached } from "./uncached";
 
 export function vanillaExtractPlugin(options: {
   config: RemixConfig;
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtractSideEffectsPlugin.ts b/packages/remix-dev/compiler/plugins/vanillaExtract/sideEffects.ts
similarity index 100%
rename from packages/remix-dev/compiler/plugins/vanillaExtractSideEffectsPlugin.ts
rename to packages/remix-dev/compiler/plugins/vanillaExtract/sideEffects.ts
diff --git a/packages/remix-dev/compiler/plugins/vanillaExtractPluginUncached.ts b/packages/remix-dev/compiler/plugins/vanillaExtract/uncached.ts
similarity index 94%
rename from packages/remix-dev/compiler/plugins/vanillaExtractPluginUncached.ts
rename to packages/remix-dev/compiler/plugins/vanillaExtract/uncached.ts
index f33fb12de..658a1dc90 100644
--- a/packages/remix-dev/compiler/plugins/vanillaExtractPluginUncached.ts
+++ b/packages/remix-dev/compiler/plugins/vanillaExtract/uncached.ts
@@ -10,11 +10,11 @@ import {
 import * as fse from "fs-extra";
 import * as esbuild from "esbuild";
 
-import type { RemixConfig } from "../../config";
-import type { CompileOptions } from "../options";
-import { loaders } from "../loaders";
-import { getPostcssProcessor } from "../utils/postcss";
-import { vanillaExtractSideEffectsPlugin } from "./vanillaExtractSideEffectsPlugin";
+import type { RemixConfig } from "../../../config";
+import type { CompileOptions } from "../../options";
+import { loaders } from "../../loaders";
+import { getPostcssProcessor } from "../../utils/postcss";
+import { vanillaExtractSideEffectsPlugin } from "./sideEffects";
 
 const pluginName = "vanilla-extract-plugin-uncached";
 const namespace = `${pluginName}-ns`;
diff --git a/packages/remix-dev/compiler/remixCompiler.ts b/packages/remix-dev/compiler/remixCompiler.ts
deleted file mode 100644
index 2e40d0891..000000000
--- a/packages/remix-dev/compiler/remixCompiler.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import type esbuild from "esbuild";
-
-import { createChannel } from "../channel";
-import type { RemixConfig } from "../config";
-import type { AssetsManifest } from "./assets";
-import type { BrowserCompiler } from "./compileBrowser";
-import { createBrowserCompiler } from "./compileBrowser";
-import type { ServerCompiler } from "./compilerServer";
-import { createServerCompiler } from "./compilerServer";
-import type { OnCompileFailure } from "./onCompileFailure";
-import type { CompileOptions } from "./options";
-
-type RemixCompiler = {
-  browser: BrowserCompiler;
-  server: ServerCompiler;
-};
-
-export const createRemixCompiler = (
-  remixConfig: RemixConfig,
-  options: CompileOptions
-): RemixCompiler => {
-  return {
-    browser: createBrowserCompiler(remixConfig, options),
-    server: createServerCompiler(remixConfig, options),
-  };
-};
-
-export type CompileResult = {
-  assetsManifest: AssetsManifest;
-  metafile: {
-    browser: esbuild.Metafile;
-    server: esbuild.Metafile;
-  };
-};
-
-export const compile = async (
-  compiler: RemixCompiler,
-  options: {
-    onCompileFailure?: OnCompileFailure;
-  } = {}
-): Promise<CompileResult | undefined> => {
-  try {
-    let assetsManifestChannel = createChannel<AssetsManifest>();
-    let browserPromise = compiler.browser.compile(assetsManifestChannel);
-    let serverPromise = compiler.server.compile(assetsManifestChannel);
-
-    // await browser/server _before_ assets manifest channel
-    // to fix https://github.com/remix-run/remix/issues/5631
-    // this is temporary and is actively being refactored
-    let browser = await browserPromise;
-    let server = await serverPromise;
-    return {
-      assetsManifest: await assetsManifestChannel.read(),
-      metafile: {
-        browser,
-        server,
-      },
-    };
-  } catch (error: unknown) {
-    options.onCompileFailure?.(error as Error);
-    return undefined;
-  }
-};
-
-export const dispose = (compiler: RemixCompiler): void => {
-  compiler.browser.dispose();
-  compiler.server.dispose();
-};
diff --git a/packages/remix-dev/compiler/compilerServer.ts b/packages/remix-dev/compiler/serverjs/compiler.ts
similarity index 76%
rename from packages/remix-dev/compiler/compilerServer.ts
rename to packages/remix-dev/compiler/serverjs/compiler.ts
index f087d23f4..8d5592789 100644
--- a/packages/remix-dev/compiler/compilerServer.ts
+++ b/packages/remix-dev/compiler/serverjs/compiler.ts
@@ -3,37 +3,33 @@ import * as esbuild from "esbuild";
 import * as fse from "fs-extra";
 import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill";
 
-import invariant from "../invariant";
-import type { ReadChannel } from "../channel";
-import type { RemixConfig } from "../config";
-import type { AssetsManifest } from "./assets";
-import { loaders } from "./loaders";
-import type { CompileOptions } from "./options";
-import { cssModulesPlugin } from "./plugins/cssModulesPlugin";
-import { cssSideEffectImportsPlugin } from "./plugins/cssSideEffectImportsPlugin";
-import { vanillaExtractPlugin } from "./plugins/vanillaExtractPlugin";
-import { cssFilePlugin } from "./plugins/cssFilePlugin";
-import { deprecatedRemixPackagePlugin } from "./plugins/deprecatedRemixPackagePlugin";
-import { emptyModulesPlugin } from "./plugins/emptyModulesPlugin";
-import { mdxPlugin } from "./plugins/mdx";
-import { serverAssetsManifestPlugin } from "./plugins/serverAssetsManifestPlugin";
-import { serverBareModulesPlugin } from "./plugins/serverBareModulesPlugin";
-import { serverEntryModulePlugin } from "./plugins/serverEntryModulePlugin";
-import { serverRouteModulesPlugin } from "./plugins/serverRouteModulesPlugin";
-import { urlImportsPlugin } from "./plugins/urlImportsPlugin";
-import { NodeProtocolExternalPlugin } from "./plugins/nodeProtocolExternalPlugin";
+import invariant from "../../invariant";
+import type { RemixConfig } from "../../config";
+import type * as Manifest from "../manifest";
+import { loaders } from "../loaders";
+import type { CompileOptions } from "../options";
+import { cssModulesPlugin } from "../plugins/cssModuleImports";
+import { cssSideEffectImportsPlugin } from "../plugins/cssSideEffectImports";
+import { vanillaExtractPlugin } from "../plugins/vanillaExtract";
+import { cssFilePlugin } from "../plugins/cssImports";
+import { deprecatedRemixPackagePlugin } from "../plugins/deprecatedRemixPackage";
+import { emptyModulesPlugin } from "../plugins/emptyModules";
+import { mdxPlugin } from "../plugins/mdx";
+import { serverAssetsManifestPlugin } from "./plugins/manifest";
+import { serverBareModulesPlugin } from "./plugins/bareImports";
+import { serverEntryModulePlugin } from "./plugins/entry";
+import { serverRouteModulesPlugin } from "./plugins/routes";
+import { externalPlugin } from "../plugins/external";
 
-export type ServerCompiler = {
+type Compiler = {
   // produce ./build/index.js
-  compile: (
-    manifestChannel: ReadChannel<AssetsManifest>
-  ) => Promise<esbuild.Metafile>;
+  compile: (manifest: Manifest.Type) => Promise<esbuild.Metafile>;
   dispose: () => void;
 };
 
 const createEsbuildConfig = (
   config: RemixConfig,
-  assetsManifestChannel: ReadChannel<AssetsManifest>,
+  manifest: Manifest.Type,
   options: CompileOptions
 ): esbuild.BuildOptions => {
   let stdin: esbuild.StdinOptions | undefined;
@@ -64,14 +60,14 @@ const createEsbuildConfig = (
       ? cssSideEffectImportsPlugin({ config, options })
       : null,
     cssFilePlugin({ config, options }),
-    urlImportsPlugin(),
+    externalPlugin(/^https?:\/\//, { sideEffects: false }),
     mdxPlugin(config),
     emptyModulesPlugin(config, /\.client(\.[jt]sx?)?$/),
     serverRouteModulesPlugin(config),
     serverEntryModulePlugin(config, { liveReloadPort: options.liveReloadPort }),
-    serverAssetsManifestPlugin(assetsManifestChannel.read()),
+    serverAssetsManifestPlugin(manifest),
     serverBareModulesPlugin(config, options.onWarning),
-    NodeProtocolExternalPlugin(),
+    externalPlugin(/^node:.*/, { sideEffects: false }),
   ].filter(isNotNull);
 
   if (config.serverPlatform !== "node") {
@@ -165,16 +161,12 @@ async function writeServerBuildResult(
   }
 }
 
-export const createServerCompiler = (
+export const create = (
   remixConfig: RemixConfig,
   options: CompileOptions
-): ServerCompiler => {
-  let compile = async (manifestChannel: ReadChannel<AssetsManifest>) => {
-    let esbuildConfig = createEsbuildConfig(
-      remixConfig,
-      manifestChannel,
-      options
-    );
+): Compiler => {
+  let compile = async (manifest: Manifest.Type) => {
+    let esbuildConfig = createEsbuildConfig(remixConfig, manifest, options);
     let { metafile, outputFiles } = await esbuild.build({
       ...esbuildConfig,
       write: false,
diff --git a/packages/remix-dev/compiler/serverjs/index.ts b/packages/remix-dev/compiler/serverjs/index.ts
new file mode 100644
index 000000000..6b608d454
--- /dev/null
+++ b/packages/remix-dev/compiler/serverjs/index.ts
@@ -0,0 +1,1 @@
+export * as compiler from "./compiler";
diff --git a/packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts b/packages/remix-dev/compiler/serverjs/plugins/bareImports.ts
similarity index 96%
rename from packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts
rename to packages/remix-dev/compiler/serverjs/plugins/bareImports.ts
index c6a673f9d..1be82b625 100644
--- a/packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts
+++ b/packages/remix-dev/compiler/serverjs/plugins/bareImports.ts
@@ -4,14 +4,14 @@ import { builtinModules } from "module";
 import { isAbsolute, relative } from "path";
 import type { Plugin } from "esbuild";
 
-import type { RemixConfig } from "../../config";
+import type { RemixConfig } from "../../../config";
 import {
   serverBuildVirtualModule,
   assetsManifestVirtualModule,
 } from "../virtualModules";
-import { isCssSideEffectImportPath } from "./cssSideEffectImportsPlugin";
-import { createMatchPath } from "../utils/tsconfig";
-import { getPreferredPackageManager } from "../../cli/getPreferredPackageManager";
+import { isCssSideEffectImportPath } from "../../plugins/cssSideEffectImports";
+import { createMatchPath } from "../../utils/tsconfig";
+import { getPreferredPackageManager } from "../../../cli/getPreferredPackageManager";
 
 /**
  * A plugin responsible for resolving bare module ids based on server target.
diff --git a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts b/packages/remix-dev/compiler/serverjs/plugins/entry.ts
similarity index 97%
rename from packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
rename to packages/remix-dev/compiler/serverjs/plugins/entry.ts
index bb6b4137b..9ade9aa4b 100644
--- a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
+++ b/packages/remix-dev/compiler/serverjs/plugins/entry.ts
@@ -1,6 +1,6 @@
 import type { Plugin } from "esbuild";
 
-import type { RemixConfig } from "../../config";
+import type { RemixConfig } from "../../../config";
 import {
   serverBuildVirtualModule,
   assetsManifestVirtualModule,
diff --git a/packages/remix-dev/compiler/plugins/serverAssetsManifestPlugin.ts b/packages/remix-dev/compiler/serverjs/plugins/manifest.ts
similarity index 63%
rename from packages/remix-dev/compiler/plugins/serverAssetsManifestPlugin.ts
rename to packages/remix-dev/compiler/serverjs/plugins/manifest.ts
index 06041f4f2..5600635cc 100644
--- a/packages/remix-dev/compiler/plugins/serverAssetsManifestPlugin.ts
+++ b/packages/remix-dev/compiler/serverjs/plugins/manifest.ts
@@ -1,17 +1,15 @@
 import type { Plugin } from "esbuild";
 import jsesc from "jsesc";
 
-import type { AssetsManifest } from "../../compiler/assets";
-import { assetsManifestVirtualModule } from "../../compiler/virtualModules";
+import type * as Manifest from "../../manifest";
+import { assetsManifestVirtualModule } from "../virtualModules";
 
 /**
  * Creates a virtual module called `@remix-run/dev/assets-manifest` that exports
  * the assets manifest. This is used in the server entry module to access the
  * assets manifest in the server build.
  */
-export function serverAssetsManifestPlugin(
-  assetsManifestPromise: Promise<AssetsManifest>
-): Plugin {
+export function serverAssetsManifestPlugin(manifest: Manifest.Type): Plugin {
   let filter = assetsManifestVirtualModule.filter;
 
   return {
@@ -25,9 +23,8 @@ export function serverAssetsManifestPlugin(
       });
 
       build.onLoad({ filter }, async () => {
-        let assetsManifest = await assetsManifestPromise;
         return {
-          contents: `export default ${jsesc(assetsManifest, { es6: true })};`,
+          contents: `export default ${jsesc(manifest, { es6: true })};`,
           loader: "js",
         };
       });
diff --git a/packages/remix-dev/compiler/plugins/serverRouteModulesPlugin.ts b/packages/remix-dev/compiler/serverjs/plugins/routes.ts
similarity index 93%
rename from packages/remix-dev/compiler/plugins/serverRouteModulesPlugin.ts
rename to packages/remix-dev/compiler/serverjs/plugins/routes.ts
index fd778131e..9c9f27857 100644
--- a/packages/remix-dev/compiler/plugins/serverRouteModulesPlugin.ts
+++ b/packages/remix-dev/compiler/serverjs/plugins/routes.ts
@@ -2,8 +2,8 @@ import * as path from "path";
 import * as fse from "fs-extra";
 import type esbuild from "esbuild";
 
-import type { RemixConfig } from "../../config";
-import { getLoaderForFile } from "../loaders";
+import type { RemixConfig } from "../../../config";
+import { getLoaderForFile } from "../../loaders";
 
 /**
  * This plugin loads route modules for the server build and prevents errors
diff --git a/packages/remix-dev/compiler/virtualModules.ts b/packages/remix-dev/compiler/serverjs/virtualModules.ts
similarity index 100%
rename from packages/remix-dev/compiler/virtualModules.ts
rename to packages/remix-dev/compiler/serverjs/virtualModules.ts
diff --git a/packages/remix-dev/compiler/utils/fs.ts b/packages/remix-dev/compiler/utils/fs.ts
deleted file mode 100644
index c64ac0622..000000000
--- a/packages/remix-dev/compiler/utils/fs.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { promises as fsp } from "fs";
-import * as path from "path";
-
-export async function writeFileSafe(
-  file: string,
-  contents: string
-): Promise<string> {
-  await fsp.mkdir(path.dirname(file), { recursive: true });
-  await fsp.writeFile(file, contents);
-  return file;
-}
diff --git a/packages/remix-dev/compiler/utils/url.ts b/packages/remix-dev/compiler/utils/url.ts
deleted file mode 100644
index 7c30e4d24..000000000
--- a/packages/remix-dev/compiler/utils/url.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import * as path from "path";
-
-export function createUrl(publicPath: string, file: string): string {
-  return publicPath + file.split(path.win32.sep).join("/");
-}
diff --git a/packages/remix-dev/compiler/watch.ts b/packages/remix-dev/compiler/watch.ts
index 7d6dfefef..bde96dc6d 100644
--- a/packages/remix-dev/compiler/watch.ts
+++ b/packages/remix-dev/compiler/watch.ts
@@ -4,11 +4,11 @@ import * as path from "path";
 
 import type { RemixConfig } from "../config";
 import { readConfig } from "../config";
+import { warnOnce } from "../warnOnce";
 import { logCompileFailure } from "./onCompileFailure";
 import type { CompileOptions } from "./options";
-import type { CompileResult } from "./remixCompiler";
-import { compile, createRemixCompiler, dispose } from "./remixCompiler";
-import { warnOnce } from "./warnings";
+import type { CompileResult } from "./compiler";
+import * as Compiler from "./compiler";
 
 function isEntryPoint(config: RemixConfig, file: string): boolean {
   let appFile = path.relative(config.appDirectory, file);
@@ -58,16 +58,16 @@ export async function watch(
   };
 
   let start = Date.now();
-  let compiler = createRemixCompiler(config, options);
+  let compiler = Compiler.create(config, options);
 
   // initial build
-  let result = await compile(compiler, { onCompileFailure });
+  let result = await compiler.compile();
   onInitialBuild?.(Date.now() - start, result);
 
   let restart = debounce(async () => {
     onRebuildStart?.();
     let start = Date.now();
-    dispose(compiler);
+    compiler.dispose();
 
     try {
       config = await reloadConfig(config.rootDirectory);
@@ -76,17 +76,15 @@ export async function watch(
       return;
     }
 
-    compiler = createRemixCompiler(config, options);
-    let result = await compile(compiler, { onCompileFailure });
+    compiler = Compiler.create(config, options);
+    let result = await compiler.compile();
     onRebuildFinish?.(Date.now() - start, result);
   }, 500);
 
   let rebuild = debounce(async () => {
     onRebuildStart?.();
     let start = Date.now();
-    let result = await compile(compiler, {
-      onCompileFailure,
-    });
+    let result = await compiler.compile();
     onRebuildFinish?.(Date.now() - start, result);
   }, 100);
 
@@ -132,6 +130,6 @@ export async function watch(
 
   return async () => {
     await watcher.close().catch(() => undefined);
-    dispose(compiler);
+    compiler.dispose();
   };
 }
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index 3331b5c00..78a91825e 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -10,11 +10,11 @@ import type { RouteManifest, DefineRoutesFunction } from "./config/routes";
 import { defineRoutes } from "./config/routes";
 import { defineConventionalRoutes } from "./config/routesConvention";
 import { ServerMode, isValidServerMode } from "./config/serverModes";
-import { serverBuildVirtualModule } from "./compiler/virtualModules";
+import { serverBuildVirtualModule } from "./compiler/serverjs/virtualModules";
 import { writeConfigDefaults } from "./compiler/utils/tsconfig/write-config-defaults";
 import { flatRoutes } from "./config/flat-routes";
 import { getPreferredPackageManager } from "./cli/getPreferredPackageManager";
-import { warnOnce } from "./compiler/warnings";
+import { warnOnce } from "./warnOnce";
 
 export interface RemixMdxConfig {
   rehypePlugins?: any[];
diff --git a/packages/remix-dev/compiler/dependencies.ts b/packages/remix-dev/dependencies.ts
similarity index 91%
rename from packages/remix-dev/compiler/dependencies.ts
rename to packages/remix-dev/dependencies.ts
index 743fcef22..6e4fde792 100644
--- a/packages/remix-dev/compiler/dependencies.ts
+++ b/packages/remix-dev/dependencies.ts
@@ -1,13 +1,11 @@
 import * as fs from "fs";
 import * as path from "path";
 
-import type { RemixConfig } from "../config";
+import type { RemixConfig } from "./config";
 
 type PackageDependencies = { [packageName: string]: string };
 
-export function getPackageDependencies(
-  packageJsonFile: string
-): PackageDependencies {
+function getPackageDependencies(packageJsonFile: string): PackageDependencies {
   let pkg = JSON.parse(fs.readFileSync(packageJsonFile, "utf8"));
   return pkg?.dependencies || {};
 }
diff --git a/packages/remix-dev/devServer/serve.ts b/packages/remix-dev/devServer/serve.ts
index cba4599f5..b38dc6b47 100644
--- a/packages/remix-dev/devServer/serve.ts
+++ b/packages/remix-dev/devServer/serve.ts
@@ -4,7 +4,7 @@ import os from "os";
 
 import type { RemixConfig } from "../config";
 import type { CompileOptions } from "../compiler";
-import { loadEnv } from "../env";
+import { loadEnv } from "../devServer_unstable/env";
 import { liveReload } from "./liveReload";
 
 function purgeAppRequireCache(buildPath: string) {
diff --git a/packages/remix-dev/env.ts b/packages/remix-dev/devServer_unstable/env.ts
similarity index 100%
rename from packages/remix-dev/env.ts
rename to packages/remix-dev/devServer_unstable/env.ts
diff --git a/packages/remix-dev/hmr.ts b/packages/remix-dev/devServer_unstable/hmr.ts
similarity index 95%
rename from packages/remix-dev/hmr.ts
rename to packages/remix-dev/devServer_unstable/hmr.ts
index bf54f7688..ca5de4b90 100644
--- a/packages/remix-dev/hmr.ts
+++ b/packages/remix-dev/devServer_unstable/hmr.ts
@@ -1,7 +1,7 @@
 import path from "node:path";
 
-import type { RemixConfig } from "./config";
-import type { CompileResult } from "./compiler";
+import type { RemixConfig } from "../config";
+import type { CompileResult } from "../compiler";
 
 export type Update = {
   id: string;
diff --git a/packages/remix-dev/devServer2.ts b/packages/remix-dev/devServer_unstable/index.ts
similarity index 94%
rename from packages/remix-dev/devServer2.ts
rename to packages/remix-dev/devServer_unstable/index.ts
index e271cf567..e9c4ba54b 100644
--- a/packages/remix-dev/devServer2.ts
+++ b/packages/remix-dev/devServer_unstable/index.ts
@@ -6,9 +6,9 @@ import path from "node:path";
 import prettyMs from "pretty-ms";
 import fetch from "node-fetch";
 
-import { type AssetsManifest } from "./assets-manifest";
-import * as Compiler from "./compiler";
-import { type RemixConfig } from "./config";
+import type * as Manifest from "../compiler/manifest";
+import * as Compiler from "../compiler";
+import { type RemixConfig } from "../config";
 import { loadEnv } from "./env";
 import * as LiveReload from "./liveReload";
 import * as HMR from "./hmr";
@@ -45,11 +45,11 @@ let findPort = async (portPreference?: number) =>
 let fetchAssetsManifest = async (
   origin: string,
   remixRequestHandlerPath: string
-): Promise<AssetsManifest | undefined> => {
+): Promise<Manifest.Type | undefined> => {
   try {
     let url = origin + remixRequestHandlerPath + "/__REMIX_ASSETS_MANIFEST";
     let res = await fetch(url);
-    let assetsManifest = (await res.json()) as AssetsManifest;
+    let assetsManifest = (await res.json()) as Manifest.Type;
     return assetsManifest;
   } catch (error) {
     return undefined;
diff --git a/packages/remix-dev/liveReload.ts b/packages/remix-dev/devServer_unstable/liveReload.ts
similarity index 84%
rename from packages/remix-dev/liveReload.ts
rename to packages/remix-dev/devServer_unstable/liveReload.ts
index 61f097f30..023989695 100644
--- a/packages/remix-dev/liveReload.ts
+++ b/packages/remix-dev/devServer_unstable/liveReload.ts
@@ -1,6 +1,6 @@
 import WebSocket from "ws";
 
-import type { AssetsManifest } from "./compiler/assets";
+import type * as Manifest from "../compiler/manifest";
 import type * as HMR from "./hmr";
 
 type Message =
@@ -8,7 +8,7 @@ type Message =
   | { type: "LOG"; message: string }
   | {
       type: "HMR";
-      assetsManifest: AssetsManifest;
+      assetsManifest: Manifest.Type;
       updates: HMR.Update[];
     };
 
@@ -33,7 +33,7 @@ export let serve = (options: { port: number }) => {
     broadcast({ type: "LOG", message: _message });
   };
 
-  let hmr = (assetsManifest: AssetsManifest, updates: HMR.Update[]) => {
+  let hmr = (assetsManifest: Manifest.Type, updates: HMR.Update[]) => {
     broadcast({ type: "HMR", assetsManifest, updates });
   };
 
diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts
index 206f30f19..bf2154fb2 100644
--- a/packages/remix-dev/index.ts
+++ b/packages/remix-dev/index.ts
@@ -6,5 +6,5 @@ export * as cli from "./cli/index";
 export { createApp } from "./cli/create";
 export { CliError } from "./cli/error";
 
-export type { AssetsManifest } from "./compiler/assets";
-export { getDependenciesToBundle } from "./compiler/dependencies";
+export type { Type as AssetsManifest } from "./compiler/manifest";
+export { getDependenciesToBundle } from "./dependencies";
diff --git a/packages/remix-dev/compiler/warnings.ts b/packages/remix-dev/warnOnce.ts
similarity index 100%
rename from packages/remix-dev/compiler/warnings.ts
rename to packages/remix-dev/warnOnce.ts
diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx
index 35993a5d4..3d212b1f3 100644
--- a/packages/remix-react/browser.tsx
+++ b/packages/remix-react/browser.tsx
@@ -14,7 +14,7 @@ import {
 import { deserializeErrors } from "./errors";
 import type { RouteModules } from "./routeModules";
 import { createClientRoutes } from "./routes";
-import { warnOnce } from "./warnings";
+import { logDeprecationOnce } from "./warnings";
 
 /* eslint-disable prefer-let/prefer-let */
 declare global {
@@ -140,8 +140,7 @@ if (import.meta && import.meta.hot) {
 export function RemixBrowser(_props: RemixBrowserProps): ReactElement {
   if (!router) {
     if (!window.__remixContext.future.v2_errorBoundary) {
-      warnOnce(
-        false,
+      logDeprecationOnce(
         "⚠️  DEPRECATED: The separation of `CatchBoundary` and `ErrorBoundary` has " +
           "been deprecated and Remix v2 will use a singular `ErrorBoundary` for " +
           "all thrown values (`Response` and `Error`). Please migrate to the new " +
@@ -152,8 +151,7 @@ export function RemixBrowser(_props: RemixBrowserProps): ReactElement {
     }
 
     if (!window.__remixContext.future.v2_normalizeFormMethod) {
-      warnOnce(
-        false,
+      logDeprecationOnce(
         "⚠️  DEPRECATED: Please enable the `future.v2_normalizeFormMethod` flag to " +
           "prepare for the Remix v2 release. Lowercase `useNavigation().formMethod`" +
           "values are being normalized to uppercase in v2 to align with the `fetch()` " +
diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx
index 68f663b52..484e4b2e0 100644
--- a/packages/remix-react/components.tsx
+++ b/packages/remix-react/components.tsx
@@ -78,7 +78,7 @@ import type {
   TransitionStates,
 } from "./transition";
 import { IDLE_TRANSITION, IDLE_FETCHER } from "./transition";
-import { warnOnce } from "./warnings";
+import { logDeprecationOnce } from "./warnings";
 
 function useDataRouterContext() {
   let context = React.useContext(DataRouterContext);
@@ -371,13 +371,14 @@ export function Links() {
   );
 
   React.useEffect(() => {
-    warnOnce(
-      links.some((link) => "imagesizes" in link || "imagesrcset" in link),
-      "⚠️ DEPRECATED: The `imagesizes` & `imagesrcset` properties in " +
-        "your links have been deprecated in favor of `imageSizes` & " +
-        "`imageSrcSet` and support will be removed in Remix v2. Please update " +
-        "your code to use the new property names instead."
-    );
+    if (links.some((link) => "imagesizes" in link || "imagesrcset" in link)) {
+      logDeprecationOnce(
+        "⚠️ DEPRECATED: The `imagesizes` & `imagesrcset` properties in " +
+          "your links have been deprecated in favor of `imageSizes` & " +
+          "`imageSrcSet` and support will be removed in Remix v2. Please update " +
+          "your code to use the new property names instead."
+      );
+    }
   }, [links]);
 
   return (
@@ -1237,8 +1238,7 @@ export function useTransition(): Transition {
   let navigation = useNavigation();
 
   React.useEffect(() => {
-    warnOnce(
-      false,
+    logDeprecationOnce(
       "⚠️ DEPRECATED: The `useTransition` hook has been deprecated in favor of " +
         "`useNavigation` and will be removed in Remix v2.  Please update your " +
         "code to leverage `useNavigation`.\n\nSee https://remix.run/docs/hooks/use-transition " +
@@ -1475,8 +1475,7 @@ function addFetcherDeprecationWarnings(fetcher: Fetcher) {
   let type: Fetcher["type"] = fetcher.type;
   Object.defineProperty(fetcher, "type", {
     get() {
-      warnOnce(
-        false,
+      logDeprecationOnce(
         "⚠️ DEPRECATED: The `useFetcher().type` field has been deprecated and " +
           "will be removed in Remix v2.  Please update your code to rely on " +
           "`fetcher.state`.\n\nSee https://remix.run/docs/hooks/use-fetcher for " +
@@ -1497,8 +1496,7 @@ function addFetcherDeprecationWarnings(fetcher: Fetcher) {
   let submission: Fetcher["submission"] = fetcher.submission;
   Object.defineProperty(fetcher, "submission", {
     get() {
-      warnOnce(
-        false,
+      logDeprecationOnce(
         "⚠️ DEPRECATED: The `useFetcher().submission` field has been deprecated and " +
           "will be removed in Remix v2.  The submission fields now live directly " +
           "on the fetcher (`fetcher.formData`).\n\n" +
diff --git a/packages/remix-react/warnings.ts b/packages/remix-react/warnings.ts
index 45acd9601..0a1272d46 100644
--- a/packages/remix-react/warnings.ts
+++ b/packages/remix-react/warnings.ts
@@ -6,3 +6,13 @@ export function warnOnce(condition: boolean, message: string): void {
     console.warn(message);
   }
 }
+
+export function logDeprecationOnce(
+  message: string,
+  key: string = message
+): void {
+  if (process.env.NODE_ENV !== "production" && !alreadyWarned[key]) {
+    alreadyWarned[key] = true;
+    console.warn(message);
+  }
+}
diff --git a/scripts/release/find-release-from-changeset.js b/scripts/find-release-from-changeset.js
similarity index 100%
rename from scripts/release/find-release-from-changeset.js
rename to scripts/find-release-from-changeset.js
diff --git a/scripts/release/comment.ts b/scripts/release/comment.ts
deleted file mode 100644
index 5d6354486..000000000
--- a/scripts/release/comment.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import {
-  VERSION,
-  OWNER,
-  REPO,
-  PR_FILES_STARTS_WITH,
-  IS_STABLE_RELEASE,
-  AWAITING_RELEASE_LABEL,
-  DRY_RUN,
-} from "./constants";
-import {
-  closeIssue,
-  commentOnIssue,
-  commentOnPullRequest,
-  getIssuesClosedByPullRequests,
-  prsMergedSinceLastTag,
-  removeLabel,
-} from "./github";
-import { getGitHubUrl } from "./utils";
-
-async function commentOnIssuesAndPrsAboutRelease() {
-  if (VERSION.includes("experimental")) {
-    return;
-  }
-
-  let { merged, previousTag } = await prsMergedSinceLastTag({
-    owner: OWNER,
-    repo: REPO,
-    githubRef: VERSION,
-  });
-
-  let suffix = merged.length === 1 ? "" : "s";
-  let prFilesDirs = PR_FILES_STARTS_WITH.join(", ");
-  console.log(
-    `Found ${merged.length} PR${suffix} merged ` +
-      `that touched \`${prFilesDirs}\` since ` +
-      `previous release (current: ${VERSION}, previous: ${previousTag})`
-  );
-
-  let promises: Array<ReturnType<typeof commentOnIssue>> = [];
-  let issuesCommentedOn = new Set();
-
-  for (let pr of merged) {
-    console.log(`commenting on pr ${getGitHubUrl("pull", pr.number)}`);
-
-    if (!DRY_RUN) {
-      promises.push(
-        commentOnPullRequest({
-          owner: OWNER,
-          repo: REPO,
-          pr: pr.number,
-          version: VERSION,
-        })
-      );
-    }
-
-    let prLabels = pr.labels.map((label) => label.name);
-    let prIsAwaitingRelease = prLabels.includes(AWAITING_RELEASE_LABEL);
-
-    if (IS_STABLE_RELEASE && prIsAwaitingRelease && !DRY_RUN) {
-      promises.push(
-        removeLabel({ owner: OWNER, repo: REPO, issue: pr.number })
-      );
-    }
-
-    let issuesClosed = await getIssuesClosedByPullRequests(
-      pr.html_url,
-      pr.body
-    );
-
-    for (let issue of issuesClosed) {
-      if (issuesCommentedOn.has(issue.number)) {
-        // we already commented on this issue
-        // so we don't need to do it again
-        continue;
-      }
-
-      issuesCommentedOn.add(issue.number);
-      let issueUrl = getGitHubUrl("issue", issue.number);
-      console.log(`commenting on issue ${issueUrl}`);
-
-      if (!DRY_RUN) {
-        promises.push(
-          commentOnIssue({
-            owner: OWNER,
-            repo: REPO,
-            issue: issue.number,
-            version: VERSION,
-          })
-        );
-      }
-
-      if (IS_STABLE_RELEASE) {
-        console.log(`closing issue ${issueUrl}`);
-        if (!DRY_RUN) {
-          promises.push(
-            closeIssue({ owner: OWNER, repo: REPO, issue: issue.number })
-          );
-        }
-      }
-    }
-  }
-
-  let result = await Promise.allSettled(promises);
-  let rejected = result.filter((r) => r.status === "rejected");
-  if (rejected.length > 0) {
-    console.error("🚨 failed to comment on some issues/prs", rejected);
-  }
-}
-
-commentOnIssuesAndPrsAboutRelease();
diff --git a/scripts/release/constants.ts b/scripts/release/constants.ts
deleted file mode 100644
index 8039c782e..000000000
--- a/scripts/release/constants.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { cleanupRef, cleanupTagName, isNightly, isStable } from "./utils";
-
-if (!process.env.DEFAULT_BRANCH) {
-  throw new Error("DEFAULT_BRANCH is required");
-}
-if (!process.env.RELEASE_BRANCH) {
-  throw new Error("RELEASE_BRANCH is required");
-}
-if (!process.env.GITHUB_TOKEN) {
-  throw new Error("GITHUB_TOKEN is required");
-}
-if (!process.env.GITHUB_REPOSITORY) {
-  throw new Error("GITHUB_REPOSITORY is required");
-}
-if (!process.env.VERSION) {
-  throw new Error("VERSION is required");
-}
-if (!/^refs\/tags\//.test(process.env.VERSION)) {
-  throw new Error("VERSION must start with refs/tags/");
-}
-if (!process.env.PACKAGE_VERSION_TO_FOLLOW) {
-  throw new Error("PACKAGE_VERSION_TO_FOLLOW is required");
-}
-
-export const [OWNER, REPO] = process.env.GITHUB_REPOSITORY.split("/");
-export const PACKAGE_VERSION_TO_FOLLOW = process.env.PACKAGE_VERSION_TO_FOLLOW;
-export const VERSION = cleanupTagName(cleanupRef(process.env.VERSION));
-export const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
-export const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY;
-export const DEFAULT_BRANCH = process.env.DEFAULT_BRANCH;
-export const RELEASE_BRANCH = process.env.RELEASE_BRANCH;
-export const PR_FILES_STARTS_WITH = ["packages/"];
-export const IS_NIGHTLY_RELEASE = isNightly(VERSION);
-export const AWAITING_RELEASE_LABEL = "awaiting release";
-export const IS_STABLE_RELEASE = isStable(VERSION);
-export const DRY_RUN = process.env.DRY_RUN === "true";
diff --git a/scripts/release/github.ts b/scripts/release/github.ts
deleted file mode 100644
index c51ed223d..000000000
--- a/scripts/release/github.ts
+++ /dev/null
@@ -1,474 +0,0 @@
-import type { RestEndpointMethodTypes } from "@octokit/rest";
-import * as semver from "semver";
-
-import {
-  PR_FILES_STARTS_WITH,
-  RELEASE_BRANCH,
-  DEFAULT_BRANCH,
-  PACKAGE_VERSION_TO_FOLLOW,
-  AWAITING_RELEASE_LABEL,
-  IS_NIGHTLY_RELEASE,
-  IS_STABLE_RELEASE,
-} from "./constants";
-import { gql, graphqlWithAuth, octokit } from "./octokit";
-import type { MinimalTag } from "./utils";
-import { isNightly, isStable } from "./utils";
-import { cleanupTagName } from "./utils";
-import { checkIfStringStartsWith } from "./utils";
-
-type PullRequest =
-  RestEndpointMethodTypes["pulls"]["list"]["response"]["data"][number];
-
-type PullRequestFiles =
-  RestEndpointMethodTypes["pulls"]["listFiles"]["response"]["data"];
-
-interface PrsMergedSinceLastTagOptions {
-  owner: string;
-  repo: string;
-  githubRef: string;
-}
-
-interface PrsMergedSinceLastTagResult {
-  merged: Awaited<ReturnType<typeof getPullRequestWithFiles>>;
-  previousTag: string;
-}
-
-export async function prsMergedSinceLastTag({
-  owner,
-  repo,
-  githubRef,
-}: PrsMergedSinceLastTagOptions): Promise<PrsMergedSinceLastTagResult> {
-  let tags = await getTags(owner, repo);
-  let { currentTag, previousTag } = getPreviousTagFromCurrentTag(
-    githubRef,
-    tags
-  );
-
-  console.log(`Getting PRs merged ${previousTag.tag}...${currentTag.tag}`);
-
-  /**
-    nightly > nightly => 'dev'
-    nightly > stable => 'main'
-    stable > nightly => 'dev'
-   */
-  let prs: Awaited<ReturnType<typeof getMergedPRsBetweenTags>> = [];
-
-  // if both the current and previous tags are prereleases
-  // we can just get the PRs for the `release` branch
-  // but if one of them is stable, we should wind up all of them from both the main and `release` branches
-  if (currentTag.isPrerelease && previousTag.isPrerelease) {
-    prs = await getMergedPRsBetweenTags(
-      owner,
-      repo,
-      previousTag,
-      currentTag,
-      RELEASE_BRANCH
-    );
-  } else {
-    let [nightly, stable] = await Promise.all([
-      getMergedPRsBetweenTags(
-        owner,
-        repo,
-        previousTag,
-        currentTag,
-        RELEASE_BRANCH
-      ),
-      getMergedPRsBetweenTags(
-        owner,
-        repo,
-        previousTag,
-        currentTag,
-        DEFAULT_BRANCH
-      ),
-    ]);
-    prs = nightly.concat(stable);
-  }
-
-  let prsThatTouchedFiles = await getPullRequestWithFiles(owner, repo, prs);
-
-  return {
-    merged: prsThatTouchedFiles,
-    previousTag: previousTag.tag,
-  };
-}
-
-type PullRequestWithFiles = PullRequest & {
-  files: PullRequestFiles;
-};
-
-async function getPullRequestWithFiles(
-  owner: string,
-  repo: string,
-  prs: Array<PullRequest>
-): Promise<Array<PullRequestWithFiles>> {
-  let prsWithFiles = await Promise.all(
-    prs.map(async (pr) => {
-      let files = await octokit.paginate(octokit.pulls.listFiles, {
-        owner,
-        repo,
-        per_page: 100,
-        pull_number: pr.number,
-      });
-
-      return { ...pr, files };
-    })
-  );
-
-  return prsWithFiles.filter((pr) => {
-    return pr.files.some((file) => {
-      return checkIfStringStartsWith(file.filename, PR_FILES_STARTS_WITH);
-    });
-  });
-}
-
-function getPreviousTagFromCurrentTag(
-  currentTag: string,
-  tags: Awaited<ReturnType<typeof getTags>>
-): {
-  previousTag: MinimalTag;
-  currentTag: MinimalTag;
-} {
-  let validTags = tags
-    .map((tag) => {
-      let tagName = cleanupTagName(tag.name);
-      let isPrerelease = semver.prerelease(tagName) !== null;
-
-      let date = tag.target.committer?.date
-        ? new Date(tag.target.committer.date)
-        : tag.target.tagger?.date
-        ? new Date(tag.target.tagger.date)
-        : undefined;
-
-      if (!date) return undefined;
-
-      return { tag: tagName, date, isPrerelease };
-    })
-    .filter((v: any): v is MinimalTag => typeof v !== "undefined")
-    .filter((tag) => {
-      if (IS_STABLE_RELEASE) return isStable(tag.tag);
-      let isNightlyTag = isNightly(tag.tag);
-      if (IS_NIGHTLY_RELEASE) return isNightlyTag;
-      return !isNightlyTag;
-    })
-    .sort((a, b) => {
-      if (IS_NIGHTLY_RELEASE) {
-        return b.date.getTime() - a.date.getTime();
-      }
-
-      return semver.rcompare(a.tag, b.tag);
-    });
-
-  let currentTagIndex = validTags.findIndex((tag) => tag.tag === currentTag);
-  let currentTagInfo: MinimalTag | undefined = validTags.at(currentTagIndex);
-  let previousTagInfo: MinimalTag | undefined;
-
-  if (!currentTagInfo) {
-    throw new Error(`Could not find tag ${currentTag}`);
-  }
-
-  previousTagInfo = validTags.at(currentTagIndex + 1);
-  if (!previousTagInfo) {
-    throw new Error(`Could not find previous tag from ${currentTag}`);
-  }
-
-  return {
-    currentTag: currentTagInfo,
-    previousTag: previousTagInfo,
-  };
-}
-
-async function getMergedPRsBetweenTags(
-  owner: string,
-  repo: string,
-  startTag: MinimalTag,
-  endTag: MinimalTag,
-  baseRef: string,
-  page: number = 1,
-  nodes: Array<PullRequest> = []
-): Promise<Array<PullRequest>> {
-  let pulls = await octokit.pulls.list({
-    owner,
-    repo,
-    state: "closed",
-    sort: "updated",
-    direction: "desc",
-    per_page: 100,
-    page,
-    base: baseRef,
-  });
-
-  let merged = pulls.data.filter((pull) => {
-    if (!pull.merged_at) return false;
-    let mergedDate = new Date(pull.merged_at);
-    return mergedDate > startTag.date && mergedDate < endTag.date;
-  });
-
-  if (pulls.data.length !== 0) {
-    return getMergedPRsBetweenTags(
-      owner,
-      repo,
-      startTag,
-      endTag,
-      baseRef,
-      page + 1,
-      [...nodes, ...merged]
-    );
-  }
-
-  return [...nodes, ...merged];
-}
-
-interface GitHubGraphqlTag {
-  name: string;
-  target: {
-    oid: string;
-    committer?: {
-      date: string;
-    };
-    tagger?: {
-      date: string;
-    };
-  };
-}
-interface GitHubGraphqlTagResponse {
-  repository: {
-    refs: {
-      pageInfo: {
-        hasNextPage: boolean;
-        endCursor: string;
-      };
-      nodes: Array<GitHubGraphqlTag>;
-    };
-  };
-}
-
-async function getTags(
-  owner: string,
-  repo: string,
-  endCursor?: string,
-  nodes: Array<GitHubGraphqlTag> = []
-): Promise<GitHubGraphqlTag[]> {
-  let response: GitHubGraphqlTagResponse = await graphqlWithAuth(
-    gql`
-      query GET_TAGS($owner: String!, $repo: String!, $endCursor: String) {
-        repository(owner: $owner, name: $repo) {
-          refs(
-            refPrefix: "refs/tags/"
-            first: 100
-            orderBy: { field: TAG_COMMIT_DATE, direction: DESC }
-            after: $endCursor
-          ) {
-            pageInfo {
-              hasNextPage
-              endCursor
-            }
-            nodes {
-              name
-              target {
-                oid
-                ... on Commit {
-                  committer {
-                    date
-                  }
-                }
-                ... on Tag {
-                  tagger {
-                    date
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-    `,
-    { owner, repo, endCursor }
-  );
-
-  let filtered = response.repository.refs.nodes.filter((node) => {
-    return (
-      node.name.startsWith(`${PACKAGE_VERSION_TO_FOLLOW}@`) ||
-      node.name.startsWith("v0.0.0-nightly-")
-    );
-  });
-
-  if (response.repository.refs.pageInfo.hasNextPage) {
-    console.log("has next page", response.repository.refs.pageInfo.endCursor);
-
-    return getTags(owner, repo, response.repository.refs.pageInfo.endCursor, [
-      ...nodes,
-      ...filtered,
-    ]);
-  }
-
-  return [...nodes, ...filtered];
-}
-
-export async function getIssuesClosedByPullRequests(
-  prHtmlUrl: string,
-  prBody: string | null
-): Promise<Array<{ number: number }>> {
-  let linkedIssues = await getIssuesLinkedToPullRequest(prHtmlUrl);
-  if (!prBody) {
-    return linkedIssues.map((issue) => {
-      return { number: issue.number };
-    });
-  }
-
-  /**
-   * This regex matches for one of github's issue references for auto linking an issue to a PR
-   * as that only happens when the PR is sent to the default branch of the repo
-   * https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-   */
-  let regex =
-    /(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)(:)?\s#([0-9]+)/gi;
-  let matches = prBody.match(regex);
-  if (!matches) {
-    return linkedIssues.map((issue) => {
-      return { number: issue.number };
-    });
-  }
-
-  let issuesMatch = matches.map((match) => {
-    let [, issueNumber] = match.split(" #");
-    return { number: parseInt(issueNumber, 10) };
-  });
-
-  let issues = await Promise.all(
-    issuesMatch.map(async (issue) => {
-      return { number: issue.number };
-    })
-  );
-
-  return [...linkedIssues, ...issues.filter((issue) => issue !== null)];
-}
-
-interface GitHubClosingIssueReference {
-  resource: {
-    closingIssuesReferences: {
-      pageInfo: {
-        endCursor: string;
-        hasNextPage: boolean;
-      };
-      nodes: Array<{ number: number }>;
-    };
-  };
-}
-
-type IssuesLinkedToPullRequest = Array<{ number: number }>;
-
-async function getIssuesLinkedToPullRequest(
-  prHtmlUrl: string,
-  nodes: IssuesLinkedToPullRequest = [],
-  after?: string
-): Promise<IssuesLinkedToPullRequest> {
-  let res: GitHubClosingIssueReference = await graphqlWithAuth(
-    gql`
-      query GET_ISSUES_CLOSED_BY_PR($prHtmlUrl: URI!, $after: String) {
-        resource(url: $prHtmlUrl) {
-          ... on PullRequest {
-            closingIssuesReferences(first: 100, after: $after) {
-              nodes {
-                number
-              }
-              pageInfo {
-                hasNextPage
-                endCursor
-              }
-            }
-          }
-        }
-      }
-    `,
-    { prHtmlUrl, after }
-  );
-
-  let newNodes = res?.resource?.closingIssuesReferences?.nodes ?? [];
-  nodes.push(
-    ...newNodes.map((node) => {
-      return { number: node.number };
-    })
-  );
-
-  if (res?.resource?.closingIssuesReferences?.pageInfo?.hasNextPage) {
-    return getIssuesLinkedToPullRequest(
-      prHtmlUrl,
-      nodes,
-      res?.resource?.closingIssuesReferences?.pageInfo?.endCursor
-    );
-  }
-
-  return nodes;
-}
-
-export async function commentOnPullRequest({
-  owner,
-  repo,
-  pr,
-  version,
-}: {
-  owner: string;
-  repo: string;
-  pr: number;
-  version: string;
-}) {
-  await octokit.issues.createComment({
-    owner,
-    repo,
-    issue_number: pr,
-    body: `🤖 Hello there,\n\nWe just published version \`${version}\` which includes this pull request. If you'd like to take it for a test run please try it out and let us know what you think!\n\nThanks!`,
-  });
-}
-
-export async function commentOnIssue({
-  owner,
-  repo,
-  issue,
-  version,
-}: {
-  owner: string;
-  repo: string;
-  issue: number;
-  version: string;
-}) {
-  await octokit.issues.createComment({
-    owner,
-    repo,
-    issue_number: issue,
-    body: `🤖 Hello there,\n\nWe just published version \`${version}\` which involves this issue. If you'd like to take it for a test run please try it out and let us know what you think!\n\nThanks!`,
-  });
-}
-
-export async function closeIssue({
-  owner,
-  repo,
-  issue,
-}: {
-  owner: string;
-  repo: string;
-  issue: number;
-}) {
-  await octokit.issues.update({
-    owner,
-    repo,
-    issue_number: issue,
-    state: "closed",
-  });
-}
-
-export async function removeLabel({
-  owner,
-  repo,
-  issue,
-}: {
-  owner: string;
-  repo: string;
-  issue: number;
-}) {
-  await octokit.issues.removeLabel({
-    owner,
-    repo,
-    issue_number: issue,
-    name: AWAITING_RELEASE_LABEL,
-  });
-}
diff --git a/scripts/release/octokit.ts b/scripts/release/octokit.ts
deleted file mode 100644
index 6be758a13..000000000
--- a/scripts/release/octokit.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Octokit as RestOctokit } from "@octokit/rest";
-import type { Octokit as OctokitType } from "@octokit/rest";
-import { paginateRest } from "@octokit/plugin-paginate-rest";
-import { throttling } from "@octokit/plugin-throttling";
-import { graphql } from "@octokit/graphql";
-
-import { GITHUB_TOKEN } from "./constants";
-
-export const graphqlWithAuth = graphql.defaults({
-  headers: { authorization: `token ${GITHUB_TOKEN}` },
-});
-
-const Octokit = RestOctokit.plugin(paginateRest, throttling);
-
-export const octokit = new Octokit({
-  auth: GITHUB_TOKEN,
-  throttle: {
-    onRateLimit(retryAfter: number, options: any, octokit: OctokitType) {
-      octokit.log.warn(
-        `Request quota exhausted for request ${options.method} ${options.url}`
-      );
-
-      if (options.request.retryCount === 0) {
-        // only retries once
-        octokit.log.info(`Retrying after ${retryAfter} seconds!`);
-        return true;
-      }
-    },
-    onSecondaryRateLimit(
-      _retryAfter: number,
-      options: any,
-      octokit: OctokitType
-    ) {
-      // does not retry, only logs a warning
-      octokit.log.warn(
-        `SecondaryRateLimit detected for request ${options.method} ${options.url}`
-      );
-    },
-  },
-});
-
-export const gql = String.raw;
diff --git a/scripts/release/package-lock.json b/scripts/release/package-lock.json
deleted file mode 100644
index 2d5d9de69..000000000
--- a/scripts/release/package-lock.json
+++ /dev/null
@@ -1,355 +0,0 @@
-{
-  "name": "release-comments",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "release-comments",
-      "dependencies": {
-        "@octokit/plugin-paginate-rest": "^2.17.0",
-        "@octokit/plugin-throttling": "^3.6.2",
-        "@octokit/rest": "^18.12.0",
-        "esbuild": "^0.16.3",
-        "esbuild-register": "^3.3.2",
-        "semver": "^7.3.7"
-      },
-      "devDependencies": {
-        "@tsconfig/node16": "^1.0.2",
-        "@types/node": "^17.0.23",
-        "@types/semver": "^7.3.9",
-        "typescript": "^4.7.4"
-      }
-    },
-    "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.16.3",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.3.tgz",
-      "integrity": "sha512-DO8WykMyB+N9mIDfI/Hug70Dk1KipavlGAecxS3jDUwAbTpDXj0Lcwzw9svkhxfpCagDmpaTMgxWK8/C/XcXvw==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@octokit/auth-token": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
-      "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
-      "dependencies": {
-        "@octokit/types": "^6.0.3"
-      }
-    },
-    "node_modules/@octokit/core": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
-      "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
-      "dependencies": {
-        "@octokit/auth-token": "^2.4.4",
-        "@octokit/graphql": "^4.5.8",
-        "@octokit/request": "^5.6.3",
-        "@octokit/request-error": "^2.0.5",
-        "@octokit/types": "^6.0.3",
-        "before-after-hook": "^2.2.0",
-        "universal-user-agent": "^6.0.0"
-      }
-    },
-    "node_modules/@octokit/endpoint": {
-      "version": "6.0.12",
-      "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
-      "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
-      "dependencies": {
-        "@octokit/types": "^6.0.3",
-        "is-plain-object": "^5.0.0",
-        "universal-user-agent": "^6.0.0"
-      }
-    },
-    "node_modules/@octokit/graphql": {
-      "version": "4.8.0",
-      "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
-      "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
-      "dependencies": {
-        "@octokit/request": "^5.6.0",
-        "@octokit/types": "^6.0.3",
-        "universal-user-agent": "^6.0.0"
-      }
-    },
-    "node_modules/@octokit/openapi-types": {
-      "version": "12.1.0",
-      "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.1.0.tgz",
-      "integrity": "sha512-kQzJh3ZUv3lDpi6M+uekMRHULvf9DlWoI1XgKN6nPeGDzkSgtkhVq1MMz3bFKQ6H6GbdC3ZqG/a6VzKhIx0VeA=="
-    },
-    "node_modules/@octokit/plugin-paginate-rest": {
-      "version": "2.18.0",
-      "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.18.0.tgz",
-      "integrity": "sha512-n5/AzIoy5Wzp85gqzSbR+dWQDHlyHZrGijnDfLh452547Ynu0hCvszH7EfRE0eqM5ZjfkplO0k+q+P8AAIIJEA==",
-      "dependencies": {
-        "@octokit/types": "^6.35.0"
-      },
-      "peerDependencies": {
-        "@octokit/core": ">=2"
-      }
-    },
-    "node_modules/@octokit/plugin-request-log": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
-      "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==",
-      "peerDependencies": {
-        "@octokit/core": ">=3"
-      }
-    },
-    "node_modules/@octokit/plugin-rest-endpoint-methods": {
-      "version": "5.14.0",
-      "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.14.0.tgz",
-      "integrity": "sha512-MRnMs4Dcm1OSaz/g/RLr4YY9otgysS7vN5SUkHGd7t+R8323cHsHFoEWHYPSmgUC0BieHRhvnCRWb4i3Pl+Lgg==",
-      "dependencies": {
-        "@octokit/types": "^6.35.0",
-        "deprecation": "^2.3.1"
-      },
-      "peerDependencies": {
-        "@octokit/core": ">=3"
-      }
-    },
-    "node_modules/@octokit/plugin-throttling": {
-      "version": "3.6.2",
-      "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.6.2.tgz",
-      "integrity": "sha512-0az5fxgVlhFfFtiKLKVXTpmCG2tK3BG0fYI8SO4pmGlN1kyJktJVQA+6KKaFxtxMIWsuHmSEAkR6zSgtk86g2A==",
-      "dependencies": {
-        "@octokit/types": "^6.0.1",
-        "bottleneck": "^2.15.3"
-      },
-      "peerDependencies": {
-        "@octokit/core": "^3.5.0"
-      }
-    },
-    "node_modules/@octokit/request": {
-      "version": "5.6.3",
-      "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
-      "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
-      "dependencies": {
-        "@octokit/endpoint": "^6.0.1",
-        "@octokit/request-error": "^2.1.0",
-        "@octokit/types": "^6.16.1",
-        "is-plain-object": "^5.0.0",
-        "node-fetch": "^2.6.7",
-        "universal-user-agent": "^6.0.0"
-      }
-    },
-    "node_modules/@octokit/request-error": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
-      "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
-      "dependencies": {
-        "@octokit/types": "^6.0.3",
-        "deprecation": "^2.0.0",
-        "once": "^1.4.0"
-      }
-    },
-    "node_modules/@octokit/rest": {
-      "version": "18.12.0",
-      "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz",
-      "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==",
-      "dependencies": {
-        "@octokit/core": "^3.5.1",
-        "@octokit/plugin-paginate-rest": "^2.16.8",
-        "@octokit/plugin-request-log": "^1.0.4",
-        "@octokit/plugin-rest-endpoint-methods": "^5.12.0"
-      }
-    },
-    "node_modules/@octokit/types": {
-      "version": "6.35.0",
-      "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.35.0.tgz",
-      "integrity": "sha512-DhLfdUuv3H37u6jBDfkwamypx3HflHg29b26nbA6iVFYkAlZ5cMEtu/9pQoihGnQE5M7jJFnNo25Rr1UwQNF8Q==",
-      "dependencies": {
-        "@octokit/openapi-types": "^12.1.0"
-      }
-    },
-    "node_modules/@tsconfig/node16": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
-      "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
-      "dev": true
-    },
-    "node_modules/@types/node": {
-      "version": "17.0.45",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
-      "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
-      "dev": true
-    },
-    "node_modules/@types/semver": {
-      "version": "7.3.10",
-      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.10.tgz",
-      "integrity": "sha512-zsv3fsC7S84NN6nPK06u79oWgrPVd0NvOyqgghV1haPaFcVxIrP4DLomRwGAXk0ui4HZA7mOcSFL98sMVW9viw==",
-      "dev": true
-    },
-    "node_modules/before-after-hook": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
-      "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
-    },
-    "node_modules/bottleneck": {
-      "version": "2.19.5",
-      "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
-      "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
-    },
-    "node_modules/deprecation": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
-      "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
-    },
-    "node_modules/esbuild": {
-      "version": "0.16.3",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.3.tgz",
-      "integrity": "sha512-71f7EjPWTiSguen8X/kxEpkAS7BFHwtQKisCDDV3Y4GLGWBaoSCyD5uXkaUew6JDzA9FEN1W23mdnSwW9kqCeg==",
-      "hasInstallScript": true,
-      "bin": {
-        "esbuild": "bin/esbuild"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "optionalDependencies": {
-        "@esbuild/android-arm": "0.16.3",
-        "@esbuild/android-arm64": "0.16.3",
-        "@esbuild/android-x64": "0.16.3",
-        "@esbuild/darwin-arm64": "0.16.3",
-        "@esbuild/darwin-x64": "0.16.3",
-        "@esbuild/freebsd-arm64": "0.16.3",
-        "@esbuild/freebsd-x64": "0.16.3",
-        "@esbuild/linux-arm": "0.16.3",
-        "@esbuild/linux-arm64": "0.16.3",
-        "@esbuild/linux-ia32": "0.16.3",
-        "@esbuild/linux-loong64": "0.16.3",
-        "@esbuild/linux-mips64el": "0.16.3",
-        "@esbuild/linux-ppc64": "0.16.3",
-        "@esbuild/linux-riscv64": "0.16.3",
-        "@esbuild/linux-s390x": "0.16.3",
-        "@esbuild/linux-x64": "0.16.3",
-        "@esbuild/netbsd-x64": "0.16.3",
-        "@esbuild/openbsd-x64": "0.16.3",
-        "@esbuild/sunos-x64": "0.16.3",
-        "@esbuild/win32-arm64": "0.16.3",
-        "@esbuild/win32-ia32": "0.16.3",
-        "@esbuild/win32-x64": "0.16.3"
-      }
-    },
-    "node_modules/esbuild-register": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.3.3.tgz",
-      "integrity": "sha512-eFHOkutgIMJY5gc8LUp/7c+LLlDqzNi9T6AwCZ2WKKl3HmT+5ef3ZRyPPxDOynInML0fgaC50yszPKfPnjC0NQ==",
-      "peerDependencies": {
-        "esbuild": ">=0.12 <1"
-      }
-    },
-    "node_modules/is-plain-object": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
-      "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/node-fetch": {
-      "version": "2.6.7",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
-      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
-      "dependencies": {
-        "whatwg-url": "^5.0.0"
-      },
-      "engines": {
-        "node": "4.x || >=6.0.0"
-      },
-      "peerDependencies": {
-        "encoding": "^0.1.0"
-      },
-      "peerDependenciesMeta": {
-        "encoding": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-      "dependencies": {
-        "wrappy": "1"
-      }
-    },
-    "node_modules/semver": {
-      "version": "7.3.7",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
-      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/tr46": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
-    },
-    "node_modules/typescript": {
-      "version": "4.7.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
-      "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
-      "dev": true,
-      "bin": {
-        "tsc": "bin/tsc",
-        "tsserver": "bin/tsserver"
-      },
-      "engines": {
-        "node": ">=4.2.0"
-      }
-    },
-    "node_modules/universal-user-agent": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
-      "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
-    },
-    "node_modules/webidl-conversions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
-    },
-    "node_modules/whatwg-url": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-      "dependencies": {
-        "tr46": "~0.0.3",
-        "webidl-conversions": "^3.0.0"
-      }
-    },
-    "node_modules/wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
-    },
-    "node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-    }
-  }
-}
diff --git a/scripts/release/package.json b/scripts/release/package.json
deleted file mode 100644
index e1b7e516a..000000000
--- a/scripts/release/package.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "name": "release-comments",
-  "description": "deps needed for running our release comment script",
-  "private": true,
-  "scripts": {},
-  "dependencies": {
-    "@octokit/plugin-paginate-rest": "^2.17.0",
-    "@octokit/plugin-throttling": "^3.6.2",
-    "@octokit/rest": "^18.12.0",
-    "esbuild": "^0.16.3",
-    "esbuild-register": "^3.3.2",
-    "semver": "^7.3.7"
-  },
-  "devDependencies": {
-    "@tsconfig/node16": "^1.0.2",
-    "@types/node": "^17.0.23",
-    "@types/semver": "^7.3.9",
-    "typescript": "^4.7.4"
-  }
-}
diff --git a/scripts/release/tsconfig.json b/scripts/release/tsconfig.json
deleted file mode 100644
index e9c0bafec..000000000
--- a/scripts/release/tsconfig.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "extends": "@tsconfig/node16/tsconfig.json"
-}
diff --git a/scripts/release/utils.ts b/scripts/release/utils.ts
deleted file mode 100644
index a84a60673..000000000
--- a/scripts/release/utils.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import * as semver from "semver";
-
-import { GITHUB_REPOSITORY, PACKAGE_VERSION_TO_FOLLOW } from "./constants";
-
-export function checkIfStringStartsWith(
-  string: string,
-  substrings: Array<string>
-): boolean {
-  return substrings.some((substr) => string.startsWith(substr));
-}
-
-export interface MinimalTag {
-  tag: string;
-  date: Date;
-  isPrerelease: boolean;
-}
-
-export function getGitHubUrl(type: "pull" | "issue", number: number) {
-  let segment = type === "pull" ? "pull" : "issues";
-  return `https://github.com/${GITHUB_REPOSITORY}/${segment}/${number}`;
-}
-
-export function cleanupTagName(tagName: string) {
-  if (PACKAGE_VERSION_TO_FOLLOW) {
-    let regex = new RegExp(`^${PACKAGE_VERSION_TO_FOLLOW}@`);
-    return tagName.replace(regex, "");
-  }
-
-  return tagName;
-}
-
-export function cleanupRef(ref: string) {
-  return ref.replace(/^refs\/tags\//, "");
-}
-
-export function isNightly(tagName: string) {
-  return tagName.startsWith("v0.0.0-nightly-");
-}
-
-export function isStable(tagName: string) {
-  return semver.prerelease(tagName) === null;
-}


Test patch:
diff --git a/packages/remix-dev/__tests__/cssSideEffectsPlugin-test.ts b/packages/remix-dev/__tests__/cssSideEffectImports-test.ts
similarity index 99%
rename from packages/remix-dev/__tests__/cssSideEffectsPlugin-test.ts
rename to packages/remix-dev/__tests__/cssSideEffectImports-test.ts
index f9eac955a..55e3f7a24 100644
--- a/packages/remix-dev/__tests__/cssSideEffectsPlugin-test.ts
+++ b/packages/remix-dev/__tests__/cssSideEffectImports-test.ts
@@ -1,6 +1,6 @@
 import dedent from "dedent";
 
-import { addSuffixToCssSideEffectImports } from "../compiler/plugins/cssSideEffectImportsPlugin";
+import { addSuffixToCssSideEffectImports } from "../compiler/plugins/cssSideEffectImports";
 
 describe("addSuffixToCssSideEffectImports", () => {
   describe("adds suffix", () => {