@markless/router 0.1.1 → 0.2.0
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/debug-channel-C7v9u5SM.js +250 -0
- package/dist/index.js +4 -2
- package/dist/typescript-plugin.cjs +227 -0
- package/dist/typescript-plugin.js +92 -2
- package/dist/vite/runtime/create-route-discovery.js +1 -1
- package/dist/vite/runtime/create-server-entry.d.ts +2 -0
- package/dist/vite/runtime/create-server-entry.js +91 -21
- package/dist/vite.js +311 -28
- package/package.json +20 -6
- package/src/vite/entries/server-entry.ts +3 -0
- /package/dist/{request-files-DTkeaKxU.js → request-files-C-0LLSsx.js} +0 -0
- /package/dist/{route-manifest-OTaWQ_TC.js → route-manifest-DPgGIyrS.js} +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region ../web/src/debug-channel.ts
|
|
14
|
+
var debug_channel_exports = /* @__PURE__ */ __exportAll({
|
|
15
|
+
__marklessDebugBootstrapSource: () => __marklessDebugBootstrapSource,
|
|
16
|
+
__marklessDebugRegisterRouter: () => __marklessDebugRegisterRouter
|
|
17
|
+
});
|
|
18
|
+
function installDebugChannelLayer(root, initialPhase, initiallyActive = true) {
|
|
19
|
+
const global = globalThis;
|
|
20
|
+
const previous = global.__MARKLESS_DEBUG__?.version === 1 ? global.__MARKLESS_DEBUG__ : void 0;
|
|
21
|
+
let priorRoot;
|
|
22
|
+
try {
|
|
23
|
+
priorRoot = previous?.explainInteraction(root, "__markless_debug_root__");
|
|
24
|
+
} catch {}
|
|
25
|
+
const priorContainer = priorRoot && "containerId" in priorRoot ? previous?.containers.find((entry) => entry.id === priorRoot.containerId) : void 0;
|
|
26
|
+
const seed = `${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
27
|
+
const id = priorContainer?.id ?? `container:${seed}`;
|
|
28
|
+
const rootKey = priorContainer?.root.key ?? `root:${seed}`;
|
|
29
|
+
const rootRef = new WeakRef(root);
|
|
30
|
+
const events = /* @__PURE__ */ new WeakMap();
|
|
31
|
+
const keys = /* @__PURE__ */ new WeakMap();
|
|
32
|
+
const invalidated = /* @__PURE__ */ new WeakSet();
|
|
33
|
+
const routerSources = global[Symbol.for("markless.debug.channel.v1.router-sources")] ??= /* @__PURE__ */ new Set();
|
|
34
|
+
const ownViolations = [];
|
|
35
|
+
let phase = initialPhase;
|
|
36
|
+
let lifecycle = initiallyActive ? "active" : "registered";
|
|
37
|
+
let boundarySnapshots = Object.freeze([]);
|
|
38
|
+
let nextElement = 1;
|
|
39
|
+
let nextSequence = (previous?.violations.at(-1)?.sequence ?? 0) + 1;
|
|
40
|
+
let dropped = 0;
|
|
41
|
+
let pruned = false;
|
|
42
|
+
const freeze = (value) => Object.freeze(value);
|
|
43
|
+
const contains = (element) => {
|
|
44
|
+
const currentRoot = rootRef.deref();
|
|
45
|
+
return !!currentRoot && (currentRoot === element || currentRoot.contains?.(element) === true);
|
|
46
|
+
};
|
|
47
|
+
const keyFor = (element) => {
|
|
48
|
+
let key = keys.get(element);
|
|
49
|
+
if (!key) {
|
|
50
|
+
key = `${rootKey}:element:${nextElement++}`;
|
|
51
|
+
keys.set(element, key);
|
|
52
|
+
}
|
|
53
|
+
return key;
|
|
54
|
+
};
|
|
55
|
+
const eligibleAnchor = (element) => {
|
|
56
|
+
const anchor = element.closest?.("a[href]");
|
|
57
|
+
if (!anchor?.hasAttribute("data-markless-router-link") || anchor.hasAttribute("download")) return false;
|
|
58
|
+
const target = anchor.getAttribute("target");
|
|
59
|
+
if (target && target !== "_self" || anchor.relList?.contains("external")) return false;
|
|
60
|
+
try {
|
|
61
|
+
const base = global.location?.href ?? "http://localhost/";
|
|
62
|
+
return new URL(anchor.href ?? "", base).origin === new URL(base).origin;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const none = (element, eventName, reason) => freeze({
|
|
68
|
+
kind: "none",
|
|
69
|
+
eventName,
|
|
70
|
+
reason,
|
|
71
|
+
containerId: id,
|
|
72
|
+
elementKey: keyFor(element)
|
|
73
|
+
});
|
|
74
|
+
const explain = (element, eventName) => {
|
|
75
|
+
if (lifecycle === "disposed" && (contains(element) || keys.has(element))) return none(element, eventName, "container-disposed");
|
|
76
|
+
if (!contains(element)) try {
|
|
77
|
+
return previous?.explainInteraction(element, eventName) ?? freeze({
|
|
78
|
+
kind: "none",
|
|
79
|
+
eventName,
|
|
80
|
+
reason: "outside-known-container"
|
|
81
|
+
});
|
|
82
|
+
} catch {
|
|
83
|
+
return freeze({
|
|
84
|
+
kind: "none",
|
|
85
|
+
eventName,
|
|
86
|
+
reason: "outside-known-container"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (element.isConnected === false) return none(element, eventName, "element-disconnected");
|
|
90
|
+
if (eligibleAnchor(element)) {
|
|
91
|
+
const source = eventName === "navigate" && routerSources.has("navigation-event") ? "navigation-event" : eventName === "click" && routerSources.has("spa-click-listener") ? "spa-click-listener" : eventName === "click" && routerSources.has("ssr-link-bridge") ? "ssr-link-bridge" : void 0;
|
|
92
|
+
if (source) return freeze({
|
|
93
|
+
kind: "router-delegation",
|
|
94
|
+
source,
|
|
95
|
+
containerId: id,
|
|
96
|
+
elementKey: keyFor(element),
|
|
97
|
+
eventName
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const interaction = events.get(element)?.get(eventName);
|
|
101
|
+
if (interaction) return freeze({ ...interaction });
|
|
102
|
+
if (invalidated.has(element)) return none(element, eventName, "not-registered");
|
|
103
|
+
try {
|
|
104
|
+
const prior = previous?.explainInteraction(element, eventName);
|
|
105
|
+
if (prior && prior.kind !== "none") return prior;
|
|
106
|
+
} catch {}
|
|
107
|
+
return none(element, eventName, "not-registered");
|
|
108
|
+
};
|
|
109
|
+
const cloneJson = (value, seen = /* @__PURE__ */ new Set()) => {
|
|
110
|
+
if (value === null) return null;
|
|
111
|
+
if (typeof value === "boolean" || typeof value === "string") return value;
|
|
112
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
113
|
+
if (!value || typeof value !== "object" || seen.has(value)) return void 0;
|
|
114
|
+
seen.add(value);
|
|
115
|
+
if (Array.isArray(value)) {
|
|
116
|
+
const copy = value.map((entry) => cloneJson(entry, seen));
|
|
117
|
+
return copy.some((entry) => entry === void 0) ? void 0 : freeze(copy);
|
|
118
|
+
}
|
|
119
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) return void 0;
|
|
120
|
+
const copy = {};
|
|
121
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
122
|
+
const cloned = cloneJson(entry, seen);
|
|
123
|
+
if (cloned === void 0) return void 0;
|
|
124
|
+
copy[name] = cloned;
|
|
125
|
+
}
|
|
126
|
+
return freeze(copy);
|
|
127
|
+
};
|
|
128
|
+
const pushViolation = (input) => {
|
|
129
|
+
const cloned = input.details === void 0 ? void 0 : cloneJson(input.details);
|
|
130
|
+
const details = cloned && !Array.isArray(cloned) && typeof cloned === "object" ? cloned : void 0;
|
|
131
|
+
const { details: _details, ...metadata } = input;
|
|
132
|
+
ownViolations.push(freeze({
|
|
133
|
+
...metadata,
|
|
134
|
+
...details ? { details } : {},
|
|
135
|
+
sequence: nextSequence++,
|
|
136
|
+
timestamp: Date.now()
|
|
137
|
+
}));
|
|
138
|
+
if (input.details !== void 0 && !details) ownViolations.push(freeze({
|
|
139
|
+
code: "MARKLESS_DEBUG_DETAILS_DROPPED",
|
|
140
|
+
message: "Debug violation details were not JSON-safe and were dropped.",
|
|
141
|
+
sequence: nextSequence++,
|
|
142
|
+
timestamp: Date.now()
|
|
143
|
+
}));
|
|
144
|
+
while (ownViolations.length > 100) {
|
|
145
|
+
ownViolations.shift();
|
|
146
|
+
dropped++;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const channel = freeze({
|
|
150
|
+
version: 1,
|
|
151
|
+
get containers() {
|
|
152
|
+
const inherited = previous?.containers.filter((entry) => entry.id !== id) ?? [];
|
|
153
|
+
if (pruned || lifecycle === "disposed") return freeze([...inherited]);
|
|
154
|
+
const currentRoot = rootRef.deref();
|
|
155
|
+
const connected = !!currentRoot && currentRoot.isConnected !== false;
|
|
156
|
+
const snapshot = freeze({
|
|
157
|
+
id,
|
|
158
|
+
phase,
|
|
159
|
+
lifecycle: connected ? lifecycle : "disposed",
|
|
160
|
+
root: freeze({
|
|
161
|
+
key: rootKey,
|
|
162
|
+
connected
|
|
163
|
+
}),
|
|
164
|
+
boundaries: freeze([...boundarySnapshots])
|
|
165
|
+
});
|
|
166
|
+
if (!connected) pruned = true;
|
|
167
|
+
return freeze([...inherited, snapshot]);
|
|
168
|
+
},
|
|
169
|
+
get violations() {
|
|
170
|
+
return freeze([...previous?.violations ?? [], ...ownViolations].slice(-100));
|
|
171
|
+
},
|
|
172
|
+
violationCapacity: 100,
|
|
173
|
+
get droppedViolationCount() {
|
|
174
|
+
return (previous?.droppedViolationCount ?? 0) + dropped;
|
|
175
|
+
},
|
|
176
|
+
explainInteraction: explain
|
|
177
|
+
});
|
|
178
|
+
Object.defineProperty(global, "__MARKLESS_DEBUG__", {
|
|
179
|
+
configurable: true,
|
|
180
|
+
enumerable: false,
|
|
181
|
+
value: channel,
|
|
182
|
+
writable: false
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
start(nextPhase, active = true) {
|
|
186
|
+
phase = nextPhase === "ssr-resume" && phase === "csr" ? "csr" : nextPhase;
|
|
187
|
+
if (active) lifecycle = "active";
|
|
188
|
+
return id;
|
|
189
|
+
},
|
|
190
|
+
activate: () => void (lifecycle === "disposed" || (lifecycle = "active")),
|
|
191
|
+
dispose() {
|
|
192
|
+
lifecycle = "disposed";
|
|
193
|
+
boundarySnapshots = freeze([]);
|
|
194
|
+
},
|
|
195
|
+
invalidate(element) {
|
|
196
|
+
events.delete(element);
|
|
197
|
+
invalidated.add(element);
|
|
198
|
+
},
|
|
199
|
+
record(element, eventName, input) {
|
|
200
|
+
if (lifecycle === "disposed") return;
|
|
201
|
+
let byName = events.get(element);
|
|
202
|
+
if (!byName) events.set(element, byName = /* @__PURE__ */ new Map());
|
|
203
|
+
const symbolIds = "symbolIds" in input ? freeze([...input.symbolIds]) : void 0;
|
|
204
|
+
byName.set(eventName, freeze({
|
|
205
|
+
...input,
|
|
206
|
+
...symbolIds ? { symbolIds } : {},
|
|
207
|
+
containerId: id,
|
|
208
|
+
elementKey: keyFor(element),
|
|
209
|
+
eventName
|
|
210
|
+
}));
|
|
211
|
+
},
|
|
212
|
+
violation: pushViolation,
|
|
213
|
+
router: (source) => void routerSources.add(source),
|
|
214
|
+
boundaries(value) {
|
|
215
|
+
boundarySnapshots = freeze(value.map((entry) => freeze({ ...entry })));
|
|
216
|
+
},
|
|
217
|
+
disposed: () => lifecycle === "disposed"
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
let moduleRoots = /* @__PURE__ */ new WeakMap();
|
|
221
|
+
let latestControls;
|
|
222
|
+
function controlsFor(root, phase, active = true) {
|
|
223
|
+
let controls = moduleRoots.get(root);
|
|
224
|
+
if (!controls || controls.disposed()) {
|
|
225
|
+
controls = installDebugChannelLayer(root, phase, active);
|
|
226
|
+
moduleRoots.set(root, controls);
|
|
227
|
+
} else controls.start(phase, active);
|
|
228
|
+
latestControls = controls;
|
|
229
|
+
return controls;
|
|
230
|
+
}
|
|
231
|
+
function safely(run) {
|
|
232
|
+
try {
|
|
233
|
+
return run();
|
|
234
|
+
} catch {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function __marklessDebugBootstrapSource() {
|
|
239
|
+
return `((root,phase,active)=>{const controls=(${installDebugChannelLayer.toString()})(root,phase,active);return Object.freeze({record:(element,eventName,input)=>controls.record(element,eventName,input),router:(source)=>controls.router(source),activate:()=>controls.activate()})})`;
|
|
240
|
+
}
|
|
241
|
+
function __marklessDebugRegisterRouter(root, source) {
|
|
242
|
+
if (root) safely(() => (moduleRoots.get(root) ?? controlsFor(root, "ssr-resume")).router(source));
|
|
243
|
+
else safely(() => {
|
|
244
|
+
(globalThis[Symbol.for("markless.debug.channel.v1.router-sources")] ??= /* @__PURE__ */ new Set()).add(source);
|
|
245
|
+
const inlineControls = globalThis[Symbol.for("markless.debug.channel.v1.bootstrap")];
|
|
246
|
+
(latestControls ?? inlineControls)?.router(source);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
export { debug_channel_exports as n, __exportAll as r, __marklessDebugBootstrapSource as t };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as normalizeRouteFileId, n as matchRouteManifest, r as normalizeRequestPathname, t as buildRouteManifestFromFileIds } from "./route-manifest-
|
|
2
|
-
import { n as parseRequestFile, r as transformRequestFileSource, t as normalizeRequestFileId } from "./request-files-
|
|
1
|
+
import { i as normalizeRouteFileId, n as matchRouteManifest, r as normalizeRequestPathname, t as buildRouteManifestFromFileIds } from "./route-manifest-DPgGIyrS.js";
|
|
2
|
+
import { n as parseRequestFile, r as transformRequestFileSource, t as normalizeRequestFileId } from "./request-files-C-0LLSsx.js";
|
|
3
3
|
import { render } from "@markless/web/render";
|
|
4
4
|
import { MARKLESS_PENDING_MIN_VISIBLE_MS as MARKLESS_NAV_PENDING_MIN_MS, settleOrPendingDeadline } from "@markless/web";
|
|
5
5
|
//#region src/navigation-hold.ts
|
|
@@ -89,6 +89,8 @@ async function __marklessRouterStartSpaNavigation(options) {
|
|
|
89
89
|
navigation.addEventListener("navigate", (event) => {
|
|
90
90
|
handleNavigateEvent(event, context);
|
|
91
91
|
});
|
|
92
|
+
if (typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__) await import("./debug-channel-C7v9u5SM.js").then((n) => n.n).then((debug) => debug.__marklessDebugRegisterRouter(void 0, "spa-click-listener")).catch(() => {});
|
|
93
|
+
if (typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__) await import("./debug-channel-C7v9u5SM.js").then((n) => n.n).then((debug) => debug.__marklessDebugRegisterRouter(void 0, "navigation-event")).catch(() => {});
|
|
92
94
|
{
|
|
93
95
|
const bootUrl = parseSameOriginUrl(runtimeWindow.location.href, runtimeWindow.location.href);
|
|
94
96
|
const bootHashPath = bootUrl ? hashRoutePath(bootUrl) : void 0;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
let pathe = require("pathe");
|
|
2
|
+
//#region src/typescript-plugin/index.ts
|
|
3
|
+
const PAGE_EXTENSIONS = new Set([".tsrx", ".mdx"]);
|
|
4
|
+
function init(modules) {
|
|
5
|
+
const typeScript = modules.typescript;
|
|
6
|
+
return { create(info) {
|
|
7
|
+
(info.project.projectService?.logger)?.info("[markless-router] TypeScript plugin loaded");
|
|
8
|
+
const languageService = info.languageService;
|
|
9
|
+
const proxy = Object.create(null);
|
|
10
|
+
for (const key of Object.keys(languageService)) {
|
|
11
|
+
const value = languageService[key];
|
|
12
|
+
proxy[key] = typeof value === "function" ? value.bind(languageService) : value;
|
|
13
|
+
}
|
|
14
|
+
const pluginConfig = info.config ?? {};
|
|
15
|
+
const projectRoot = getProjectRoot(typeScript, info);
|
|
16
|
+
const pagesDir = resolvePagesDir(getConfiguredProjectRoot(info) ?? projectRoot, pluginConfig);
|
|
17
|
+
proxy.getCompletionsAtPosition = (fileName, position, options, formattingSettings) => {
|
|
18
|
+
return withRouteHrefCompletions(typeScript, info, pagesDir, fileName, position, languageService.getCompletionsAtPosition(fileName, position, options, formattingSettings));
|
|
19
|
+
};
|
|
20
|
+
return proxy;
|
|
21
|
+
} };
|
|
22
|
+
}
|
|
23
|
+
function withRouteHrefCompletions(typeScript, info, pagesDir, fileName, position, completions) {
|
|
24
|
+
if (!isRouteHrefCompletionContext(typeScript, info, fileName, position)) return completions;
|
|
25
|
+
const routeHrefs = routeHrefCompletions(typeScript, info, pagesDir);
|
|
26
|
+
if (routeHrefs.length === 0) return completions;
|
|
27
|
+
const baseCompletions = completions ?? emptyCompletionInfo();
|
|
28
|
+
const routeHrefSet = new Set(routeHrefs);
|
|
29
|
+
const routeEntries = routeHrefs.map((href) => ({
|
|
30
|
+
name: href,
|
|
31
|
+
kind: typeScript.ScriptElementKind.string,
|
|
32
|
+
kindModifiers: "",
|
|
33
|
+
sortText: `0 ${href}`
|
|
34
|
+
}));
|
|
35
|
+
return {
|
|
36
|
+
...baseCompletions,
|
|
37
|
+
entries: [...routeEntries, ...baseCompletions.entries.filter((entry) => !routeHrefSet.has(entry.name))]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function isRouteHrefCompletionContext(typeScript, info, fileName, position) {
|
|
41
|
+
if ((0, pathe.extname)(fileName).toLowerCase() === ".tsrx") {
|
|
42
|
+
const snapshot = info.project.getScriptInfo(fileName)?.getSnapshot();
|
|
43
|
+
if (!snapshot || position < 0 || position > snapshot.getLength()) return false;
|
|
44
|
+
const source = snapshot.getText(0, snapshot.getLength());
|
|
45
|
+
return isInsideStringLikeToken(typeScript, source, position) || isInsideHrefAttributeInitializer(source, position);
|
|
46
|
+
}
|
|
47
|
+
const sourceFile = info.languageService.getProgram()?.getSourceFile(fileName);
|
|
48
|
+
if (!sourceFile || position < 0 || position > sourceFile.getFullText().length) return false;
|
|
49
|
+
const node = deepestNodeAtPosition(sourceFile, position);
|
|
50
|
+
if (node && isStringLikeNode(typeScript, node)) return true;
|
|
51
|
+
for (let current = node; current; current = current.parent) {
|
|
52
|
+
if (!typeScript.isJsxAttribute(current) || current.name.getText(sourceFile) !== "href") continue;
|
|
53
|
+
const initializer = current.initializer;
|
|
54
|
+
return Boolean(initializer && initializer.getStart(sourceFile) <= position && position <= initializer.end);
|
|
55
|
+
}
|
|
56
|
+
return isInsideHrefAttributeInitializer(sourceFile.getFullText(), position);
|
|
57
|
+
}
|
|
58
|
+
function isInsideStringLikeToken(typeScript, source, position) {
|
|
59
|
+
const scanner = typeScript.createScanner(typeScript.ScriptTarget.Latest, false, typeScript.LanguageVariant.JSX, source);
|
|
60
|
+
const stringLikeTokens = new Set([
|
|
61
|
+
typeScript.SyntaxKind.StringLiteral,
|
|
62
|
+
typeScript.SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
63
|
+
typeScript.SyntaxKind.TemplateHead,
|
|
64
|
+
typeScript.SyntaxKind.TemplateMiddle,
|
|
65
|
+
typeScript.SyntaxKind.TemplateTail
|
|
66
|
+
]);
|
|
67
|
+
for (let token = scanner.scan(); token !== typeScript.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
|
|
68
|
+
const start = scanner.getTokenPos();
|
|
69
|
+
const end = scanner.getTextPos();
|
|
70
|
+
if (position < start) return false;
|
|
71
|
+
if (stringLikeTokens.has(token) && start < position) {
|
|
72
|
+
if (position < end || position === end && scanner.isUnterminated()) return true;
|
|
73
|
+
}
|
|
74
|
+
if (position < end) return false;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function isInsideHrefAttributeInitializer(source, position) {
|
|
79
|
+
const tagStart = openJsxTagStartAtPosition(source, position);
|
|
80
|
+
if (tagStart === void 0) return false;
|
|
81
|
+
const prefix = source.slice(tagStart + 1, position);
|
|
82
|
+
return /(?:^|\s)href\s*=\s*(?:\{(?:[^{}]|\{[^{}]*\})*|[^\s>]*)$/s.test(prefix);
|
|
83
|
+
}
|
|
84
|
+
function openJsxTagStartAtPosition(source, position) {
|
|
85
|
+
let tagStart;
|
|
86
|
+
let braceDepth = 0;
|
|
87
|
+
let quote;
|
|
88
|
+
let escaped = false;
|
|
89
|
+
for (let index = 0; index < position; index += 1) {
|
|
90
|
+
const character = source[index];
|
|
91
|
+
if (quote) {
|
|
92
|
+
if (escaped) escaped = false;
|
|
93
|
+
else if (character === "\\") escaped = true;
|
|
94
|
+
else if (character === quote) quote = void 0;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (character === "'" || character === "\"" || character === "`") {
|
|
98
|
+
quote = character;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (tagStart === void 0 && character === "<") {
|
|
102
|
+
const next = source[index + 1];
|
|
103
|
+
if (next && /[A-Za-z_$/{>]/.test(next)) {
|
|
104
|
+
tagStart = index;
|
|
105
|
+
braceDepth = 0;
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (tagStart === void 0) continue;
|
|
110
|
+
if (character === "{") braceDepth += 1;
|
|
111
|
+
else if (character === "}" && braceDepth > 0) braceDepth -= 1;
|
|
112
|
+
else if (character === ">" && braceDepth === 0) tagStart = void 0;
|
|
113
|
+
}
|
|
114
|
+
return tagStart;
|
|
115
|
+
}
|
|
116
|
+
function deepestNodeAtPosition(sourceFile, position) {
|
|
117
|
+
let deepest;
|
|
118
|
+
const visit = (node) => {
|
|
119
|
+
if (position < node.getFullStart() || position > node.end) return;
|
|
120
|
+
deepest = node;
|
|
121
|
+
node.forEachChild(visit);
|
|
122
|
+
};
|
|
123
|
+
visit(sourceFile);
|
|
124
|
+
return deepest;
|
|
125
|
+
}
|
|
126
|
+
function isStringLikeNode(typeScript, node) {
|
|
127
|
+
return typeScript.isStringLiteral(node) || typeScript.isNoSubstitutionTemplateLiteral(node) || node.kind === typeScript.SyntaxKind.TemplateHead || node.kind === typeScript.SyntaxKind.TemplateMiddle || node.kind === typeScript.SyntaxKind.TemplateTail;
|
|
128
|
+
}
|
|
129
|
+
function emptyCompletionInfo() {
|
|
130
|
+
return {
|
|
131
|
+
isGlobalCompletion: false,
|
|
132
|
+
isMemberCompletion: false,
|
|
133
|
+
isNewIdentifierLocation: false,
|
|
134
|
+
entries: []
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function routeHrefCompletions(typeScript, info, pagesDir) {
|
|
138
|
+
const hrefs = /* @__PURE__ */ new Set();
|
|
139
|
+
for (const pageFileName of routeCompletionPageFileNames(typeScript, info, pagesDir)) {
|
|
140
|
+
const relativeFileId = (0, pathe.relative)(pagesDir, pageFileName);
|
|
141
|
+
if (!isRelativeInsidePath(relativeFileId)) continue;
|
|
142
|
+
if (!PAGE_EXTENSIONS.has((0, pathe.extname)(relativeFileId))) continue;
|
|
143
|
+
const href = routeHrefCompletion(relativeFileId);
|
|
144
|
+
if (href) hrefs.add(href);
|
|
145
|
+
}
|
|
146
|
+
return Array.from(hrefs).toSorted();
|
|
147
|
+
}
|
|
148
|
+
function routeCompletionPageFileNames(typeScript, info, pagesDir) {
|
|
149
|
+
const fileNames = new Set(info.languageServiceHost.getScriptFileNames?.() ?? []);
|
|
150
|
+
for (const fileName of readPageFileNames(typeScript, pagesDir)) fileNames.add(fileName);
|
|
151
|
+
return [...fileNames];
|
|
152
|
+
}
|
|
153
|
+
function readPageFileNames(typeScript, pagesDir) {
|
|
154
|
+
if (typeScript.sys.directoryExists && !typeScript.sys.directoryExists(pagesDir)) return [];
|
|
155
|
+
try {
|
|
156
|
+
return typeScript.sys.readDirectory(pagesDir, Array.from(PAGE_EXTENSIONS), void 0, void 0);
|
|
157
|
+
} catch {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function routeHrefCompletion(fileId) {
|
|
162
|
+
if (isReservedOrUnsupportedPage(fileId)) return;
|
|
163
|
+
return routeFromFileId(fileId).pattern;
|
|
164
|
+
}
|
|
165
|
+
function isReservedOrUnsupportedPage(fileId) {
|
|
166
|
+
const extension = (0, pathe.extname)(fileId);
|
|
167
|
+
const routeFile = (0, pathe.normalize)(fileId.slice(0, -extension.length));
|
|
168
|
+
const segments = routeFile === "." ? [] : routeFile.split("/").filter(Boolean);
|
|
169
|
+
return segments[0] === "api" || segments.length === 1 && (segments[0] === "404" || segments[0] === "500");
|
|
170
|
+
}
|
|
171
|
+
function routeFromFileId(fileId) {
|
|
172
|
+
const extension = (0, pathe.extname)(fileId);
|
|
173
|
+
const routeFile = (0, pathe.normalize)(fileId.slice(0, -extension.length));
|
|
174
|
+
const segments = routeFile === "." ? [] : routeFile.split("/").filter(Boolean);
|
|
175
|
+
if (segments.at(-1) === "index") segments.pop();
|
|
176
|
+
const params = [];
|
|
177
|
+
const routeSegments = [];
|
|
178
|
+
for (const segment of segments) {
|
|
179
|
+
const catchAllName = bracketParamName(segment, "[...");
|
|
180
|
+
if (catchAllName) {
|
|
181
|
+
routeSegments.push(`[...${catchAllName}]`);
|
|
182
|
+
params.push(catchAllName);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const dynamicName = bracketParamName(segment, "[");
|
|
186
|
+
if (dynamicName) {
|
|
187
|
+
routeSegments.push(`[${dynamicName}]`);
|
|
188
|
+
params.push(dynamicName);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
routeSegments.push(segment);
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
pattern: routePathname(routeSegments),
|
|
195
|
+
params
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function bracketParamName(segment, opening) {
|
|
199
|
+
if (!segment.startsWith(opening) || !segment.endsWith("]")) return;
|
|
200
|
+
const name = segment.slice(opening.length, -1);
|
|
201
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : void 0;
|
|
202
|
+
}
|
|
203
|
+
function routePathname(segments) {
|
|
204
|
+
if (segments.length === 0) return "/";
|
|
205
|
+
return `/${segments.join("/")}`;
|
|
206
|
+
}
|
|
207
|
+
function getProjectRoot(typeScript, info) {
|
|
208
|
+
return info.project.getCurrentDirectory() ?? info.languageServiceHost.getCurrentDirectory?.() ?? typeScript.sys.getCurrentDirectory();
|
|
209
|
+
}
|
|
210
|
+
function getConfiguredProjectRoot(info) {
|
|
211
|
+
const project = info.project;
|
|
212
|
+
try {
|
|
213
|
+
const configFilePath = project.getConfigFilePath?.();
|
|
214
|
+
return configFilePath ? configFilePath.slice(0, configFilePath.lastIndexOf("/")) : void 0;
|
|
215
|
+
} catch {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function resolvePagesDir(projectRoot, config) {
|
|
220
|
+
const configured = config.pagesDir ?? "pages";
|
|
221
|
+
return (0, pathe.isAbsolute)(configured) ? configured : (0, pathe.resolve)(projectRoot, configured);
|
|
222
|
+
}
|
|
223
|
+
function isRelativeInsidePath(path) {
|
|
224
|
+
return path !== "" && path !== ".." && !path.startsWith("../") && !(0, pathe.isAbsolute)(path);
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
module.exports = init;
|
|
@@ -15,12 +15,13 @@ function init(modules) {
|
|
|
15
15
|
const projectRoot = getProjectRoot(typeScript, info);
|
|
16
16
|
const pagesDir = resolvePagesDir(getConfiguredProjectRoot(info) ?? projectRoot, pluginConfig);
|
|
17
17
|
proxy.getCompletionsAtPosition = (fileName, position, options, formattingSettings) => {
|
|
18
|
-
return withRouteHrefCompletions(typeScript, info, pagesDir, languageService.getCompletionsAtPosition(fileName, position, options, formattingSettings));
|
|
18
|
+
return withRouteHrefCompletions(typeScript, info, pagesDir, fileName, position, languageService.getCompletionsAtPosition(fileName, position, options, formattingSettings));
|
|
19
19
|
};
|
|
20
20
|
return proxy;
|
|
21
21
|
} };
|
|
22
22
|
}
|
|
23
|
-
function withRouteHrefCompletions(typeScript, info, pagesDir, completions) {
|
|
23
|
+
function withRouteHrefCompletions(typeScript, info, pagesDir, fileName, position, completions) {
|
|
24
|
+
if (!isRouteHrefCompletionContext(typeScript, info, fileName, position)) return completions;
|
|
24
25
|
const routeHrefs = routeHrefCompletions(typeScript, info, pagesDir);
|
|
25
26
|
if (routeHrefs.length === 0) return completions;
|
|
26
27
|
const baseCompletions = completions ?? emptyCompletionInfo();
|
|
@@ -36,6 +37,95 @@ function withRouteHrefCompletions(typeScript, info, pagesDir, completions) {
|
|
|
36
37
|
entries: [...routeEntries, ...baseCompletions.entries.filter((entry) => !routeHrefSet.has(entry.name))]
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
function isRouteHrefCompletionContext(typeScript, info, fileName, position) {
|
|
41
|
+
if (extname(fileName).toLowerCase() === ".tsrx") {
|
|
42
|
+
const snapshot = info.project.getScriptInfo(fileName)?.getSnapshot();
|
|
43
|
+
if (!snapshot || position < 0 || position > snapshot.getLength()) return false;
|
|
44
|
+
const source = snapshot.getText(0, snapshot.getLength());
|
|
45
|
+
return isInsideStringLikeToken(typeScript, source, position) || isInsideHrefAttributeInitializer(source, position);
|
|
46
|
+
}
|
|
47
|
+
const sourceFile = info.languageService.getProgram()?.getSourceFile(fileName);
|
|
48
|
+
if (!sourceFile || position < 0 || position > sourceFile.getFullText().length) return false;
|
|
49
|
+
const node = deepestNodeAtPosition(sourceFile, position);
|
|
50
|
+
if (node && isStringLikeNode(typeScript, node)) return true;
|
|
51
|
+
for (let current = node; current; current = current.parent) {
|
|
52
|
+
if (!typeScript.isJsxAttribute(current) || current.name.getText(sourceFile) !== "href") continue;
|
|
53
|
+
const initializer = current.initializer;
|
|
54
|
+
return Boolean(initializer && initializer.getStart(sourceFile) <= position && position <= initializer.end);
|
|
55
|
+
}
|
|
56
|
+
return isInsideHrefAttributeInitializer(sourceFile.getFullText(), position);
|
|
57
|
+
}
|
|
58
|
+
function isInsideStringLikeToken(typeScript, source, position) {
|
|
59
|
+
const scanner = typeScript.createScanner(typeScript.ScriptTarget.Latest, false, typeScript.LanguageVariant.JSX, source);
|
|
60
|
+
const stringLikeTokens = new Set([
|
|
61
|
+
typeScript.SyntaxKind.StringLiteral,
|
|
62
|
+
typeScript.SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
63
|
+
typeScript.SyntaxKind.TemplateHead,
|
|
64
|
+
typeScript.SyntaxKind.TemplateMiddle,
|
|
65
|
+
typeScript.SyntaxKind.TemplateTail
|
|
66
|
+
]);
|
|
67
|
+
for (let token = scanner.scan(); token !== typeScript.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
|
|
68
|
+
const start = scanner.getTokenPos();
|
|
69
|
+
const end = scanner.getTextPos();
|
|
70
|
+
if (position < start) return false;
|
|
71
|
+
if (stringLikeTokens.has(token) && start < position) {
|
|
72
|
+
if (position < end || position === end && scanner.isUnterminated()) return true;
|
|
73
|
+
}
|
|
74
|
+
if (position < end) return false;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function isInsideHrefAttributeInitializer(source, position) {
|
|
79
|
+
const tagStart = openJsxTagStartAtPosition(source, position);
|
|
80
|
+
if (tagStart === void 0) return false;
|
|
81
|
+
const prefix = source.slice(tagStart + 1, position);
|
|
82
|
+
return /(?:^|\s)href\s*=\s*(?:\{(?:[^{}]|\{[^{}]*\})*|[^\s>]*)$/s.test(prefix);
|
|
83
|
+
}
|
|
84
|
+
function openJsxTagStartAtPosition(source, position) {
|
|
85
|
+
let tagStart;
|
|
86
|
+
let braceDepth = 0;
|
|
87
|
+
let quote;
|
|
88
|
+
let escaped = false;
|
|
89
|
+
for (let index = 0; index < position; index += 1) {
|
|
90
|
+
const character = source[index];
|
|
91
|
+
if (quote) {
|
|
92
|
+
if (escaped) escaped = false;
|
|
93
|
+
else if (character === "\\") escaped = true;
|
|
94
|
+
else if (character === quote) quote = void 0;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (character === "'" || character === "\"" || character === "`") {
|
|
98
|
+
quote = character;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (tagStart === void 0 && character === "<") {
|
|
102
|
+
const next = source[index + 1];
|
|
103
|
+
if (next && /[A-Za-z_$/{>]/.test(next)) {
|
|
104
|
+
tagStart = index;
|
|
105
|
+
braceDepth = 0;
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (tagStart === void 0) continue;
|
|
110
|
+
if (character === "{") braceDepth += 1;
|
|
111
|
+
else if (character === "}" && braceDepth > 0) braceDepth -= 1;
|
|
112
|
+
else if (character === ">" && braceDepth === 0) tagStart = void 0;
|
|
113
|
+
}
|
|
114
|
+
return tagStart;
|
|
115
|
+
}
|
|
116
|
+
function deepestNodeAtPosition(sourceFile, position) {
|
|
117
|
+
let deepest;
|
|
118
|
+
const visit = (node) => {
|
|
119
|
+
if (position < node.getFullStart() || position > node.end) return;
|
|
120
|
+
deepest = node;
|
|
121
|
+
node.forEachChild(visit);
|
|
122
|
+
};
|
|
123
|
+
visit(sourceFile);
|
|
124
|
+
return deepest;
|
|
125
|
+
}
|
|
126
|
+
function isStringLikeNode(typeScript, node) {
|
|
127
|
+
return typeScript.isStringLiteral(node) || typeScript.isNoSubstitutionTemplateLiteral(node) || node.kind === typeScript.SyntaxKind.TemplateHead || node.kind === typeScript.SyntaxKind.TemplateMiddle || node.kind === typeScript.SyntaxKind.TemplateTail;
|
|
128
|
+
}
|
|
39
129
|
function emptyCompletionInfo() {
|
|
40
130
|
return {
|
|
41
131
|
isGlobalCompletion: false,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as normalizeRouteFileId } from "../../route-manifest-
|
|
1
|
+
import { i as normalizeRouteFileId } from "../../route-manifest-DPgGIyrS.js";
|
|
2
2
|
//#region src/vite/runtime/create-route-discovery.ts
|
|
3
3
|
function createRouteDiscovery(discoveredPageModuleLoaders) {
|
|
4
4
|
const pageModuleLoaders = Object.fromEntries(Object.entries(discoveredPageModuleLoaders).map(([file, loader]) => [normalizeRouteFileId(file), loader]));
|
|
@@ -2,10 +2,12 @@ import { ModulePreloadInput } from "@markless/web/render-to-string";
|
|
|
2
2
|
|
|
3
3
|
//#region src/vite/runtime/create-server-entry.d.ts
|
|
4
4
|
interface ServerEntryOptions {
|
|
5
|
+
readonly dev?: boolean;
|
|
5
6
|
readonly navigationEntryPath?: string;
|
|
6
7
|
readonly resumeEntryPath?: string;
|
|
7
8
|
readonly routeModulePreloads?: Record<string, readonly ModulePreloadInput[]>;
|
|
8
9
|
readonly routeSsrModulePreloads?: Record<string, readonly ModulePreloadInput[]>;
|
|
10
|
+
readonly routeStylesheets?: Record<string, readonly string[]>;
|
|
9
11
|
readonly documentModuleLoader: (() => Promise<unknown>) | undefined;
|
|
10
12
|
readonly pageModuleLoaders: Record<string, () => Promise<unknown>>;
|
|
11
13
|
readonly routeFileIds: readonly string[];
|