@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 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/App.svelte"
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.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
- "./App.svelte": {
16
- "types": "./App.svelte.d.ts",
17
- "svelte": "./App.svelte",
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
@@ -1,5 +0,0 @@
1
- import { SvelteComponent } from "svelte"
2
-
3
- declare const App: typeof SvelteComponent
4
- export default App
5
- export declare function link(node: HTMLAnchorElement): { destroy(): void }
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