@gracile/engine 0.8.2 → 0.9.0-next.10

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 (80) hide show
  1. package/dist/dev/development.d.ts.map +1 -1
  2. package/dist/dev/development.js +1 -1
  3. package/dist/dev/ssr-ce-tracker.d.ts +27 -0
  4. package/dist/dev/ssr-ce-tracker.d.ts.map +1 -0
  5. package/dist/dev/ssr-ce-tracker.js +113 -0
  6. package/dist/errors/create-vite-better-error.js +1 -1
  7. package/dist/plugin.d.ts.map +1 -1
  8. package/dist/plugin.js +56 -256
  9. package/dist/render/light-dom.d.ts +2 -0
  10. package/dist/render/light-dom.d.ts.map +1 -0
  11. package/dist/render/light-dom.js +31 -0
  12. package/dist/render/{utils.d.ts → lit-ssr.d.ts} +1 -1
  13. package/dist/render/lit-ssr.d.ts.map +1 -0
  14. package/dist/render/route-template-pipeline.d.ts +64 -0
  15. package/dist/render/route-template-pipeline.d.ts.map +1 -0
  16. package/dist/render/route-template-pipeline.js +144 -0
  17. package/dist/render/route-template.d.ts +4 -3
  18. package/dist/render/route-template.d.ts.map +1 -1
  19. package/dist/render/route-template.js +41 -87
  20. package/dist/routes/collect.d.ts +5 -1
  21. package/dist/routes/collect.d.ts.map +1 -1
  22. package/dist/routes/collect.js +11 -6
  23. package/dist/routes/load-module.d.ts.map +1 -1
  24. package/dist/routes/load-module.js +5 -2
  25. package/dist/routes/match.d.ts +31 -1
  26. package/dist/routes/match.d.ts.map +1 -1
  27. package/dist/routes/match.js +22 -4
  28. package/dist/routes/render.d.ts.map +1 -1
  29. package/dist/routes/render.js +12 -3
  30. package/dist/server/adapters/hono.d.ts +1 -1
  31. package/dist/server/adapters/hono.d.ts.map +1 -1
  32. package/dist/server/adapters/hono.js +1 -1
  33. package/dist/server/adapters/node.d.ts +1 -1
  34. package/dist/server/adapters/node.d.ts.map +1 -1
  35. package/dist/server/adapters/node.js +2 -2
  36. package/dist/server/request-pipeline.d.ts +109 -0
  37. package/dist/server/request-pipeline.d.ts.map +1 -0
  38. package/dist/server/request-pipeline.js +198 -0
  39. package/dist/server/request.d.ts +3 -16
  40. package/dist/server/request.d.ts.map +1 -1
  41. package/dist/server/request.js +78 -173
  42. package/dist/server/{utils.d.ts → utilities.d.ts} +1 -1
  43. package/dist/server/utilities.d.ts.map +1 -0
  44. package/dist/test/init.d.ts +2 -0
  45. package/dist/test/init.d.ts.map +1 -0
  46. package/dist/test/init.js +7 -0
  47. package/dist/user-config.d.ts +48 -0
  48. package/dist/user-config.d.ts.map +1 -1
  49. package/dist/vite/build-routes.d.ts +1 -2
  50. package/dist/vite/build-routes.d.ts.map +1 -1
  51. package/dist/vite/build-routes.js +0 -98
  52. package/dist/vite/plugin-build-environment.d.ts +21 -0
  53. package/dist/vite/plugin-build-environment.d.ts.map +1 -0
  54. package/dist/vite/plugin-build-environment.js +83 -0
  55. package/dist/vite/plugin-ce-tracker.d.ts +19 -0
  56. package/dist/vite/plugin-ce-tracker.d.ts.map +1 -0
  57. package/dist/vite/plugin-ce-tracker.js +87 -0
  58. package/dist/vite/plugin-client-build.d.ts +20 -0
  59. package/dist/vite/plugin-client-build.d.ts.map +1 -0
  60. package/dist/vite/plugin-client-build.js +55 -0
  61. package/dist/vite/plugin-html-routes-build.d.ts +22 -0
  62. package/dist/vite/plugin-html-routes-build.d.ts.map +1 -0
  63. package/dist/vite/plugin-html-routes-build.js +140 -0
  64. package/dist/vite/plugin-serve.d.ts +18 -0
  65. package/dist/vite/plugin-serve.d.ts.map +1 -0
  66. package/dist/vite/plugin-serve.js +61 -0
  67. package/dist/vite/plugin-server-build.d.ts +43 -0
  68. package/dist/vite/plugin-server-build.d.ts.map +1 -0
  69. package/dist/vite/plugin-server-build.js +108 -0
  70. package/dist/vite/plugin-shared-state.d.ts +33 -0
  71. package/dist/vite/plugin-shared-state.d.ts.map +1 -0
  72. package/dist/vite/plugin-shared-state.js +23 -0
  73. package/dist/vite/virtual-routes.d.ts +8 -5
  74. package/dist/vite/virtual-routes.d.ts.map +1 -1
  75. package/dist/vite/virtual-routes.js +32 -31
  76. package/package.json +11 -11
  77. package/dist/render/utils.d.ts.map +0 -1
  78. package/dist/server/utils.d.ts.map +0 -1
  79. /package/dist/render/{utils.js → lit-ssr.js} +0 -0
  80. /package/dist/server/{utils.js → utilities.js} +0 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Vite plugin that tracks Custom Elements registrations on the server
