polen
Version:
A framework for delightful GraphQL developer portals
123 lines • 5.27 kB
JavaScript
import { reportError } from '#api/server/report-error';
import { createHtmlTransformer } from '#lib/html-utils/html-transformer';
import { ResponseInternalServerError } from '#lib/kit-temp';
import { debugPolen } from '#singletons/debug';
import * as HonoNodeServer from '@hono/node-server';
import { Err } from '@wollybeard/kit';
export const Serve = (config) => {
const debug = debugPolen.sub(`serve`);
const appModulePath = config.paths.framework.template.absolute.server.app;
let appPromise;
const isNeedAppLoadOrReload = (server) => {
const appModule = server.moduleGraph.getModuleById(appModulePath);
if (!appModule)
return true; // Not loaded yet
// Check if the module or any of its dependencies are invalidated
const checkInvalidated = (mod, visited = new Set()) => {
if (!mod.id || visited.has(mod.id))
return false;
visited.add(mod.id);
// Check if this module is invalidated
// SSR modules use ssrInvalidationState, client modules use invalidationState
if (mod.ssrInvalidationState === `HARD_INVALIDATED` || mod.invalidationState === `HARD_INVALIDATED`) {
return true;
}
// Also check if transformResult is null (indicates invalidation)
if (mod.ssrTransformResult === null && mod.transformResult === null) {
return true;
}
// Check all imported modules recursively
for (const imported of mod.importedModules) {
if (checkInvalidated(imported, visited))
return true;
}
return false;
};
return checkInvalidated(appModule);
};
const reloadApp = async (server) => {
debug(`reloadApp`);
return server.ssrLoadModule(config.paths.framework.template.absolute.server.app)
.then(module => module)
.then(module => {
return module.createApp({
hooks: {
transformHtml: [
// Inject entry client script for development
createHtmlTransformer((html, ___ctx) => {
const entryClientPath = `/${config.paths.framework.template.relative.client.entrypoint}`;
const entryClientScript = `<script type="module" src="${entryClientPath}"></script>`;
return html.replace(`</body>`, `${entryClientScript}\n</body>`);
}),
// Apply Vite's transformations
createHtmlTransformer(async (html, ctx) => {
return await server.transformIndexHtml(ctx.req.url, html);
}),
],
},
});
})
.catch(async (error) => {
if (Err.is(error)) {
// ━ Clean Stack Trace
server.ssrFixStacktrace(error);
reportError(error);
return error;
}
throw error;
});
};
return {
name: `polen:serve`,
apply: `serve`,
config() {
return {
server: {
port: config.server.port,
watch: {
disableGlobbing: false,
},
fs: {
strict: false, // bring back true, with the allow below might already work now
allow: [
config.paths.project.rootDir,
],
},
},
};
},
handleHotUpdate({ server }) {
debug(`handleHotUpdate`);
// Reload app server immediately in the background
appPromise = reloadApp(server);
},
async configureServer(server) {
debug(`configureServer`);
// Initial load
appPromise = reloadApp(server);
return () => {
// Remove index.html serving middleware.
server.middlewares.stack.splice(
// @ts-expect-error
server.middlewares.stack.findIndex(m => m.handle.name === `viteHtmlFallbackMiddleware`), 1);
// Add middleware that runs our entry server
server.middlewares.use((req, res, ___next) => {
// Check if app needs reloading due to module invalidation
if (isNeedAppLoadOrReload(server)) {
appPromise = reloadApp(server);
}
void HonoNodeServer.getRequestListener(async (request) => {
// Always await the current app promise
const app = await appPromise;
if (Err.is(app)) {
// Err.log(app)
return ResponseInternalServerError();
}
return await app.fetch(request);
})(req, res);
});
};
},
};
};
//# sourceMappingURL=serve.js.map