@gracile/engine 0.9.0-next.5 → 0.9.0-next.7
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/dev/development.d.ts.map +1 -1
- package/dist/dev/development.js +1 -1
- package/dist/dev/ssr-ce-tracker.d.ts +27 -0
- package/dist/dev/ssr-ce-tracker.d.ts.map +1 -0
- package/dist/dev/ssr-ce-tracker.js +113 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -0
- package/dist/render/route-template.d.ts.map +1 -1
- package/dist/render/route-template.js +5 -1
- package/dist/routes/collect.d.ts +2 -2
- package/dist/routes/collect.d.ts.map +1 -1
- package/dist/routes/collect.js +7 -6
- package/dist/routes/match.d.ts +6 -2
- package/dist/routes/match.d.ts.map +1 -1
- package/dist/routes/match.js +19 -3
- package/dist/routes/render.d.ts.map +1 -1
- package/dist/routes/render.js +9 -2
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +20 -3
- package/dist/user-config.d.ts +13 -0
- package/dist/user-config.d.ts.map +1 -1
- package/dist/vite/plugin-ce-tracker.d.ts +19 -0
- package/dist/vite/plugin-ce-tracker.d.ts.map +1 -0
- package/dist/vite/plugin-ce-tracker.js +87 -0
- package/dist/vite/plugin-client-build.d.ts.map +1 -1
- package/dist/vite/plugin-client-build.js +10 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"development.d.ts","sourceRoot":"","sources":["../../src/dev/development.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAEN,KAAK,cAAc,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,wBAAsB,wBAAwB,CAAC,EAC9C,MAAM,EACN,IAAI,EACJ,aAAa,GACb,EAAE;IACF,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;IACpB,aAAa,EAAE,aAAa,CAAC;CAC7B,GAAG,OAAO,CAAC;IACX,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,cAAc,CAAC;CACvB,CAAC,
|
|
1
|
+
{"version":3,"file":"development.d.ts","sourceRoot":"","sources":["../../src/dev/development.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAEN,KAAK,cAAc,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,wBAAsB,wBAAwB,CAAC,EAC9C,MAAM,EACN,IAAI,EACJ,aAAa,GACb,EAAE;IACF,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;IACpB,aAAa,EAAE,aAAa,CAAC;CAC7B,GAAG,OAAO,CAAC;IACX,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,cAAc,CAAC;CACvB,CAAC,CAqDD"}
|
package/dist/dev/development.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function createDevelopmentHandler({ routes, vite, gracileConfig, })
|
|
|
10
10
|
logger.info('');
|
|
11
11
|
logger.info(c.dim('Creating the request handler…'), { timestamp: true });
|
|
12
12
|
const collectAndCodegen = async () => {
|
|
13
|
-
await collectRoutes(routes, root, gracileConfig.routes?.exclude);
|
|
13
|
+
await collectRoutes(routes, root, gracileConfig.routes?.exclude, gracileConfig.trailingSlash);
|
|
14
14
|
if (gracileConfig.experimental?.generateRoutesTypings)
|
|
15
15
|
await generateRoutesTypings(root, routes).catch((error) => logger.error(String(error)));
|
|
16
16
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side Custom Elements registry tracker for dev HMR.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the Lit SSR DOM shim's `customElements` so orphaned CEs
|
|
5
|
+
* can be blocked when their import is removed during development.
|
|
6
|
+
*
|
|
7
|
+
* @internal — dev-only, never runs in production builds.
|
|
8
|
+
*/
|
|
9
|
+
export declare function installCeTracker(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Block all CEs registered by a given module.
|
|
12
|
+
* They will be unblocked if the module is re-evaluated and calls define() again.
|
|
13
|
+
*/
|
|
14
|
+
export declare function blockCesForModule(moduleId: string): void;
|
|
15
|
+
/** Check whether a module has registered any CEs. */
|
|
16
|
+
export declare function hasCeRegistrations(moduleId: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Reset tracking state.
|
|
19
|
+
* @param full - Also reset installation state (for test suite teardown
|
|
20
|
+
* so a subsequent `installCeTracker` call takes effect).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resetCeTracker(full?: boolean): void;
|
|
23
|
+
/** Read-only view of currently blocked tags. For test assertions. */
|
|
24
|
+
export declare function getBlockedTags(): ReadonlySet<string>;
|
|
25
|
+
/** Read-only view of module → tag-names mappings. For test assertions. */
|
|
26
|
+
export declare function getModuleToTags(): ReadonlyMap<string, ReadonlySet<string>>;
|
|
27
|
+
//# sourceMappingURL=ssr-ce-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-ce-tracker.d.ts","sourceRoot":"","sources":["../../src/dev/ssr-ce-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwDH,wBAAgB,gBAAgB,SA+B/B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,QAMjD;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,UAAQ,QAK1C;AAED,qEAAqE;AACrE,wBAAgB,cAAc,IAAI,WAAW,CAAC,MAAM,CAAC,CAEpD;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,IAAI,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAE1E"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side Custom Elements registry tracker for dev HMR.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the Lit SSR DOM shim's `customElements` so orphaned CEs
|
|
5
|
+
* can be blocked when their import is removed during development.
|
|
6
|
+
*
|
|
7
|
+
* @internal — dev-only, never runs in production builds.
|
|
8
|
+
*/
|
|
9
|
+
// ── Tracking state ──────────────────────────────────────────────────
|
|
10
|
+
/** module ID → tag names that module registered */
|
|
11
|
+
const moduleToTags = new Map();
|
|
12
|
+
/** Tags blocked from registry lookup (orphaned modules) */
|
|
13
|
+
const blocked = new Set();
|
|
14
|
+
/** Which module is currently being evaluated (set by transform-injected code) */
|
|
15
|
+
let currentModuleId = null;
|
|
16
|
+
let installed = false;
|
|
17
|
+
// ── Registry wrapper ────────────────────────────────────────────────
|
|
18
|
+
function wrapRegistry(registry) {
|
|
19
|
+
const origDefine = registry.define.bind(registry);
|
|
20
|
+
const origGet = registry.get.bind(registry);
|
|
21
|
+
registry.define = function (name, ctor, options) {
|
|
22
|
+
// If re-registered after being blocked, unblock.
|
|
23
|
+
blocked.delete(name);
|
|
24
|
+
if (currentModuleId) {
|
|
25
|
+
let tags = moduleToTags.get(currentModuleId);
|
|
26
|
+
if (!tags) {
|
|
27
|
+
tags = new Set();
|
|
28
|
+
moduleToTags.set(currentModuleId, tags);
|
|
29
|
+
}
|
|
30
|
+
tags.add(name);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
origDefine(name, ctor, options);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Already defined — expected during HMR re-evaluation.
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
registry.get = function (name) {
|
|
40
|
+
if (blocked.has(name))
|
|
41
|
+
return;
|
|
42
|
+
return origGet(name);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// ── Public API ──────────────────────────────────────────────────────
|
|
46
|
+
export function installCeTracker() {
|
|
47
|
+
if (installed)
|
|
48
|
+
return;
|
|
49
|
+
installed = true;
|
|
50
|
+
if (globalThis.customElements) {
|
|
51
|
+
wrapRegistry(globalThis.customElements);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Shim not yet installed — intercept the assignment so we can
|
|
55
|
+
// wrap it the moment Lit SSR's DOM shim sets it up.
|
|
56
|
+
let _ce;
|
|
57
|
+
Object.defineProperty(globalThis, 'customElements', {
|
|
58
|
+
get: () => _ce,
|
|
59
|
+
set(value) {
|
|
60
|
+
_ce = value;
|
|
61
|
+
wrapRegistry(value);
|
|
62
|
+
},
|
|
63
|
+
configurable: true,
|
|
64
|
+
enumerable: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Expose for transform-injected code.
|
|
68
|
+
// Runs in the same process via Vite's ssrLoadModule.
|
|
69
|
+
globalThis['__gracile_ce_tracker'] = {
|
|
70
|
+
setModule(id) {
|
|
71
|
+
currentModuleId = id;
|
|
72
|
+
},
|
|
73
|
+
clearModule() {
|
|
74
|
+
currentModuleId = null;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Block all CEs registered by a given module.
|
|
80
|
+
* They will be unblocked if the module is re-evaluated and calls define() again.
|
|
81
|
+
*/
|
|
82
|
+
export function blockCesForModule(moduleId) {
|
|
83
|
+
const tags = moduleToTags.get(moduleId);
|
|
84
|
+
if (!tags)
|
|
85
|
+
return;
|
|
86
|
+
for (const tag of tags) {
|
|
87
|
+
blocked.add(tag);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Check whether a module has registered any CEs. */
|
|
91
|
+
export function hasCeRegistrations(moduleId) {
|
|
92
|
+
return moduleToTags.has(moduleId);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Reset tracking state.
|
|
96
|
+
* @param full - Also reset installation state (for test suite teardown
|
|
97
|
+
* so a subsequent `installCeTracker` call takes effect).
|
|
98
|
+
*/
|
|
99
|
+
export function resetCeTracker(full = false) {
|
|
100
|
+
moduleToTags.clear();
|
|
101
|
+
blocked.clear();
|
|
102
|
+
currentModuleId = null;
|
|
103
|
+
if (full)
|
|
104
|
+
installed = false;
|
|
105
|
+
}
|
|
106
|
+
/** Read-only view of currently blocked tags. For test assertions. */
|
|
107
|
+
export function getBlockedTags() {
|
|
108
|
+
return blocked;
|
|
109
|
+
}
|
|
110
|
+
/** Read-only view of module → tag-names mappings. For test assertions. */
|
|
111
|
+
export function getModuleToTags() {
|
|
112
|
+
return moduleToTags;
|
|
113
|
+
}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAetD;;;;;;;;;;;;;;;;GAgBG;AAIH,eAAO,MAAM,OAAO,GAAI,SAAS,aAAa,KAAG,GAAG,EAoEnD,CAAC;AAEF,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createPluginSharedState } from './vite/plugin-shared-state.js';
|
|
|
7
7
|
import { gracileServePlugin } from './vite/plugin-serve.js';
|
|
8
8
|
import { gracileClientBuildPlugin } from './vite/plugin-client-build.js';
|
|
9
9
|
import { gracileCollectClientAssetsPlugin, gracileServerBuildPlugin, } from './vite/plugin-server-build.js';
|
|
10
|
+
import { gracileCETrackerPlugin } from './vite/plugin-ce-tracker.js';
|
|
10
11
|
let isClientBuilt = false;
|
|
11
12
|
/**
|
|
12
13
|
* The main Vite plugin for loading the Gracile framework.
|
|
@@ -53,6 +54,8 @@ export const gracile = (config) => {
|
|
|
53
54
|
sharedPluginContext.litSsrRenderInfo;
|
|
54
55
|
},
|
|
55
56
|
},
|
|
57
|
+
// MARK: 1.5. CE registry tracker (dev HMR cleanup)
|
|
58
|
+
gracileCETrackerPlugin(),
|
|
56
59
|
// MARK: 2. HMR SSR reload
|
|
57
60
|
hmrSsrReload(),
|
|
58
61
|
// MARK: 3. Dev serve middleware
|
|
@@ -1 +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;AAIvC,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
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;AAIvC,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAOxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAO1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,KAAK,CAAC,MAAM,oBAAoB,CAAC;AAa7C,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEhF,wBAAsB,mBAAmB,CAAC,EACzC,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,UAAU,EACV,WAAW,EACX,UAAU,EACV,OAAO,EACP,UAAU,GACV,EAAE;IACF,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,CAAC,YAAY,GAAG,SAAS,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;CAC7C,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAC;IAAC,QAAQ,EAAE,IAAI,GAAG,MAAM,CAAA;CAAE,CAAC,CA4JhE"}
|
|
@@ -32,6 +32,9 @@ export async function renderRouteTemplate({ url, vite, mode, routeInfos, routeAs
|
|
|
32
32
|
});
|
|
33
33
|
const fragmentRender = renderLitSsr(fragmentOutput, mergedRenderInfo);
|
|
34
34
|
const output = Readable.from(fragmentRender);
|
|
35
|
+
// TODO: Disabled for now. Causes issue in static renders.
|
|
36
|
+
// Needs investigations.
|
|
37
|
+
// const output = new RenderResultReadable(fragmentRender);
|
|
35
38
|
return { output, document: null };
|
|
36
39
|
}
|
|
37
40
|
// MARK: Document
|
|
@@ -87,7 +90,8 @@ export async function renderRouteTemplate({ url, vite, mode, routeInfos, routeAs
|
|
|
87
90
|
const routeOutput = await Promise.resolve(routeInfos.routeModule.template(context));
|
|
88
91
|
if (assert.isLitTemplate(routeOutput) === false)
|
|
89
92
|
throw new Error(`Wrong template result for page template ${routeInfos.foundRoute.filePath}.`);
|
|
90
|
-
const renderStream =
|
|
93
|
+
const renderStream =
|
|
94
|
+
/* TODO: Use `new RenderResultReadable` */ Readable.from(renderLitSsr(routeOutput, mergedRenderInfo));
|
|
91
95
|
const output = Readable.from(concatStreams(baseDocumentRenderStreamPre, renderStream, baseDocumentRenderStreamPost));
|
|
92
96
|
return { output, document: baseDocumentHtml };
|
|
93
97
|
}
|
package/dist/routes/collect.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type * as R from './route.js';
|
|
2
2
|
/** @internal Exported for unit testing. */
|
|
3
|
-
export declare function extractRoutePatterns(routeFilePath: string): Pick<R.Route, 'pattern' | 'hasParams'> & {
|
|
3
|
+
export declare function extractRoutePatterns(routeFilePath: string, trailingSlash?: 'always' | 'never' | 'ignore'): Pick<R.Route, 'pattern' | 'hasParams'> & {
|
|
4
4
|
patternString: string;
|
|
5
5
|
};
|
|
6
6
|
export declare const WATCHED_FILES_REGEX: RegExp;
|
|
7
|
-
export declare function collectRoutes(routes: R.RoutesManifest, root: string, excludePatterns?: string[]): Promise<void>;
|
|
7
|
+
export declare function collectRoutes(routes: R.RoutesManifest, root: string, excludePatterns?: string[], trailingSlash?: 'always' | 'never' | 'ignore'): Promise<void>;
|
|
8
8
|
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/routes/collect.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,KAAK,CAAC,MAAM,YAAY,CAAC;AAIrC,2CAA2C;AAC3C,wBAAgB,oBAAoB,CACnC,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/routes/collect.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,KAAK,CAAC,MAAM,YAAY,CAAC;AAIrC,2CAA2C;AAC3C,wBAAgB,oBAAoB,CACnC,aAAa,EAAE,MAAM,EACrB,aAAa,GAAE,QAAQ,GAAG,OAAO,GAAG,QAAmB,GACrD,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAAC,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,CAmDpE;AAED,eAAO,MAAM,mBAAmB,QAC4C,CAAC;AAE7E,wBAAsB,aAAa,CAClC,MAAM,EAAE,CAAC,CAAC,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,eAAe,GAAE,MAAM,EAAO,EAC9B,aAAa,GAAE,QAAQ,GAAG,OAAO,GAAG,QAAmB,GACrD,OAAO,CAAC,IAAI,CAAC,CAiGf"}
|
package/dist/routes/collect.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fdir as Fdir } from 'fdir';
|
|
|
5
5
|
import c from 'picocolors';
|
|
6
6
|
// eslint-disable-next-line import-x/order
|
|
7
7
|
import { URLPattern as URLPatternPolyfill } from 'urlpattern-polyfill/urlpattern';
|
|
8
|
-
//
|
|
8
|
+
// HACK: The polyfill type lacks `hasRegExpGroups` from the global URLPattern.
|
|
9
9
|
const URLPattern = URLPatternPolyfill;
|
|
10
10
|
import { createFilter } from 'vite';
|
|
11
11
|
import { emptyRoutes } from '../logging/messages.js';
|
|
@@ -13,7 +13,7 @@ import { prepareSortableRoutes, routeComparator } from './comparator.js';
|
|
|
13
13
|
import { REGEXES } from './load-module.js';
|
|
14
14
|
const logger = getLogger();
|
|
15
15
|
/** @internal Exported for unit testing. */
|
|
16
|
-
export function extractRoutePatterns(routeFilePath) {
|
|
16
|
+
export function extractRoutePatterns(routeFilePath, trailingSlash = 'ignore') {
|
|
17
17
|
const routePathname = routeFilePath.replace(/\.(js|ts|jsx|tsx|html)$/, '');
|
|
18
18
|
let pathParts = routePathname.split(paths.isWindows() ? paths.WINDOWS_PATH_SEPARATOR : '/');
|
|
19
19
|
const last = pathParts.at(-1);
|
|
@@ -42,8 +42,9 @@ export function extractRoutePatterns(routeFilePath) {
|
|
|
42
42
|
}
|
|
43
43
|
return entry;
|
|
44
44
|
});
|
|
45
|
-
const
|
|
46
|
-
const
|
|
45
|
+
const isRoot = pathRelativeNormalized.length === 0;
|
|
46
|
+
const slash = isRoot || trailingSlash === 'never' ? '' : '/';
|
|
47
|
+
const normalizedUrlPattern = `/${pathRelativeNormalized.join('/')}${slash}`;
|
|
47
48
|
return {
|
|
48
49
|
patternString: normalizedUrlPattern,
|
|
49
50
|
pattern: new URLPattern(normalizedUrlPattern, 'http://gracile/'),
|
|
@@ -51,7 +52,7 @@ export function extractRoutePatterns(routeFilePath) {
|
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
export const WATCHED_FILES_REGEX = /\/src\/routes\/(.*)\.(js|ts|jsx|tsx|html|css|scss|sass|less|styl|stylus)$/;
|
|
54
|
-
export async function collectRoutes(routes, root, excludePatterns = []) {
|
|
55
|
+
export async function collectRoutes(routes, root, excludePatterns = [], trailingSlash = 'ignore') {
|
|
55
56
|
routes.clear();
|
|
56
57
|
const routesFolder = 'src/routes';
|
|
57
58
|
const routesFolderAbsolute = join(root, routesFolder);
|
|
@@ -105,7 +106,7 @@ export async function collectRoutes(routes, root, excludePatterns = []) {
|
|
|
105
106
|
// MARK: Associate
|
|
106
107
|
for (const routePath of serverEntrypointsSorted) {
|
|
107
108
|
const filePath = join(routesFolder, routePath);
|
|
108
|
-
const routeWithPatterns = extractRoutePatterns(routePath);
|
|
109
|
+
const routeWithPatterns = extractRoutePatterns(routePath, trailingSlash);
|
|
109
110
|
routes.set(routeWithPatterns.patternString, {
|
|
110
111
|
filePath,
|
|
111
112
|
pattern: routeWithPatterns.pattern,
|
package/dist/routes/match.d.ts
CHANGED
|
@@ -7,8 +7,11 @@ type MatchedRoute = {
|
|
|
7
7
|
params: Parameters_;
|
|
8
8
|
pathname: string;
|
|
9
9
|
};
|
|
10
|
+
export type TrailingSlashRedirect = {
|
|
11
|
+
redirect: string;
|
|
12
|
+
};
|
|
10
13
|
/** @internal Exported for unit testing. */
|
|
11
|
-
export declare function matchRouteFromUrl(url: string, routes: R.RoutesManifest): MatchedRoute | null;
|
|
14
|
+
export declare function matchRouteFromUrl(url: string, routes: R.RoutesManifest, trailingSlash?: 'always' | 'never' | 'ignore'): MatchedRoute | TrailingSlashRedirect | null;
|
|
12
15
|
type ExtractedStaticPaths = {
|
|
13
16
|
staticPaths: R.StaticPathOptionsGeneric[];
|
|
14
17
|
props: unknown;
|
|
@@ -39,6 +42,7 @@ export declare function getRoute(options: {
|
|
|
39
42
|
vite?: ViteDevServer | undefined;
|
|
40
43
|
routes: R.RoutesManifest;
|
|
41
44
|
routeImports?: R.RoutesImports | undefined;
|
|
42
|
-
|
|
45
|
+
trailingSlash?: 'always' | 'never' | 'ignore';
|
|
46
|
+
}): Promise<RouteInfos | TrailingSlashRedirect | null>;
|
|
43
47
|
export {};
|
|
44
48
|
//# sourceMappingURL=match.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/routes/match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,KAAK,CAAC,MAAM,YAAY,CAAC;AAErC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEtD,KAAK,YAAY,GAAG;IACnB,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAChC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,CAAC,cAAc,
|
|
1
|
+
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/routes/match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,KAAK,CAAC,MAAM,YAAY,CAAC;AAErC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEtD,KAAK,YAAY,GAAG;IACnB,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAChC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,CAAC,CAAC,cAAc,EACxB,aAAa,GAAE,QAAQ,GAAG,OAAO,GAAG,QAAmB,GACrD,YAAY,GAAG,qBAAqB,GAAG,IAAI,CAyC7C;AAED,KAAK,oBAAoB,GAAG;IAC3B,WAAW,EAAE,CAAC,CAAC,wBAAwB,EAAE,CAAC;IAC1C,KAAK,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CAAC;AACT;;;;;;GAMG;AACH,2CAA2C;AAC3C,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA6BhC;AAED,MAAM,MAAM,UAAU,GAAG;IACxB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACrC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,QAAQ,CAAC,OAAO,EAAE;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,CAAC,CAAC,cAAc,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,CAAC,aAAa,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;CAC9C,GAAG,OAAO,CAAC,UAAU,GAAG,qBAAqB,GAAG,IAAI,CAAC,CAkCrD"}
|
package/dist/routes/match.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { loadForeignRouteObject } from './load-module.js';
|
|
2
2
|
/** @internal Exported for unit testing. */
|
|
3
|
-
export function matchRouteFromUrl(url, routes) {
|
|
3
|
+
export function matchRouteFromUrl(url, routes, trailingSlash = 'ignore') {
|
|
4
4
|
let match;
|
|
5
5
|
let foundRoute;
|
|
6
|
-
const
|
|
6
|
+
const rawPathname = new URL(url).pathname;
|
|
7
|
+
// Handle redirect cases for 'always' and 'never' before matching.
|
|
8
|
+
// Root '/' is exempt — it always keeps its slash.
|
|
9
|
+
if (rawPathname !== '/') {
|
|
10
|
+
if (trailingSlash === 'always' && !rawPathname.endsWith('/'))
|
|
11
|
+
return { redirect: rawPathname + '/' };
|
|
12
|
+
if (trailingSlash === 'never' && rawPathname.endsWith('/'))
|
|
13
|
+
return { redirect: rawPathname.slice(0, -1) };
|
|
14
|
+
}
|
|
15
|
+
// For 'ignore', normalize to trailing-slash so it matches stored patterns.
|
|
16
|
+
const pathname = trailingSlash === 'ignore' &&
|
|
17
|
+
rawPathname !== '/' &&
|
|
18
|
+
!rawPathname.endsWith('/')
|
|
19
|
+
? rawPathname + '/'
|
|
20
|
+
: rawPathname;
|
|
7
21
|
for (const [, route] of routes) {
|
|
8
22
|
if (match)
|
|
9
23
|
break;
|
|
@@ -49,8 +63,10 @@ export async function extractStaticPaths(options) {
|
|
|
49
63
|
return { staticPaths, props: properties };
|
|
50
64
|
}
|
|
51
65
|
export async function getRoute(options) {
|
|
52
|
-
const matchedRoute = matchRouteFromUrl(options.url, options.routes);
|
|
66
|
+
const matchedRoute = matchRouteFromUrl(options.url, options.routes, options.trailingSlash);
|
|
53
67
|
if (!matchedRoute)
|
|
68
|
+
return null;
|
|
69
|
+
if ('redirect' in matchedRoute)
|
|
54
70
|
return matchedRoute;
|
|
55
71
|
const { foundRoute, pathname, params } = matchedRoute;
|
|
56
72
|
const routeModule = await loadForeignRouteObject({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/routes/render.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,uBAAuB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,MAAM,EAAE;QACP,KAAK,EAAE,OAAO,CAAC;QACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,CAAC;IAEF,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/routes/render.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,uBAAuB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,MAAM,EAAE;QACP,KAAK,EAAE,OAAO,CAAC;QACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,CAAC;IAEF,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;AAwBD,wBAAsB,YAAY,CAAC,EAClC,MAAM,EACN,IAAI,EACJ,UAAU,EACV,IAAoB,EACpB,aAAa,GACb,EAAE;IACF,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,aAAa,CAAC;CAC7B;;;GAwKA"}
|
package/dist/routes/render.js
CHANGED
|
@@ -8,11 +8,18 @@ import { loadForeignRouteObject } from './load-module.js';
|
|
|
8
8
|
async function streamToString(stream) {
|
|
9
9
|
const chunks = [];
|
|
10
10
|
for await (const chunk of stream) {
|
|
11
|
+
// NOTE: Since using Lit's `RenderResultReadable` instead of pure
|
|
12
|
+
// Node Readable, the chunk can be a string or a Buffer.
|
|
13
|
+
// If it's a string, convert it to Buffer first.
|
|
11
14
|
if (typeof chunk === 'string') {
|
|
12
15
|
chunks.push(Buffer.from(chunk));
|
|
13
16
|
}
|
|
14
17
|
else
|
|
15
|
-
throw new TypeError('Wrong buffer');
|
|
18
|
+
throw new TypeError('Wrong buffer type from stream. Should be a `string` only.');
|
|
19
|
+
// NOTE: Disabled for now. Causes issues with `RenderResultReadable`.
|
|
20
|
+
/* else {
|
|
21
|
+
chunks.push(chunk);
|
|
22
|
+
} */
|
|
16
23
|
}
|
|
17
24
|
return Buffer.concat(chunks).toString('utf8');
|
|
18
25
|
}
|
|
@@ -20,7 +27,7 @@ export async function renderRoutes({ routes, vite, serverMode, root = process.cw
|
|
|
20
27
|
const logger = getLogger();
|
|
21
28
|
logger.info(c.green('Rendering routes…'), { timestamp: true });
|
|
22
29
|
// MARK: Collect
|
|
23
|
-
await collectRoutes(routes, root, gracileConfig.routes?.exclude);
|
|
30
|
+
await collectRoutes(routes, root, gracileConfig.routes?.exclude, gracileConfig.trailingSlash);
|
|
24
31
|
const renderedRoutes = [];
|
|
25
32
|
// MARK: Iterate modules
|
|
26
33
|
await Promise.all([...routes].map(async ([patternString, route]) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/server/request.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAOlD,OAAO,KAAK,KAAK,CAAC,MAAM,oBAAoB,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EACN,KAAK,aAAa,EAOlB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,cAAc;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAC5B,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,OAAO,KACZ,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B,wBAAgB,oBAAoB,CAAC,EACpC,IAAI,EACJ,MAAM,EACN,YAAY,EACZ,WAAW,EACX,IAAI,EACJ,UAAU,EACV,aAAa,GACb,EAAE;IACF,IAAI,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,CAAC,CAAC,cAAc,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,CAAC,aAAa,GAAG,SAAS,CAAC;IAC3C,WAAW,CAAC,EAAE,CAAC,CAAC,YAAY,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;CAC7B,
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/server/request.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAOlD,OAAO,KAAK,KAAK,CAAC,MAAM,oBAAoB,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EACN,KAAK,aAAa,EAOlB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,cAAc;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAC5B,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,OAAO,KACZ,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B,wBAAgB,oBAAoB,CAAC,EACpC,IAAI,EACJ,MAAM,EACN,YAAY,EACZ,WAAW,EACX,IAAI,EACJ,UAAU,EACV,aAAa,GACb,EAAE;IACF,IAAI,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,CAAC,CAAC,cAAc,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,CAAC,aAAa,GAAG,SAAS,CAAC;IAC3C,WAAW,CAAC,EAAE,CAAC,CAAC,YAAY,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,aAAa,CAAC;CAC7B,kBAuIA;AAyBD,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,UAAU,GACV,MAAM,uBAAuB,CAAC"}
|
package/dist/server/request.js
CHANGED
|
@@ -25,14 +25,31 @@ export function createGracileHandler({ vite, routes, routeImports, routeAssets,
|
|
|
25
25
|
vite,
|
|
26
26
|
routes,
|
|
27
27
|
routeImports,
|
|
28
|
+
trailingSlash: gracileConfig.trailingSlash ?? 'ignore',
|
|
28
29
|
};
|
|
29
30
|
const responseInit = {};
|
|
30
|
-
|
|
31
|
+
const routeResult = await getRoute(routeOptions);
|
|
32
|
+
// Trailing slash redirect (301 for GET, 308 for other methods)
|
|
33
|
+
if (routeResult && 'redirect' in routeResult) {
|
|
34
|
+
const redirectUrl = new URL(routeResult.redirect, fullUrl).href;
|
|
35
|
+
const status = method === 'GET' ? 301 : 308;
|
|
36
|
+
return { response: Response.redirect(redirectUrl, status) };
|
|
37
|
+
}
|
|
38
|
+
let routeInfos = routeResult;
|
|
31
39
|
if (routeInfos === null) {
|
|
32
40
|
responseInit.status = 404;
|
|
41
|
+
// Use 'ignore' for the internal 404 lookup to avoid redirect loops.
|
|
33
42
|
const url = new URL('/404/', fullUrl).href;
|
|
34
|
-
const options = {
|
|
35
|
-
|
|
43
|
+
const options = {
|
|
44
|
+
...routeOptions,
|
|
45
|
+
url,
|
|
46
|
+
trailingSlash: 'ignore',
|
|
47
|
+
};
|
|
48
|
+
const notFoundResult = await getRoute(options);
|
|
49
|
+
routeInfos =
|
|
50
|
+
notFoundResult && !('redirect' in notFoundResult)
|
|
51
|
+
? notFoundResult
|
|
52
|
+
: null;
|
|
36
53
|
}
|
|
37
54
|
if (routeInfos === null) {
|
|
38
55
|
const page = builtIn404Page(new URL(fullUrl).pathname, Boolean(vite));
|
package/dist/user-config.d.ts
CHANGED
|
@@ -38,6 +38,19 @@ export interface GracileConfig {
|
|
|
38
38
|
* @defaultValue 'static'
|
|
39
39
|
*/
|
|
40
40
|
output?: 'static' | 'server';
|
|
41
|
+
/**
|
|
42
|
+
* Controls how trailing slashes are matched on incoming URLs.
|
|
43
|
+
*
|
|
44
|
+
* - `'ignore'` — Match regardless of whether a trailing `/` is present.
|
|
45
|
+
* `/about` and `/about/` both resolve to the same route. *(default)*
|
|
46
|
+
* - `'always'` — Only match URLs that include a trailing slash (e.g. `/about/`).
|
|
47
|
+
* Requests without one are redirected: `301` for GET, `308` for other methods.
|
|
48
|
+
* - `'never'` — Only match URLs that do not include a trailing slash (e.g. `/about`).
|
|
49
|
+
* Requests with one are redirected: `301` for GET, `308` for other methods.
|
|
50
|
+
*
|
|
51
|
+
* @defaultValue 'ignore'
|
|
52
|
+
*/
|
|
53
|
+
trailingSlash?: 'always' | 'never' | 'ignore';
|
|
41
54
|
/**
|
|
42
55
|
* Settings for the development mode.
|
|
43
56
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-config.d.ts","sourceRoot":"","sources":["../src/user-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,aAAa;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAE7B;;OAEG;IACH,GAAG,CAAC,EAAE;QACL;;;;;WAKG;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;YAAE,WAAW,EAAE,OAAO,CAAC,eAAe,CAAA;SAAE,KAAK,OAAO,CAAC;KACxE,CAAC;IAEF;;OAEG;IACH,MAAM,CAAC,EAAE;QACR;;WAEG;QACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IAEF;;OAEG;IACH,KAAK,CAAC,EAAE;QACP;;;;;;;;;;;;;WAaG;QACH,QAAQ,CAAC,EAAE;YACV;;eAEG;YACH,MAAM,CAAC,EAAE,OAAO,CAAC;YAGjB;;eAEG;YACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;YAEnB;;eAEG;YACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;SACnB,CAAC;KACF,CAAC;IACF,MAAM,CAAC,EAAE;QACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA8BG;QACH,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;KACjC,CAAC;IAEF;;OAEG;IACH,YAAY,CAAC,EAAE;QACd;;;WAGG;QACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;KAOhC,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"user-config.d.ts","sourceRoot":"","sources":["../src/user-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,aAAa;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAE7B;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IAE9C;;OAEG;IACH,GAAG,CAAC,EAAE;QACL;;;;;WAKG;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;YAAE,WAAW,EAAE,OAAO,CAAC,eAAe,CAAA;SAAE,KAAK,OAAO,CAAC;KACxE,CAAC;IAEF;;OAEG;IACH,MAAM,CAAC,EAAE;QACR;;WAEG;QACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IAEF;;OAEG;IACH,KAAK,CAAC,EAAE;QACP;;;;;;;;;;;;;WAaG;QACH,QAAQ,CAAC,EAAE;YACV;;eAEG;YACH,MAAM,CAAC,EAAE,OAAO,CAAC;YAGjB;;eAEG;YACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;YAEnB;;eAEG;YACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;SACnB,CAAC;KACF,CAAC;IACF,MAAM,CAAC,EAAE;QACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA8BG;QACH,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;KACjC,CAAC;IAEF;;OAEG;IACH,YAAY,CAAC,EAAE;QACd;;;WAGG;QACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;KAOhC,CAAC;CACF"}
|
|
@@ -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
|
+
}
|
|
@@ -1 +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,
|
|
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,CA+Df"}
|
|
@@ -34,6 +34,16 @@ export function gracileClientBuildPlugin({ state, virtualRoutesForClient, }) {
|
|
|
34
34
|
routes: state.routes,
|
|
35
35
|
});
|
|
36
36
|
state.renderedRoutes = htmlPages.renderedRoutes;
|
|
37
|
+
// NOTE: Vite's dev server does not invoke Rollup's `closeWatcher`
|
|
38
|
+
// hook when shutting down. Plugins like @rollup/plugin-typescript
|
|
39
|
+
// use `ts.createWatchProgram()` which sets up hundreds of FS
|
|
40
|
+
// watchers; without an explicit `closeWatcher` call they are
|
|
41
|
+
// leaked and the Node process hangs after build.
|
|
42
|
+
for (const plugin of viteServerForClientHtmlBuild.config.plugins) {
|
|
43
|
+
if (typeof plugin.closeWatcher === 'function') {
|
|
44
|
+
await plugin.closeWatcher();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
37
47
|
await viteServerForClientHtmlBuild.close();
|
|
38
48
|
return {
|
|
39
49
|
build: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gracile/engine",
|
|
3
|
-
"version": "0.9.0-next.
|
|
3
|
+
"version": "0.9.0-next.7",
|
|
4
4
|
"description": "A thin, full-stack, web framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"custom-elements",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"!/dist/typedoc-entrypoint.*"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@gracile-labs/better-errors": "^0.2.
|
|
47
|
-
"@gracile/internal-utils": "^0.6.
|
|
46
|
+
"@gracile-labs/better-errors": "^0.2.1-next.0",
|
|
47
|
+
"@gracile/internal-utils": "^0.6.1-next.0",
|
|
48
48
|
"@whatwg-node/server": "^0.10.18",
|
|
49
49
|
"fdir": "^6.5.0",
|
|
50
50
|
"picocolors": "^1.1.1",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"access": "public",
|
|
61
61
|
"provenance": true
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "9abb3f9de0217a6284a88b114194b4d254d0a104"
|
|
64
64
|
}
|