import * as path from "path" ; import type * as esbuild from "esbuild" ; import type { RemixConfig } from "../config" ; import invariant from "../invariant" ; import { getRouteModuleExportsCached } from "./routes" ; import { getHash } from "./utils/crypto" ; import { createUrl } from "./utils/url" ; type Route = RemixConfig [ "routes" ] [ string ] ; interface AssetsManifest { version : string ; url ? : string ; entry : { module : string ; imports : string [ ] ; } ; routes : { [ routeId ] : { id : string ; parentId ? : string ; path ? : string ; index ? : boolean ; caseSensitive ? : boolean ; module : string ; imports ? : string [ ] ; hasAction : boolean ; hasLoader : boolean ; hasCatchBoundary : boolean ; hasErrorBoundary : boolean ; } ; } ; } export async function createAssetsManifest ( config , metafile ) { function resolveUrl ( outputPath ) { return createUrl ( config . publicPath , path . relative ( config . assetsBuildDirectory , path . resolve ( outputPath ) ) ) ; } function resolveImports ( imports ) { return imports . filter ( im => im . kind === "import-statement" ) . map ( im => resolveUrl ( im . path ) ) ; } let entryClientFile = path . resolve ( config . appDirectory , config . entryClientFile ) ; let routesByFile = Object . keys ( config . routes ) . reduce ( ( map , key ) => { let route = config . routes [ key ] ; map . set ( path . resolve ( config . appDirectory , route . file ) , route ) ; return map ; } , new Map ( ) ) ; let entry ; let routes = { } ; for ( let key of Object . keys ( metafile . outputs ) . sort ( ) ) { let output = metafile . outputs [ key ] ; if ( ! output . entryPoint ) continue ; let entryPointFile = path . resolve ( output . entryPoint . replace ( /(^browser-route-module:|\?browser$)/g , "" ) ) ; if ( entryPointFile === entryClientFile ) { entry = { module : resolveUrl ( key ) , imports : resolveImports ( output . imports ) } ; } else if ( output . entryPoint . startsWith ( "browser-route-module:" ) ) { let route = routesByFile . get ( entryPointFile ) ; invariant ( route , `Cannot get route for entry point ${ output . entryPoint }` ) ; let sourceExports = await getRouteModuleExportsCached ( config , route . id ) ; routes [ route . id ] = { id : route . id , parentId : route . parentId , path : route . path , index : route . index , caseSensitive : route . caseSensitive , module : resolveUrl ( key ) , imports : resolveImports ( output . imports ) , hasAction : sourceExports . includes ( "action" ) , hasLoader : sourceExports . includes ( "loader" ) , hasCatchBoundary : sourceExports . includes ( "CatchBoundary" ) , hasErrorBoundary : sourceExports . includes ( "ErrorBoundary" ) } ; } } invariant ( entry , `Missing output for entry point` ) ; optimizeRoutes ( routes , entry . imports ) ; let version = getHash ( JSON . stringify ( { entry , routes } ) ) . slice ( 0 , 8 ) ; return { version , entry , routes } ; } type ImportsCache = { [ routeId ] : string [ ] ; } ; function optimizeRoutes ( routes , entryImports ) { let importsCache = Object . create ( null ) ; for ( let key in routes ) { optimizeRouteImports ( key , routes , entryImports , importsCache ) ; } } function optimizeRouteImports ( routeId , routes , parentImports , importsCache ) { if ( importsCache [ routeId ] ) return importsCache [ routeId ] ; let route = routes [ routeId ] ; if ( route . parentId ) { parentImports = parentImports . concat ( optimizeRouteImports ( route . parentId , routes , parentImports , importsCache ) ) ; } let routeImports = ( route . imports || [ ] ) . filter ( url => ! parentImports . includes ( url ) ) ; route . imports = routeImports . length > 0 ? routeImports : undefined ; importsCache [ routeId ] = routeImports ; return routeImports ; }