@volpe/astro-svelte-spa 0.1.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/README.md +61 -0
- package/index.d.ts +37 -0
- package/index.js +444 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @volpe/astro-svelte-spa
|
|
2
|
+
|
|
3
|
+
Vite plugin for file-based routing with svelte5-router in Astro.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- File-based routing with `page.svelte` and `layout.svelte`
|
|
8
|
+
- Auto-generates `App.svelte`, `Link.svelte`, `[...path].astro`, `page.svelte`, and `404.svelte`
|
|
9
|
+
- Virtual module `virtual:svelte-routes` with typed routes
|
|
10
|
+
- `SvelteAppRoutes` type for autocomplete
|
|
11
|
+
- 404 handling with `StatusCode.NotFound`
|
|
12
|
+
- HMR support with full reload on changes
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @volpe/astro-svelte-spa @mateothegreat/svelte5-router
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// astro.config.mjs
|
|
24
|
+
import { defineConfig } from "astro/config"
|
|
25
|
+
import svelte from "@astrojs/svelte"
|
|
26
|
+
import { astroSvelteSpa } from "@volpe/astro-svelte-spa"
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
integrations: [svelte()],
|
|
30
|
+
vite: {
|
|
31
|
+
plugins: [astroSvelteSpa({ basePath: "/app" })]
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## File Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
src/
|
|
40
|
+
pages/
|
|
41
|
+
svelte-app/ # or your custom basePath
|
|
42
|
+
page.svelte # -> /svelte-app
|
|
43
|
+
404.svelte # -> catch-all 404
|
|
44
|
+
[...path].astro # Astro catch-all
|
|
45
|
+
about/
|
|
46
|
+
page.svelte # -> /svelte-app/about
|
|
47
|
+
settings/
|
|
48
|
+
page.svelte # -> /svelte-app/settings
|
|
49
|
+
layout.svelte # wraps settings pages
|
|
50
|
+
components/
|
|
51
|
+
App.svelte # auto-generated
|
|
52
|
+
Link.svelte # auto-generated
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Options
|
|
56
|
+
|
|
57
|
+
- `basePath` (string, default: `"/svelte-app"`): Base path for all routes
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
export interface AstroSvelteSpaOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Base path for all routes.
|
|
6
|
+
* When set to "/app", the plugin scans `src/pages/app/` and routes start with "/app".
|
|
7
|
+
* @default "/svelte-app"
|
|
8
|
+
*/
|
|
9
|
+
basePath?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Vite plugin for file-based routing with svelte5-router in Astro.
|
|
14
|
+
*
|
|
15
|
+
* Features:
|
|
16
|
+
* - File-based routing with `page.svelte` and `layout.svelte`
|
|
17
|
+
* - Auto-generates `App.svelte`, `Link.svelte`, `[...path].astro`, `page.svelte`, and `404.svelte`
|
|
18
|
+
* - Virtual module `virtual:svelte-routes` with typed routes
|
|
19
|
+
* - `SvelteAppRoutes` type for autocomplete
|
|
20
|
+
* - 404 handling with StatusCode.NotFound
|
|
21
|
+
* - HMR support with full reload on changes
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```js
|
|
25
|
+
* // astro.config.mjs
|
|
26
|
+
* import { astroSvelteSpa } from "@volpe/astro-svelte-spa"
|
|
27
|
+
*
|
|
28
|
+
* export default defineConfig({
|
|
29
|
+
* vite: {
|
|
30
|
+
* plugins: [astroSvelteSpa({ basePath: "/app" })]
|
|
31
|
+
* }
|
|
32
|
+
* })
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function astroSvelteSpa(options?: AstroSvelteSpaOptions): Plugin;
|
|
36
|
+
|
|
37
|
+
export default astroSvelteSpa;
|
package/index.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const VIRTUAL_MODULE_ID = "virtual:svelte-routes";
|
|
5
|
+
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} RouteNode
|
|
9
|
+
* @property {string} path
|
|
10
|
+
* @property {string} [page]
|
|
11
|
+
* @property {string} [layout]
|
|
12
|
+
* @property {RouteNode[]} children
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} dir
|
|
17
|
+
* @param {string} basePath
|
|
18
|
+
* @returns {RouteNode}
|
|
19
|
+
*/
|
|
20
|
+
function scanPages(dir, basePath = "") {
|
|
21
|
+
const node = {
|
|
22
|
+
path: basePath || "/",
|
|
23
|
+
children: [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(dir)) return node;
|
|
27
|
+
|
|
28
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
29
|
+
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const fullPath = path.join(dir, entry.name);
|
|
32
|
+
|
|
33
|
+
if (entry.isFile()) {
|
|
34
|
+
if (entry.name === "page.svelte") {
|
|
35
|
+
node.page = fullPath;
|
|
36
|
+
} else if (entry.name === "layout.svelte") {
|
|
37
|
+
node.layout = fullPath;
|
|
38
|
+
}
|
|
39
|
+
} else if (entry.isDirectory() && !entry.name.startsWith("[") && !entry.name.startsWith("_")) {
|
|
40
|
+
const childPath = basePath ? `${basePath}/${entry.name}` : `/${entry.name}`;
|
|
41
|
+
const childNode = scanPages(fullPath, childPath);
|
|
42
|
+
if (childNode.page || childNode.layout || childNode.children.length > 0) {
|
|
43
|
+
node.children.push(childNode);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return node;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {RouteNode} node
|
|
53
|
+
* @param {string[]} paths
|
|
54
|
+
* @returns {string[]}
|
|
55
|
+
*/
|
|
56
|
+
function collectPaths(node, paths = []) {
|
|
57
|
+
if (node.page) {
|
|
58
|
+
paths.push(node.path);
|
|
59
|
+
}
|
|
60
|
+
for (const child of node.children) {
|
|
61
|
+
collectPaths(child, paths);
|
|
62
|
+
}
|
|
63
|
+
return paths;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {RouteNode} node
|
|
68
|
+
* @param {Map<string, string>} imports
|
|
69
|
+
* @param {{ value: number }} counter
|
|
70
|
+
*/
|
|
71
|
+
function generateImports(node, imports, counter = { value: 0 }) {
|
|
72
|
+
if (node.layout) {
|
|
73
|
+
const name = `Layout${counter.value++}`;
|
|
74
|
+
imports.set(node.layout, name);
|
|
75
|
+
}
|
|
76
|
+
if (node.page) {
|
|
77
|
+
const name = `Page${counter.value++}`;
|
|
78
|
+
imports.set(node.page, name);
|
|
79
|
+
}
|
|
80
|
+
for (const child of node.children) {
|
|
81
|
+
generateImports(child, imports, counter);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} routePath
|
|
87
|
+
* @param {string} basePath
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function applyBasePath(routePath, basePath) {
|
|
91
|
+
if (!basePath) return routePath;
|
|
92
|
+
if (routePath === "/") return basePath;
|
|
93
|
+
return basePath + routePath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {RouteNode} node
|
|
98
|
+
* @param {Map<string, string>} imports
|
|
99
|
+
* @returns {string[]}
|
|
100
|
+
*/
|
|
101
|
+
function generateRoutes(node, imports) {
|
|
102
|
+
const routes = [];
|
|
103
|
+
|
|
104
|
+
// Use relative paths (basePath is handled by Router component)
|
|
105
|
+
const routePath = node.path;
|
|
106
|
+
const layoutName = node.layout ? imports.get(node.layout) : null;
|
|
107
|
+
const pageName = node.page ? imports.get(node.page) : null;
|
|
108
|
+
|
|
109
|
+
// Add page route
|
|
110
|
+
if (pageName) {
|
|
111
|
+
if (layoutName) {
|
|
112
|
+
// With layout - use children
|
|
113
|
+
routes.push(`{
|
|
114
|
+
path: "${routePath}",
|
|
115
|
+
component: ${layoutName},
|
|
116
|
+
children: [
|
|
117
|
+
{ path: "${routePath}", component: async () => import("${node.page}") }
|
|
118
|
+
]
|
|
119
|
+
}`);
|
|
120
|
+
} else {
|
|
121
|
+
// No layout - simple route with async loading
|
|
122
|
+
routes.push(`{
|
|
123
|
+
path: "${routePath}",
|
|
124
|
+
component: async () => import("${node.page}")
|
|
125
|
+
}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add children routes
|
|
130
|
+
for (const child of node.children) {
|
|
131
|
+
routes.push(...generateRoutes(child, imports));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return routes;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {string} pagesDir
|
|
139
|
+
* @param {string} basePath
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
function generateRoutesModule(pagesDir, basePath) {
|
|
143
|
+
const tree = scanPages(pagesDir);
|
|
144
|
+
const imports = new Map();
|
|
145
|
+
generateImports(tree, imports);
|
|
146
|
+
|
|
147
|
+
// Import layouts (pages are loaded async)
|
|
148
|
+
const layoutImports = Array.from(imports.entries())
|
|
149
|
+
.filter(([filePath]) => filePath.includes("layout.svelte"))
|
|
150
|
+
.map(([filePath, name]) => `import ${name} from "${filePath}"`)
|
|
151
|
+
.join("\n");
|
|
152
|
+
|
|
153
|
+
// Routes use relative paths - basePath is handled by Router component
|
|
154
|
+
const routes = generateRoutes(tree, imports);
|
|
155
|
+
|
|
156
|
+
// Check for 404.svelte for statuses
|
|
157
|
+
const notFoundPath = path.join(pagesDir, "404.svelte");
|
|
158
|
+
const hasNotFound = fs.existsSync(notFoundPath);
|
|
159
|
+
const notFoundImportStatement = hasNotFound
|
|
160
|
+
? `import NotFoundComponent from "${notFoundPath.replace(/\\/g, "/")}"`
|
|
161
|
+
: "";
|
|
162
|
+
const notFoundExport = hasNotFound
|
|
163
|
+
? `export const notFoundComponent = NotFoundComponent`
|
|
164
|
+
: `export const notFoundComponent = null`;
|
|
165
|
+
|
|
166
|
+
const allImports = [layoutImports, notFoundImportStatement].filter(Boolean).join("\n");
|
|
167
|
+
|
|
168
|
+
return `${allImports}
|
|
169
|
+
|
|
170
|
+
export const basePath = "${basePath}"
|
|
171
|
+
|
|
172
|
+
export const routes = [
|
|
173
|
+
${routes.join(",\n ")}
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
${notFoundExport}
|
|
177
|
+
|
|
178
|
+
export default routes
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @param {string} pagesDir
|
|
184
|
+
* @param {string} outputPath
|
|
185
|
+
* @param {string} basePath
|
|
186
|
+
*/
|
|
187
|
+
function generateTypesFile(pagesDir, outputPath, basePath) {
|
|
188
|
+
const tree = scanPages(pagesDir);
|
|
189
|
+
const paths = collectPaths(tree).map((p) => applyBasePath(p, basePath));
|
|
190
|
+
|
|
191
|
+
const pathsUnion = paths.map((p) => `"${p}"`).join(" | ") || "string";
|
|
192
|
+
|
|
193
|
+
const content = `// Auto-generated by @volpe/astro-svelte-spa
|
|
194
|
+
// Do not edit manually
|
|
195
|
+
|
|
196
|
+
declare module "virtual:svelte-routes" {
|
|
197
|
+
import type { Component } from "svelte"
|
|
198
|
+
|
|
199
|
+
export type SvelteAppRoutes = ${pathsUnion}
|
|
200
|
+
export const basePath: string
|
|
201
|
+
export const routes: Array<{
|
|
202
|
+
path: string
|
|
203
|
+
component: any
|
|
204
|
+
children?: Array<{ path: string; component: any }>
|
|
205
|
+
}>
|
|
206
|
+
export const notFoundComponent: Component<any> | null
|
|
207
|
+
export default routes
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
const currentContent = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, "utf-8") : "";
|
|
212
|
+
if (currentContent !== content) {
|
|
213
|
+
fs.writeFileSync(outputPath, content);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @param {string} rootDir
|
|
219
|
+
* @param {string} pagesDir
|
|
220
|
+
* @param {string} basePath
|
|
221
|
+
*/
|
|
222
|
+
function generateComponentFiles(rootDir, pagesDir, basePath) {
|
|
223
|
+
const componentsDir = path.resolve(rootDir, "src/components");
|
|
224
|
+
|
|
225
|
+
if (!fs.existsSync(componentsDir)) {
|
|
226
|
+
fs.mkdirSync(componentsDir, { recursive: true });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!fs.existsSync(pagesDir)) {
|
|
230
|
+
fs.mkdirSync(pagesDir, { recursive: true });
|
|
231
|
+
}
|
|
232
|
+
|
|
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
|
+
// Generate [...path].astro if it doesn't exist
|
|
309
|
+
const catchAllPath = path.resolve(pagesDir, "[...path].astro");
|
|
310
|
+
if (!fs.existsSync(catchAllPath)) {
|
|
311
|
+
const catchAllContent = `---
|
|
312
|
+
import App from "~/components/App.svelte"
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
<App client:only="svelte" />
|
|
316
|
+
`;
|
|
317
|
+
fs.writeFileSync(catchAllPath, catchAllContent);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Generate page.svelte if it doesn't exist
|
|
321
|
+
const pagePath = path.resolve(pagesDir, "page.svelte");
|
|
322
|
+
if (!fs.existsSync(pagePath)) {
|
|
323
|
+
const pageContent = `<h1>Edit me :)</h1>
|
|
324
|
+
`;
|
|
325
|
+
fs.writeFileSync(pagePath, pageContent);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Generate 404.svelte if it doesn't exist
|
|
329
|
+
const notFoundPagePath = path.resolve(pagesDir, "404.svelte");
|
|
330
|
+
if (!fs.existsSync(notFoundPagePath)) {
|
|
331
|
+
const notFoundContent = `<script>
|
|
332
|
+
import { goto } from "@mateothegreat/svelte5-router"
|
|
333
|
+
import { basePath } from "virtual:svelte-routes"
|
|
334
|
+
</script>
|
|
335
|
+
|
|
336
|
+
<div class="not-found">
|
|
337
|
+
<h1>404</h1>
|
|
338
|
+
<p>Page not found</p>
|
|
339
|
+
<button onclick={() => goto(basePath)}>Go Home</button>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<style>
|
|
343
|
+
.not-found {
|
|
344
|
+
display: flex;
|
|
345
|
+
flex-direction: column;
|
|
346
|
+
align-items: center;
|
|
347
|
+
justify-content: center;
|
|
348
|
+
min-height: 50vh;
|
|
349
|
+
text-align: center;
|
|
350
|
+
}
|
|
351
|
+
h1 {
|
|
352
|
+
font-size: 4rem;
|
|
353
|
+
margin: 0;
|
|
354
|
+
}
|
|
355
|
+
p {
|
|
356
|
+
color: #666;
|
|
357
|
+
margin: 1rem 0;
|
|
358
|
+
}
|
|
359
|
+
button {
|
|
360
|
+
padding: 0.5rem 1rem;
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
}
|
|
363
|
+
</style>
|
|
364
|
+
`;
|
|
365
|
+
fs.writeFileSync(notFoundPagePath, notFoundContent);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Vite plugin for file-based routing with svelte5-router in Astro
|
|
371
|
+
* @param {Object} options - Plugin options
|
|
372
|
+
* @param {string} [options.basePath] - Base path for all routes (defaults to "/svelte-app")
|
|
373
|
+
* @returns {import('vite').Plugin}
|
|
374
|
+
*/
|
|
375
|
+
export function astroSvelteSpa(options = {}) {
|
|
376
|
+
// Normalize basePath, defaults to "/svelte-app"
|
|
377
|
+
const basePath = options.basePath?.replace(/\/$/, "") || "/svelte-app";
|
|
378
|
+
// Directory suffix: "/svelte-app" -> "svelte-app"
|
|
379
|
+
const dirSuffix = basePath.replace(/^\//, "");
|
|
380
|
+
|
|
381
|
+
const rootDir = process.cwd();
|
|
382
|
+
const pagesDir = path.resolve(rootDir, "src/pages", dirSuffix);
|
|
383
|
+
const typesPath = path.resolve(rootDir, "src/svelte-routes.d.ts");
|
|
384
|
+
|
|
385
|
+
generateComponentFiles(rootDir, pagesDir, basePath);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
name: "vite-plugin-astro-svelte-spa",
|
|
389
|
+
enforce: "pre",
|
|
390
|
+
|
|
391
|
+
buildStart() {
|
|
392
|
+
generateTypesFile(pagesDir, typesPath, basePath);
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
configureServer(server) {
|
|
396
|
+
generateTypesFile(pagesDir, typesPath, basePath);
|
|
397
|
+
|
|
398
|
+
const watchPath = `/src/pages/${dirSuffix}/`;
|
|
399
|
+
server.watcher.on("all", (event, filePath) => {
|
|
400
|
+
if (
|
|
401
|
+
filePath.includes(watchPath) &&
|
|
402
|
+
(filePath.endsWith("page.svelte") || filePath.endsWith("layout.svelte") || filePath.endsWith("404.svelte"))
|
|
403
|
+
) {
|
|
404
|
+
generateTypesFile(pagesDir, typesPath, basePath);
|
|
405
|
+
|
|
406
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
|
|
407
|
+
if (mod) {
|
|
408
|
+
server.moduleGraph.invalidateModule(mod);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (event === "add" || event === "unlink") {
|
|
412
|
+
server.ws.send({ type: "full-reload" });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (
|
|
417
|
+
event === "change" &&
|
|
418
|
+
filePath.endsWith(".svelte") &&
|
|
419
|
+
(filePath.includes("/src/components/") || filePath.includes(watchPath))
|
|
420
|
+
) {
|
|
421
|
+
server.ws.send({ type: "full-reload" });
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
resolveId(id) {
|
|
427
|
+
if (id === VIRTUAL_MODULE_ID) {
|
|
428
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
load(id) {
|
|
433
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
434
|
+
return generateRoutesModule(pagesDir, basePath);
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
handleHotUpdate() {
|
|
439
|
+
return [];
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export default astroSvelteSpa;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@volpe/astro-svelte-spa",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite plugin for file-based routing with svelte5-router in Astro",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"vite",
|
|
16
|
+
"vite-plugin",
|
|
17
|
+
"astro",
|
|
18
|
+
"svelte",
|
|
19
|
+
"svelte5-router",
|
|
20
|
+
"spa",
|
|
21
|
+
"file-based-routing"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"vite": "^5.0.0 || ^6.0.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
}
|