From 924a17bc79c9f3b6021a9a51e93dde52aae5b69d Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Mon, 22 Jan 2024 11:14:29 +1100 Subject: [PATCH 1/2] feat(remix-dev/vite): add buildDirectory and manifest options --- .changeset/bright-days-brush.md | 9 ++ .changeset/thirty-coins-give.md | 24 ++++ docs/future/vite.md | 23 ++-- integration/vite-adapter-test.ts | 26 ++-- integration/vite-build-manifest-test.ts | 95 ++++++++++++++ integration/vite-server-bundles-test.ts | 5 +- packages/remix-dev/index.ts | 2 +- packages/remix-dev/vite/build.ts | 69 +++++----- packages/remix-dev/vite/index.ts | 2 +- packages/remix-dev/vite/plugin.ts | 164 ++++++++++++++---------- 10 files changed, 289 insertions(+), 130 deletions(-) create mode 100644 .changeset/bright-days-brush.md create mode 100644 .changeset/thirty-coins-give.md create mode 100644 integration/vite-build-manifest-test.ts diff --git a/.changeset/bright-days-brush.md b/.changeset/bright-days-brush.md new file mode 100644 index 00000000000..4f93c9bdd8c --- /dev/null +++ b/.changeset/bright-days-brush.md @@ -0,0 +1,9 @@ +--- +"@remix-run/dev": patch +--- + +Vite: Add `manifest` option to Vite plugin to enable writing a `manifest.json` file to the build directory + +**This is a breaking change for consumers of the Vite plugin's "server bundles" feature.** + +The `build/server/bundles.json` file has been superseded by the more general `build/manifest.json`. While the old server bundles manifest was always written to disk when generating server bundles, the build manifest file must be explicitly enabled via the `manifest` option. diff --git a/.changeset/thirty-coins-give.md b/.changeset/thirty-coins-give.md new file mode 100644 index 00000000000..8e94fd520fc --- /dev/null +++ b/.changeset/thirty-coins-give.md @@ -0,0 +1,24 @@ +--- +"@remix-run/dev": patch +--- + +Vite: Add new `buildDirectory` option with a default value of `"build"`. This replaces the old `assetsBuildDirectory` and `serverBuildDirectory` options which defaulted to `"build/client"` and `"build/server"` respectively. + +**This is a breaking change for consumers of the Vite plugin that were using the `assetsBuildDirectory` and `serverBuildDirectory` options.** + +The Remix Vite plugin now builds into a single directory containing `client` and `server` directories. If you've customized your build output directories, you'll need to migrate to the new `buildDirectory` option, e.g. + +```diff +import { unstable_vitePlugin as remix } from "@remix-run/dev"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + remix({ +- serverBuildDirectory: "dist/server", +- assetsBuildDirectory: "dist/client", ++ buildDirectory: "dist", + }) + ], +}); +``` diff --git a/docs/future/vite.md b/docs/future/vite.md index ca38c998c76..c1434e199f2 100644 --- a/docs/future/vite.md +++ b/docs/future/vite.md @@ -50,7 +50,6 @@ All other bundling-related options are now [configured with Vite][vite-config]. The following subset of Remix config options are supported: - [appDirectory][app-directory] -- [assetsBuildDirectory][assets-build-directory] - [ignoredRouteFiles][ignored-route-files] - [publicPath][public-path] - [routes][routes] @@ -63,9 +62,13 @@ The Vite plugin also accepts the following additional options: A function for adapting the build output and/or development environment for different hosting providers. -#### serverBuildDirectory +#### buildDirectory -The path to the server build directory, relative to the project root. Defaults to `"build/server"`. +The path to the build directory, relative to the project root. Defaults to `"build"`. + +#### manifest + +Whether to write a `manifest.json` file to the build directory. Defaults to `false`. #### serverBuildFile @@ -75,6 +78,8 @@ The name of the server file generated in the server build directory. Defaults to A function for assigning addressable routes to [server bundles][server-bundles]. +You may also want to enable the `manifest` option since, when server bundles are enabled, it contains mappings between routes and server bundles. + ## Splitting up client and server code Remix lets you write code that [runs on both the client and the server][server-vs-client]. @@ -130,18 +135,14 @@ export const PostPreview = ({ title, description }) => { ## New build output paths -There is a notable difference with the way Vite manages the `public` directory compared to the existing Remix compiler. During the build, Vite copies files from the `public` directory into `build/client`, whereas the Remix compiler left the `public` directory untouched and used a subdirectory (`public/build`) as the client build directory. +There is a notable difference with the way Vite manages the `public` directory compared to the existing Remix compiler. Vite copies files from the `public` directory into the client build directory, whereas the Remix compiler left the `public` directory untouched and used a subdirectory (`public/build`) as the client build directory. -In order to align the default Remix project structure with the way Vite works, the build output paths have been changed. - -- The server is now compiled into `build/server` by default. -- The client is now compiled into `build/client` by default. +In order to align the default Remix project structure with the way Vite works, the build output paths have been changed. There is now a single `buildDirectory` option that defaults to `"build"`, replacing the separate `assetsBuildDirectory` and `serverBuildDirectory` options. This means that, by default, the server is now compiled into `build/server` and the client is now compiled into `build/client`. -This means that the following configuration defaults have been changed: +This also means that the following configuration defaults have been changed: -- [assetsBuildDirectory][assets-build-directory] defaults to `"build/client"` rather than `"public/build"` - [publicPath][public-path] defaults to `"/"` rather than `"/build/"` -- [serverBuildPath][server-build-path] has been split into `serverBuildDirectory` and `serverBuildFile`, with the equivalent default for `serverBuildDirectory` being `"build/server"` rather than `"build"` +- [serverBuildPath][server-build-path] has been replaced by `serverBuildFile` which defaults to `"index.js"`. This file will be written into the server directory within your configured `buildDirectory`. ## Additional features & plugins diff --git a/integration/vite-adapter-test.ts b/integration/vite-adapter-test.ts index 95fc337c2a6..8222d32b6b1 100644 --- a/integration/vite-adapter-test.ts +++ b/integration/vite-adapter-test.ts @@ -20,7 +20,7 @@ test.describe(async () => { return normalizePath(pathname).startsWith(normalizePath(cwd)); } - function pathRelativeToCwd(pathname: string) { + function relativeToCwd(pathname: string) { return normalizePath(path.relative(cwd, pathname)); } @@ -60,7 +60,7 @@ test.describe(async () => { expect( Object.keys( JSON.parse( - fs.readFileSync(path.join(cwd, "build/server/bundles.json"), "utf8") + fs.readFileSync(path.join(cwd, "build/manifest.json"), "utf8") ).serverBundles ) ).toEqual(["user-options--adapter-options"]); @@ -68,24 +68,21 @@ test.describe(async () => { let buildEndArgs: any = JSON.parse( fs.readFileSync(path.join(cwd, "BUILD_END_ARGS.json"), "utf8") ); + let { remixConfig } = buildEndArgs; // Before rewriting to relative paths, assert that paths are absolute within cwd - expect(pathStartsWithCwd(buildEndArgs.serverBuildDirectory)).toBe(true); - expect(pathStartsWithCwd(buildEndArgs.assetsBuildDirectory)).toBe(true); + expect(pathStartsWithCwd(remixConfig.buildDirectory)).toBe(true); // Rewrite path args to be relative and normalized for snapshot test - buildEndArgs.serverBuildDirectory = pathRelativeToCwd( - buildEndArgs.serverBuildDirectory - ); - buildEndArgs.assetsBuildDirectory = pathRelativeToCwd( - buildEndArgs.assetsBuildDirectory - ); + remixConfig.buildDirectory = relativeToCwd(remixConfig.buildDirectory); expect(buildEndArgs).toEqual({ - assetsBuildDirectory: "build/client", - serverBuildDirectory: "build/server", - serverBuildFile: "index.js", - unstable_serverBundlesManifest: { + remixConfig: { + buildDirectory: "build", + serverBuildFile: "index.js", + unstable_ssr: true, + }, + buildManifest: { routeIdToServerBundleId: { "routes/_index": "user-options--adapter-options", }, @@ -109,7 +106,6 @@ test.describe(async () => { }, }, }, - unstable_ssr: true, }); }); }); diff --git a/integration/vite-build-manifest-test.ts b/integration/vite-build-manifest-test.ts new file mode 100644 index 00000000000..a1ef528f8b3 --- /dev/null +++ b/integration/vite-build-manifest-test.ts @@ -0,0 +1,95 @@ +import fs from "node:fs"; +import path from "node:path"; +import { test, expect } from "@playwright/test"; +import getPort from "get-port"; + +import { createProject, viteBuild, VITE_CONFIG } from "./helpers/vite.js"; + +function createRoute(path: string) { + return { + [`app/routes/${path}`]: ` + export default function Route() { + return

