@volpe/astro-svelte-spa 0.1.1 → 0.1.2

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,11 @@ 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
+ const VIRTUAL_APP_ID = "\0svelte-spa-app.svelte";
10
+ const VIRTUAL_LINK_ID = "\0svelte-spa-link.svelte";
11
+
7
12
  /**
8
13
  * @typedef {Object} RouteNode
9
14
  * @property {string} path
@@ -101,15 +106,12 @@ function applyBasePath(routePath, basePath) {
101
106
  function generateRoutes(node, imports) {
102
107
  const routes = [];
103
108
 
104
- // Use relative paths (basePath is handled by Router component)
105
109
  const routePath = node.path;
106
110
  const layoutName = node.layout ? imports.get(node.layout) : null;
107
111
  const pageName = node.page ? imports.get(node.page) : null;
108
112
 
109
- // Add page route
110
113
  if (pageName) {
111
114
  if (layoutName) {
112
- // With layout - use children
113
115
  routes.push(`{
114
116
  path: "${routePath}",
115
117
  component: ${layoutName},
@@ -118,7 +120,6 @@ function generateRoutes(node, imports) {
118
120
  ]
119
121
  }`);
120
122
  } else {
121
- // No layout - simple route with async loading
122
123
  routes.push(`{
123
124
  path: "${routePath}",
124
125
  component: async () => import("${node.page}")
@@ -126,7 +127,6 @@ function generateRoutes(node, imports) {
126
127
  }
127
128
  }
128
129
 
129
- // Add children routes
130
130
  for (const child of node.children) {
131
131
  routes.push(...generateRoutes(child, imports));
132
132
  }
@@ -144,16 +144,13 @@ function generateRoutesModule(pagesDir, basePath) {
144
144
  const imports = new Map();
145
145
  generateImports(tree, imports);
146
146
 
147
- // Import layouts (pages are loaded async)
148
147
  const layoutImports = Array.from(imports.entries())
149
148
  .filter(([filePath]) => filePath.includes("layout.svelte"))
150
149
  .map(([filePath, name]) => `import ${name} from "${filePath}"`)
151
150
  .join("\n");
152
151
 
153
- // Routes use relative paths - basePath is handled by Router component
154
152
  const routes = generateRoutes(tree, imports);
155
153
 
156
- // Check for 404.svelte for statuses
157
154
  const notFoundPath = path.join(pagesDir, "404.svelte");
158
155
  const hasNotFound = fs.existsSync(notFoundPath);
159
156
  const notFoundImportStatement = hasNotFound
@@ -222,11 +219,10 @@ function generatePageFiles(pagesDir) {
222
219
  fs.mkdirSync(pagesDir, { recursive: true });
223
220
  }
224
221
 
225
- // Generate [...path].astro if it doesn't exist
226
222
  const catchAllPath = path.resolve(pagesDir, "[...path].astro");
227
223
  if (!fs.existsSync(catchAllPath)) {
228
224
  const catchAllContent = `---
229
- import App from "@volpe/astro-svelte-spa/App.svelte"
225
+ import { App } from "@volpe/astro-svelte-spa"
230
226
  ---
231
227
 
232
228
  <App client:only="svelte" />
@@ -234,7 +230,6 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
234
230
  fs.writeFileSync(catchAllPath, catchAllContent);
235
231
  }
236
232
 
237
- // Generate page.svelte if it doesn't exist
238
233
  const pagePath = path.resolve(pagesDir, "page.svelte");
239
234
  if (!fs.existsSync(pagePath)) {
240
235
  const pageContent = `<h1>Edit me :)</h1>
@@ -242,7 +237,6 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
242
237
  fs.writeFileSync(pagePath, pageContent);
243
238
  }
244
239
 
245
- // Generate 404.svelte if it doesn't exist
246
240
  const notFoundPagePath = path.resolve(pagesDir, "404.svelte");
247
241
  if (!fs.existsSync(notFoundPagePath)) {
248
242
  const notFoundContent = `<script>
@@ -283,6 +277,46 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
283
277
  }
284
278
  }
285
279
 
280
+ /**
281
+ * Generates the virtual App component code
282
+ * @returns {string}
283
+ */
284
+ function generateAppComponent() {
285
+ return `<script>
286
+ import { Router, StatusCode } from "@mateothegreat/svelte5-router"
287
+ import routes, { basePath, notFoundComponent } from "virtual:svelte-routes"
288
+
289
+ const statuses = notFoundComponent ? {
290
+ [StatusCode.NotFound]: { component: notFoundComponent }
291
+ } : undefined
292
+ </script>
293
+
294
+ <Router {routes} {basePath} {statuses} />
295
+ `;
296
+ }
297
+
298
+ /**
299
+ * Generates the virtual Link component code
300
+ * @returns {string}
301
+ */
302
+ function generateLinkComponent() {
303
+ return `<script>
304
+ import { route, goto } from "@mateothegreat/svelte5-router"
305
+
306
+ let { href, children, ...rest } = $props()
307
+
308
+ const handleClick = (e) => {
309
+ e.preventDefault()
310
+ goto(href)
311
+ }
312
+ </script>
313
+
314
+ <a {href} onclick={handleClick} class:active={$route.path === href} {...rest}>
315
+ {@render children?.()}
316
+ </a>
317
+ `;
318
+ }
319
+
286
320
  /**
287
321
  * Vite plugin for file-based routing with svelte5-router in Astro
288
322
  * @param {Object} options - Plugin options
@@ -290,9 +324,7 @@ import App from "@volpe/astro-svelte-spa/App.svelte"
290
324
  * @returns {import('vite').Plugin}
291
325
  */
292
326
  export function plugin(options = {}) {
293
- // Normalize basePath, defaults to "/svelte-app"
294
327
  const basePath = options.basePath?.replace(/\/$/, "") || "/svelte-app";
295
- // Directory suffix: "/svelte-app" -> "svelte-app"
296
328
  const dirSuffix = basePath.replace(/^\//, "");
297
329
 
298
330
  const rootDir = process.cwd();
@@ -344,12 +376,34 @@ export function plugin(options = {}) {
344
376
  if (id === VIRTUAL_MODULE_ID) {
345
377
  return RESOLVED_VIRTUAL_MODULE_ID;
346
378
  }
379
+ // Intercept main package import (but not /plugin subpath)
380
+ if (id === PACKAGE_ID) {
381
+ return RESOLVED_PACKAGE_ID;
382
+ }
383
+ // Handle virtual component IDs
384
+ if (id === VIRTUAL_APP_ID || id === VIRTUAL_LINK_ID) {
385
+ return id;
386
+ }
347
387
  },
348
388
 
349
389
  load(id) {
350
390
  if (id === RESOLVED_VIRTUAL_MODULE_ID) {
351
391
  return generateRoutesModule(pagesDir, basePath);
352
392
  }
393
+ // Virtual package module - exports App, Link, and re-exports plugin
394
+ if (id === RESOLVED_PACKAGE_ID) {
395
+ return `
396
+ export { default as App } from "${VIRTUAL_APP_ID}";
397
+ export { default as Link } from "${VIRTUAL_LINK_ID}";
398
+ export { plugin, default } from "@volpe/astro-svelte-spa/plugin";
399
+ `;
400
+ }
401
+ if (id === VIRTUAL_APP_ID) {
402
+ return generateAppComponent();
403
+ }
404
+ if (id === VIRTUAL_LINK_ID) {
405
+ return generateLinkComponent();
406
+ }
353
407
  },
354
408
 
355
409
  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.2",
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