dev
uses node:path
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { sentByReference, useFlight } from "#codebase/tcmts/cli/flight"; import { bold, dim, yellow } from "#codebase/tcmts/cli/style"; import { cacheableComponents, clearLeftovers, httpHandler, isPageRequest, lateCookieHandler, ownId, planApp, printRoutes, renderPage, reportPlan, rpcHandler, sourceMapHandler, startBackground, tcmPlugin, watchCodebase, writeBuildRoot, } from "#codebase/tcmts/cli/web"; import type { ComponentPlan, HttpRoutes, RenderApp } from "#codebase/tcmts/cli/web"; import { shortHash } from "#codebase/tcmts/core/hash"; import fs from "node:fs"; import path from "node:path"; export async function dev( cb: Codebase, appName: string, port: number, routes?: HttpRoutes, ): Promise<{ url: string; close: () => Promise<void> }> { const { createServer } = await import("vite"); // its files are its own: another dev server (of this app or another) writes its own as it goes const web = path.join(cb.lay.tcmDir, "web"); clearLeftovers(web); const root = path.join(web, ownId()); let state = planApp(cb, appName); writeBuildRoot(cb, root, appName, state); let cacheable = cacheableComponents(cb, state.plan.components); let sent = sentByReference(state.plan); // server code loads as React's server environment has it, what server components send by reference as references useFlight(cb, { client: (h) => sent.has(h), server: (h) => state.plan.serverRefs.has(h), packageImports: (c) => [...(state.plan.packageImports.get(c) ?? [])], }); const components: ComponentPlan = { isComponent: (h) => state.plan.components.has(h), isClientRef: (h) => sent.has(h), isCacheable: (h) => cacheable.has(h), }; const rpc = rpcHandler(cb, (h) => state.plan.endpoints.has(h), undefined, undefined, components); const server = await createServer({ configFile: false, root, logLevel: "warn", clearScreen: false, appType: "spa", plugins: [ tcmPlugin(cb, () => state.plan), { name: "tcm-rpc", configureServer: (s) => { s.middlewares.use(rpc); // server components' code, for their stacks in the browser's tools s.middlewares.use(sourceMapHandler(cb)); s.middlewares.use(lateCookieHandler); if (routes) s.middlewares.use(httpHandler(cb, routes, { production: false, secure: false })); // a component app's pages are rendered on the server first, then hydrated s.middlewares.use(async (req, res, next) => { if (!state.react || state.plan.errors.length || !isPageRequest(req)) return next(); try { const template = await s.transformIndexHtml( req.url ?? "/", fs.readFileSync(path.join(root, "index.html"), "utf8"), ); const { render } = (await s.ssrLoadModule(path.join(root, "ssr.ts"))) as { render: RenderApp; }; if ( await renderPage(cb, req, res, template, render, components, { production: false, secure: false, }) ) return; res.setHeader("content-type", "text/html"); res.end(template); } catch (e) { next(e); } }); }, }, ], server: { port, host: "127.0.0.1", // (and where it really is: a codebase reached by a symlink, as a worktree's is, is served from its real path) fs: { allow: [cb.lay.tcmDir, fs.realpathSync(cb.lay.tcmDir)] }, }, }); await server.listen(); const url = server.resolvedUrls?.local[0] ?? `http://127.0.0.1:${port}/`; console.log(`${bold("tcm dev")} ${appName} ${dim(shortHash(state.hash))}\n url ${url}`); reportPlan(cb, appName, state.plan); if (routes) printRoutes(cb, routes, url); const background = await startBackground(cb, appName); const stop = watchCodebase(cb, async () => { const before = state.hash; state = planApp(cb, appName); cacheable = cacheableComponents(cb, state.plan.components); sent = sentByReference(state.plan); writeBuildRoot(cb, root, appName, state); server.moduleGraph.invalidateAll(); server.ws.send({ type: "full-reload" }); console.log( `\n${yellow("reloaded")} ${appName} ${dim(before === state.hash ? "(names changed)" : `${shortHash(before)} → ${shortHash(state.hash)}`)}`, ); reportPlan(cb, appName, state.plan); await background.refresh(); }); return { url, close: async () => { stop(); background.stop(); await server.close(); fs.rmSync(root, { recursive: true, force: true }); }, }; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.flight.sentByReference
- tcmts.cli.flight.useFlight
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.yellow
- tcmts.cli.web.ComponentPlan
- tcmts.cli.web.HttpRoutes
- tcmts.cli.web.RenderApp
- tcmts.cli.web.cacheableComponents
- tcmts.cli.web.clearLeftovers
- tcmts.cli.web.httpHandler
- tcmts.cli.web.isPageRequest
- tcmts.cli.web.lateCookieHandler
- tcmts.cli.web.ownId
- tcmts.cli.web.planApp
- tcmts.cli.web.printRoutes
- tcmts.cli.web.renderPage
- tcmts.cli.web.reportPlan
- tcmts.cli.web.rpcHandler
- tcmts.cli.web.sourceMapHandler
- tcmts.cli.web.startBackground
- tcmts.cli.web.tcmPlugin
- tcmts.cli.web.watchCodebase
- tcmts.cli.web.writeBuildRoot
- tcmts.core.hash.shortHash