@gracile/engine 0.9.0-next.4 → 0.9.0-next.5
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.
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +37 -262
- package/dist/render/route-template-pipeline.d.ts +64 -0
- package/dist/render/route-template-pipeline.d.ts.map +1 -0
- package/dist/render/route-template-pipeline.js +144 -0
- package/dist/render/route-template.d.ts +1 -2
- package/dist/render/route-template.d.ts.map +1 -1
- package/dist/render/route-template.js +12 -92
- package/dist/routes/collect.d.ts +4 -0
- package/dist/routes/collect.d.ts.map +1 -1
- package/dist/routes/collect.js +2 -1
- package/dist/routes/match.d.ts +26 -0
- package/dist/routes/match.d.ts.map +1 -1
- package/dist/routes/match.js +4 -2
- package/dist/server/request-pipeline.d.ts +109 -0
- package/dist/server/request-pipeline.d.ts.map +1 -0
- package/dist/server/request-pipeline.js +198 -0
- package/dist/server/request.d.ts +3 -16
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +55 -169
- package/dist/test/init.d.ts +2 -0
- package/dist/test/init.d.ts.map +1 -0
- package/dist/test/init.js +7 -0
- package/dist/vite/plugin-client-build.d.ts +16 -0
- package/dist/vite/plugin-client-build.d.ts.map +1 -0
- package/dist/vite/plugin-client-build.js +49 -0
- package/dist/vite/plugin-serve.d.ts +18 -0
- package/dist/vite/plugin-serve.d.ts.map +1 -0
- package/dist/vite/plugin-serve.js +62 -0
- package/dist/vite/plugin-server-build.d.ts +33 -0
- package/dist/vite/plugin-server-build.d.ts.map +1 -0
- package/dist/vite/plugin-server-build.js +157 -0
- package/dist/vite/plugin-shared-state.d.ts +31 -0
- package/dist/vite/plugin-shared-state.d.ts.map +1 -0
- package/dist/vite/plugin-shared-state.js +22 -0
- package/package.json +2 -2
package/dist/server/request.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
-
import * as assert from '@gracile/internal-utils/assertions';
|
|
3
2
|
import { getLogger } from '@gracile/internal-utils/logger/helpers';
|
|
4
3
|
import c from 'picocolors';
|
|
5
|
-
// import { GracileError } from '../errors/errors.js';
|
|
6
4
|
import { builtIn404Page, builtInErrorPage } from '../errors/pages.js';
|
|
7
5
|
import { renderRouteTemplate } from '../render/route-template.js';
|
|
8
6
|
import { renderLitTemplate } from '../render/lit-ssr.js';
|
|
9
7
|
import { getRoute } from '../routes/match.js';
|
|
10
|
-
|
|
11
|
-
const PREMISE_REGEXES = {
|
|
12
|
-
properties: /\/__(.*?)\.props\.json$/,
|
|
13
|
-
document: /\/__(.*?)\.doc\.html$/,
|
|
14
|
-
};
|
|
15
|
-
export function isRedirect(response) {
|
|
16
|
-
const location = response.headers.get('location');
|
|
17
|
-
if (response.status >= 300 && response.status <= 303 && location) {
|
|
18
|
-
return { location };
|
|
19
|
-
}
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
8
|
+
import { CONTENT_TYPE_HTML, rewriteHiddenRoutes, resolvePremises, executeHandler, renderWithoutHandler, buildResponse, } from './request-pipeline.js';
|
|
22
9
|
export function createGracileHandler({ vite, routes, routeImports, routeAssets, root, serverMode, gracileConfig, }) {
|
|
23
10
|
const logger = getLogger();
|
|
24
11
|
const middleware = async (request, locals) => {
|
|
@@ -28,16 +15,11 @@ export function createGracileHandler({ vite, routes, routeImports, routeAssets,
|
|
|
28
15
|
emitViteBetterError =
|
|
29
16
|
await import('../errors/create-vite-better-error.js').then(({ emitViteBetterError: error }) => error);
|
|
30
17
|
try {
|
|
31
|
-
// MARK: Rewrite hidden route siblings
|
|
32
|
-
const fullUrl = requestedUrl
|
|
33
|
-
// MARK: Setup premises
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const allowPremises = Boolean(gracileConfig.pages?.premises?.expose);
|
|
37
|
-
if (allowPremises === false && (propertiesOnly || documentOnly))
|
|
38
|
-
throw new Error('Accessed a page premise but they are not activated. You must enable `pages.premises.expose`.');
|
|
39
|
-
const premises = allowPremises ? { propertiesOnly, documentOnly } : null;
|
|
40
|
-
// MARK: Get route infos
|
|
18
|
+
// MARK: 1. Rewrite hidden route siblings
|
|
19
|
+
const fullUrl = rewriteHiddenRoutes(requestedUrl);
|
|
20
|
+
// MARK: 2. Setup premises
|
|
21
|
+
const premises = resolvePremises(requestedUrl, gracileConfig);
|
|
22
|
+
// MARK: 3. Route resolution + 404 fallback
|
|
41
23
|
const routeOptions = {
|
|
42
24
|
url: fullUrl,
|
|
43
25
|
vite,
|
|
@@ -46,15 +28,12 @@ export function createGracileHandler({ vite, routes, routeImports, routeAssets,
|
|
|
46
28
|
};
|
|
47
29
|
const responseInit = {};
|
|
48
30
|
let routeInfos = await getRoute(routeOptions);
|
|
49
|
-
// MARK: 404
|
|
50
31
|
if (routeInfos === null) {
|
|
51
32
|
responseInit.status = 404;
|
|
52
33
|
const url = new URL('/404/', fullUrl).href;
|
|
53
34
|
const options = { ...routeOptions, url };
|
|
54
|
-
|
|
55
|
-
routeInfos = notFound;
|
|
35
|
+
routeInfos = await getRoute(options);
|
|
56
36
|
}
|
|
57
|
-
// MARK: fallback 404
|
|
58
37
|
if (routeInfos === null) {
|
|
59
38
|
const page = builtIn404Page(new URL(fullUrl).pathname, Boolean(vite));
|
|
60
39
|
return {
|
|
@@ -66,7 +45,7 @@ export function createGracileHandler({ vite, routes, routeImports, routeAssets,
|
|
|
66
45
|
const routeTemplateOptions = {
|
|
67
46
|
url: fullUrl,
|
|
68
47
|
vite,
|
|
69
|
-
mode: 'dev',
|
|
48
|
+
mode: 'dev',
|
|
70
49
|
routeAssets,
|
|
71
50
|
root,
|
|
72
51
|
serverMode,
|
|
@@ -77,153 +56,38 @@ export function createGracileHandler({ vite, routes, routeImports, routeAssets,
|
|
|
77
56
|
logger.info(`[${c.yellow(method)}] ${c.yellow(fullUrl)}`, {
|
|
78
57
|
timestamp: true,
|
|
79
58
|
});
|
|
59
|
+
// MARK: 4. Handler dispatch
|
|
80
60
|
let output;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
// MARK: Run user middleware
|
|
97
|
-
// NOTE: Experimental
|
|
98
|
-
/// eslint-disable-next-line no-inner-declarations
|
|
99
|
-
// async function useHandler() {}
|
|
100
|
-
// if (vite) {
|
|
101
|
-
// const middleware = await vite
|
|
102
|
-
// .ssrLoadModule('/src/middleware.ts')
|
|
103
|
-
// .catch(() => null)
|
|
104
|
-
// .then((m) => m.default);
|
|
105
|
-
// if (middleware)
|
|
106
|
-
// await middleware(
|
|
107
|
-
// routeContext,
|
|
108
|
-
// async () => {
|
|
109
|
-
// await useHandler();
|
|
110
|
-
// },
|
|
111
|
-
// );
|
|
112
|
-
// else await useHandler();
|
|
113
|
-
// } else {
|
|
114
|
-
// await useHandler();
|
|
115
|
-
// }
|
|
116
|
-
//
|
|
117
|
-
// MARK: Handler(s)
|
|
118
|
-
const hasTopLevelHandler = typeof handler === 'function';
|
|
119
|
-
if (hasTopLevelHandler || method in handler) {
|
|
120
|
-
const handlerWithMethod = hasTopLevelHandler
|
|
121
|
-
? handler
|
|
122
|
-
: handler[method];
|
|
123
|
-
if (typeof handlerWithMethod !== 'function')
|
|
124
|
-
throw new TypeError('Handler must be a function.');
|
|
125
|
-
const handlerOutput = await Promise.resolve(handlerWithMethod(routeContext));
|
|
126
|
-
if (assert.isResponseOrPatchedResponse(handlerOutput))
|
|
127
|
-
output = handlerOutput;
|
|
128
|
-
else {
|
|
129
|
-
routeTemplateOptions.routeInfos.props = hasTopLevelHandler
|
|
130
|
-
? handlerOutput
|
|
131
|
-
: { [method]: handlerOutput };
|
|
132
|
-
if (premises?.documentOnly) {
|
|
133
|
-
const { document } = await renderRouteTemplate(routeTemplateOptions);
|
|
134
|
-
return {
|
|
135
|
-
response: new Response(document, {
|
|
136
|
-
headers: { ...CONTENT_TYPE_HTML },
|
|
137
|
-
}),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
if (premises?.propertiesOnly)
|
|
141
|
-
return {
|
|
142
|
-
response: Response.json(routeTemplateOptions.routeInfos.props),
|
|
143
|
-
};
|
|
144
|
-
output = await renderRouteTemplate(routeTemplateOptions).then((r) => r.output);
|
|
145
|
-
}
|
|
146
|
-
// MARK: No GET, render page
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
const statusText = `This route doesn't handle the \`${method}\` method!`;
|
|
150
|
-
return {
|
|
151
|
-
response: new Response(statusText, { status: 405, statusText }),
|
|
152
|
-
};
|
|
153
|
-
}
|
|
61
|
+
const handlerResult = await executeHandler({
|
|
62
|
+
routeInfos,
|
|
63
|
+
method,
|
|
64
|
+
request,
|
|
65
|
+
fullUrl,
|
|
66
|
+
locals,
|
|
67
|
+
responseInit,
|
|
68
|
+
premises,
|
|
69
|
+
routeTemplateOptions,
|
|
70
|
+
});
|
|
71
|
+
if (handlerResult.type === 'response')
|
|
72
|
+
return handlerResult.value;
|
|
73
|
+
if (handlerResult.type === 'output') {
|
|
74
|
+
output = handlerResult.value;
|
|
154
75
|
}
|
|
155
76
|
else {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (premises?.propertiesOnly)
|
|
165
|
-
return {
|
|
166
|
-
response: Response.json(routeTemplateOptions.routeInfos.props || {}),
|
|
167
|
-
};
|
|
168
|
-
output = await renderRouteTemplate(routeTemplateOptions).then((r) => r.output);
|
|
169
|
-
}
|
|
170
|
-
// MARK: Return response
|
|
171
|
-
// NOTE: try directly with the requestPonyfill. This might not be necessary
|
|
172
|
-
if (assert.isResponseOrPatchedResponse(output)) {
|
|
173
|
-
const redirect = isRedirect(output);
|
|
174
|
-
if (redirect?.location)
|
|
175
|
-
return {
|
|
176
|
-
response: Response.redirect(redirect.location, output.status),
|
|
177
|
-
};
|
|
178
|
-
return { response: output };
|
|
179
|
-
// MARK: Stream page render
|
|
180
|
-
}
|
|
181
|
-
// MARK: Page stream error
|
|
182
|
-
if (output instanceof Readable) {
|
|
183
|
-
responseInit.headers = {
|
|
184
|
-
...responseInit.headers,
|
|
185
|
-
...CONTENT_TYPE_HTML,
|
|
186
|
-
};
|
|
187
|
-
return {
|
|
188
|
-
body: output.on('error', (error) => {
|
|
189
|
-
const errorMessage = `[SSR Error] There was an error while rendering a template chunk on server-side.\n` +
|
|
190
|
-
`It was omitted from the resulting HTML.\n`;
|
|
191
|
-
if (vite) {
|
|
192
|
-
logger.error(errorMessage + error.stack);
|
|
193
|
-
// emitViteBetterError(new GracileError(GracileErrorData.FailedToGlobalLogger), vite);
|
|
194
|
-
const payload = {
|
|
195
|
-
type: 'error',
|
|
196
|
-
// FIXME: Use the emitViteBetterError instead (but flaky for now with streaming)
|
|
197
|
-
// err: new GracileError({}),
|
|
198
|
-
err: {
|
|
199
|
-
name: 'StreamingError',
|
|
200
|
-
message: errorMessage,
|
|
201
|
-
stack: error.stack,
|
|
202
|
-
hint: 'This is often caused by a wrong template location dynamic interpolation.',
|
|
203
|
-
cause: error,
|
|
204
|
-
// highlightedCode: error.message,
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
//
|
|
208
|
-
setTimeout(() => {
|
|
209
|
-
// @ts-expect-error ...........
|
|
210
|
-
vite.hot.send(payload);
|
|
211
|
-
// NOTE: Arbitrary value. Lower seems to be too fast, higher is not guaranteed to work.
|
|
212
|
-
}, 200);
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
logger.error(errorMessage);
|
|
216
|
-
}
|
|
217
|
-
}),
|
|
218
|
-
init: responseInit,
|
|
219
|
-
};
|
|
77
|
+
// MARK: 5. Template-only render (no handler)
|
|
78
|
+
const renderResult = await renderWithoutHandler({
|
|
79
|
+
premises,
|
|
80
|
+
routeTemplateOptions,
|
|
81
|
+
});
|
|
82
|
+
if (renderResult && 'response' in renderResult)
|
|
83
|
+
return renderResult;
|
|
84
|
+
output = renderResult;
|
|
220
85
|
}
|
|
221
|
-
|
|
86
|
+
// MARK: 6. Build final response
|
|
87
|
+
return buildResponse({ output, responseInit, vite, logger });
|
|
222
88
|
// MARK: Errors
|
|
223
89
|
}
|
|
224
90
|
catch (error) {
|
|
225
|
-
// const safeError = createSafeError(error);
|
|
226
|
-
// TODO: User defined dev/runtime 500 error
|
|
227
91
|
const ultimateErrorPage = vite && emitViteBetterError
|
|
228
92
|
? await emitViteBetterError({ vite, error: error })
|
|
229
93
|
: await renderLitTemplate(builtInErrorPage(error.name));
|
|
@@ -238,3 +102,25 @@ export function createGracileHandler({ vite, routes, routeImports, routeAssets,
|
|
|
238
102
|
};
|
|
239
103
|
return middleware;
|
|
240
104
|
}
|
|
105
|
+
// MARK: Run user middleware
|
|
106
|
+
// NOTE: Experimental
|
|
107
|
+
/// eslint-disable-next-line no-inner-declarations
|
|
108
|
+
// async function useHandler() {}
|
|
109
|
+
// if (vite) {
|
|
110
|
+
// const middleware = await vite
|
|
111
|
+
// .ssrLoadModule('/src/middleware.ts')
|
|
112
|
+
// .catch(() => null)
|
|
113
|
+
// .then((m) => m.default);
|
|
114
|
+
// if (middleware)
|
|
115
|
+
// await middleware(
|
|
116
|
+
// routeContext,
|
|
117
|
+
// async () => {
|
|
118
|
+
// await useHandler();
|
|
119
|
+
// },
|
|
120
|
+
// );
|
|
121
|
+
// else await useHandler();
|
|
122
|
+
// } else {
|
|
123
|
+
// await useHandler();
|
|
124
|
+
// }
|
|
125
|
+
//
|
|
126
|
+
export { isRedirect, } from './request-pipeline.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/test/init.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-effect import: initializes the Gracile logger globally.
|
|
3
|
+
* Import this at the top of any engine unit test that touches modules
|
|
4
|
+
* which call `getLogger()` at module scope.
|
|
5
|
+
*/
|
|
6
|
+
import { createLogger } from '@gracile/internal-utils/logger/helpers';
|
|
7
|
+
createLogger();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin: client-side build configuration.
|
|
3
|
+
*
|
|
4
|
+
* During `vite build`, this spins up a temporary dev server to render
|
|
5
|
+
* all routes into static HTML, then configures Rollup with the
|
|
6
|
+
* generated HTML pages as inputs.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
import { type PluginOption } from 'vite';
|
|
11
|
+
import type { PluginSharedState } from './plugin-shared-state.js';
|
|
12
|
+
export declare function gracileClientBuildPlugin({ state, virtualRoutesForClient, }: {
|
|
13
|
+
state: PluginSharedState;
|
|
14
|
+
virtualRoutesForClient: PluginOption;
|
|
15
|
+
}): PluginOption;
|
|
16
|
+
//# sourceMappingURL=plugin-client-build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-client-build.d.ts","sourceRoot":"","sources":["../../src/vite/plugin-client-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,wBAAgB,wBAAwB,CAAC,EACxC,KAAK,EACL,sBAAsB,GACtB,EAAE;IACF,KAAK,EAAE,iBAAiB,CAAC;IACzB,sBAAsB,EAAE,YAAY,CAAC;CACrC,GAAG,YAAY,CAkDf"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin: client-side build configuration.
|
|
3
|
+
*
|
|
4
|
+
* During `vite build`, this spins up a temporary dev server to render
|
|
5
|
+
* all routes into static HTML, then configures Rollup with the
|
|
6
|
+
* generated HTML pages as inputs.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { getPluginContext } from '@gracile/internal-utils/plugin-context';
|
|
12
|
+
import { createServer } from 'vite';
|
|
13
|
+
import { buildRoutes } from './build-routes.js';
|
|
14
|
+
export function gracileClientBuildPlugin({ state, virtualRoutesForClient, }) {
|
|
15
|
+
return {
|
|
16
|
+
name: 'vite-plugin-gracile-build',
|
|
17
|
+
apply: 'build',
|
|
18
|
+
async config(viteConfig) {
|
|
19
|
+
const viteServerForClientHtmlBuild = await createServer({
|
|
20
|
+
root: viteConfig.root || process.cwd(),
|
|
21
|
+
server: { middlewareMode: true },
|
|
22
|
+
// NOTE: Stub. KEEP IT!
|
|
23
|
+
optimizeDeps: { include: [] },
|
|
24
|
+
plugins: [virtualRoutesForClient],
|
|
25
|
+
});
|
|
26
|
+
// NOTE: Important. Get the dev. server elements renderers.
|
|
27
|
+
state.gracileConfig.litSsr ??= {};
|
|
28
|
+
state.gracileConfig.litSsr.renderInfo = getPluginContext(viteServerForClientHtmlBuild.config)?.litSsrRenderInfo;
|
|
29
|
+
const htmlPages = await buildRoutes({
|
|
30
|
+
viteServerForBuild: viteServerForClientHtmlBuild,
|
|
31
|
+
root: viteConfig.root || process.cwd(),
|
|
32
|
+
gracileConfig: state.gracileConfig,
|
|
33
|
+
serverMode: state.outputMode === 'server',
|
|
34
|
+
routes: state.routes,
|
|
35
|
+
});
|
|
36
|
+
state.renderedRoutes = htmlPages.renderedRoutes;
|
|
37
|
+
await viteServerForClientHtmlBuild.close();
|
|
38
|
+
return {
|
|
39
|
+
build: {
|
|
40
|
+
rollupOptions: {
|
|
41
|
+
input: htmlPages.inputList,
|
|
42
|
+
plugins: [htmlPages.plugin],
|
|
43
|
+
},
|
|
44
|
+
outDir: join(viteConfig.build?.outDir || 'dist', state.outputMode === 'server' ? 'client' : ''),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin: development server middleware.
|
|
3
|
+
*
|
|
4
|
+
* Sets up the Gracile request handler, route watcher, and dev-time
|
|
5
|
+
* logging for `vite dev`.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
import type { Logger, PluginOption } from 'vite';
|
|
10
|
+
import type { GracileConfig } from '../user-config.js';
|
|
11
|
+
import type { PluginSharedState } from './plugin-shared-state.js';
|
|
12
|
+
export declare function gracileServePlugin({ state, config, logger, resetClientBuiltFlag, }: {
|
|
13
|
+
state: PluginSharedState;
|
|
14
|
+
config: GracileConfig | undefined;
|
|
15
|
+
logger: Logger;
|
|
16
|
+
resetClientBuiltFlag: () => void;
|
|
17
|
+
}): PluginOption;
|
|
18
|
+
//# sourceMappingURL=plugin-serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-serve.d.ts","sourceRoot":"","sources":["../../src/vite/plugin-serve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAIjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,wBAAgB,kBAAkB,CAAC,EAClC,KAAK,EACL,MAAM,EACN,MAAM,EACN,oBAAoB,GACpB,EAAE;IACF,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,EAAE,MAAM,IAAI,CAAC;CACjC,GAAG,YAAY,CA6Df"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin: development server middleware.
|
|
3
|
+
*
|
|
4
|
+
* Sets up the Gracile request handler, route watcher, and dev-time
|
|
5
|
+
* logging for `vite dev`.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
import { getVersion } from '@gracile/internal-utils/version';
|
|
10
|
+
import c from 'picocolors';
|
|
11
|
+
import { createDevelopmentHandler } from '../dev/development.js';
|
|
12
|
+
import { nodeAdapter } from '../server/adapters/node.js';
|
|
13
|
+
export function gracileServePlugin({ state, config, logger, resetClientBuiltFlag, }) {
|
|
14
|
+
return {
|
|
15
|
+
name: 'vite-plugin-gracile-serve-middleware',
|
|
16
|
+
apply: 'serve',
|
|
17
|
+
config(_, environment) {
|
|
18
|
+
if (environment.isPreview)
|
|
19
|
+
return null;
|
|
20
|
+
return {
|
|
21
|
+
// NOTE: Supresses message: `Could not auto-determine entry point from rollupOptions or html files…`
|
|
22
|
+
// FIXME: It's not working when reloading the Vite config.
|
|
23
|
+
// Is user config, putting `optimizeDeps: { include: [] }` solve this.
|
|
24
|
+
optimizeDeps: { include: [] },
|
|
25
|
+
// NOTE: Useful? It breaks preview (expected)
|
|
26
|
+
appType: 'custom',
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
async configureServer(server) {
|
|
30
|
+
// HACK: We know we are in dev here, this will prevent incorrect
|
|
31
|
+
// vite.config hot reloading. Will be removed when adopting env. API.
|
|
32
|
+
resetClientBuiltFlag();
|
|
33
|
+
const version = getVersion();
|
|
34
|
+
logger.info(`${c.cyan(c.italic(c.underline('🧚 Gracile')))}` +
|
|
35
|
+
` ${c.dim(`~`)} ${c.green(`v${version ?? 'X'}`)}`);
|
|
36
|
+
const { handler } = await createDevelopmentHandler({
|
|
37
|
+
routes: state.routes,
|
|
38
|
+
vite: server,
|
|
39
|
+
gracileConfig: state.gracileConfig,
|
|
40
|
+
});
|
|
41
|
+
logger.info(c.dim('Vite development server is starting…'), {
|
|
42
|
+
timestamp: true,
|
|
43
|
+
});
|
|
44
|
+
server.watcher.on('ready', () => {
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
logger.info('');
|
|
47
|
+
logger.info(c.green('Watching for file changes…'), {
|
|
48
|
+
timestamp: true,
|
|
49
|
+
});
|
|
50
|
+
logger.info('');
|
|
51
|
+
// NOTE: We want it to show after the Vite intro stuff
|
|
52
|
+
}, 100);
|
|
53
|
+
});
|
|
54
|
+
return () => {
|
|
55
|
+
server.middlewares.use((request, response, next) => {
|
|
56
|
+
const locals = config?.dev?.locals?.({ nodeRequest: request });
|
|
57
|
+
Promise.resolve(nodeAdapter(handler, { logger })(request, response, locals)).catch((error) => next(error));
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugins: server-side build pipeline.
|
|
3
|
+
*
|
|
4
|
+
* After the client build completes, these plugins run a nested
|
|
5
|
+
* `vite build` in SSR mode to produce the server entrypoint.
|
|
6
|
+
*
|
|
7
|
+
* Includes:
|
|
8
|
+
* - Client asset filename collector (from the client writeBundle)
|
|
9
|
+
* - Server build trigger (closeBundle)
|
|
10
|
+
* - Virtual entrypoint codegen
|
|
11
|
+
* - Server-to-client asset mover
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
import { type PluginOption } from 'vite';
|
|
16
|
+
import type { PluginSharedState } from './plugin-shared-state.js';
|
|
17
|
+
/**
|
|
18
|
+
* Tracks client bundle assets so the server build can reference them
|
|
19
|
+
* with their hashed filenames.
|
|
20
|
+
*/
|
|
21
|
+
export declare function gracileCollectClientAssetsPlugin({ state, }: {
|
|
22
|
+
state: PluginSharedState;
|
|
23
|
+
}): PluginOption;
|
|
24
|
+
/**
|
|
25
|
+
* After the client build finishes (`closeBundle`), run a nested SSR
|
|
26
|
+
* build that produces the server entrypoint and moves assets back into
|
|
27
|
+
* the client output directory.
|
|
28
|
+
*/
|
|
29
|
+
export declare function gracileServerBuildPlugin({ state, virtualRoutesForClient, }: {
|
|
30
|
+
state: PluginSharedState;
|
|
31
|
+
virtualRoutesForClient: PluginOption;
|
|
32
|
+
}): PluginOption;
|
|
33
|
+
//# sourceMappingURL=plugin-server-build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-server-build.d.ts","sourceRoot":"","sources":["../../src/vite/plugin-server-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AAGhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAIlE;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,EAChD,KAAK,GACL,EAAE;IACF,KAAK,EAAE,iBAAiB,CAAC;CACzB,GAAG,YAAY,CAYf;AAID;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,EACxC,KAAK,EACL,sBAAsB,GACtB,EAAE;IACF,KAAK,EAAE,iBAAiB,CAAC;IACzB,sBAAsB,EAAE,YAAY,CAAC;CACrC,GAAG,YAAY,CAqEf"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugins: server-side build pipeline.
|
|
3
|
+
*
|
|
4
|
+
* After the client build completes, these plugins run a nested
|
|
5
|
+
* `vite build` in SSR mode to produce the server entrypoint.
|
|
6
|
+
*
|
|
7
|
+
* Includes:
|
|
8
|
+
* - Client asset filename collector (from the client writeBundle)
|
|
9
|
+
* - Server build trigger (closeBundle)
|
|
10
|
+
* - Virtual entrypoint codegen
|
|
11
|
+
* - Server-to-client asset mover
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { rename, rm } from 'node:fs/promises';
|
|
17
|
+
import { build } from 'vite';
|
|
18
|
+
import { virtualRoutes } from './virtual-routes.js';
|
|
19
|
+
// ── Client asset collector ───────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Tracks client bundle assets so the server build can reference them
|
|
22
|
+
* with their hashed filenames.
|
|
23
|
+
*/
|
|
24
|
+
export function gracileCollectClientAssetsPlugin({ state, }) {
|
|
25
|
+
return {
|
|
26
|
+
name: 'vite-plugin-gracile-collect-client-assets-for-server',
|
|
27
|
+
writeBundle(_, bundle) {
|
|
28
|
+
if (state.outputMode === 'static')
|
|
29
|
+
return;
|
|
30
|
+
for (const file of Object.values(bundle))
|
|
31
|
+
if (file.type === 'asset' && file.name)
|
|
32
|
+
state.clientAssets[file.name] = file.fileName;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// ── Server build trigger ─────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* After the client build finishes (`closeBundle`), run a nested SSR
|
|
39
|
+
* build that produces the server entrypoint and moves assets back into
|
|
40
|
+
* the client output directory.
|
|
41
|
+
*/
|
|
42
|
+
export function gracileServerBuildPlugin({ state, virtualRoutesForClient, }) {
|
|
43
|
+
return {
|
|
44
|
+
name: 'vite-plugin-gracile-server-build',
|
|
45
|
+
apply: 'build',
|
|
46
|
+
config(viteConfig) {
|
|
47
|
+
state.root = viteConfig.root || null;
|
|
48
|
+
},
|
|
49
|
+
async closeBundle() {
|
|
50
|
+
if (state.outputMode === 'static' ||
|
|
51
|
+
!state.routes ||
|
|
52
|
+
!state.renderedRoutes)
|
|
53
|
+
return;
|
|
54
|
+
const root = state.root || process.cwd();
|
|
55
|
+
await build({
|
|
56
|
+
root,
|
|
57
|
+
ssr: { external: ['@gracile/gracile'] },
|
|
58
|
+
build: {
|
|
59
|
+
target: 'esnext',
|
|
60
|
+
ssr: true,
|
|
61
|
+
copyPublicDir: false,
|
|
62
|
+
outDir: 'dist/server',
|
|
63
|
+
ssrEmitAssets: true,
|
|
64
|
+
cssMinify: true,
|
|
65
|
+
cssCodeSplit: true,
|
|
66
|
+
rollupOptions: {
|
|
67
|
+
input: 'entrypoint.js',
|
|
68
|
+
output: {
|
|
69
|
+
entryFileNames: '[name].js',
|
|
70
|
+
assetFileNames: (chunkInfo) => {
|
|
71
|
+
if (chunkInfo.name) {
|
|
72
|
+
const fileName = state.clientAssets[chunkInfo.name];
|
|
73
|
+
if (fileName)
|
|
74
|
+
return fileName;
|
|
75
|
+
// NOTE: When not imported at all from client
|
|
76
|
+
return `assets/${chunkInfo.name.replace(/\.(.*)$/, '')}-[hash].[ext]`;
|
|
77
|
+
}
|
|
78
|
+
return 'assets/[name]-[hash].[ext]';
|
|
79
|
+
},
|
|
80
|
+
chunkFileNames: 'chunk/[name].js',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
plugins: [
|
|
85
|
+
virtualRoutesForClient,
|
|
86
|
+
virtualRoutes({
|
|
87
|
+
routes: state.routes,
|
|
88
|
+
renderedRoutes: state.renderedRoutes,
|
|
89
|
+
}),
|
|
90
|
+
gracileEntrypointPlugin(state),
|
|
91
|
+
gracileMoveServerAssetsPlugin(state),
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// ── Virtual entrypoint ───────────────────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* Generates the virtual `entrypoint.js` module for the server build.
|
|
100
|
+
* This is the server's main entry: it imports routes and creates the
|
|
101
|
+
* Gracile handler.
|
|
102
|
+
*/
|
|
103
|
+
function gracileEntrypointPlugin(state) {
|
|
104
|
+
return {
|
|
105
|
+
name: 'vite-plugin-gracile-entry',
|
|
106
|
+
resolveId(id) {
|
|
107
|
+
if (id === 'entrypoint.js') {
|
|
108
|
+
return id;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
},
|
|
112
|
+
load(id) {
|
|
113
|
+
if (id === 'entrypoint.js' && state.routes && state.renderedRoutes) {
|
|
114
|
+
return `
|
|
115
|
+
import { routeAssets, routeImports, routes } from 'gracile:routes';
|
|
116
|
+
import { createGracileHandler } from '@gracile/gracile/_internals/server-runtime';
|
|
117
|
+
import { createLogger } from '@gracile/gracile/_internals/logger';
|
|
118
|
+
|
|
119
|
+
createLogger();
|
|
120
|
+
|
|
121
|
+
export const handler = createGracileHandler({
|
|
122
|
+
root: process.cwd(),
|
|
123
|
+
routes,
|
|
124
|
+
routeImports,
|
|
125
|
+
routeAssets,
|
|
126
|
+
serverMode: true,
|
|
127
|
+
gracileConfig: ${JSON.stringify(state.gracileConfig, null, 2)}
|
|
128
|
+
});
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// ── Server asset mover ───────────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* After the server build writes its bundle, move any assets from
|
|
138
|
+
* `dist/server/assets/` into `dist/client/assets/` so the client
|
|
139
|
+
* can serve them.
|
|
140
|
+
*/
|
|
141
|
+
function gracileMoveServerAssetsPlugin(state) {
|
|
142
|
+
return {
|
|
143
|
+
name: 'gracile-move-server-assets',
|
|
144
|
+
async writeBundle(_, bundle) {
|
|
145
|
+
const cwd = state.root || process.cwd();
|
|
146
|
+
await Promise.all(Object.entries(bundle).map(async ([file]) => {
|
|
147
|
+
if (file.startsWith('assets/') === false)
|
|
148
|
+
return;
|
|
149
|
+
await rename(join(cwd, `/dist/server/${file}`), join(cwd, `/dist/client/${file}`));
|
|
150
|
+
}));
|
|
151
|
+
// NOTE: Disabled for now, because it conflict with test's folder comparer
|
|
152
|
+
await rm(join(cwd, `/dist/server/assets`), {
|
|
153
|
+
recursive: true,
|
|
154
|
+
}).catch(() => null);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|