diff --git a/integration/deterministic-build-output-test.ts b/integration/deterministic-build-output-test.ts
new file mode 100644
index 00000000000..11222760870
--- /dev/null
+++ b/integration/deterministic-build-output-test.ts
@@ -0,0 +1,46 @@
+import globby from "globby";
+import fs from "fs";
+import path from "path";
+
+import { createFixtureProject, js } from "./helpers/create-fixture";
+
+it("builds deterministically under different paths", async () => {
+ let appInit = {
+ files: {
+ "app/routes/index.jsx": js`
+ import { json, useLoaderData } from "remix";
+
+ export function loader() {
+ return json("pizza");
+ }
+
+ export default function Index() {
+ let data = useLoaderData();
+ return (
+
+ {data}
+
+ )
+ }
+ `,
+ },
+ };
+ let dir1 = await createFixtureProject(appInit);
+ let dir2 = await createFixtureProject(appInit);
+
+ expect(dir1).not.toEqual(dir2);
+
+ let files1 = await globby(["build/index.js", "public/build/**/*.js"], {
+ cwd: dir1,
+ });
+ let files2 = await globby(["build/index.js", "public/build/**/*.js"], {
+ cwd: dir2,
+ });
+
+ expect(files1).toEqual(files2);
+ files1.forEach((file, i) => {
+ expect(fs.readFileSync(path.join(dir1, file))).toEqual(
+ fs.readFileSync(path.join(dir2, files2[i]))
+ );
+ });
+});
diff --git a/packages/remix-dev/compiler.ts b/packages/remix-dev/compiler.ts
index def1c9c7f90..86ea225d7b7 100644
--- a/packages/remix-dev/compiler.ts
+++ b/packages/remix-dev/compiler.ts
@@ -337,8 +337,7 @@ async function createBrowserBuild(
// 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] =
- path.resolve(config.appDirectory, config.routes[id].file) + "?browser";
+ entryPoints[id] = config.routes[id].file + "?browser";
}
return esbuild.build({
diff --git a/packages/remix-dev/compiler/assets.ts b/packages/remix-dev/compiler/assets.ts
index 4656c8328ee..4cccbb849a7 100644
--- a/packages/remix-dev/compiler/assets.ts
+++ b/packages/remix-dev/compiler/assets.ts
@@ -59,7 +59,7 @@ export async function createAssetsManifest(
let routesByFile: Map = Object.keys(config.routes).reduce(
(map, key) => {
let route = config.routes[key];
- map.set(path.resolve(config.appDirectory, route.file), route);
+ map.set(route.file, route);
return map;
},
new Map()
@@ -72,16 +72,17 @@ export async function createAssetsManifest(
let output = metafile.outputs[key];
if (!output.entryPoint) continue;
- let entryPointFile = path.resolve(
- output.entryPoint.replace(/(^browser-route-module:|\?browser$)/g, "")
- );
- if (entryPointFile === entryClientFile) {
+ if (path.resolve(output.entryPoint) === entryClientFile) {
entry = {
module: resolveUrl(key),
imports: resolveImports(output.imports),
};
// Only parse routes otherwise dynamic imports can fall into here and fail the build
} else if (output.entryPoint.startsWith("browser-route-module:")) {
+ let entryPointFile = output.entryPoint.replace(
+ /(^browser-route-module:|\?browser$)/g,
+ ""
+ );
let route = routesByFile.get(entryPointFile);
invariant(route, `Cannot get route for entry point ${output.entryPoint}`);
let sourceExports = await getRouteModuleExportsCached(config, route.id);
diff --git a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts b/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts
index 6af2f9c0911..c68475bdc2e 100644
--- a/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts
+++ b/packages/remix-dev/compiler/plugins/browserRouteModulesPlugin.ts
@@ -1,4 +1,3 @@
-import * as path from "path";
import type esbuild from "esbuild";
import type { RemixConfig } from "../../config";
@@ -31,7 +30,7 @@ export function browserRouteModulesPlugin(
let routesByFile: Map = Object.keys(config.routes).reduce(
(map, key) => {
let route = config.routes[key];
- map.set(path.resolve(config.appDirectory, route.file), route);
+ map.set(route.file, route);
return map;
},
new Map()
@@ -69,11 +68,11 @@ export function browserRouteModulesPlugin(
}
let spec =
theExports.length > 0 ? `{ ${theExports.join(", ")} }` : "*";
- let contents = `export ${spec} from ${JSON.stringify(file)};`;
+ let contents = `export ${spec} from ${JSON.stringify(`./${file}`)};`;
return {
contents,
- resolveDir: path.dirname(file),
+ resolveDir: config.appDirectory,
loader: "js",
};
}
diff --git a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
index 79e20f4f419..662cfe769df 100644
--- a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
+++ b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
@@ -1,4 +1,3 @@
-import * as path from "path";
import type { Plugin } from "esbuild";
import type { RemixConfig } from "../../config";
@@ -31,14 +30,12 @@ export function serverEntryModulePlugin(config: RemixConfig): Plugin {
resolveDir: config.appDirectory,
loader: "js",
contents: `
-import * as entryServer from ${JSON.stringify(
- path.resolve(config.appDirectory, config.entryServerFile)
- )};
+import * as entryServer from ${JSON.stringify(`./${config.entryServerFile}`)};
${Object.keys(config.routes)
.map((key, index) => {
let route = config.routes[key];
return `import * as route${index} from ${JSON.stringify(
- path.resolve(config.appDirectory, route.file)
+ `./${route.file}`
)};`;
})
.join("\n")}