@volpe/astro-svelte-spa 0.1.1 → 0.1.3
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/index.d.ts +31 -0
- package/index.js +75 -14
- package/package.json +5 -17
- package/App.svelte +0 -40
- package/App.svelte.d.ts +0 -5
- package/Link.svelte +0 -32
- package/Link.svelte.d.ts +0 -12
package/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
+
import type { Component } from "svelte";
|
|
2
3
|
|
|
3
4
|
export interface PluginOptions {
|
|
4
5
|
/**
|
|
@@ -35,3 +36,33 @@ export interface PluginOptions {
|
|
|
35
36
|
export function plugin(options?: PluginOptions): Plugin;
|
|
36
37
|
|
|
37
38
|
export default plugin;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The main App component that renders the router.
|
|
42
|
+
* Use with `client:only="svelte"` in Astro.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```astro
|
|
46
|
+
* ---
|
|
47
|
+
* import { App } from "@volpe/astro-svelte-spa"
|
|
48
|
+
* ---
|
|
49
|
+
* <App client:only="svelte" />
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export const App: Component<{}>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Link component for client-side navigation.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```svelte
|
|
59
|
+
* <script>
|
|
60
|
+
* import { Link } from "@volpe/astro-svelte-spa"
|
|
61
|
+
* </script>
|
|
62
|
+
* <Link href="/about">About</Link>
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export const Link: Component<{
|
|
66
|
+
href: string;
|
|
67
|
+
[key: string]: any;
|
|
68
|
+
}>;
|
package/index.js
CHANGED
|
@@ -4,6 +4,15 @@ import path from "node:path";
|
|
|
4
4
|
const VIRTUAL_MODULE_ID = "virtual:svelte-routes";
|
|
5
5
|
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
6
6
|
|
|
7
|
+
const PACKAGE_ID = "@volpe/astro-svelte-spa";
|
|
8
|
+
const RESOLVED_PACKAGE_ID = "\0" + PACKAGE_ID;
|
|
9
|
+
|
|
10
|
+
const VIRTUAL_APP_ID = "virtual:svelte-spa-app";
|
|
11
|
+
const RESOLVED_VIRTUAL_APP_ID = "\0" + VIRTUAL_APP_ID;
|
|
12
|
+
|
|
13
|
+
const VIRTUAL_LINK_ID = "virtual:svelte-spa-link";
|
|
14
|
+
const RESOLVED_VIRTUAL_LINK_ID = "\0" + VIRTUAL_LINK_ID;
|
|
15
|
+
|
|
7
16
|
/**
|
|
8
17
|
* @typedef {Object} RouteNode
|
|
9
18
|
* @property {string} path
|
|
@@ -101,15 +110,12 @@ function applyBasePath(routePath, basePath) {
|
|
|
101
110
|
function generateRoutes(node, imports) {
|
|
102
111
|
const routes = [];
|
|
103
112
|
|
|
104
|
-
// Use relative paths (basePath is handled by Router component)
|
|
105
113
|
const routePath = node.path;
|
|
106
114
|
const layoutName = node.layout ? imports.get(node.layout) : null;
|
|
107
115
|
const pageName = node.page ? imports.get(node.page) : null;
|
|
108
116
|
|
|
109
|
-
// Add page route
|
|
110
117
|
if (pageName) {
|
|
111
118
|
if (layoutName) {
|
|
112
|
-
// With layout - use children
|
|
113
119
|
routes.push(`{
|
|
114
120
|
path: "${routePath}",
|
|
115
121
|
component: ${layoutName},
|
|
@@ -118,7 +124,6 @@ function generateRoutes(node, imports) {
|
|
|
118
124
|
]
|
|
119
125
|
}`);
|
|
120
126
|
} else {
|
|
121
|
-
// No layout - simple route with async loading
|
|
122
127
|
routes.push(`{
|
|
123
128
|
path: "${routePath}",
|
|
124
129
|
component: async () => import("${node.page}")
|
|
@@ -126,7 +131,6 @@ function generateRoutes(node, imports) {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
// Add children routes
|
|
130
134
|
for (const child of node.children) {
|
|
131
135
|
routes.push(...generateRoutes(child, imports));
|
|
132
136
|
}
|
|
@@ -144,16 +148,13 @@ function generateRoutesModule(pagesDir, basePath) {
|
|
|
144
148
|
const imports = new Map();
|
|
145
149
|
generateImports(tree, imports);
|
|
146
150
|
|
|
147
|
-
// Import layouts (pages are loaded async)
|
|
148
151
|
const layoutImports = Array.from(imports.entries())
|
|
149
152
|
.filter(([filePath]) => filePath.includes("layout.svelte"))
|
|
150
153
|
.map(([filePath, name]) => `import ${name} from "${filePath}"`)
|
|
151
154
|
.join("\n");
|
|
152
155
|
|
|
153
|
-
// Routes use relative paths - basePath is handled by Router component
|
|
154
156
|
const routes = generateRoutes(tree, imports);
|
|
155
157
|
|
|
156
|
-
// Check for 404.svelte for statuses
|
|
157
158
|
const notFoundPath = path.join(pagesDir, "404.svelte");
|
|
158
159
|
const hasNotFound = fs.existsSync(notFoundPath);
|
|
159
160
|
const notFoundImportStatement = hasNotFound
|
|
@@ -222,11 +223,10 @@ function generatePageFiles(pagesDir) {
|
|
|
222
223
|
fs.mkdirSync(pagesDir, { recursive: true });
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
// Generate [...path].astro if it doesn't exist
|
|
226
226
|
const catchAllPath = path.resolve(pagesDir, "[...path].astro");
|
|
227
227
|
if (!fs.existsSync(catchAllPath)) {
|
|
228
228
|
const catchAllContent = `---
|
|
229
|
-
import App from "@volpe/astro-svelte-spa
|
|
229
|
+
import { App } from "@volpe/astro-svelte-spa"
|
|
230
230
|
---
|
|
231
231
|
|
|
232
232
|
<App client:only="svelte" />
|
|
@@ -234,7 +234,6 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
|
|
|
234
234
|
fs.writeFileSync(catchAllPath, catchAllContent);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
// Generate page.svelte if it doesn't exist
|
|
238
237
|
const pagePath = path.resolve(pagesDir, "page.svelte");
|
|
239
238
|
if (!fs.existsSync(pagePath)) {
|
|
240
239
|
const pageContent = `<h1>Edit me :)</h1>
|
|
@@ -242,7 +241,6 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
|
|
|
242
241
|
fs.writeFileSync(pagePath, pageContent);
|
|
243
242
|
}
|
|
244
243
|
|
|
245
|
-
// Generate 404.svelte if it doesn't exist
|
|
246
244
|
const notFoundPagePath = path.resolve(pagesDir, "404.svelte");
|
|
247
245
|
if (!fs.existsSync(notFoundPagePath)) {
|
|
248
246
|
const notFoundContent = `<script>
|
|
@@ -283,6 +281,46 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
|
|
|
283
281
|
}
|
|
284
282
|
}
|
|
285
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Generates the virtual App component code
|
|
286
|
+
* @returns {string}
|
|
287
|
+
*/
|
|
288
|
+
function generateAppComponent() {
|
|
289
|
+
return `<script>
|
|
290
|
+
import { Router, StatusCode } from "@mateothegreat/svelte5-router"
|
|
291
|
+
import routes, { basePath, notFoundComponent } from "virtual:svelte-routes"
|
|
292
|
+
|
|
293
|
+
const statuses = notFoundComponent ? {
|
|
294
|
+
[StatusCode.NotFound]: { component: notFoundComponent }
|
|
295
|
+
} : undefined
|
|
296
|
+
</script>
|
|
297
|
+
|
|
298
|
+
<Router {routes} {basePath} {statuses} />
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Generates the virtual Link component code
|
|
304
|
+
* @returns {string}
|
|
305
|
+
*/
|
|
306
|
+
function generateLinkComponent() {
|
|
307
|
+
return `<script>
|
|
308
|
+
import { route, goto } from "@mateothegreat/svelte5-router"
|
|
309
|
+
|
|
310
|
+
let { href, children, ...rest } = $props()
|
|
311
|
+
|
|
312
|
+
const handleClick = (e) => {
|
|
313
|
+
e.preventDefault()
|
|
314
|
+
goto(href)
|
|
315
|
+
}
|
|
316
|
+
</script>
|
|
317
|
+
|
|
318
|
+
<a {href} onclick={handleClick} class:active={$route.path === href} {...rest}>
|
|
319
|
+
{@render children?.()}
|
|
320
|
+
</a>
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
|
|
286
324
|
/**
|
|
287
325
|
* Vite plugin for file-based routing with svelte5-router in Astro
|
|
288
326
|
* @param {Object} options - Plugin options
|
|
@@ -290,9 +328,7 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
|
|
|
290
328
|
* @returns {import('vite').Plugin}
|
|
291
329
|
*/
|
|
292
330
|
export function plugin(options = {}) {
|
|
293
|
-
// Normalize basePath, defaults to "/svelte-app"
|
|
294
331
|
const basePath = options.basePath?.replace(/\/$/, "") || "/svelte-app";
|
|
295
|
-
// Directory suffix: "/svelte-app" -> "svelte-app"
|
|
296
332
|
const dirSuffix = basePath.replace(/^\//, "");
|
|
297
333
|
|
|
298
334
|
const rootDir = process.cwd();
|
|
@@ -344,12 +380,37 @@ export function plugin(options = {}) {
|
|
|
344
380
|
if (id === VIRTUAL_MODULE_ID) {
|
|
345
381
|
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
346
382
|
}
|
|
383
|
+
// Intercept main package import (but not /plugin subpath)
|
|
384
|
+
if (id === PACKAGE_ID) {
|
|
385
|
+
return RESOLVED_PACKAGE_ID;
|
|
386
|
+
}
|
|
387
|
+
// Handle virtual component IDs
|
|
388
|
+
if (id === VIRTUAL_APP_ID) {
|
|
389
|
+
return RESOLVED_VIRTUAL_APP_ID;
|
|
390
|
+
}
|
|
391
|
+
if (id === VIRTUAL_LINK_ID) {
|
|
392
|
+
return RESOLVED_VIRTUAL_LINK_ID;
|
|
393
|
+
}
|
|
347
394
|
},
|
|
348
395
|
|
|
349
396
|
load(id) {
|
|
350
397
|
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
351
398
|
return generateRoutesModule(pagesDir, basePath);
|
|
352
399
|
}
|
|
400
|
+
// Virtual package module - exports App, Link, and re-exports plugin
|
|
401
|
+
if (id === RESOLVED_PACKAGE_ID) {
|
|
402
|
+
return `
|
|
403
|
+
export { default as App } from "${VIRTUAL_APP_ID}";
|
|
404
|
+
export { default as Link } from "${VIRTUAL_LINK_ID}";
|
|
405
|
+
export { plugin, default } from "@volpe/astro-svelte-spa/plugin";
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
if (id === RESOLVED_VIRTUAL_APP_ID) {
|
|
409
|
+
return generateAppComponent();
|
|
410
|
+
}
|
|
411
|
+
if (id === RESOLVED_VIRTUAL_LINK_ID) {
|
|
412
|
+
return generateLinkComponent();
|
|
413
|
+
}
|
|
353
414
|
},
|
|
354
415
|
|
|
355
416
|
handleHotUpdate() {
|
package/package.json
CHANGED
|
@@ -1,35 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volpe/astro-svelte-spa",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Vite plugin for file-based routing with svelte5-router in Astro",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
|
-
"svelte": "./index.js",
|
|
9
8
|
"exports": {
|
|
10
9
|
".": {
|
|
11
10
|
"types": "./index.d.ts",
|
|
12
|
-
"svelte": "./index.js",
|
|
13
11
|
"default": "./index.js"
|
|
14
12
|
},
|
|
15
|
-
"./
|
|
16
|
-
"types": "./
|
|
17
|
-
"
|
|
18
|
-
"default": "./App.svelte"
|
|
19
|
-
},
|
|
20
|
-
"./Link.svelte": {
|
|
21
|
-
"types": "./Link.svelte.d.ts",
|
|
22
|
-
"svelte": "./Link.svelte",
|
|
23
|
-
"default": "./Link.svelte"
|
|
13
|
+
"./plugin": {
|
|
14
|
+
"types": "./index.d.ts",
|
|
15
|
+
"default": "./index.js"
|
|
24
16
|
}
|
|
25
17
|
},
|
|
26
18
|
"files": [
|
|
27
19
|
"index.js",
|
|
28
|
-
"index.d.ts"
|
|
29
|
-
"App.svelte",
|
|
30
|
-
"App.svelte.d.ts",
|
|
31
|
-
"Link.svelte",
|
|
32
|
-
"Link.svelte.d.ts"
|
|
20
|
+
"index.d.ts"
|
|
33
21
|
],
|
|
34
22
|
"keywords": [
|
|
35
23
|
"vite",
|
package/App.svelte
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Router, route, StatusCode } from "@mateothegreat/svelte5-router"
|
|
3
|
-
import routes, { basePath, notFoundComponent } from "virtual:svelte-routes"
|
|
4
|
-
|
|
5
|
-
// Build statuses config for 404 handling
|
|
6
|
-
const statuses = notFoundComponent ? {
|
|
7
|
-
[StatusCode.NotFound]: { component: notFoundComponent }
|
|
8
|
-
} : undefined
|
|
9
|
-
|
|
10
|
-
// Action that combines route + prefetch on hover
|
|
11
|
-
function link(node: HTMLAnchorElement) {
|
|
12
|
-
const href = node.getAttribute("href")
|
|
13
|
-
|
|
14
|
-
function preload() {
|
|
15
|
-
// Convert full path to relative path for matching
|
|
16
|
-
const relativePath = href === basePath ? "/" : href?.replace(basePath, "") || "/"
|
|
17
|
-
const r = routes.find(r => r.path === relativePath)
|
|
18
|
-
if (r?.component && typeof r.component === "function") {
|
|
19
|
-
r.component()
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
node.addEventListener("mouseenter", preload)
|
|
24
|
-
node.addEventListener("touchstart", preload, { passive: true })
|
|
25
|
-
|
|
26
|
-
const routeAction = route(node)
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
destroy() {
|
|
30
|
-
node.removeEventListener("mouseenter", preload)
|
|
31
|
-
node.removeEventListener("touchstart", preload)
|
|
32
|
-
routeAction?.destroy?.()
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export { link }
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<Router {routes} {basePath} {statuses} />
|
package/App.svelte.d.ts
DELETED
package/Link.svelte
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { route } from "@mateothegreat/svelte5-router"
|
|
3
|
-
import routes, { basePath, type SvelteAppRoutes } from "virtual:svelte-routes"
|
|
4
|
-
import type { Snippet } from "svelte"
|
|
5
|
-
import type { HTMLAnchorAttributes } from "svelte/elements"
|
|
6
|
-
|
|
7
|
-
type Props = Omit<HTMLAnchorAttributes, "href"> & {
|
|
8
|
-
href: SvelteAppRoutes
|
|
9
|
-
children: Snippet
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let { href, children, ...rest }: Props = $props()
|
|
13
|
-
|
|
14
|
-
function preload() {
|
|
15
|
-
// Convert full path to relative path for matching
|
|
16
|
-
const relativePath = href === basePath ? "/" : href.replace(basePath, "")
|
|
17
|
-
const r = routes.find(r => r.path === relativePath)
|
|
18
|
-
if (r?.component && typeof r.component === "function") {
|
|
19
|
-
r.component()
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<a
|
|
25
|
-
{href}
|
|
26
|
-
use:route
|
|
27
|
-
onmouseenter={preload}
|
|
28
|
-
ontouchstart={preload}
|
|
29
|
-
{...rest}
|
|
30
|
-
>
|
|
31
|
-
{@render children()}
|
|
32
|
-
</a>
|
package/Link.svelte.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { SvelteComponent } from "svelte"
|
|
2
|
-
import type { SvelteAppRoutes } from "virtual:svelte-routes"
|
|
3
|
-
import type { Snippet } from "svelte"
|
|
4
|
-
import type { HTMLAnchorAttributes } from "svelte/elements"
|
|
5
|
-
|
|
6
|
-
type Props = Omit<HTMLAnchorAttributes, "href"> & {
|
|
7
|
-
href: SvelteAppRoutes
|
|
8
|
-
children: Snippet
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
declare const Link: typeof SvelteComponent<Props>
|
|
12
|
-
export default Link
|