Skip to content

Commit

Permalink
Relativize route modules to make builds deterministic
Browse files Browse the repository at this point in the history
Fixes #2024

If virtual modules have non-deterministic paths or content (e.g. due to
importing from other absolute paths), the input is technically different,
and deterministic build output is not guaranteed.

Depending on how you build/deploy (e.g. if you need to build and deploy
your server separately from your browser build), this can result in a broken
app, since the server and browser manifests may differ (i.e. due to different
fingerprints). By using relative paths for route modules, we can ensure the
same result no matter the absolute path.

Possibly worth pointing out that this fix also affects file path comments in
the server build, e.g. you'll now see stuff like:
 // app/root.tsx
instead of:
 // /absolute/path/on/the/build/machine/to/app/root.tsx

Testing notes:
 1. Added integration test
 2. Verified manually, i.e.
    1. Create two remix projects (via npx create-remix@latest)
    2. `npm run build` them both
    3. `diff -r project1/build project2/build` has no differences
    4. `diff -r project1/public/build project2/public/build` has no differences
    5. `dev` and `start` still work as per usual
  • Loading branch information
jenseng committed Feb 22, 2022
1 parent b9f3215 commit 7eb108f
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 16 deletions.
46 changes: 46 additions & 0 deletions integration/deterministic-build-output-test.ts
Original file line number Diff line number Diff line change
@@ -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 (
<div>
{data}
</div>
)
}
`,
},
};
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]))
);
});
});
3 changes: 1 addition & 2 deletions packages/remix-dev/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
11 changes: 6 additions & 5 deletions packages/remix-dev/compiler/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function createAssetsManifest(
let routesByFile: Map<string, Route> = 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()
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as path from "path";
import type esbuild from "esbuild";

import type { RemixConfig } from "../../config";
Expand Down Expand Up @@ -31,7 +30,7 @@ export function browserRouteModulesPlugin(
let routesByFile: Map<string, Route> = 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()
Expand Down Expand Up @@ -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",
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as path from "path";
import type { Plugin } from "esbuild";

import type { RemixConfig } from "../../config";
Expand Down Expand Up @@ -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")}
Expand Down

0 comments on commit 7eb108f

Please sign in to comment.