@volpe/astro-svelte-spa 0.1.0 → 0.1.1
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/App.svelte +40 -0
- package/App.svelte.d.ts +5 -0
- package/Link.svelte +32 -0
- package/Link.svelte.d.ts +12 -0
- package/README.md +28 -8
- package/index.d.ts +6 -6
- package/index.js +5 -88
- package/package.json +23 -2
package/App.svelte
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
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
ADDED
package/Link.svelte
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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
|
package/README.md
CHANGED
|
@@ -5,9 +5,10 @@ Vite plugin for file-based routing with svelte5-router in Astro.
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- File-based routing with `page.svelte` and `layout.svelte`
|
|
8
|
-
-
|
|
8
|
+
- Pre-built `App.svelte` and `Link.svelte` components
|
|
9
|
+
- Auto-generates `[...path].astro`, `page.svelte`, and `404.svelte`
|
|
9
10
|
- Virtual module `virtual:svelte-routes` with typed routes
|
|
10
|
-
- `SvelteAppRoutes` type for autocomplete
|
|
11
|
+
- `SvelteAppRoutes` type for autocomplete in `<Link>`
|
|
11
12
|
- 404 handling with `StatusCode.NotFound`
|
|
12
13
|
- HMR support with full reload on changes
|
|
13
14
|
|
|
@@ -23,16 +24,38 @@ npm install @volpe/astro-svelte-spa @mateothegreat/svelte5-router
|
|
|
23
24
|
// astro.config.mjs
|
|
24
25
|
import { defineConfig } from "astro/config"
|
|
25
26
|
import svelte from "@astrojs/svelte"
|
|
26
|
-
import {
|
|
27
|
+
import { plugin } from "@volpe/astro-svelte-spa"
|
|
27
28
|
|
|
28
29
|
export default defineConfig({
|
|
29
30
|
integrations: [svelte()],
|
|
30
31
|
vite: {
|
|
31
|
-
plugins: [
|
|
32
|
+
plugins: [plugin({ basePath: "/app" })]
|
|
32
33
|
}
|
|
33
34
|
})
|
|
34
35
|
```
|
|
35
36
|
|
|
37
|
+
## Components
|
|
38
|
+
|
|
39
|
+
Import the pre-built components:
|
|
40
|
+
|
|
41
|
+
```svelte
|
|
42
|
+
<!-- src/pages/svelte-app/[...path].astro -->
|
|
43
|
+
---
|
|
44
|
+
import App from "@volpe/astro-svelte-spa/App.svelte"
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
<App client:only="svelte" />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```svelte
|
|
51
|
+
<!-- In your Svelte pages -->
|
|
52
|
+
<script>
|
|
53
|
+
import Link from "@volpe/astro-svelte-spa/Link.svelte"
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<Link href="/svelte-app/about">About</Link>
|
|
57
|
+
```
|
|
58
|
+
|
|
36
59
|
## File Structure
|
|
37
60
|
|
|
38
61
|
```
|
|
@@ -41,15 +64,12 @@ src/
|
|
|
41
64
|
svelte-app/ # or your custom basePath
|
|
42
65
|
page.svelte # -> /svelte-app
|
|
43
66
|
404.svelte # -> catch-all 404
|
|
44
|
-
[...path].astro # Astro catch-all
|
|
67
|
+
[...path].astro # Astro catch-all (auto-generated)
|
|
45
68
|
about/
|
|
46
69
|
page.svelte # -> /svelte-app/about
|
|
47
70
|
settings/
|
|
48
71
|
page.svelte # -> /svelte-app/settings
|
|
49
72
|
layout.svelte # wraps settings pages
|
|
50
|
-
components/
|
|
51
|
-
App.svelte # auto-generated
|
|
52
|
-
Link.svelte # auto-generated
|
|
53
73
|
```
|
|
54
74
|
|
|
55
75
|
## Options
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface PluginOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Base path for all routes.
|
|
6
6
|
* When set to "/app", the plugin scans `src/pages/app/` and routes start with "/app".
|
|
@@ -14,7 +14,7 @@ export interface AstroSvelteSpaOptions {
|
|
|
14
14
|
*
|
|
15
15
|
* Features:
|
|
16
16
|
* - File-based routing with `page.svelte` and `layout.svelte`
|
|
17
|
-
* - Auto-generates `
|
|
17
|
+
* - Auto-generates `[...path].astro`, `page.svelte`, and `404.svelte`
|
|
18
18
|
* - Virtual module `virtual:svelte-routes` with typed routes
|
|
19
19
|
* - `SvelteAppRoutes` type for autocomplete
|
|
20
20
|
* - 404 handling with StatusCode.NotFound
|
|
@@ -23,15 +23,15 @@ export interface AstroSvelteSpaOptions {
|
|
|
23
23
|
* @example
|
|
24
24
|
* ```js
|
|
25
25
|
* // astro.config.mjs
|
|
26
|
-
* import {
|
|
26
|
+
* import { plugin } from "@volpe/astro-svelte-spa"
|
|
27
27
|
*
|
|
28
28
|
* export default defineConfig({
|
|
29
29
|
* vite: {
|
|
30
|
-
* plugins: [
|
|
30
|
+
* plugins: [plugin({ basePath: "/app" })]
|
|
31
31
|
* }
|
|
32
32
|
* })
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
|
-
export function
|
|
35
|
+
export function plugin(options?: PluginOptions): Plugin;
|
|
36
36
|
|
|
37
|
-
export default
|
|
37
|
+
export default plugin;
|
package/index.js
CHANGED
|
@@ -215,101 +215,18 @@ declare module "virtual:svelte-routes" {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
/**
|
|
218
|
-
* @param {string} rootDir
|
|
219
218
|
* @param {string} pagesDir
|
|
220
|
-
* @param {string} basePath
|
|
221
219
|
*/
|
|
222
|
-
function
|
|
223
|
-
const componentsDir = path.resolve(rootDir, "src/components");
|
|
224
|
-
|
|
225
|
-
if (!fs.existsSync(componentsDir)) {
|
|
226
|
-
fs.mkdirSync(componentsDir, { recursive: true });
|
|
227
|
-
}
|
|
228
|
-
|
|
220
|
+
function generatePageFiles(pagesDir) {
|
|
229
221
|
if (!fs.existsSync(pagesDir)) {
|
|
230
222
|
fs.mkdirSync(pagesDir, { recursive: true });
|
|
231
223
|
}
|
|
232
224
|
|
|
233
|
-
// Generate App.svelte if it doesn't exist
|
|
234
|
-
const appPath = path.resolve(componentsDir, "App.svelte");
|
|
235
|
-
if (!fs.existsSync(appPath)) {
|
|
236
|
-
const appContent = `<script>
|
|
237
|
-
import { Router, route, StatusCode } from "@mateothegreat/svelte5-router"
|
|
238
|
-
import routes, { basePath, notFoundComponent } from "virtual:svelte-routes"
|
|
239
|
-
|
|
240
|
-
// Build statuses config for 404 handling
|
|
241
|
-
const statuses = notFoundComponent ? {
|
|
242
|
-
[StatusCode.NotFound]: { component: notFoundComponent }
|
|
243
|
-
} : undefined
|
|
244
|
-
|
|
245
|
-
// Action that combines route + prefetch on hover
|
|
246
|
-
function link(node) {
|
|
247
|
-
const href = node.getAttribute("href")
|
|
248
|
-
|
|
249
|
-
function preload() {
|
|
250
|
-
const r = routes.find(r => r.path === href)
|
|
251
|
-
if (r?.component && typeof r.component === "function") {
|
|
252
|
-
r.component()
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
node.addEventListener("mouseenter", preload)
|
|
257
|
-
node.addEventListener("touchstart", preload, { passive: true })
|
|
258
|
-
|
|
259
|
-
const routeAction = route(node)
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
destroy() {
|
|
263
|
-
node.removeEventListener("mouseenter", preload)
|
|
264
|
-
node.removeEventListener("touchstart", preload)
|
|
265
|
-
routeAction?.destroy?.()
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export { link }
|
|
271
|
-
</script>
|
|
272
|
-
|
|
273
|
-
<Router {routes} {basePath} {statuses} />
|
|
274
|
-
`;
|
|
275
|
-
fs.writeFileSync(appPath, appContent);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Generate Link.svelte if it doesn't exist
|
|
279
|
-
const linkPath = path.resolve(componentsDir, "Link.svelte");
|
|
280
|
-
if (!fs.existsSync(linkPath)) {
|
|
281
|
-
const linkContent = `<script>
|
|
282
|
-
import { route } from "@mateothegreat/svelte5-router"
|
|
283
|
-
import routes from "virtual:svelte-routes"
|
|
284
|
-
|
|
285
|
-
let { href, children, ...rest } = $props()
|
|
286
|
-
|
|
287
|
-
function preload() {
|
|
288
|
-
const r = routes.find(r => r.path === href)
|
|
289
|
-
if (r?.component && typeof r.component === "function") {
|
|
290
|
-
r.component()
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
</script>
|
|
294
|
-
|
|
295
|
-
<a
|
|
296
|
-
{href}
|
|
297
|
-
use:route
|
|
298
|
-
onmouseenter={preload}
|
|
299
|
-
ontouchstart={preload}
|
|
300
|
-
{...rest}
|
|
301
|
-
>
|
|
302
|
-
{@render children()}
|
|
303
|
-
</a>
|
|
304
|
-
`;
|
|
305
|
-
fs.writeFileSync(linkPath, linkContent);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
225
|
// Generate [...path].astro if it doesn't exist
|
|
309
226
|
const catchAllPath = path.resolve(pagesDir, "[...path].astro");
|
|
310
227
|
if (!fs.existsSync(catchAllPath)) {
|
|
311
228
|
const catchAllContent = `---
|
|
312
|
-
import App from "
|
|
229
|
+
import App from "@volpe/astro-svelte-spa/App.svelte"
|
|
313
230
|
---
|
|
314
231
|
|
|
315
232
|
<App client:only="svelte" />
|
|
@@ -372,7 +289,7 @@ import App from "~/components/App.svelte"
|
|
|
372
289
|
* @param {string} [options.basePath] - Base path for all routes (defaults to "/svelte-app")
|
|
373
290
|
* @returns {import('vite').Plugin}
|
|
374
291
|
*/
|
|
375
|
-
export function
|
|
292
|
+
export function plugin(options = {}) {
|
|
376
293
|
// Normalize basePath, defaults to "/svelte-app"
|
|
377
294
|
const basePath = options.basePath?.replace(/\/$/, "") || "/svelte-app";
|
|
378
295
|
// Directory suffix: "/svelte-app" -> "svelte-app"
|
|
@@ -382,7 +299,7 @@ export function astroSvelteSpa(options = {}) {
|
|
|
382
299
|
const pagesDir = path.resolve(rootDir, "src/pages", dirSuffix);
|
|
383
300
|
const typesPath = path.resolve(rootDir, "src/svelte-routes.d.ts");
|
|
384
301
|
|
|
385
|
-
|
|
302
|
+
generatePageFiles(pagesDir);
|
|
386
303
|
|
|
387
304
|
return {
|
|
388
305
|
name: "vite-plugin-astro-svelte-spa",
|
|
@@ -441,4 +358,4 @@ export function astroSvelteSpa(options = {}) {
|
|
|
441
358
|
};
|
|
442
359
|
}
|
|
443
360
|
|
|
444
|
-
export default
|
|
361
|
+
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volpe/astro-svelte-spa",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./index.d.ts",
|
|
12
|
+
"svelte": "./index.js",
|
|
11
13
|
"default": "./index.js"
|
|
14
|
+
},
|
|
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"
|
|
12
24
|
}
|
|
13
25
|
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"index.d.ts",
|
|
29
|
+
"App.svelte",
|
|
30
|
+
"App.svelte.d.ts",
|
|
31
|
+
"Link.svelte",
|
|
32
|
+
"Link.svelte.d.ts"
|
|
33
|
+
],
|
|
14
34
|
"keywords": [
|
|
15
35
|
"vite",
|
|
16
36
|
"vite-plugin",
|
|
@@ -23,7 +43,8 @@
|
|
|
23
43
|
"author": "",
|
|
24
44
|
"license": "MIT",
|
|
25
45
|
"peerDependencies": {
|
|
26
|
-
"vite": "^5.0.0 || ^6.0.0"
|
|
46
|
+
"vite": "^5.0.0 || ^6.0.0",
|
|
47
|
+
"@mateothegreat/svelte5-router": "^2.0.0"
|
|
27
48
|
},
|
|
28
49
|
"publishConfig": {
|
|
29
50
|
"access": "public"
|