3
+ * and cleans up orphaned CEs when imports are removed during dev HMR.
4
+ *
5
+ * How it works:
6
+ * 1. `configureServer` — installs the registry wrapper (before any modules load).
7
+ * 2. `transform` (SSR only) — injects module-context markers around files that
8
+ * might call `customElements.define`, so the wrapper knows which module is
9
+ * responsible for each registration.
10
+ * 3. `hotUpdate` — when a file changes, walks the OLD import tree, finds CE
11
+ * modules in that tree, blocks their tags, and invalidates them. If they are
12
+ * still imported after re-evaluation, `define()` fires again and unblocks.
13
+ * If removed, they stay blocked.
14
+ *
15
+ * @internal
16
+ */
17
+ import type { Plugin } from 'vite';
18
+ export declare function gracileCETrackerPlugin(): Plugin;
19
+ //# sourceMappingURL=plugin-ce-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-ce-tracker.d.ts","sourceRoot":"","sources":["../../src/vite/plugin-ce-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAyB,MAAM,MAAM,CAAC;AAmC1D,wBAAgB,sBAAsB,IAAI,MAAM,CA6D/C"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Vite plugin that tracks Custom Elements registrations on the server
3
+ * and cleans up orphaned CEs when imports are removed during dev HMR.
4
+ *
5
+ * How it works:
6
+ * 1. `configureServer` — installs the registry wrapper (before any modules load).
7
+ * 2. `transform` (SSR only) — injects module-context markers around files that
8
+ * might call `customElements.define`, so the wrapper knows which module is
9
+ * responsible for each registration.
10
+ * 3. `hotUpdate` — when a file changes, walks the OLD import tree, finds CE
11
+ * modules in that tree, blocks their tags, and invalidates them. If they are
12
+ * still imported after re-evaluation, `define()` fires again and unblocks.
13
+ * If removed, they stay blocked.
14
+ *
15
+ * @internal
16
+ */
17
+ import { installCeTracker, blockCesForModule, hasCeRegistrations, } from '../dev/ssr-ce-tracker.js';
18
+ // ── Helpers ─────────────────────────────────────────────────────────
19
+ /** Recursively collect all transitive imports of a module. */
20
+ function collectImportTree(module_, seen = new Set()) {
21
+ if (!module_.id || seen.has(module_.id))
22
+ return seen;
23
+ seen.add(module_.id);
24
+ for (const imported of module_.importedModules) {
25
+ collectImportTree(imported, seen);
26
+ }
27
+ return seen;
28
+ }
29
+ /** Heuristic: does this source likely define a Custom Element? */
30
+ function mightDefineCE(code) {
31
+ return (code.includes('customElements.define') ||
32
+ // Lit @customElement decorator — calls define() at eval time.
33
+ // Matches both TS source (`@customElement(`) and compiled output.
34
+ /\bcustomElement\s*\(/.test(code));
35
+ }
36
+ // ── Plugin ──────────────────────────────────────────────────────────
37
+ export function gracileCETrackerPlugin() {
38
+ return {
39
+ name: 'vite-plugin-gracile-ce-tracker',
40
+ configureServer() {
41
+ installCeTracker();
42
+ },
43
+ // Inject module-context markers so the registry wrapper knows
44
+ // which module is responsible for each define() call.
45
+ transform(code, id, options) {
46
+ if (!options?.ssr)
47
+ return;
48
+ if (!mightDefineCE(code))
49
+ return;
50
+ const escaped = JSON.stringify(id);
51
+ return {
52
+ code: [
53
+ `globalThis.__gracile_ce_tracker?.setModule(${escaped});`,
54
+ code,
55
+ `globalThis.__gracile_ce_tracker?.clearModule();`,
56
+ ].join('\n'),
57
+ map: null,
58
+ };
59
+ },
60
+ hotUpdate: {
61
+ order: 'pre',
62
+ handler({ modules, timestamp }) {
63
+ if (this.environment.name !== 'ssr')
64
+ return;
65
+ const invalidated = new Set();
66
+ for (const module_ of modules) {
67
+ if (!module_.id)
68
+ continue;
69
+ // Walk the OLD import tree (graph hasn't updated yet).
70
+ const tree = collectImportTree(module_);
71
+ for (const depId of tree) {
72
+ if (!hasCeRegistrations(depId))
73
+ continue;
74
+ // Block this module's CEs. If the module is still imported
75
+ // after re-evaluation, its define() will unblock them.
76
+ blockCesForModule(depId);
77
+ // Force re-evaluation so define() can re-fire.
78
+ const depModule = this.environment.moduleGraph.getModuleById(depId);
79
+ if (depModule) {
80
+ this.environment.moduleGraph.invalidateModule(depModule, invalidated, timestamp, true);
81
+ }
82
+ }
83
+ }
84
+ },
85
+ },
86
+ };
87
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Vite plugin: client-side build route rendering.
3
+ *
4
+ * During `vite build`, this spins up a temporary dev server to render
5
+ * all routes into static HTML, then populates shared state with the
6
+ * rendered routes and input list.
7
+ *
8
+ * The actual HTML route resolution and asset collection are handled by
9
+ * separate Vite plugins (see `plugin-html-routes-build.ts`) that read
10
+ * from shared state and use `applyToEnvironment` to scope to the client.
11
+ *
12
+ * @internal
13
+ */
14
+ import { type PluginOption } from 'vite';
15
+ import type { PluginSharedState } from './plugin-shared-state.js';
16
+ export declare function gracileClientBuildPlugin({ state, virtualRoutesForClient, }: {
17
+ state: PluginSharedState;
18
+ virtualRoutesForClient: PluginOption;
19
+ }): PluginOption;
20
+ //# 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;;;;;;;;;;;;GAYG;AAGH,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,CAoDf"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Vite plugin: client-side build route rendering.
3
+ *
4
+ * During `vite build`, this spins up a temporary dev server to render
5
+ * all routes into static HTML, then populates shared state with the
6
+ * rendered routes and input list.
7
+ *
8
+ * The actual HTML route resolution and asset collection are handled by
9
+ * separate Vite plugins (see `plugin-html-routes-build.ts`) that read
10
+ * from shared state and use `applyToEnvironment` to scope to the client.
11
+ *
12
+ * @internal
13
+ */
14
+ import { getPluginContext } from '@gracile/internal-utils/plugin-context';
15
+ import { createServer } from 'vite';
16
+ import { buildRoutes } from './build-routes.js';
17
+ export function gracileClientBuildPlugin({ state, virtualRoutesForClient, }) {
18
+ return {
19
+ name: 'vite-plugin-gracile-build',
20
+ apply: 'build',
21
+ async config(viteConfig) {
22
+ state.root = viteConfig.root || process.cwd();
23
+ const viteServerForClientHtmlBuild = await createServer({
24
+ root: state.root,
25
+ server: { middlewareMode: true },
26
+ // NOTE: Stub. KEEP IT!
27
+ optimizeDeps: { include: [] },
28
+ plugins: [virtualRoutesForClient],
29
+ });
30
+ // NOTE: Important. Get the dev. server elements renderers.
31
+ state.gracileConfig.litSsr ??= {};
32
+ state.gracileConfig.litSsr.renderInfo = getPluginContext(viteServerForClientHtmlBuild.config)?.litSsrRenderInfo;
33
+ const htmlPages = await buildRoutes({
34
+ viteServerForBuild: viteServerForClientHtmlBuild,
35
+ root: viteConfig.root || process.cwd(),
36
+ gracileConfig: state.gracileConfig,
37
+ serverMode: state.outputMode === 'server',
38
+ routes: state.routes,
39
+ });
40
+ state.renderedRoutes = htmlPages.renderedRoutes;
41
+ state.clientBuildInputList = htmlPages.inputList;
42
+ // NOTE: Vite's dev server does not invoke Rollup's `closeWatcher`
43
+ // hook when shutting down. Plugins like @rollup/plugin-typescript
44
+ // use `ts.createWatchProgram()` which sets up hundreds of FS
45
+ // watchers; without an explicit `closeWatcher` call they are
46
+ // leaked and the Node process hangs after build.
47
+ for (const plugin of viteServerForClientHtmlBuild.config.plugins) {
48
+ if (typeof plugin.closeWatcher === 'function') {
49
+ await plugin.closeWatcher();
50
+ }
51
+ }
52
+ await viteServerForClientHtmlBuild.close();
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Vite plugins: HTML route resolution and asset collection for builds.
3
+ *
4
+ * These are registered as Vite plugins (not Rollup plugins via
5
+ * `rollupOptions.plugins`) so they work correctly with the Environment
6
+ * API's `builder.build(env)`, which does not inherit top-level
7
+ * `rollupOptions` into environment builds.
8
+ *
9
+ * They use `applyToEnvironment` to scope to the client build only,
10
+ * preventing the SSR build from trying to resolve HTML entries.
11
+ *
12
+ * Data is read lazily from shared state, which is populated by the
13
+ * client build plugin's `config` hook before these hooks run.
14
+ *
15
+ * @internal
16
+ */
17
+ import { type PluginOption } from 'vite';
18
+ import type { PluginSharedState } from './plugin-shared-state.js';
19
+ export declare function gracileHtmlRoutesBuildPlugins({ state, }: {
20
+ state: PluginSharedState;
21
+ }): PluginOption[];
22
+ //# sourceMappingURL=plugin-html-routes-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-html-routes-build.d.ts","sourceRoot":"","sources":["../../src/vite/plugin-html-routes-build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AAIvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AASlE,wBAAgB,6BAA6B,CAAC,EAC7C,KAAK,GACL,EAAE;IACF,KAAK,EAAE,iBAAiB,CAAC;CACzB,GAAG,YAAY,EAAE,CAiJjB"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Vite plugins: HTML route resolution and asset collection for builds.
3
+ *
4
+ * These are registered as Vite plugins (not Rollup plugins via
5
+ * `rollupOptions.plugins`) so they work correctly with the Environment
6
+ * API's `builder.build(env)`, which does not inherit top-level
7
+ * `rollupOptions` into environment builds.
8
+ *
9
+ * They use `applyToEnvironment` to scope to the client build only,
10
+ * preventing the SSR build from trying to resolve HTML entries.
11
+ *
12
+ * Data is read lazily from shared state, which is populated by the
13
+ * client build plugin's `config` hook before these hooks run.
14
+ *
15
+ * @internal
16
+ */
17
+ import { basename, extname, join } from 'node:path';
18
+ import { createFilter } from 'vite';
19
+ import { REGEX_TAG_LINK, REGEX_TAG_SCRIPT } from '../render/route-template.js';
20
+ function stripPremises(input) {
21
+ return input
22
+ .replace(/index\.html$/, '__index.doc.html')
23
+ .replace(/404\.html$/, '__404.doc.html')
24
+ .replace(/500\.html$/, '__500.doc.html');
25
+ }
26
+ export function gracileHtmlRoutesBuildPlugins({ state, }) {
27
+ return [
28
+ {
29
+ name: 'gracile-html-routes',
30
+ apply: 'build',
31
+ enforce: 'pre',
32
+ applyToEnvironment(environment) {
33
+ return environment.name !== 'ssr';
34
+ },
35
+ resolveId(id) {
36
+ const inputList = state.clientBuildInputList;
37
+ const root = state.root;
38
+ if (!inputList || !root)
39
+ return null;
40
+ if (extname(id) === '.html' && inputList.includes(id))
41
+ return join(root, id);
42
+ return null;
43
+ },
44
+ load(id) {
45
+ const renderedRoutes = state.renderedRoutes;
46
+ if (!renderedRoutes)
47
+ return null;
48
+ if (extname(id) === '.html') {
49
+ if (['index.html', '404.html', '500.html'].includes(basename(id))) {
50
+ const content = renderedRoutes.find((index) => index.absoluteId === id)?.html;
51
+ if (content)
52
+ return content;
53
+ }
54
+ if (state.gracileConfig.pages?.premises?.expose) {
55
+ if (basename(id).endsWith('doc.html')) {
56
+ const content = renderedRoutes.find((index) => stripPremises(index.absoluteId) === id)?.static.document;
57
+ if (content)
58
+ return content;
59
+ }
60
+ const content = renderedRoutes.find((index) => index.name === basename(id))?.html;
61
+ if (content)
62
+ return content;
63
+ }
64
+ }
65
+ return null;
66
+ },
67
+ },
68
+ {
69
+ name: 'gracile-collect-handler-assets',
70
+ apply: 'build',
71
+ enforce: 'post',
72
+ applyToEnvironment(environment) {
73
+ return environment.name !== 'ssr';
74
+ },
75
+ buildStart() {
76
+ const renderedRoutes = state.renderedRoutes;
77
+ if (!renderedRoutes)
78
+ return;
79
+ if (!state.gracileConfig.pages?.premises?.expose)
80
+ return;
81
+ const premisesFilter = createFilter(state.gracileConfig.pages.premises.include, state.gracileConfig.pages.premises.exclude);
82
+ for (const route of renderedRoutes) {
83
+ if (premisesFilter(route.name) === false)
84
+ continue;
85
+ if (state.outputMode === 'server' && route.savePrerender !== true)
86
+ continue;
87
+ const fileNameParts = route.name.split('/');
88
+ const last = fileNameParts.pop();
89
+ const properties = last?.replace(/(.*)\.html$/, (_, r) => `__${r}.props.json`);
90
+ if (!properties)
91
+ throw new Error('No props.');
92
+ fileNameParts.push(properties);
93
+ const fileName = fileNameParts.join('/');
94
+ this.emitFile({
95
+ fileName,
96
+ type: 'asset',
97
+ source: JSON.stringify(route.static.props ?? {}),
98
+ });
99
+ }
100
+ },
101
+ generateBundle(_, bundle) {
102
+ const renderedRoutes = state.renderedRoutes;
103
+ if (!renderedRoutes)
104
+ return;
105
+ if (state.outputMode !== 'server')
106
+ return;
107
+ for (const fileKey in bundle) {
108
+ const file = bundle[fileKey];
109
+ if (fileKey.endsWith('.html') && file && 'source' in file) {
110
+ const source = file.source.toString();
111
+ const collectedAssets = [
112
+ ...[...source.matchAll(REGEX_TAG_SCRIPT)]
113
+ .map((v) => v[0])
114
+ // NOTE: Too brittle
115
+ .filter((v) => v.includes(`type="module"`)),
116
+ ...[...source.matchAll(REGEX_TAG_LINK)]
117
+ .map((v) => v[0])
118
+ // NOTE: Too brittle
119
+ .filter((v) => /rel="(stylesheet|modulepreload)"/.test(v)),
120
+ ].join('\n');
121
+ const route = renderedRoutes.find((r) => {
122
+ if (state.gracileConfig.pages?.premises?.expose)
123
+ return (`/${r.name}` ===
124
+ `/${fileKey}`.replace(/(.*?)\/__(.*?)\.doc\.html$/, (a, b, c) => `${b}/${c}.html`));
125
+ return r.name === fileKey;
126
+ });
127
+ if (route)
128
+ route.handlerAssets = collectedAssets;
129
+ if (route?.savePrerender !== true) {
130
+ delete bundle[fileKey];
131
+ // NOTE: Not sure if it's useful
132
+ if (route?.html)
133
+ route.html = null;
134
+ }
135
+ }
136
+ }
137
+ },
138
+ },
139
+ ];
140
+ }
@@ -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,CA4Df"}
@@ -0,0 +1,61 @@
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
+ // Reset so dev-mode config hot-reloads don't hit the guard.
31
+ resetClientBuiltFlag();
32
+ const version = getVersion();
33
+ logger.info(`${c.cyan(c.italic(c.underline('🧚 Gracile')))}` +
34
+ ` ${c.dim(`~`)} ${c.green(`v${version ?? 'X'}`)}`);
35
+ const { handler } = await createDevelopmentHandler({
36
+ routes: state.routes,
37
+ vite: server,
38
+ gracileConfig: state.gracileConfig,
39
+ });
40
+ logger.info(c.dim('Vite development server is starting…'), {
41
+ timestamp: true,
42
+ });
43
+ server.watcher.on('ready', () => {
44
+ setTimeout(() => {
45
+ logger.info('');
46
+ logger.info(c.green('Watching for file changes…'), {
47
+ timestamp: true,
48
+ });
49
+ logger.info('');
50
+ // NOTE: We want it to show after the Vite intro stuff
51
+ }, 100);
52
+ });
53
+ return () => {
54
+ server.middlewares.use((request, response, next) => {
55
+ const locals = config?.dev?.locals?.({ nodeRequest: request });
56
+ Promise.resolve(nodeAdapter(handler, { logger })(request, response, locals)).catch((error) => next(error));
57
+ });
58
+ };
59
+ },
60
+ };
61
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Vite plugins: server-side build pipeline.
3
+ *
4
+ * Uses the Vite Environment API (`builder.buildApp()`) to coordinate
5
+ * client and SSR builds instead of a nested `build()` call.
6
+ *
7
+ * Includes:
8
+ * - Client asset filename collector (client env writeBundle)
9
+ * - Virtual entrypoint codegen (SSR env only)
10
+ * - Server-to-client asset mover (SSR env only)
11
+ *
12
+ * @internal
13
+ */
14
+ import type { PluginOption } from 'vite';
15
+ import type { PluginSharedState } from './plugin-shared-state.js';
16
+ /**
17
+ * Tracks client bundle assets so the server build can reference them
18
+ * with their hashed filenames.
19
+ */
20
+ export declare function gracileCollectClientAssetsPlugin({ state, }: {
21
+ state: PluginSharedState;
22
+ }): PluginOption;
23
+ /**
24
+ * Generates the virtual `entrypoint.js` module for the server build.
25
+ * This is the server's main entry: it imports routes and creates the
26
+ * Gracile handler.
27
+ *
28
+ * Scoped to the SSR environment via `applyToEnvironment`.
29
+ */
30
+ export declare function gracileEntrypointPlugin({ state, }: {
31
+ state: PluginSharedState;
32
+ }): PluginOption;
33
+ /**
34
+ * After the server build writes its bundle, move any assets from
35
+ * `dist/server/assets/` into `dist/client/assets/` so the client
36
+ * can serve them.
37
+ *
38
+ * Scoped to the SSR environment via `applyToEnvironment`.
39
+ */
40
+ export declare function gracileMoveServerAssetsPlugin({ state, }: {
41
+ state: PluginSharedState;
42
+ }): PluginOption;
43
+ //# 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;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,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,CAgBf;AAID;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,EACvC,KAAK,GACL,EAAE;IACF,KAAK,EAAE,iBAAiB,CAAC;CACzB,GAAG,YAAY,CAsCf;AAID;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,EAC7C,KAAK,GACL,EAAE;IACF,KAAK,EAAE,iBAAiB,CAAC;CACzB,GAAG,YAAY,CA2Bf"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Vite plugins: server-side build pipeline.
3
+ *
4
+ * Uses the Vite Environment API (`builder.buildApp()`) to coordinate
5
+ * client and SSR builds instead of a nested `build()` call.
6
+ *
7
+ * Includes:
8
+ * - Client asset filename collector (client env writeBundle)
9
+ * - Virtual entrypoint codegen (SSR env only)
10
+ * - Server-to-client asset mover (SSR env only)
11
+ *
12
+ * @internal
13
+ */
14
+ import { join } from 'node:path';
15
+ import { rename, rm } from 'node:fs/promises';
16
+ // ── Client asset collector ───────────────────────────────────────────
17
+ /**
18
+ * Tracks client bundle assets so the server build can reference them
19
+ * with their hashed filenames.
20
+ */
21
+ export function gracileCollectClientAssetsPlugin({ state, }) {
22
+ return {
23
+ name: 'vite-plugin-gracile-collect-client-assets-for-server',
24
+ applyToEnvironment(environment) {
25
+ return environment.name !== 'ssr';
26
+ },
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
+ // ── Virtual entrypoint ───────────────────────────────────────────────
37
+ /**
38
+ * Generates the virtual `entrypoint.js` module for the server build.
39
+ * This is the server's main entry: it imports routes and creates the
40
+ * Gracile handler.
41
+ *
42
+ * Scoped to the SSR environment via `applyToEnvironment`.
43
+ */
44
+ export function gracileEntrypointPlugin({ state, }) {
45
+ return {
46
+ name: 'vite-plugin-gracile-entry',
47
+ apply: 'build',
48
+ applyToEnvironment(environment) {
49
+ return environment.name === 'ssr';
50
+ },
51
+ resolveId(id) {
52
+ if (id === 'entrypoint.js') {
53
+ return id;
54
+ }
55
+ return null;
56
+ },
57
+ load(id) {
58
+ if (id === 'entrypoint.js' && state.routes && state.renderedRoutes) {
59
+ return `
60
+ import { routeAssets, routeImports, routes } from 'gracile:routes';
61
+ import { createGracileHandler } from '@gracile/gracile/_internals/server-runtime';
62
+ import { createLogger } from '@gracile/gracile/_internals/logger';
63
+
64
+ createLogger();
65
+
66
+ export const handler = createGracileHandler({
67
+ root: process.cwd(),
68
+ routes,
69
+ routeImports,
70
+ routeAssets,
71
+ serverMode: true,
72
+ gracileConfig: ${JSON.stringify(state.gracileConfig, null, 2)}
73
+ });
74
+ `;
75
+ }
76
+ return null;
77
+ },
78
+ };
79
+ }
80
+ // ── Server asset mover ───────────────────────────────────────────────
81
+ /**
82
+ * After the server build writes its bundle, move any assets from
83
+ * `dist/server/assets/` into `dist/client/assets/` so the client
84
+ * can serve them.
85
+ *
86
+ * Scoped to the SSR environment via `applyToEnvironment`.
87
+ */
88
+ export function gracileMoveServerAssetsPlugin({ state, }) {
89
+ return {
90
+ name: 'gracile-move-server-assets',
91
+ apply: 'build',
92
+ applyToEnvironment(environment) {
93
+ return environment.name === 'ssr';
94
+ },
95
+ async writeBundle(_, bundle) {
96
+ const cwd = state.root || process.cwd();
97
+ await Promise.all(Object.entries(bundle).map(async ([file]) => {
98
+ if (file.startsWith('assets/') === false)
99
+ return;
100
+ await rename(join(cwd, `/dist/server/${file}`), join(cwd, `/dist/client/${file}`));
101
+ }));
102
+ // NOTE: Disabled for now, because it conflict with test's folder comparer
103
+ await rm(join(cwd, `/dist/server/assets`), {
104
+ recursive: true,
105
+ }).catch(() => null);
106
+ },
107
+ };
108
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared mutable state passed between the Gracile Vite plugin phases.
3
+ *
4
+ * This exists because Vite plugins in the same array share data through
5
+ * closures. Making the shared state explicit (instead of ad-hoc `let`
6
+ * variables) makes the data flow between plugins visible and auditable.
7
+ *
8
+ * @internal
9
+ */
10
+ import type { RenderedRouteDefinition } from '../routes/render.js';
11
+ import type { RoutesManifest } from '../routes/route.js';
12
+ import type { GracileConfig } from '../user-config.js';
13
+ export interface PluginSharedState {
14
+ /** Collected file-system routes (populated during dev and build). */
15
+ routes: RoutesManifest;
16
+ /** Rendered route definitions from the client build phase. */
17
+ renderedRoutes: RenderedRouteDefinition[] | null;
18
+ /** Rollup input list for the client build (populated by route renderer). */
19
+ clientBuildInputList: string[] | null;
20
+ /** Maps original asset names → hashed filenames from the client bundle. */
21
+ clientAssets: Record<string, string>;
22
+ /** Vite `root` captured during build config. */
23
+ root: string | null;
24
+ /** The resolved Gracile configuration. */
25
+ gracileConfig: GracileConfig;
26
+ /** The output mode: 'static' or 'server'. */
27
+ outputMode: 'static' | 'server';
28
+ }
29
+ /**
30
+ * Create a fresh shared state object.
31
+ */
32
+ export declare function createPluginSharedState(config: GracileConfig | undefined): PluginSharedState;
33
+ //# sourceMappingURL=plugin-shared-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-shared-state.d.ts","sourceRoot":"","sources":["../../src/vite/plugin-shared-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IACjC,qEAAqE;IACrE,MAAM,EAAE,cAAc,CAAC;IAEvB,8DAA8D;IAC9D,cAAc,EAAE,uBAAuB,EAAE,GAAG,IAAI,CAAC;IAEjD,4EAA4E;IAC5E,oBAAoB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEtC,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC,gDAAgD;IAChD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpB,0CAA0C;IAC1C,aAAa,EAAE,aAAa,CAAC;IAE7B,6CAA6C;IAC7C,UAAU,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,aAAa,GAAG,SAAS,GAC/B,iBAAiB,CAUnB"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared mutable state passed between the Gracile Vite plugin phases.
3
+ *
4
+ * This exists because Vite plugins in the same array share data through
5
+ * closures. Making the shared state explicit (instead of ad-hoc `let`
6
+ * variables) makes the data flow between plugins visible and auditable.
7
+ *
8
+ * @internal
9
+ */
10
+ /**
11
+ * Create a fresh shared state object.
12
+ */
13
+ export function createPluginSharedState(config) {
14
+ return {
15
+ routes: new Map(),
16
+ renderedRoutes: null,
17
+ clientBuildInputList: null,
18
+ clientAssets: {},
19
+ root: null,
20
+ gracileConfig: config || {},
21
+ outputMode: config?.output || 'static',
22
+ };
23
+ }