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")}