diff --git a/integration/deterministic-build-output-test.ts b/integration/deterministic-build-output-test.ts new file mode 100644 index 00000000000..f713717491d --- /dev/null +++ b/integration/deterministic-build-output-test.ts @@ -0,0 +1,28 @@ +import { test, expect } from "@playwright/test"; +import globby from "globby"; +import fs from "fs"; +import path from "path"; + +import { createFixtureProject } from "./helpers/create-fixture"; + +test("builds deterministically under different paths", async () => { + let dir1 = await createFixtureProject(); + let dir2 = await createFixtureProject(); + + 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.length).toBeGreaterThan(0); + 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/integration/helpers/create-fixture.ts b/integration/helpers/create-fixture.ts index a3ca94c5ead..bde7c2453b3 100644 --- a/integration/helpers/create-fixture.ts +++ b/integration/helpers/create-fixture.ts @@ -16,7 +16,7 @@ const TMP_DIR = path.join(process.cwd(), ".tmp", "integration"); interface FixtureInit { buildStdio?: Writable; sourcemap?: boolean; - files: { [filename: string]: string }; + files?: { [filename: string]: string }; template?: "cf-template" | "deno-template" | "node-template"; setup?: "node" | "cloudflare"; } @@ -133,7 +133,9 @@ export async function createAppFixture(fixture: Fixture) { } //////////////////////////////////////////////////////////////////////////////// -export async function createFixtureProject(init: FixtureInit): Promise { +export async function createFixtureProject( + init: FixtureInit = {} +): Promise { let template = init.template ?? "node-template"; let integrationTemplateDir = path.join(__dirname, template); let projectName = `remix-${template}-${Math.random().toString(32).slice(2)}`; @@ -202,7 +204,7 @@ function build(projectDir: string, buildStdio?: Writable, sourcemap?: boolean) { async function writeTestFiles(init: FixtureInit, dir: string) { await Promise.all( - Object.keys(init.files).map(async (filename) => { + Object.keys(init.files ?? {}).map(async (filename) => { let filePath = path.join(dir, filename); await fse.ensureDir(path.dirname(filePath)); await fse.writeFile(filePath, stripIndent(init.files[filename])); diff --git a/packages/remix-dev/compiler.ts b/packages/remix-dev/compiler.ts index 6ddc2fa68e8..14d71d7242d 100644 --- a/packages/remix-dev/compiler.ts +++ b/packages/remix-dev/compiler.ts @@ -344,8 +344,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"; } let plugins = [ diff --git a/packages/remix-dev/compiler/assets.ts b/packages/remix-dev/compiler/assets.ts index 44185bcbc61..678a2f93f7d 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,19 +72,19 @@ export async function createAssetsManifest( let output = metafile.outputs[key]; if (!output.entryPoint) continue; - let entryPointFile = path.resolve( - output.entryPoint.replace( - /(^browser-route-module:|^pnp:|\?browser$)/g, - "" - ) - ); - if (entryPointFile === entryClientFile) { + // When using yarn-pnp, esbuild-plugin-pnp resolves files under the pnp namespace, even entry.client.tsx + let entryPointFile = output.entryPoint.replace(/^pnp:/, ""); + if (path.resolve(entryPointFile) === 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:")) { + } else if (entryPointFile.startsWith("browser-route-module:")) { + entryPointFile = entryPointFile.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 fb2ed3ff695..6e66da4aef1 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() @@ -71,12 +70,12 @@ export function browserRouteModulesPlugin( let contents = "module.exports = {};"; if (theExports.length !== 0) { let spec = `{ ${theExports.join(", ")} }`; - contents = `export ${spec} from ${JSON.stringify(file)};`; + 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")} diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index d8df1931262..c5b0645b8e6 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -25,7 +25,7 @@ "@esbuild-plugins/node-modules-polyfill": "^0.1.4", "@npmcli/package-json": "^2.0.0", "@remix-run/server-runtime": "1.6.1", - "@yarnpkg/esbuild-plugin-pnp": "^2.0.0", + "@yarnpkg/esbuild-plugin-pnp": "3.0.0-rc.10", "cacache": "^15.0.5", "chalk": "^4.1.2", "chokidar": "^3.5.1", diff --git a/yarn.lock b/yarn.lock index 47ad47d6ce0..584f3fceebc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2582,10 +2582,10 @@ resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz" integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== -"@yarnpkg/esbuild-plugin-pnp@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-2.0.1.tgz#120faad903d40e8f000ed3c9db9f9055c9612800" - integrity sha512-M8nYJr8S0riwy4Jgm3ja88m5ZfW6zZSV8fgLtO1mXpwTg0tD9ki1ShPOSm9DEbicc350TVf+k/jVNh6v1xApCw== +"@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.10": + version "3.0.0-rc.10" + resolved "https://registry.npmjs.org/@yarnpkg/esbuild-plugin-pnp/-/esbuild-plugin-pnp-3.0.0-rc.10.tgz#b8ecf33614b631f42ddbe834335118904fb95127" + integrity sha512-2MR46MJ73s3rJ0Lprw/xE0czs1MBqLH+qg0n9L/t0yk0uCtaLS8ImYDnG/Nf9IXFt2BZm36LDTNQQbdfB4vvtw== dependencies: tslib "^1.13.0"