@gracile/engine 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +9 -0
  3. package/ambient.d.ts +1 -0
  4. package/dist/assertions.d.ts +12 -0
  5. package/dist/assertions.d.ts.map +1 -0
  6. package/dist/assertions.js +27 -0
  7. package/dist/build/build.d.ts +2 -0
  8. package/dist/build/build.d.ts.map +1 -0
  9. package/dist/build/build.js +7 -0
  10. package/dist/build/static.d.ts +8 -0
  11. package/dist/build/static.d.ts.map +1 -0
  12. package/dist/build/static.js +82 -0
  13. package/dist/dev/dev.d.ts +6 -0
  14. package/dist/dev/dev.d.ts.map +1 -0
  15. package/dist/dev/dev.js +14 -0
  16. package/dist/dev/request.d.ts +4 -0
  17. package/dist/dev/request.d.ts.map +1 -0
  18. package/dist/dev/request.js +151 -0
  19. package/dist/dev/server.d.ts +10 -0
  20. package/dist/dev/server.d.ts.map +1 -0
  21. package/dist/dev/server.js +54 -0
  22. package/dist/errors/templates.d.ts +3 -0
  23. package/dist/errors/templates.d.ts.map +1 -0
  24. package/dist/errors/templates.js +69 -0
  25. package/dist/preview.d.ts +6 -0
  26. package/dist/preview.d.ts.map +1 -0
  27. package/dist/preview.js +9 -0
  28. package/dist/render/route-template.d.ts +15 -0
  29. package/dist/render/route-template.d.ts.map +1 -0
  30. package/dist/render/route-template.js +83 -0
  31. package/dist/routes/collect.d.ts +4 -0
  32. package/dist/routes/collect.d.ts.map +1 -0
  33. package/dist/routes/collect.js +111 -0
  34. package/dist/routes/comparator.d.ts +35 -0
  35. package/dist/routes/comparator.d.ts.map +1 -0
  36. package/dist/routes/comparator.js +132 -0
  37. package/dist/routes/load-module.d.ts +11 -0
  38. package/dist/routes/load-module.d.ts.map +1 -0
  39. package/dist/routes/load-module.js +23 -0
  40. package/dist/routes/match.d.ts +16 -0
  41. package/dist/routes/match.d.ts.map +1 -0
  42. package/dist/routes/match.js +63 -0
  43. package/dist/routes/route.d.ts +63 -0
  44. package/dist/routes/route.d.ts.map +1 -0
  45. package/dist/routes/route.js +32 -0
  46. package/dist/tsconfig.tsbuildinfo +1 -0
  47. package/dist/user-config.d.ts +8 -0
  48. package/dist/user-config.d.ts.map +1 -0
  49. package/dist/user-config.js +10 -0
  50. package/dist/vite/build.d.ts +2 -0
  51. package/dist/vite/build.d.ts.map +1 -0
  52. package/dist/vite/build.js +39 -0
  53. package/dist/vite/config.d.ts +26 -0
  54. package/dist/vite/config.d.ts.map +1 -0
  55. package/dist/vite/config.js +64 -0
  56. package/dist/vite/plugins/html-static-pages.d.ts +13 -0
  57. package/dist/vite/plugins/html-static-pages.d.ts.map +1 -0
  58. package/dist/vite/plugins/html-static-pages.js +58 -0
  59. package/dist/vite/plugins/scss.d.ts +3 -0
  60. package/dist/vite/plugins/scss.d.ts.map +1 -0
  61. package/dist/vite/plugins/scss.js +28 -0
  62. package/dist/vite/server.d.ts +3 -0
  63. package/dist/vite/server.d.ts.map +1 -0
  64. package/dist/vite/server.js +18 -0
  65. package/dist/vite/utils.d.ts +3 -0
  66. package/dist/vite/utils.d.ts.map +1 -0
  67. package/dist/vite/utils.js +5 -0
  68. package/package.json +84 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2024 Julian Cataldo — https://gracile.js.org
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Gracile — Engine
2
+
3
+ A thin, full-stack, **web** framework.
4
+
5
+ ---
6
+
7
+ - [Documentation website (gracile.netlify.app)](https://gracile.js.org/)
8
+ - [Documentation website repository](https://github.com/gracile-web/website)
9
+ - [Starter projects repository](https://github.com/gracile-web/starter-projects)
package/ambient.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,12 @@
1
+ import { type ServerRenderedTemplate } from '@lit-labs/ssr';
2
+ import type { TemplateResult } from 'lit';
3
+ export type UnknownObject = Record<string, unknown>;
4
+ /**
5
+ * Used for user provided modules with unknown/possibly malformed shapes.
6
+ * Avoid this for well typed sources.
7
+ */
8
+ export declare function isUnknownObject(input: unknown): input is UnknownObject;
9
+ export declare function isLitTemplate(input: unknown): input is TemplateResult<1>;
10
+ export declare function isLitNormalTemplate(input: unknown): input is TemplateResult<1>;
11
+ export declare function isLitServerTemplate(input: unknown): input is ServerRenderedTemplate;
12
+ //# sourceMappingURL=assertions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../src/assertions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAE1C,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEtE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAAC,CAAC,CAAC,CAWxE;AAED,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,OAAO,GACZ,KAAK,IAAI,cAAc,CAAC,CAAC,CAAC,CAE5B;AAED,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,OAAO,GACZ,KAAK,IAAI,sBAAsB,CAOjC"}
@@ -0,0 +1,27 @@
1
+ import {} from '@lit-labs/ssr';
2
+ /**
3
+ * Used for user provided modules with unknown/possibly malformed shapes.
4
+ * Avoid this for well typed sources.
5
+ */
6
+ export function isUnknownObject(input) {
7
+ return typeof input === 'object' && input !== null && !Array.isArray(input);
8
+ }
9
+ export function isLitTemplate(input) {
10
+ return ((typeof input === 'object' &&
11
+ input &&
12
+ '_$litType$' in input &&
13
+ // eslint-disable-next-line no-underscore-dangle
14
+ input._$litType$ === 1 &&
15
+ 'strings' in input &&
16
+ Array.isArray(input.strings)) ||
17
+ false);
18
+ }
19
+ export function isLitNormalTemplate(input) {
20
+ return isLitTemplate(input) && '_$litServerRenderMode' in input === false;
21
+ }
22
+ export function isLitServerTemplate(input) {
23
+ return (isLitTemplate(input) &&
24
+ '_$litServerRenderMode' in input &&
25
+ // eslint-disable-next-line no-underscore-dangle
26
+ input._$litServerRenderMode === 1);
27
+ }
@@ -0,0 +1,2 @@
1
+ export declare function build(root?: string): Promise<void>;
2
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/build/build.ts"],"names":[],"mappings":"AAKA,wBAAsB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,iBAIxC"}
@@ -0,0 +1,7 @@
1
+ import { logger } from '@gracile/internal-utils/logger';
2
+ import c from 'picocolors';
3
+ import { viteBuild } from '../vite/build.js';
4
+ export async function build(root) {
5
+ logger.info(c.gray('\n— Build mode —\n'));
6
+ await viteBuild(root ?? process.cwd());
7
+ }
@@ -0,0 +1,8 @@
1
+ import type { ViteDevServer } from 'vite';
2
+ export interface RouteDefinition {
3
+ absoluteId: string;
4
+ name: string;
5
+ html: string;
6
+ }
7
+ export declare function renderRoutes(vite: ViteDevServer, root?: string): Promise<RouteDefinition[]>;
8
+ //# sourceMappingURL=static.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/build/static.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAM1C,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAeD,wBAAsB,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,SAAgB,8BAwG3E"}
@@ -0,0 +1,82 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import path, { join } from 'node:path';
3
+ import { logger } from '@gracile/internal-utils/logger';
4
+ import c from 'picocolors';
5
+ import { renderRouteTempalte } from '../render/route-template.js';
6
+ import { collectRoutes, routes } from '../routes/collect.js';
7
+ import { loadForeignRouteObject } from '../routes/load-module.js';
8
+ async function streamToString(stream) {
9
+ const chunks = [];
10
+ // eslint-disable-next-line no-restricted-syntax
11
+ for await (const chunk of stream) {
12
+ if (typeof chunk === 'string') {
13
+ chunks.push(Buffer.from(chunk));
14
+ }
15
+ else
16
+ throw new Error('Wrong buffer');
17
+ }
18
+ return Buffer.concat(chunks).toString('utf-8');
19
+ }
20
+ export async function renderRoutes(vite, root = process.cwd()) {
21
+ logger.info(c.green('Rendering routes…'), { timestamp: true });
22
+ // MARK: Collect
23
+ await collectRoutes(root /* vite */);
24
+ logger.info(c.green('Rendering routes finished'), { timestamp: true });
25
+ const renderedRoutes = [];
26
+ // MARK: Iterate modules
27
+ await Promise.all([...routes].map(async ([patternString, options]) => {
28
+ const routeModule = await loadForeignRouteObject(vite, options.filePath);
29
+ const routeStaticPaths = routeModule.staticPaths?.();
30
+ // MARK: Extract data
31
+ await Promise.all((routeStaticPaths ?? [patternString]).map(async (staticPathOptions) => {
32
+ let pathnameWithParams = patternString;
33
+ let params = {};
34
+ let props;
35
+ // MARK: Convert pattern
36
+ // to real route with static parameters + get props. for after
37
+ if (typeof staticPathOptions === 'object') {
38
+ params = staticPathOptions.params;
39
+ props = staticPathOptions.props;
40
+ Object.entries(staticPathOptions.params).forEach(([paramName, value]) => {
41
+ if (typeof value === 'string' || typeof value === 'undefined')
42
+ pathnameWithParams = pathnameWithParams
43
+ .replace(`:${paramName}*`, value || '')
44
+ .replace(`{:${paramName}}`, value || '');
45
+ });
46
+ }
47
+ // MARK: Prepare
48
+ // NOTE: Unused for now
49
+ const isErrorPage = pathnameWithParams.match(/\/__(.*)$/); /* Could add more (error, etc) */
50
+ const base = isErrorPage
51
+ ? path.dirname(pathnameWithParams.slice(1))
52
+ : pathnameWithParams.slice(1);
53
+ const name = path.join(base, isErrorPage
54
+ ? `${pathnameWithParams.split('/').at(-2)?.replace('__', '')}.html`
55
+ : 'index.html');
56
+ const url = new URL(pathnameWithParams, 'http://gracile-static');
57
+ // MARK: Render
58
+ const { output } = await renderRouteTempalte(
59
+ //
60
+ { url: url.href }, vite, 'build', {
61
+ routeModule,
62
+ params,
63
+ foundRoute: options,
64
+ pathname: pathnameWithParams,
65
+ props,
66
+ });
67
+ const htmlString = await streamToString(output);
68
+ const existing = renderedRoutes.find((rendered) => rendered?.name === name);
69
+ if (existing)
70
+ throw new Error(`${c.red(`"${existing.name}" page was defined twice!`)}\n`);
71
+ renderedRoutes.push({
72
+ // NOTE:
73
+ // Vite's internal build-html plugin only expects *absolute* ids.
74
+ // See https://github.com/vitejs/vite/issues/13406#issuecomment-1801659561
75
+ absoluteId: join(root, name),
76
+ name,
77
+ html: htmlString,
78
+ });
79
+ }));
80
+ }));
81
+ return renderedRoutes;
82
+ }
@@ -0,0 +1,6 @@
1
+ export declare function dev(options: {
2
+ port?: number | undefined;
3
+ root?: string;
4
+ expose?: boolean | undefined;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/dev/dev.ts"],"names":[],"mappings":"AAQA,wBAAsB,GAAG,CAAC,OAAO,EAAE;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B,iBAeA"}
@@ -0,0 +1,14 @@
1
+ import { logger } from '@gracile/internal-utils/logger';
2
+ import c from 'picocolors';
3
+ import { getConfigs } from '../vite/config.js';
4
+ import { startServer } from './server.js';
5
+ const DEFAULT_DEV_SERVER_PORT = 9090;
6
+ export async function dev(options) {
7
+ logger.info(c.gray('\n— Development mode —\n'));
8
+ const { userConfigGracile } = await getConfigs(options.root ?? process.cwd(), 'dev');
9
+ const port = options.port ?? userConfigGracile?.port ?? DEFAULT_DEV_SERVER_PORT;
10
+ startServer({
11
+ ...options,
12
+ port,
13
+ }).catch((e) => logger.error(String(e)));
14
+ }
@@ -0,0 +1,4 @@
1
+ import type { NextFunction, Request as ExpressRequest, Response as ExpressResponse } from 'express';
2
+ import type { ViteDevServer } from 'vite';
3
+ export declare function createDevRequestHandler(vite: ViteDevServer): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => Promise<void | ExpressResponse<any, Record<string, any>>>;
4
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/dev/request.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,YAAY,EACZ,OAAO,IAAI,cAAc,EACzB,QAAQ,IAAI,eAAe,EAC3B,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAc1C,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,SAEpD,cAAc,OACd,eAAe,QACd,YAAY,+DA+LnB"}
@@ -0,0 +1,151 @@
1
+ import { Writable } from 'node:stream';
2
+ import { logger } from '@gracile/internal-utils/logger';
3
+ import { createServerAdapter } from '@whatwg-node/server';
4
+ import c from 'picocolors';
5
+ import { /* errorInline, */ errorPage } from '../errors/templates.js';
6
+ import { renderRouteTempalte, } from '../render/route-template.js';
7
+ import { getRoute } from '../routes/match.js';
8
+ import { renderSsrTemplate } from '../vite/utils.js';
9
+ // NOTE: Find a more canonical way to ponyfill the Node HTTP request to standard Request
10
+ // @ts-expect-error Abusing this feature!
11
+ const adapter = createServerAdapter((request) => request);
12
+ export function createDevRequestHandler(vite) {
13
+ return async (req, res, next) => {
14
+ const url = req.originalUrl;
15
+ logger.info(`[${c.yellow(req.method)}] ${c.yellow(url)}`, {
16
+ timestamp: true,
17
+ });
18
+ // MARK: Skip unwanted requests
19
+ if (
20
+ //
21
+ url.endsWith('favicon.ico') ||
22
+ url.endsWith('favicon.svg'))
23
+ return next();
24
+ const requestPonyfilled = (await Promise.resolve(adapter.handleNodeRequest(req)));
25
+ async function renderPageFn(handlerInfos, routeInfos) {
26
+ const { output } = await renderRouteTempalte(requestPonyfilled, vite, 'dev', routeInfos, handlerInfos);
27
+ return output;
28
+ }
29
+ try {
30
+ // MARK: Get route infos
31
+ const moduleInfos = await getRoute({
32
+ url: requestPonyfilled.url,
33
+ vite,
34
+ });
35
+ let output;
36
+ // TODO: should move this to `special-file` so we don't recalculate on each request
37
+ // + we would be able to do some route codegen.
38
+ const response = {};
39
+ // MARK: Server handler
40
+ const handler = moduleInfos.routeModule.handler;
41
+ if ('handler' in moduleInfos.routeModule &&
42
+ typeof handler !== 'undefined') {
43
+ const options = {
44
+ request: requestPonyfilled,
45
+ url: new URL(requestPonyfilled.url),
46
+ response,
47
+ params: moduleInfos.params,
48
+ };
49
+ // MARK: Top level handler
50
+ if (typeof handler === 'function') {
51
+ const handlerOutput = (await Promise.resolve(handler(options)));
52
+ if (handlerOutput instanceof Response)
53
+ output = handlerOutput;
54
+ else
55
+ throw new Error('Catch-all handler must return a Response object.');
56
+ // MARK: Handler with method
57
+ }
58
+ else if (requestPonyfilled.method in handler) {
59
+ const handlerWithMethod = handler[requestPonyfilled.method];
60
+ if (typeof handlerWithMethod !== 'function')
61
+ throw Error('Handler must be a function.');
62
+ const handlerOutput = await Promise.resolve(handlerWithMethod(options));
63
+ if (handlerOutput instanceof Response)
64
+ output = handlerOutput;
65
+ else {
66
+ output = await renderPageFn({
67
+ data: handlerOutput,
68
+ method: requestPonyfilled.method,
69
+ }, moduleInfos);
70
+ }
71
+ // MARK: No GET, render page
72
+ }
73
+ else if (handler &&
74
+ 'GET' in handler === false &&
75
+ requestPonyfilled.method === 'GET') {
76
+ output = await renderPageFn({ data: null, method: 'GET' }, moduleInfos);
77
+ }
78
+ // MARK: No handler, render page
79
+ }
80
+ else {
81
+ output = await renderPageFn({ data: null, method: 'GET' }, moduleInfos);
82
+ }
83
+ // MARK: Return response
84
+ // NOTE: try directly with the requestPonyfill. This might not be necessary
85
+ if (output instanceof Response) {
86
+ if (output.status >= 300 && output.status <= 303) {
87
+ const location = output.headers.get('location');
88
+ if (location)
89
+ return res.redirect(location);
90
+ }
91
+ output.headers?.forEach((content, header) => res.set(header, content));
92
+ if (output.status)
93
+ res.statusCode = output.status;
94
+ if (output.statusText)
95
+ res.statusMessage = output.statusText;
96
+ // TODO: use this with page only?
97
+ // if (output.bodyUsed === false)
98
+ // throw new Error('Missing body.');
99
+ if (output.body)
100
+ output.body
101
+ .pipeTo(Writable.toWeb(res))
102
+ .catch((e) => logger.error(String(e)));
103
+ else
104
+ throw new Error('Missing body.');
105
+ // MARK: Stream page render
106
+ }
107
+ else {
108
+ new Headers(response.headers)?.forEach((content, header) => res.set(header, content));
109
+ if (response.status)
110
+ res.statusCode = response.status;
111
+ if (response.statusText)
112
+ res.statusMessage = response.statusText;
113
+ res.set({ 'Content-Type': 'text/html' });
114
+ // MARK: Page stream error
115
+ output
116
+ ?.on('error', (error) => {
117
+ const errorMessage = `There was an error while rendering a template chunk on server-side.\n` +
118
+ `It was omitted from the resulting HTML.`;
119
+ logger.error(errorMessage);
120
+ logger.error(error.message);
121
+ res.statusCode = 500;
122
+ res.statusMessage = errorMessage;
123
+ /* NOTE: Safety closing tags, maybe add more */
124
+ // Maybe just returning nothing is better to not break the page?
125
+ // Should send a overlay message anyway via WebSocket
126
+ // vite.ws.send()
127
+ setTimeout(() => {
128
+ vite.hot.send('gracile:ssr-error', {
129
+ message: errorMessage,
130
+ });
131
+ }, 500);
132
+ res.end('' /* errorInline(error) */);
133
+ })
134
+ .pipe(res);
135
+ }
136
+ // MARK: Errors
137
+ }
138
+ catch (e) {
139
+ const error = e;
140
+ vite.ssrFixStacktrace(error);
141
+ if (error.cause === 404) {
142
+ return res.status(404).end('404');
143
+ // TODO: use a nice framework service page
144
+ // .redirect(new URL('/__404/', requestPonyfilled.url).href)
145
+ }
146
+ const errorTemplate = await renderSsrTemplate(errorPage(error));
147
+ res.status(500).end(await vite.transformIndexHtml(url, errorTemplate));
148
+ }
149
+ return next();
150
+ };
151
+ }
@@ -0,0 +1,10 @@
1
+ import type { Server } from 'node:http';
2
+ export declare function startServer(options: {
3
+ port?: number | undefined;
4
+ root?: string;
5
+ expose?: boolean | undefined;
6
+ }): Promise<{
7
+ port: number;
8
+ instance: Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
9
+ }>;
10
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAyCxC,wBAAsB,WAAW,CAAC,OAAO,EAAE;IAC1C,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B;;;GAqCA"}
@@ -0,0 +1,54 @@
1
+ import { logger } from '@gracile/internal-utils/logger';
2
+ import { setCurrentWorkingDirectory } from '@gracile/internal-utils/paths';
3
+ import express, {} from 'express';
4
+ import c from 'picocolors';
5
+ import { collectRoutes, routes } from '../routes/collect.js';
6
+ import { createViteServer } from '../vite/server.js';
7
+ import { createDevRequestHandler } from './request.js';
8
+ async function createServer(_hmrPort, root = process.cwd()) {
9
+ logger.info(c.green('starting engine…'), {
10
+ timestamp: true,
11
+ });
12
+ setCurrentWorkingDirectory(root);
13
+ const app = express();
14
+ const vite = await createViteServer(root, 'dev');
15
+ app.use(vite.middlewares);
16
+ app.get('/__routes', (req, res) => {
17
+ return res.json([...routes]);
18
+ });
19
+ await collectRoutes(root /* vite */);
20
+ vite.watcher.on('all', (event, _file) => {
21
+ if (['add', 'unlink'].includes(event))
22
+ collectRoutes(root /* , vite */).catch((e) => logger.error(String(e)));
23
+ });
24
+ const handler = createDevRequestHandler(vite);
25
+ /* NOTE: Types are wrong! Should accept an async request handler. */
26
+ app.use('*', handler);
27
+ return { app, vite };
28
+ }
29
+ export async function startServer(options) {
30
+ const port = options.port ?? 9090;
31
+ const server = await createServer(port + 1, options.root);
32
+ // NOTE: `0` will auto-alocate a random available port.
33
+ let resultingPort = port;
34
+ let resultingHost;
35
+ const instance = await new Promise((resolve) => {
36
+ const inst = server.app.listen(port, options.expose ? '0.0.0.0' : '127.0.0.1', () => {
37
+ logger.info(c.green('development server started'), { timestamp: true });
38
+ resolve(inst);
39
+ const addressInfo = inst.address();
40
+ if (typeof addressInfo === 'object' && addressInfo) {
41
+ resultingPort = addressInfo.port;
42
+ // NOTE: this is not ideal. Should have the real bounded IP,
43
+ // like with Vite's `resolvedUrls` (unavailable in middleware mode)
44
+ resultingHost = addressInfo.address;
45
+ }
46
+ logger.info(`
47
+ ${c.dim('┃')} Local ${c.cyan(`http://localhost:${resultingPort}/`)}
48
+ ${c.dim('┃')} Network ${options.expose ? c.cyan(`http://${resultingHost}:${resultingPort}/`) : c.dim(`use ${c.bold('--host')} to expose`)}
49
+ `);
50
+ resolve(inst);
51
+ });
52
+ });
53
+ return { port: resultingPort, instance };
54
+ }
@@ -0,0 +1,3 @@
1
+ export declare function errorInline(error: Error): import("@lit-labs/ssr").ServerRenderedTemplate;
2
+ export declare function errorPage(error: Error): import("@lit-labs/ssr").ServerRenderedTemplate;
3
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/errors/templates.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,kDASvC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,kDAyDrC"}
@@ -0,0 +1,69 @@
1
+ import { html } from '@lit-labs/ssr';
2
+ export function errorInline(error) {
3
+ return html `<!-- --></a>
4
+ <div data-ssr-error>
5
+ <strong style="color: red">SSR Template error!</strong>
6
+ <details>
7
+ <summary style="user-select: none; cursor: pointer">Stack trace</summary>
8
+ <pre style="overflow: auto">${error.stack}</pre>
9
+ </details>
10
+ </div>`;
11
+ }
12
+ export function errorPage(error) {
13
+ return html `
14
+ <!-- -->
15
+
16
+ <!doctype html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="UTF-8" />
20
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
21
+
22
+ <title>Error</title>
23
+ </head>
24
+ <body>
25
+ <style>
26
+ html {
27
+ color-scheme: dark;
28
+ font-size: 16px;
29
+ line-height: 1.23rem;
30
+ font-family: system-ui;
31
+ }
32
+ body {
33
+ padding: 1rem;
34
+ }
35
+
36
+ pre {
37
+ padding: 1rem;
38
+
39
+ overflow-y: auto;
40
+ }
41
+ button {
42
+ font-size: 2rem;
43
+ }
44
+
45
+ h1 {
46
+ color: tomato;
47
+ }
48
+ </style>
49
+
50
+ <main>
51
+ <h1>😵 An error has occurred!</h1>
52
+ <button id="reload">Reload</button>
53
+ <!-- -->
54
+ <hr />
55
+
56
+ <pre>${error.stack}</pre>
57
+ <!-- <pre>$ {e.name}</pre> -->
58
+ <!-- <pre>$ {e.message}</pre> -->
59
+ <!-- <pre>$ {e.cause}</pre> -->
60
+ <hr />
61
+ </main>
62
+
63
+ <script>
64
+ reload.addEventListener('click', () => document.location.reload());
65
+ </script>
66
+ </body>
67
+ </html>
68
+ `;
69
+ }
@@ -0,0 +1,6 @@
1
+ export declare function preview({ port, expose, root, }: {
2
+ port?: number | undefined;
3
+ expose?: boolean | undefined;
4
+ root?: string;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAMA,wBAAsB,OAAO,CAAC,EAC7B,IAAI,EACJ,MAAM,EACN,IAAI,GACJ,EAAE;IACF,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,iBASA"}
@@ -0,0 +1,9 @@
1
+ import { logger } from '@gracile/internal-utils/logger';
2
+ import c from 'picocolors';
3
+ import { getConfigs } from './vite/config.js';
4
+ import { vitePreview } from './vite/server.js';
5
+ export async function preview({ port, expose, root, }) {
6
+ logger.info(c.gray('\n— Preview mode —\n'));
7
+ const { userConfigGracile } = await getConfigs(root ?? process.cwd(), 'build');
8
+ await vitePreview(port ?? userConfigGracile?.port ?? 9797, expose);
9
+ }
@@ -0,0 +1,15 @@
1
+ import { Readable } from 'node:stream';
2
+ import type { ViteDevServer } from 'vite';
3
+ import type { RouteInfos } from '../routes/match.js';
4
+ import type { StaticRequest } from '../routes/route.js';
5
+ export declare const SSR_OUTLET_MARKER = "<route-template-outlet></route-template-outlet>";
6
+ export declare const PAGE_ASSETS_MARKER = "<!--__GRACILE_PAGE_ASSETS__-->";
7
+ export declare const pageAssets: import("@lit-labs/ssr").ServerRenderedTemplate;
8
+ export type HandlerInfos = {
9
+ data: unknown;
10
+ method: string;
11
+ };
12
+ export declare function renderRouteTempalte(request: Request | StaticRequest, vite: ViteDevServer, mode: 'dev' | 'build', routeInfos: RouteInfos, handlerInfos?: HandlerInfos): Promise<{
13
+ output: Readable;
14
+ }>;
15
+ //# sourceMappingURL=route-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-template.d.ts","sourceRoot":"","sources":["../../src/render/route-template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAKvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAuB,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAa7E,eAAO,MAAM,iBAAiB,oDACoB,CAAC;AAGnD,eAAO,MAAM,kBAAkB,mCAAmC,CAAC;AAEnE,eAAO,MAAM,UAAU,gDAA6C,CAAC;AAErE,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,OAAO,GAAG,aAAa,EAChC,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,KAAK,GAAG,OAAO,EACrB,UAAU,EAAE,UAAU,EACtB,YAAY,CAAC,EAAE,YAAY;;GAiH3B"}
@@ -0,0 +1,83 @@
1
+ import { Readable } from 'node:stream';
2
+ import { html } from '@gracile/internal-utils/dummy-literals';
3
+ import { html as LitSsrHtml, render as renderLitSsr } from '@lit-labs/ssr';
4
+ import { collectResult } from '@lit-labs/ssr/lib/render-result.js';
5
+ import { isLitServerTemplate, isLitTemplate } from '../assertions.js';
6
+ async function* concatStreams(...readables) {
7
+ // eslint-disable-next-line no-restricted-syntax
8
+ for (const readable of readables) {
9
+ // eslint-disable-next-line no-restricted-syntax, no-await-in-loop
10
+ for await (const chunk of readable) {
11
+ yield chunk;
12
+ }
13
+ }
14
+ }
15
+ // export const SSR_OUTLET_MARKER = '________SSR_OUTLET________';
16
+ export const SSR_OUTLET_MARKER = '<route-template-outlet></route-template-outlet>';
17
+ // const SSR_OUTLET = unsafeHTML(SSR_OUTLET_MARKER);
18
+ export const PAGE_ASSETS_MARKER = '<!--__GRACILE_PAGE_ASSETS__-->';
19
+ // FIXME: cannot be used with `unsafeHTML`, so must be duplicated…
20
+ export const pageAssets = LitSsrHtml `<!--__GRACILE_PAGE_ASSETS__-->`;
21
+ export async function renderRouteTempalte(request, vite, mode, routeInfos, handlerInfos) {
22
+ // MARK: Context
23
+ const context = {
24
+ url: new URL(request.url),
25
+ params: routeInfos.params,
26
+ props: handlerInfos?.data
27
+ ? {
28
+ [handlerInfos.method]: handlerInfos.data,
29
+ }
30
+ : routeInfos.props,
31
+ };
32
+ // MARK: Fragment
33
+ if (!routeInfos.routeModule.document) {
34
+ const fragmentOutput = await Promise.resolve(routeInfos.routeModule.template?.(context));
35
+ if (isLitTemplate(fragmentOutput) === false)
36
+ throw Error(`Wrong template result for fragment template ${routeInfos.foundRoute.filePath}.`);
37
+ const fragmentRender = renderLitSsr(fragmentOutput);
38
+ // NOTE: Should use RenderResultReadable instead?
39
+ const output = Readable.from(fragmentRender);
40
+ return { output };
41
+ }
42
+ // MARK: Document
43
+ if (!routeInfos.routeModule.document ||
44
+ typeof routeInfos.routeModule.document !== 'function')
45
+ throw new Error(`Route document must be a function ${routeInfos.foundRoute.filePath}.`);
46
+ const baseDocTemplateResult = await Promise.resolve(routeInfos.routeModule.document?.(context));
47
+ if (isLitServerTemplate(baseDocTemplateResult) === false)
48
+ throw new Error(`Incorrect document template result for ${routeInfos.foundRoute.filePath}.`);
49
+ const baseDocRendered = await collectResult(renderLitSsr(baseDocTemplateResult));
50
+ // MARK: Sibling assets
51
+ const baseDocRenderedWithAssets = baseDocRendered.replace(PAGE_ASSETS_MARKER, html `
52
+ <!-- PAGE ASSETS -->
53
+ ${routeInfos.foundRoute.pageAssets.map((path) => {
54
+ //
55
+ if (/\.(js|ts)$/.test(path)) {
56
+ return html `<script type="module" src="/${path}"></script>`;
57
+ }
58
+ if (/\.(scss|css)$/.test(path)) {
59
+ return html `<link rel="stylesheet" href="/${path}" />`;
60
+ }
61
+ throw new Error('Unknown asset.');
62
+ })}
63
+ <!-- /PAGE ASSETS -->
64
+ `);
65
+ // MARK: Base document
66
+ const baseDocHtml = mode === 'dev'
67
+ ? await vite.transformIndexHtml(routeInfos.pathname, baseDocRenderedWithAssets)
68
+ : baseDocRenderedWithAssets;
69
+ const index = baseDocHtml.indexOf(SSR_OUTLET_MARKER);
70
+ const baseDocRenderStreamPre = Readable.from(baseDocHtml.substring(0, index));
71
+ const baseDocRenderStreamPost = Readable.from(baseDocHtml.substring(index + SSR_OUTLET_MARKER.length + 1));
72
+ // MARK: Page
73
+ if (routeInfos.routeModule.template) {
74
+ const routeOutput = (await Promise.resolve(routeInfos.routeModule.template(context)));
75
+ if (isLitTemplate(routeOutput) === false)
76
+ throw Error(`Wrong template result for page template ${routeInfos.foundRoute.filePath}.`);
77
+ const renderStream = Readable.from(renderLitSsr(routeOutput));
78
+ const output = Readable.from(concatStreams(baseDocRenderStreamPre, renderStream, baseDocRenderStreamPost));
79
+ return { output };
80
+ }
81
+ const output = Readable.from(baseDocHtml);
82
+ return { output };
83
+ }