Path: ${path}

; + } + `, + }; +} + +const TEST_ROUTES = [ + "_index.tsx", + "parent-route.tsx", + "parent-route.child-route.tsx", +]; + +const files = { + "app/root.tsx": ` + import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react"; + + export default function Root() { + return ( + + + + + + + + + + + + ); + } + `, + ...Object.assign({}, ...TEST_ROUTES.map(createRoute)), +}; + +test.describe(() => { + let cwd: string; + let devPort: number; + + test.beforeAll(async () => { + devPort = await getPort(); + cwd = await createProject({ + "vite.config.ts": await VITE_CONFIG({ + port: devPort, + pluginOptions: "{ manifest: true }", + }), + ...files, + }); + + await viteBuild({ cwd }); + }); + + test("Vite / build manifest", async () => { + expect( + JSON.parse(fs.readFileSync(path.join(cwd, "build/manifest.json"), "utf8")) + ).toEqual({ + routes: { + root: { + file: "root.tsx", + id: "root", + path: "", + }, + "routes/_index": { + file: "routes/_index.tsx", + id: "routes/_index", + index: true, + parentId: "root", + }, + "routes/parent-route": { + file: "routes/parent-route.tsx", + id: "routes/parent-route", + parentId: "root", + path: "parent-route", + }, + "routes/parent-route.child-route": { + file: "routes/parent-route.child-route.tsx", + id: "routes/parent-route.child-route", + parentId: "routes/parent-route", + path: "child-route", + }, + }, + }); + }); +}); diff --git a/integration/vite-server-bundles-test.ts b/integration/vite-server-bundles-test.ts index fd017885461..5373a504aff 100644 --- a/integration/vite-server-bundles-test.ts +++ b/integration/vite-server-bundles-test.ts @@ -28,7 +28,7 @@ function createRoute(path: string) { return { [`app/routes/${path}`]: ` ${ROUTE_FILE_COMMENT} - import { Links, Meta, Outlet, Scripts, LiveReload } from "@remix-run/react"; + import { Outlet } from "@remix-run/react"; import { useState, useEffect } from "react"; export default function Route() { @@ -121,6 +121,7 @@ test.describe(() => { "vite.config.ts": await VITE_CONFIG({ port: devPort, pluginOptions: `{ + manifest: true, unstable_serverBundles: async ({ branch }) => { // Smoke test to ensure we can read the route files via 'route.file' await Promise.all(branch.map(async (route) => { @@ -291,7 +292,7 @@ test.describe(() => { test("Vite / server bundles / build / manifest", async () => { expect( JSON.parse( - fs.readFileSync(path.join(cwd, "build/server/bundles.json"), "utf8") + fs.readFileSync(path.join(cwd, "build/manifest.json"), "utf8") ) ).toEqual({ serverBundles: { diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts index 3eff0bc4783..bbbbcbd41f2 100644 --- a/packages/remix-dev/index.ts +++ b/packages/remix-dev/index.ts @@ -7,7 +7,7 @@ export * as cli from "./cli/index"; export type { Manifest as AssetsManifest } from "./manifest"; export { getDependenciesToBundle } from "./dependencies"; export type { - Unstable_ServerBundlesManifest, + Unstable_BuildManifest, Unstable_VitePluginAdapter, } from "./vite"; export { unstable_vitePlugin } from "./vite"; diff --git a/packages/remix-dev/vite/build.ts b/packages/remix-dev/vite/build.ts index 1b07b7642e3..0b24bc5bc15 100644 --- a/packages/remix-dev/vite/build.ts +++ b/packages/remix-dev/vite/build.ts @@ -6,8 +6,10 @@ import colors from "picocolors"; import { type ResolvedVitePluginConfig, type ServerBundleBuildConfig, - type ServerBundlesManifest, + type BuildManifest, + type ServerBundlesBuildManifest, configRouteToBranchRoute, + getServerBuildDirectory, } from "./plugin"; import type { ConfigRoute, RouteManifest } from "../config/routes"; import invariant from "../invariant"; @@ -96,19 +98,24 @@ type RemixViteServerBuildArgs = { type RemixViteBuildArgs = RemixViteClientBuildArgs | RemixViteServerBuildArgs; -async function getServerBuilds({ - routes, - serverBuildDirectory, - serverBuildFile, - serverBundles, - rootDirectory, - appDirectory, -}: ResolvedVitePluginConfig): Promise<{ +async function getServerBuilds(remixConfig: ResolvedVitePluginConfig): Promise<{ serverBuilds: RemixViteServerBuildArgs[]; - serverBundlesManifest?: ServerBundlesManifest; + buildManifest: BuildManifest; }> { + // eslint-disable-next-line prefer-let/prefer-let -- Improve type narrowing + const { + routes, + serverBuildFile, + serverBundles, + rootDirectory, + appDirectory, + } = remixConfig; + let serverBuildDirectory = getServerBuildDirectory(remixConfig); if (!serverBundles) { - return { serverBuilds: [{ ssr: true }] }; + return { + serverBuilds: [{ ssr: true }], + buildManifest: { routes }, + }; } let { normalizePath } = await import("vite"); @@ -124,7 +131,7 @@ async function getServerBuilds({ }) ); - let serverBundlesManifest: ServerBundlesManifest = { + let buildManifest: ServerBundlesBuildManifest = { serverBundles: {}, routeIdToServerBundleId: {}, routes: rootRelativeRoutes, @@ -149,7 +156,7 @@ async function getServerBuilds({ `The "unstable_serverBundles" function must return a string` ); } - serverBundlesManifest.routeIdToServerBundleId[route.id] = serverBundleId; + buildManifest.routeIdToServerBundleId[route.id] = serverBundleId; let relativeServerBundleDirectory = path.relative( rootDirectory, @@ -157,7 +164,7 @@ async function getServerBuilds({ ); let serverBuildConfig = serverBundleBuildConfigById.get(serverBundleId); if (!serverBuildConfig) { - serverBundlesManifest.serverBundles[serverBundleId] = { + buildManifest.serverBundles[serverBundleId] = { id: serverBundleId, file: normalizePath( path.join(relativeServerBundleDirectory, serverBuildFile) @@ -187,16 +194,20 @@ async function getServerBuilds({ return { serverBuilds, - serverBundlesManifest, + buildManifest, }; } async function cleanServerBuildDirectory( viteConfig: Vite.ResolvedConfig, - { rootDirectory, serverBuildDirectory }: ResolvedVitePluginConfig + remixConfig: ResolvedVitePluginConfig ) { + let serverBuildDirectory = getServerBuildDirectory(remixConfig); let isWithinRoot = () => { - let relativePath = path.relative(rootDirectory, serverBuildDirectory); + let relativePath = path.relative( + remixConfig.rootDirectory, + serverBuildDirectory + ); return !relativePath.startsWith("..") && !path.isAbsolute(relativePath); }; @@ -270,28 +281,26 @@ export async function build( await viteBuild({ ssr: false }); // Then run Vite SSR builds in parallel - let { serverBuilds, serverBundlesManifest } = await getServerBuilds( - remixConfig - ); + let { serverBuilds, buildManifest } = await getServerBuilds(remixConfig); await Promise.all(serverBuilds.map(viteBuild)); - if (serverBundlesManifest) { + if (remixConfig.manifest) { await fse.writeFile( - path.join(remixConfig.serverBuildDirectory, "bundles.json"), - JSON.stringify(serverBundlesManifest, null, 2), + path.join(remixConfig.buildDirectory, "manifest.json"), + JSON.stringify(buildManifest, null, 2), "utf-8" ); } - let { assetsBuildDirectory, serverBuildDirectory, serverBuildFile, ssr } = - remixConfig; + let { buildDirectory, serverBuildFile, ssr } = remixConfig; await remixConfig.adapter?.buildEnd?.({ - assetsBuildDirectory, - serverBuildDirectory, - serverBuildFile, - unstable_serverBundlesManifest: serverBundlesManifest, - unstable_ssr: ssr, + buildManifest, + remixConfig: { + buildDirectory, + serverBuildFile, + unstable_ssr: ssr, + }, }); } diff --git a/packages/remix-dev/vite/index.ts b/packages/remix-dev/vite/index.ts index 7ceba85620b..2cc7075c144 100644 --- a/packages/remix-dev/vite/index.ts +++ b/packages/remix-dev/vite/index.ts @@ -3,7 +3,7 @@ // be imported at the top level. import type { RemixVitePlugin } from "./plugin"; export type { - ServerBundlesManifest as Unstable_ServerBundlesManifest, + BuildManifest as Unstable_BuildManifest, VitePluginAdapter as Unstable_VitePluginAdapter, } from "./plugin"; diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts index 03b214fe53c..6da6b0f1023 100644 --- a/packages/remix-dev/vite/plugin.ts +++ b/packages/remix-dev/vite/plugin.ts @@ -25,7 +25,7 @@ import { type RemixConfig as ResolvedRemixEsbuildConfig, resolveConfig as resolveRemixEsbuildConfig, } from "../config"; -import { type Manifest } from "../manifest"; +import { type Manifest as BrowserManifest } from "../manifest"; import invariant from "../invariant"; import { createRequestHandler } from "./node/adapter"; import { getStylesForUrl, isCssModulesFile } from "./styles"; @@ -37,7 +37,6 @@ import { importViteEsmSync, preloadViteEsm } from "./import-vite-esm-sync"; const supportedRemixEsbuildConfigKeys = [ "appDirectory", - "assetsBuildDirectory", "future", "ignoredRouteFiles", "publicPath", @@ -67,11 +66,6 @@ const CLIENT_ROUTE_QUERY_STRING = "?client-route"; // We need to provide different JSDoc comments in some cases due to differences // between the Remix config and the Vite plugin. type RemixEsbuildUserConfigJsdocOverrides = { - /** - * The path to the browser build, relative to the project root. Defaults to - * `"build/client"`. - */ - assetsBuildDirectory?: SupportedRemixEsbuildUserConfig["assetsBuildDirectory"]; /** * The URL prefix of the browser build with a trailing slash. Defaults to * `"/"`. This is the path the browser will use to find assets. @@ -96,7 +90,16 @@ type ServerBundlesFunction = (args: { branch: BranchRoute[]; }) => string | Promise; -export type ServerBundlesManifest = { +type BaseBuildManifest = { + routes: RouteManifest; +}; + +type DefaultBuildManifest = BaseBuildManifest & { + serverBundles?: never; + routeIdToServerBundleId?: never; +}; + +export type ServerBundlesBuildManifest = BaseBuildManifest & { serverBundles: { [serverBundleId: string]: { id: string; @@ -104,9 +107,10 @@ export type ServerBundlesManifest = { }; }; routeIdToServerBundleId: Record; - routes: RouteManifest; }; +export type BuildManifest = DefaultBuildManifest | ServerBundlesBuildManifest; + const adapterRemixConfigOverrideKeys = [ "unstable_serverBundles", ] as const satisfies ReadonlyArray; @@ -140,11 +144,15 @@ export type VitePluginConfig = RemixEsbuildUserConfigJsdocOverrides & */ adapter?: VitePluginAdapter; /** - * The path to the server build directory, relative to the project. This - * directory should be deployed to your server. Defaults to - * `"build/server"`. + * The path to the build directory, relative to the project. Defaults to + * `"build"`. + */ + buildDirectory?: string; + /** + * Whether to write a `"manifest.json"` file to the build directory. + * Defaults to `false`. */ - serverBuildDirectory?: string; + manifest?: boolean; /** * The file name of the server build output. This file * should end in a `.js` extension and should be deployed to your server. @@ -166,20 +174,18 @@ export type VitePluginConfig = RemixEsbuildUserConfigJsdocOverrides & unstable_ssr?: boolean; }; -type BuildEndArgs = Pick< - ResolvedVitePluginConfig, - "assetsBuildDirectory" | "serverBuildDirectory" | "serverBuildFile" -> & { - unstable_serverBundlesManifest: ServerBundlesManifest | undefined; - unstable_ssr: boolean; -}; -type BuildEndHook = (args: BuildEndArgs) => void | Promise; +type BuildEndHook = (args: { + remixConfig: Pick< + ResolvedVitePluginConfig, + "buildDirectory" | "serverBuildFile" + > & { unstable_ssr: boolean }; + buildManifest: BuildManifest | undefined; +}) => void | Promise; export type ResolvedVitePluginConfig = Pick< ResolvedRemixEsbuildConfig, | "appDirectory" | "rootDirectory" - | "assetsBuildDirectory" | "entryClientFilePath" | "entryServerFilePath" | "future" @@ -188,8 +194,10 @@ export type ResolvedVitePluginConfig = Pick< | "serverModuleFormat" > & { adapter?: Adapter; - serverBuildDirectory: string; + buildDirectory: string; + manifest: boolean; serverBuildFile: string; + serverBundleId?: string; serverBundles?: ServerBundlesFunction; ssr: boolean; }; @@ -265,7 +273,7 @@ const resolveBuildAssetPaths = ( viteManifest: Vite.Manifest, entryFilePath: string, prependedAssetFilePaths: string[] = [] -): Manifest["entry"] & { css: string[] } => { +): BrowserManifest["entry"] & { css: string[] } => { let entryChunk = resolveChunk(pluginConfig, viteManifest, entryFilePath); // This is here to support prepending client entry assets to the root route @@ -402,6 +410,18 @@ const getViteMajorVersion = (): number => { return parseInt(vitePkg.version.split(".")[0]!); }; +export let getServerBuildDirectory = (remixConfig: ResolvedVitePluginConfig) => + path.join( + remixConfig.buildDirectory, + "server", + ...(typeof remixConfig.serverBundleId === "string" + ? [remixConfig.serverBundleId] + : []) + ); + +let getClientBuildDirectory = (remixConfig: ResolvedVitePluginConfig) => + path.join(remixConfig.buildDirectory, "client"); + export type RemixVitePlugin = (config?: VitePluginConfig) => Vite.Plugin[]; export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let viteCommand: Vite.ResolvedConfig["command"]; @@ -413,7 +433,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let cssModulesManifest: Record = {}; let ssrBuildContext: | { isSsrBuild: false } - | { isSsrBuild: true; getManifest: () => Promise }; + | { isSsrBuild: true; getBrowserManifest: () => Promise }; let viteChildCompiler: Vite.ViteDevServer | null = null; @@ -423,10 +443,10 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let resolvePluginConfig = async (): Promise => { let defaults = { - assetsBuildDirectory: "build/client", - serverBuildDirectory: "build/server", - serverBuildFile: "index.js", + buildDirectory: "build", + manifest: false, publicPath: "/", + serverBuildFile: "index.js", unstable_ssr: true, } as const satisfies Partial; @@ -451,13 +471,13 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let rootDirectory = viteUserConfig.root ?? process.env.REMIX_ROOT ?? process.cwd(); - let ssr = resolvedRemixUserConfig.unstable_ssr !== false; + let { manifest, unstable_ssr: ssr } = resolvedRemixUserConfig; + let isSpaMode = !ssr; // Only select the Remix esbuild config options that the Vite plugin uses let { appDirectory, - assetsBuildDirectory, entryClientFilePath, entryServerFilePath, future, @@ -469,9 +489,9 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { { rootDirectory, isSpaMode } ); - let serverBuildDirectory = path.resolve( + let buildDirectory = path.resolve( rootDirectory, - resolvedRemixUserConfig.serverBuildDirectory + resolvedRemixUserConfig.buildDirectory ); let { serverBuildFile, unstable_serverBundles: serverBundles } = @@ -494,26 +514,25 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { // For server bundle builds, override the relevant config. This lets us run // multiple server builds with each one targeting a subset of routes. + let serverBundleId: string | undefined = undefined; if (serverBundleBuildConfig) { routes = serverBundleBuildConfig.routes; - serverBuildDirectory = path.join( - serverBuildDirectory, - serverBundleBuildConfig.serverBundleId - ); + serverBundleId = serverBundleBuildConfig.serverBundleId; } let resolvedRemixConfig: ResolvedVitePluginConfig = { adapter, appDirectory, - assetsBuildDirectory, + buildDirectory, entryClientFilePath, entryServerFilePath, future, + manifest, publicPath, rootDirectory, routes, - serverBuildDirectory, serverBuildFile, + serverBundleId, serverBundles, serverModuleFormat, ssr, @@ -542,7 +561,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { export const assetsBuildDirectory = ${JSON.stringify( path.relative( remixConfig.rootDirectory, - remixConfig.assetsBuildDirectory + getClientBuildDirectory(remixConfig) ) )}; export const future = ${JSON.stringify(remixConfig.future)}; @@ -577,8 +596,10 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { return JSON.parse(manifestContents) as Vite.Manifest; }; - let createBuildManifest = async (): Promise => { - let viteManifest = await loadViteManifest(remixConfig.assetsBuildDirectory); + let createBrowserManifestForBuild = async (): Promise => { + let viteManifest = await loadViteManifest( + getClientBuildDirectory(remixConfig) + ); let entry = resolveBuildAssetPaths( remixConfig, @@ -586,7 +607,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { remixConfig.entryClientFilePath ); - let routes: Manifest["routes"] = {}; + let routes: BrowserManifest["routes"] = {}; let routeManifestExports = await getRouteManifestModuleExports( viteChildCompiler, @@ -627,21 +648,21 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let url = `${remixConfig.publicPath}${manifestPath}`; let nonFingerprintedValues = { url, version }; - let manifest: Manifest = { + let manifest: BrowserManifest = { ...fingerprintedValues, ...nonFingerprintedValues, }; await writeFileSafe( - path.join(remixConfig.assetsBuildDirectory, manifestPath), + path.join(getClientBuildDirectory(remixConfig), manifestPath), `window.__remixManifest=${JSON.stringify(manifest)};` ); return manifest; }; - let getDevManifest = async (): Promise => { - let routes: Manifest["routes"] = {}; + let getBrowserManifestForDev = async (): Promise => { + let routes: BrowserManifest["routes"] = {}; let routeManifestExports = await getRouteManifestModuleExports( viteChildCompiler, @@ -766,7 +787,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { ...(!isSsrBuild ? { manifest: true, - outDir: remixConfig.assetsBuildDirectory, + outDir: getClientBuildDirectory(remixConfig), rollupOptions: { ...viteUserConfig.build?.rollupOptions, preserveEntrySignatures: "exports-only", @@ -791,7 +812,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { ssrEmitAssets: true, copyPublicDir: false, // Assets in the public directory are only used by the client manifest: true, // We need the manifest to detect SSR-only assets - outDir: remixConfig.serverBuildDirectory, + outDir: getServerBuildDirectory(remixConfig), rollupOptions: { ...viteUserConfig.build?.rollupOptions, preserveEntrySignatures: "exports-only", @@ -813,8 +834,13 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { ssrBuildContext = viteConfig.build.ssr && viteCommand === "build" - ? { isSsrBuild: true, getManifest: createBuildManifest } - : { isSsrBuild: false }; + ? { + isSsrBuild: true, + getBrowserManifest: createBrowserManifestForBuild, + } + : { + isSsrBuild: false, + }; // We load the same Vite config file again for the child compiler so // that both parent and child compiler's plugins have independent state. @@ -1008,15 +1034,12 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { invariant(viteConfig); - let { - assetsBuildDirectory, - serverBuildDirectory, - serverBuildFile, - rootDirectory, - } = remixConfig; + let { serverBuildFile, rootDirectory } = remixConfig; + let serverBuildDirectory = getServerBuildDirectory(remixConfig); + let clientBuildDirectory = getClientBuildDirectory(remixConfig); let ssrViteManifest = await loadViteManifest(serverBuildDirectory); - let clientViteManifest = await loadViteManifest(assetsBuildDirectory); + let clientViteManifest = await loadViteManifest(clientBuildDirectory); let clientAssetPaths = new Set( Object.values(clientViteManifest).flatMap( @@ -1040,7 +1063,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { for (let ssrAssetPath of ssrAssetPaths) { let src = path.join(serverBuildDirectory, ssrAssetPath); if (!clientAssetPaths.has(ssrAssetPath)) { - let dest = path.join(assetsBuildDirectory, ssrAssetPath); + let dest = path.join(clientBuildDirectory, ssrAssetPath); await fse.move(src, dest); movedAssetPaths.push(dest); } else { @@ -1078,7 +1101,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { await handleSpaMode( serverBuildDirectory, serverBuildFile, - assetsBuildDirectory, + clientBuildDirectory, viteConfig ); } @@ -1100,20 +1123,21 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { return await getServerEntry(); } case VirtualModule.resolve(serverManifestId): { - let manifest = ssrBuildContext.isSsrBuild - ? await ssrBuildContext.getManifest() - : await getDevManifest(); + let browserManifest = ssrBuildContext.isSsrBuild + ? await ssrBuildContext.getBrowserManifest() + : await getBrowserManifestForDev(); - return `export default ${jsesc(manifest, { es6: true })};`; + return `export default ${jsesc(browserManifest, { es6: true })};`; } case VirtualModule.resolve(browserManifestId): { if (viteCommand === "build") { throw new Error("This module only exists in development"); } - let manifest = await getDevManifest(); + let browserManifest = await getBrowserManifestForDev(); + let browserManifestString = jsesc(browserManifest, { es6: true }); - return `window.__remixManifest=${jsesc(manifest, { es6: true })};`; + return `window.__remixManifest=${browserManifestString};`; } } }, @@ -1388,14 +1412,14 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { async handleHotUpdate({ server, file, modules, read }) { let route = getRoute(remixConfig, file); - type ManifestRoute = Manifest["routes"][string]; + type ManifestRoute = BrowserManifest["routes"][string]; type HmrEventData = { route: ManifestRoute | null }; let hmrEventData: HmrEventData = { route: null }; if (route) { // invalidate manifest on route exports change let serverManifest = (await server.ssrLoadModule(serverManifestId)) - .default as Manifest; + .default as BrowserManifest; let oldRouteMetadata = serverManifest.routes[route.id]; let newRouteMetadata = await getRouteMetadata( @@ -1558,7 +1582,7 @@ async function getRouteMetadata( async function handleSpaMode( serverBuildDirectoryPath: string, serverBuildFile: string, - assetsBuildDirectory: string, + clientBuildDirectory: string, viteConfig: Vite.ResolvedConfig ) { // Create a handler and call it for the `/` path - rendering down to the @@ -1589,11 +1613,11 @@ async function handleSpaMode( } // Write out the index.html file for the SPA - await fse.writeFile(path.join(assetsBuildDirectory, "index.html"), html); + await fse.writeFile(path.join(clientBuildDirectory, "index.html"), html); viteConfig.logger.info( "SPA Mode: index.html has been written to your " + - colors.bold(path.relative(process.cwd(), assetsBuildDirectory)) + + colors.bold(path.relative(process.cwd(), clientBuildDirectory)) + " directory" ); From 6ea11208798c0f4e7c249c27eea7ff342fcd7a9e Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Mon, 22 Jan 2024 16:44:39 +1100 Subject: [PATCH 2/2] remove manifest file assertion from adapter test --- integration/vite-adapter-test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/integration/vite-adapter-test.ts b/integration/vite-adapter-test.ts index 8222d32b6b1..470e4774d2d 100644 --- a/integration/vite-adapter-test.ts +++ b/integration/vite-adapter-test.ts @@ -57,14 +57,6 @@ test.describe(async () => { let { status } = viteBuild({ cwd }); expect(status).toBe(0); - expect( - Object.keys( - JSON.parse( - fs.readFileSync(path.join(cwd, "build/manifest.json"), "utf8") - ).serverBundles - ) - ).toEqual(["user-options--adapter-options"]); - let buildEndArgs: any = JSON.parse( fs.readFileSync(path.join(cwd, "BUILD_END_ARGS.json"), "utf8") );