From b8e3d6d5da57ac04565a0291e744f14945b16b74 Mon Sep 17 00:00:00 2001 From: Mark Dalgleish Date: Thu, 25 Jan 2024 10:14:39 +1100 Subject: [PATCH] refactor(remix-dev/vite): clean resolved Remix config (#8586) --- packages/remix-dev/vite/build.ts | 59 ++++--- packages/remix-dev/vite/plugin.ts | 249 +++++++++++++++--------------- packages/remix-dev/vite/styles.ts | 62 +++++--- 3 files changed, 185 insertions(+), 185 deletions(-) diff --git a/packages/remix-dev/vite/build.ts b/packages/remix-dev/vite/build.ts index 9368e9815c5..c5f58cc81b3 100644 --- a/packages/remix-dev/vite/build.ts +++ b/packages/remix-dev/vite/build.ts @@ -4,12 +4,12 @@ import fse from "fs-extra"; import colors from "picocolors"; import { - type ResolvedVitePluginConfig, - type ServerBundleBuildConfig, + type RemixPluginContext, type BuildManifest, + type ServerBundleBuildConfig, type ServerBundlesBuildManifest, configRouteToBranchRoute, - getServerBuildRootDirectory, + getServerBuildDirectory, } from "./plugin"; import type { ConfigRoute, RouteManifest } from "../config/routes"; import invariant from "../invariant"; @@ -38,16 +38,17 @@ async function resolveViteConfig({ return viteConfig; } -async function extractRemixConfig(viteConfig: Vite.ResolvedConfig) { - let remixConfig = viteConfig[ - "__remixPluginResolvedConfig" as keyof typeof viteConfig - ] as ResolvedVitePluginConfig | undefined; - if (!remixConfig) { +async function extractRemixPluginContext(viteConfig: Vite.ResolvedConfig) { + let ctx = viteConfig["__remixPluginContext" as keyof typeof viteConfig] as + | RemixPluginContext + | undefined; + + if (!ctx) { console.error(colors.red("Remix Vite plugin not found in Vite config")); process.exit(1); } - return remixConfig; + return ctx; } function getAddressableRoutes(routes: RouteManifest): ConfigRoute[] { @@ -102,19 +103,15 @@ type RemixViteServerBuildArgs = { type RemixViteBuildArgs = RemixViteClientBuildArgs | RemixViteServerBuildArgs; -async function getServerBuilds(remixConfig: ResolvedVitePluginConfig): Promise<{ +async function getServerBuilds(ctx: RemixPluginContext): Promise<{ serverBuilds: RemixViteServerBuildArgs[]; buildManifest: BuildManifest; }> { + let { rootDirectory } = ctx; // eslint-disable-next-line prefer-let/prefer-let -- Improve type narrowing - const { - routes, - serverBuildFile, - serverBundles, - rootDirectory, - appDirectory, - } = remixConfig; - let serverBuildRootDirectory = getServerBuildRootDirectory(remixConfig); + const { routes, serverBuildFile, serverBundles, appDirectory } = + ctx.remixConfig; + let serverBuildDirectory = getServerBuildDirectory(ctx); if (!serverBundles) { return { serverBuilds: [{ ssr: true }], @@ -164,7 +161,7 @@ async function getServerBuilds(remixConfig: ResolvedVitePluginConfig): Promise<{ let relativeServerBundleDirectory = path.relative( rootDirectory, - path.join(serverBuildRootDirectory, serverBundleId) + path.join(serverBuildDirectory, serverBundleId) ); let serverBuildConfig = serverBundleBuildConfigById.get(serverBundleId); if (!serverBuildConfig) { @@ -202,21 +199,18 @@ async function getServerBuilds(remixConfig: ResolvedVitePluginConfig): Promise<{ }; } -async function cleanServerBuildRootDirectory( +async function cleanServerBuildDirectory( viteConfig: Vite.ResolvedConfig, - remixConfig: ResolvedVitePluginConfig + ctx: RemixPluginContext ) { - let serverBuildRootDirectory = getServerBuildRootDirectory(remixConfig); + let serverBuildDirectory = getServerBuildDirectory(ctx); let isWithinRoot = () => { - let relativePath = path.relative( - remixConfig.rootDirectory, - serverBuildRootDirectory - ); + let relativePath = path.relative(ctx.rootDirectory, serverBuildDirectory); return !relativePath.startsWith("..") && !path.isAbsolute(relativePath); }; if (viteConfig.build.emptyOutDir ?? isWithinRoot()) { - await fse.remove(serverBuildRootDirectory); + await fse.remove(serverBuildDirectory); } } @@ -250,7 +244,8 @@ export async function build( await preloadViteEsm(); let viteConfig = await resolveViteConfig({ configFile, mode, root }); - let remixConfig = await extractRemixConfig(viteConfig); + let ctx = await extractRemixPluginContext(viteConfig); + let { remixConfig } = ctx; let vite = await import("vite"); @@ -276,19 +271,19 @@ export async function build( // output directories, we need to clean the root server build directory // ourselves rather than relying on Vite to do it, otherwise you can end up // with stale server bundle directories in your build output - await cleanServerBuildRootDirectory(viteConfig, remixConfig); + await cleanServerBuildDirectory(viteConfig, ctx); // Run the Vite client build first await viteBuild({ ssr: false }); // Then run Vite SSR builds in parallel - let { serverBuilds, buildManifest } = await getServerBuilds(remixConfig); + let { serverBuilds, buildManifest } = await getServerBuilds(ctx); await Promise.all(serverBuilds.map(viteBuild)); - if (remixConfig.manifest) { + if (ctx.remixConfig.manifest) { await fse.writeFile( - path.join(remixConfig.buildDirectory, "manifest.json"), + path.join(ctx.remixConfig.buildDirectory, "manifest.json"), JSON.stringify(buildManifest, null, 2), "utf-8" ); diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts index 2fe611d2ef1..412968269ec 100644 --- a/packages/remix-dev/vite/plugin.ts +++ b/packages/remix-dev/vite/plugin.ts @@ -184,14 +184,7 @@ type BuildEndHook = (args: { export type ResolvedVitePluginConfig = Pick< ResolvedRemixEsbuildConfig, - | "appDirectory" - | "rootDirectory" - | "entryClientFilePath" - | "entryServerFilePath" - | "future" - | "publicPath" - | "routes" - | "serverModuleFormat" + "appDirectory" | "future" | "publicPath" | "routes" | "serverModuleFormat" > & { adapter?: Adapter; buildDirectory: string; @@ -206,7 +199,7 @@ export type ServerBundleBuildConfig = { serverBundleId: string; }; -type BuildContext = +type RemixPluginServerContext = | { isSsrBuild: false; getBrowserManifest?: never; @@ -218,6 +211,13 @@ type BuildContext = serverBundleId: string | undefined; }; +export type RemixPluginContext = RemixPluginServerContext & { + rootDirectory: string; + entryClientFilePath: string; + entryServerFilePath: string; + remixConfig: ResolvedVitePluginConfig; +}; + let serverBuildId = VirtualModule.id("server-build"); let serverManifestId = VirtualModule.id("server-manifest"); let browserManifestId = VirtualModule.id("browser-manifest"); @@ -227,11 +227,11 @@ let injectHmrRuntimeId = VirtualModule.id("inject-hmr-runtime"); const resolveRelativeRouteFilePath = ( route: ConfigRoute, - pluginConfig: ResolvedVitePluginConfig + remixConfig: ResolvedVitePluginConfig ) => { let vite = importViteEsmSync(); let file = route.file; - let fullPath = path.resolve(pluginConfig.appDirectory, file); + let fullPath = path.resolve(remixConfig.appDirectory, file); return vite.normalizePath(fullPath); }; @@ -255,13 +255,13 @@ const getHash = (source: BinaryLike, maxLength?: number): string => { }; const resolveChunk = ( - pluginConfig: ResolvedVitePluginConfig, + ctx: RemixPluginContext, viteManifest: Vite.Manifest, absoluteFilePath: string ) => { let vite = importViteEsmSync(); let rootRelativeFilePath = vite.normalizePath( - path.relative(pluginConfig.rootDirectory, absoluteFilePath) + path.relative(ctx.rootDirectory, absoluteFilePath) ); let entryChunk = viteManifest[rootRelativeFilePath + CLIENT_ROUTE_QUERY_STRING] ?? @@ -280,16 +280,16 @@ const resolveChunk = ( }; const resolveBuildAssetPaths = ( - pluginConfig: ResolvedVitePluginConfig, + ctx: RemixPluginContext, viteManifest: Vite.Manifest, entryFilePath: string, prependedAssetFilePaths: string[] = [] ): BrowserManifest["entry"] & { css: string[] } => { - let entryChunk = resolveChunk(pluginConfig, viteManifest, entryFilePath); + let entryChunk = resolveChunk(ctx, viteManifest, entryFilePath); // This is here to support prepending client entry assets to the root route let prependedAssetChunks = prependedAssetFilePaths.map((filePath) => - resolveChunk(pluginConfig, viteManifest, filePath) + resolveChunk(ctx, viteManifest, filePath) ); let chunks = resolveDependantChunks(viteManifest, [ @@ -298,14 +298,14 @@ const resolveBuildAssetPaths = ( ]); return { - module: `${pluginConfig.publicPath}${entryChunk.file}${CLIENT_ROUTE_QUERY_STRING}`, + module: `${ctx.remixConfig.publicPath}${entryChunk.file}${CLIENT_ROUTE_QUERY_STRING}`, imports: dedupe(chunks.flatMap((e) => e.imports ?? [])).map((imported) => { - return `${pluginConfig.publicPath}${viteManifest[imported].file}`; + return `${ctx.remixConfig.publicPath}${viteManifest[imported].file}`; }) ?? [], css: dedupe(chunks.flatMap((e) => e.css ?? [])).map((href) => { - return `${pluginConfig.publicPath}${href}`; + return `${ctx.remixConfig.publicPath}${href}`; }) ?? [], }; }; @@ -348,13 +348,13 @@ const writeFileSafe = async (file: string, contents: string): Promise => { const getRouteManifestModuleExports = async ( viteChildCompiler: Vite.ViteDevServer | null, - pluginConfig: ResolvedVitePluginConfig + ctx: RemixPluginContext ): Promise> => { let entries = await Promise.all( - Object.entries(pluginConfig.routes).map(async ([key, route]) => { + Object.entries(ctx.remixConfig.routes).map(async ([key, route]) => { let sourceExports = await getRouteModuleExports( viteChildCompiler, - pluginConfig, + ctx, route.file ); return [key, sourceExports] as const; @@ -365,7 +365,7 @@ const getRouteManifestModuleExports = async ( const getRouteModuleExports = async ( viteChildCompiler: Vite.ViteDevServer | null, - pluginConfig: ResolvedVitePluginConfig, + ctx: RemixPluginContext, routeFile: string, readRouteFile?: () => string | Promise ): Promise => { @@ -380,8 +380,9 @@ const getRouteModuleExports = async ( let ssr = true; let { pluginContainer, moduleGraph } = viteChildCompiler; - let routePath = path.resolve(pluginConfig.appDirectory, routeFile); - let url = resolveFileUrl(pluginConfig, routePath); + + let routePath = path.resolve(ctx.remixConfig.appDirectory, routeFile); + let url = resolveFileUrl(ctx, routePath); let resolveId = async () => { let result = await pluginContainer.resolveId(url, undefined, { ssr }); @@ -416,20 +417,13 @@ const getServerBundleBuildConfig = ( return viteUserConfig.__remixServerBundleBuildConfig as ServerBundleBuildConfig; }; -let getServerBuildDirectory = ( - remixConfig: ResolvedVitePluginConfig, - { serverBundleId }: Pick -) => +export let getServerBuildDirectory = (ctx: RemixPluginContext) => path.join( - remixConfig.buildDirectory, + ctx.remixConfig.buildDirectory, "server", - ...(typeof serverBundleId === "string" ? [serverBundleId] : []) + ...(typeof ctx.serverBundleId === "string" ? [ctx.serverBundleId] : []) ); -export let getServerBuildRootDirectory = ( - remixConfig: ResolvedVitePluginConfig -) => getServerBuildDirectory(remixConfig, { serverBundleId: undefined }); - let getClientBuildDirectory = (remixConfig: ResolvedVitePluginConfig) => path.join(remixConfig.buildDirectory, "client"); @@ -437,18 +431,19 @@ export type RemixVitePlugin = (config?: VitePluginConfig) => Vite.Plugin[]; export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let viteCommand: Vite.ResolvedConfig["command"]; let viteUserConfig: Vite.UserConfig; + let viteConfigEnv: Vite.ConfigEnv; let viteConfig: Vite.ResolvedConfig | undefined; - let cssModulesManifest: Record = {}; - let buildContext: BuildContext; - let viteChildCompiler: Vite.ViteDevServer | null = null; - // This is initialized during `config` hook, so most of the code can assume this is defined without null check. - // During dev, this is updated on config file change or route file addition/removal. - let remixConfig: ResolvedVitePluginConfig; + // This is initialized by `updateRemixPluginContext` during Vite's `config` + // hook, so most of the code can assume this defined without null check. + // During dev, `updateRemixPluginContext` is called again on every config file + // change or route file addition/removal. + let ctx: RemixPluginContext; - let resolvePluginConfig = async (): Promise => { + /** Mutates `ctx` as a side-effect */ + let updateRemixPluginContext = async (): Promise => { let defaults = { buildDirectory: "build", manifest: false, @@ -525,16 +520,13 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { routes = serverBundleBuildConfig.routes; } - let resolvedRemixConfig: ResolvedVitePluginConfig = { + let remixConfig: ResolvedVitePluginConfig = { adapter, appDirectory, buildDirectory, - entryClientFilePath, - entryServerFilePath, future, manifest, publicPath, - rootDirectory, routes, serverBuildFile, serverBundles, @@ -542,11 +534,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { ssr, }; - return resolvedRemixConfig; - }; - - let resolveBuildContext = (viteConfigEnv: Vite.ConfigEnv): BuildContext => { - let buildContext: BuildContext = + let serverContext: RemixPluginServerContext = viteConfigEnv.isSsrBuild && viteCommand === "build" ? { isSsrBuild: true, @@ -558,21 +546,27 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { isSsrBuild: false, }; - return buildContext; + ctx = { + remixConfig, + rootDirectory, + entryClientFilePath, + entryServerFilePath, + ...serverContext, + }; }; let getServerEntry = async () => { return ` import * as entryServer from ${JSON.stringify( - resolveFileUrl(remixConfig, remixConfig.entryServerFilePath) + resolveFileUrl(ctx, ctx.entryServerFilePath) )}; - ${Object.keys(remixConfig.routes) + ${Object.keys(ctx.remixConfig.routes) .map((key, index) => { - let route = remixConfig.routes[key]!; + let route = ctx.remixConfig.routes[key]!; return `import * as route${index} from ${JSON.stringify( resolveFileUrl( - remixConfig, - resolveRelativeRouteFilePath(route, remixConfig) + ctx, + resolveRelativeRouteFilePath(route, ctx.remixConfig) ) )};`; }) @@ -580,18 +574,18 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { export { default as assets } from ${JSON.stringify(serverManifestId)}; export const assetsBuildDirectory = ${JSON.stringify( path.relative( - remixConfig.rootDirectory, - getClientBuildDirectory(remixConfig) + ctx.rootDirectory, + getClientBuildDirectory(ctx.remixConfig) ) )}; - export const future = ${JSON.stringify(remixConfig.future)}; - export const isSpaMode = ${!remixConfig.ssr}; - export const publicPath = ${JSON.stringify(remixConfig.publicPath)}; + export const future = ${JSON.stringify(ctx.remixConfig.future)}; + export const isSpaMode = ${!ctx.remixConfig.ssr}; + export const publicPath = ${JSON.stringify(ctx.remixConfig.publicPath)}; export const entry = { module: entryServer }; export const routes = { - ${Object.keys(remixConfig.routes) + ${Object.keys(ctx.remixConfig.routes) .map((key, index) => { - let route = remixConfig.routes[key]!; + let route = ctx.remixConfig.routes[key]!; return `${JSON.stringify(key)}: { id: ${JSON.stringify(route.id)}, parentId: ${JSON.stringify(route.parentId)}, @@ -615,24 +609,24 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let createBrowserManifestForBuild = async (): Promise => { let viteManifest = await loadViteManifest( - getClientBuildDirectory(remixConfig) + getClientBuildDirectory(ctx.remixConfig) ); let entry = resolveBuildAssetPaths( - remixConfig, + ctx, viteManifest, - remixConfig.entryClientFilePath + ctx.entryClientFilePath ); let routes: BrowserManifest["routes"] = {}; let routeManifestExports = await getRouteManifestModuleExports( viteChildCompiler, - remixConfig + ctx ); - for (let [key, route] of Object.entries(remixConfig.routes)) { - let routeFilePath = path.join(remixConfig.appDirectory, route.file); + for (let [key, route] of Object.entries(ctx.remixConfig.routes)) { + let routeFilePath = path.join(ctx.remixConfig.appDirectory, route.file); let sourceExports = routeManifestExports[key]; let isRootRoute = route.parentId === undefined; @@ -648,13 +642,13 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { hasClientLoader: sourceExports.includes("clientLoader"), hasErrorBoundary: sourceExports.includes("ErrorBoundary"), ...resolveBuildAssetPaths( - remixConfig, + ctx, viteManifest, routeFilePath, // If this is the root route, we also need to include assets from the // client entry file as this is a common way for consumers to import // global reset styles, etc. - isRootRoute ? [remixConfig.entryClientFilePath] : [] + isRootRoute ? [ctx.entryClientFilePath] : [] ), }; } @@ -662,7 +656,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let fingerprintedValues = { entry, routes }; let version = getHash(JSON.stringify(fingerprintedValues), 8); let manifestPath = `assets/manifest-${version}.js`; - let url = `${remixConfig.publicPath}${manifestPath}`; + let url = `${ctx.remixConfig.publicPath}${manifestPath}`; let nonFingerprintedValues = { url, version }; let manifest: BrowserManifest = { @@ -671,7 +665,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { }; await writeFileSafe( - path.join(getClientBuildDirectory(remixConfig), manifestPath), + path.join(getClientBuildDirectory(ctx.remixConfig), manifestPath), `window.__remixManifest=${JSON.stringify(manifest)};` ); @@ -683,10 +677,10 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let routeManifestExports = await getRouteManifestModuleExports( viteChildCompiler, - remixConfig + ctx ); - for (let [key, route] of Object.entries(remixConfig.routes)) { + for (let [key, route] of Object.entries(ctx.remixConfig.routes)) { let sourceExports = routeManifestExports[key]; routes[key] = { id: route.id, @@ -695,8 +689,8 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { index: route.index, caseSensitive: route.caseSensitive, module: `${resolveFileUrl( - remixConfig, - resolveRelativeRouteFilePath(route, remixConfig) + ctx, + resolveRelativeRouteFilePath(route, ctx.remixConfig) )}${CLIENT_ROUTE_QUERY_STRING}`, hasAction: sourceExports.includes("action"), hasLoader: sourceExports.includes("loader"), @@ -714,7 +708,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { runtime: VirtualModule.url(injectHmrRuntimeId), }, entry: { - module: resolveFileUrl(remixConfig, remixConfig.entryClientFilePath), + module: resolveFileUrl(ctx, ctx.entryClientFilePath), imports: [], }, routes, @@ -724,7 +718,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { return [ { name: "remix", - config: async (_viteUserConfig, viteConfigEnv) => { + config: async (_viteUserConfig, _viteConfigEnv) => { // Preload Vite's ESM build up-front as soon as we're in an async context await preloadViteEsm(); @@ -732,16 +726,16 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let vite = importViteEsmSync(); viteUserConfig = _viteUserConfig; + viteConfigEnv = _viteConfigEnv; viteCommand = viteConfigEnv.command; - remixConfig = await resolvePluginConfig(); - buildContext = resolveBuildContext(viteConfigEnv); + await updateRemixPluginContext(); Object.assign( process.env, vite.loadEnv( viteConfigEnv.mode, - remixConfig.rootDirectory, + ctx.rootDirectory, // We override default prefix of "VITE_" with a blank string since // we're targeting the server, so we want to load all environment // variables, not just those explicitly marked for the client @@ -750,7 +744,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { ); return { - __remixPluginResolvedConfig: remixConfig, + __remixPluginContext: ctx, appType: "custom", experimental: { hmrPartialAccept: true }, optimizeDeps: { @@ -793,22 +787,22 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { ], }, ...(viteCommand === "build" && { - base: remixConfig.publicPath, + base: ctx.remixConfig.publicPath, build: { ...viteUserConfig.build, ...(!viteConfigEnv.isSsrBuild ? { manifest: true, - outDir: getClientBuildDirectory(remixConfig), + outDir: getClientBuildDirectory(ctx.remixConfig), rollupOptions: { ...viteUserConfig.build?.rollupOptions, preserveEntrySignatures: "exports-only", input: [ - remixConfig.entryClientFilePath, - ...Object.values(remixConfig.routes).map( + ctx.entryClientFilePath, + ...Object.values(ctx.remixConfig.routes).map( (route) => `${path.resolve( - remixConfig.appDirectory, + ctx.remixConfig.appDirectory, route.file )}${CLIENT_ROUTE_QUERY_STRING}` ), @@ -824,14 +818,14 @@ 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: getServerBuildDirectory(remixConfig, buildContext), + outDir: getServerBuildDirectory(ctx), rollupOptions: { ...viteUserConfig.build?.rollupOptions, preserveEntrySignatures: "exports-only", input: serverBuildId, output: { - entryFileNames: remixConfig.serverBuildFile, - format: remixConfig.serverModuleFormat, + entryFileNames: ctx.remixConfig.serverBuildFile, + format: ctx.remixConfig.serverModuleFormat, }, }, }), @@ -864,7 +858,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { { command: viteConfig.command, mode: viteConfig.mode, - isSsrBuild: buildContext.isSsrBuild, + isSsrBuild: ctx.isSsrBuild, }, viteConfig.configFile ); @@ -915,7 +909,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let routeModuleId = id.replace(CLIENT_ROUTE_QUERY_STRING, ""); let sourceExports = await getRouteModuleExports( viteChildCompiler, - remixConfig, + ctx, routeModuleId ); @@ -960,13 +954,15 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { // Give the request handler access to the critical CSS in dev to avoid a // flash of unstyled content since Vite injects CSS file contents via JS getCriticalCss: async (build, url) => { - return getStylesForUrl( + return getStylesForUrl({ + rootDirectory: ctx.rootDirectory, + entryClientFilePath: ctx.entryClientFilePath, + remixConfig: ctx.remixConfig, viteDevServer, - remixConfig, cssModulesManifest, build, - url - ); + url, + }); }, // If an error is caught within the request handler, let Vite fix the // stack trace so it maps back to the actual source code @@ -984,7 +980,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let appFileAddedOrRemoved = (eventName === "add" || eventName === "unlink") && normalizePath(filepath).startsWith( - normalizePath(remixConfig.appDirectory) + normalizePath(ctx.remixConfig.appDirectory) ); invariant(viteConfig?.configFile); @@ -993,10 +989,11 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { normalizePath(filepath) === normalizePath(viteConfig.configFile); if (appFileAddedOrRemoved || viteConfigChanged) { - let lastPluginConfig = remixConfig; - remixConfig = await resolvePluginConfig(); + let lastRemixConfig = ctx.remixConfig; - if (!isEqualJson(lastPluginConfig, remixConfig)) { + await updateRemixPluginContext(); + + if (!isEqualJson(lastRemixConfig, ctx.remixConfig)) { invalidateVirtualModules(viteDevServer); } } @@ -1028,18 +1025,14 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { // After the SSR build is finished, we inspect the Vite manifest for // the SSR build and move server-only assets to client assets directory async handler() { - if (!buildContext.isSsrBuild) { + if (!ctx.isSsrBuild) { return; } invariant(viteConfig); - let { serverBuildFile, rootDirectory } = remixConfig; - let clientBuildDirectory = getClientBuildDirectory(remixConfig); - let serverBuildDirectory = getServerBuildDirectory( - remixConfig, - buildContext - ); + let clientBuildDirectory = getClientBuildDirectory(ctx.remixConfig); + let serverBuildDirectory = getServerBuildDirectory(ctx); let ssrViteManifest = await loadViteManifest(serverBuildDirectory); let clientViteManifest = await loadViteManifest(clientBuildDirectory); @@ -1093,17 +1086,17 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { movedAssetPaths.length > 1 ? "s" : "" } moved from Remix server build to client assets.`, ...movedAssetPaths.map((movedAssetPath) => - colors.dim(path.relative(rootDirectory, movedAssetPath)) + colors.dim(path.relative(ctx.rootDirectory, movedAssetPath)) ), "", ].join("\n") ); } - if (!remixConfig.ssr) { + if (!ctx.remixConfig.ssr) { await handleSpaMode( serverBuildDirectory, - serverBuildFile, + ctx.remixConfig.serverBuildFile, clientBuildDirectory, viteConfig ); @@ -1126,8 +1119,8 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { return await getServerEntry(); } case VirtualModule.resolve(serverManifestId): { - let browserManifest = buildContext.isSsrBuild - ? await buildContext.getBrowserManifest() + let browserManifest = ctx.isSsrBuild + ? await ctx.getBrowserManifest() : await getBrowserManifestForDev(); return `export default ${jsesc(browserManifest, { es6: true })};`; @@ -1173,9 +1166,9 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let vite = importViteEsmSync(); let importerShort = vite.normalizePath( - path.relative(remixConfig.rootDirectory, importer) + path.relative(ctx.rootDirectory, importer) ); - let isRoute = getRoute(remixConfig, importer); + let isRoute = getRoute(ctx.remixConfig, importer); if (isRoute) { let serverOnlyExports = SERVER_ONLY_ROUTE_EXPORTS.map( @@ -1238,10 +1231,10 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { async transform(code, id, options) { if (options?.ssr) return; - let route = getRoute(remixConfig, id); + let route = getRoute(ctx.remixConfig, id); if (!route) return; - if (!remixConfig.ssr) { + if (!ctx.remixConfig.ssr) { let serverOnlyExports = esModuleLexer(code)[1] .map((exp) => exp.n) .filter((exp) => SERVER_ONLY_ROUTE_EXPORTS.includes(exp)); @@ -1387,7 +1380,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { if (!useFastRefresh) return; if (id.endsWith(CLIENT_ROUTE_QUERY_STRING)) { - return { code: addRefreshWrapper(remixConfig, code, id) }; + return { code: addRefreshWrapper(ctx.remixConfig, code, id) }; } let result = await babel.transformAsync(code, { @@ -1405,7 +1398,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { code = result.code!; let refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/; if (refreshContentRE.test(code)) { - code = addRefreshWrapper(remixConfig, code, id); + code = addRefreshWrapper(ctx.remixConfig, code, id); } return { code, map: result.map }; }, @@ -1413,7 +1406,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { { name: "remix-hmr-updates", async handleHotUpdate({ server, file, modules, read }) { - let route = getRoute(remixConfig, file); + let route = getRoute(ctx.remixConfig, file); type ManifestRoute = BrowserManifest["routes"][string]; type HmrEventData = { route: ManifestRoute | null }; @@ -1426,7 +1419,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { let oldRouteMetadata = serverManifest.routes[route.id]; let newRouteMetadata = await getRouteMetadata( - remixConfig, + ctx, viteChildCompiler, route, read @@ -1545,14 +1538,14 @@ function getRoute( } async function getRouteMetadata( - pluginConfig: ResolvedVitePluginConfig, + ctx: RemixPluginContext, viteChildCompiler: Vite.ViteDevServer | null, route: ConfigRoute, readRouteFile?: () => string | Promise ) { let sourceExports = await getRouteModuleExports( viteChildCompiler, - pluginConfig, + ctx, route.file, readRouteFile ); @@ -1566,12 +1559,12 @@ async function getRouteMetadata( url: "/" + path.relative( - pluginConfig.rootDirectory, - resolveRelativeRouteFilePath(route, pluginConfig) + ctx.rootDirectory, + resolveRelativeRouteFilePath(route, ctx.remixConfig) ), module: `${resolveFileUrl( - pluginConfig, - resolveRelativeRouteFilePath(route, pluginConfig) + ctx, + resolveRelativeRouteFilePath(route, ctx.remixConfig) )}?import`, // Ensure the Vite dev server responds with a JS module hasAction: sourceExports.includes("action"), hasClientAction: sourceExports.includes("clientAction"), diff --git a/packages/remix-dev/vite/styles.ts b/packages/remix-dev/vite/styles.ts index dfe8406a1f7..6a9355123ec 100644 --- a/packages/remix-dev/vite/styles.ts +++ b/packages/remix-dev/vite/styles.ts @@ -21,12 +21,17 @@ const cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`); const isCssFile = (file: string) => cssFileRegExp.test(file); export const isCssModulesFile = (file: string) => cssModulesRegExp.test(file); -const getStylesForFiles = async ( - viteDevServer: ViteDevServer, - config: { rootDirectory: string }, - cssModulesManifest: Record, - files: string[] -): Promise => { +const getStylesForFiles = async ({ + viteDevServer, + rootDirectory, + cssModulesManifest, + files, +}: { + viteDevServer: ViteDevServer; + rootDirectory: string; + cssModulesManifest: Record; + files: string[]; +}): Promise => { let styles: Record = {}; let deps = new Set(); @@ -41,7 +46,7 @@ const getStylesForFiles = async ( if (!node) { try { await viteDevServer.transformRequest( - resolveFileUrl(config, normalizedPath) + resolveFileUrl({ rootDirectory }, normalizedPath) ); } catch (err) { console.error(err); @@ -166,38 +171,45 @@ const createRoutes = ( })); }; -export const getStylesForUrl = async ( - viteDevServer: ViteDevServer, - config: Pick< - ResolvedRemixConfig, - "appDirectory" | "routes" | "rootDirectory" | "entryClientFilePath" - >, - cssModulesManifest: Record, - build: ServerBuild, - url: string | undefined -): Promise => { +export const getStylesForUrl = async ({ + viteDevServer, + rootDirectory, + remixConfig, + entryClientFilePath, + cssModulesManifest, + build, + url, +}: { + viteDevServer: ViteDevServer; + rootDirectory: string; + remixConfig: Pick; + entryClientFilePath: string; + cssModulesManifest: Record; + build: ServerBuild; + url: string | undefined; +}): Promise => { if (url === undefined || url.includes("?_data=")) { return undefined; } let routes = createRoutes(build.routes); - let appPath = path.relative(process.cwd(), config.appDirectory); + let appPath = path.relative(process.cwd(), remixConfig.appDirectory); let documentRouteFiles = matchRoutes(routes, url)?.map((match) => - path.join(appPath, config.routes[match.route.id].file) + path.join(appPath, remixConfig.routes[match.route.id].file) ) ?? []; - let styles = await getStylesForFiles( + let styles = await getStylesForFiles({ viteDevServer, - config, + rootDirectory, cssModulesManifest, - [ + files: [ // Always include the client entry file when crawling the module graph for CSS - path.relative(config.rootDirectory, config.entryClientFilePath), + path.relative(rootDirectory, entryClientFilePath), // Then include any styles from the matched routes ...documentRouteFiles, - ] - ); + ], + }); return styles; };