@void/svelte 0.7.12 → 0.8.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/dist/plugin.d.mts CHANGED
@@ -11,6 +11,6 @@ type VoidSvelteOptions = Readonly<{
11
11
  viewTransitions?: boolean;
12
12
  prefetch?: PrefetchConfig;
13
13
  }>;
14
- declare function voidSvelte(options?: VoidSvelteOptions): ReadonlyArray<Plugin>;
14
+ declare function voidSvelte(options?: VoidSvelteOptions): Array<Plugin>;
15
15
  //#endregion
16
16
  export { VoidSvelteOptions, voidSvelte };
package/dist/plugin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { svelte } from "@sveltejs/vite-plugin-svelte";
2
+ import { generateComponentManifest, resolveEffectiveLayoutChain, resolvePagesDir, resolveStaticPageDataBuildOptions, scanPages } from "void/pages";
2
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
4
  import { normalizePath } from "vite";
4
- import { generateComponentManifest, resolveEffectiveLayoutChain, resolveStaticPageDataBuildOptions, scanPages } from "void/pages";
5
5
  import { ISLANDS_CLIENT_ENTRY_ID, ISLANDS_RENDERER_ID, computePagesNeedingClientJS, extractCssImports, needsIslandClientBundle, resolveSpecifier, rewriteCssImportForVirtualModule, watchPagesForRescan } from "void/pages-islands-plugin";
6
6
  //#region ../../node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
7
7
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
@@ -9,42 +9,9 @@ function normalizeWindowsPath(input = "") {
9
9
  if (!input) return input;
10
10
  return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
11
11
  }
12
- const _UNC_REGEX = /^[/\\]{2}/;
13
12
  const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
14
13
  const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
15
14
  const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
16
- const normalize = function(path) {
17
- if (path.length === 0) return ".";
18
- path = normalizeWindowsPath(path);
19
- const isUNCPath = path.match(_UNC_REGEX);
20
- const isPathAbsolute = isAbsolute(path);
21
- const trailingSeparator = path[path.length - 1] === "/";
22
- path = normalizeString(path, !isPathAbsolute);
23
- if (path.length === 0) {
24
- if (isPathAbsolute) return "/";
25
- return trailingSeparator ? "./" : ".";
26
- }
27
- if (trailingSeparator) path += "/";
28
- if (_DRIVE_LETTER_RE.test(path)) path += "/";
29
- if (isUNCPath) {
30
- if (!isPathAbsolute) return `//./${path}`;
31
- return `//${path}`;
32
- }
33
- return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;
34
- };
35
- const join = function(...segments) {
36
- let path = "";
37
- for (const seg of segments) {
38
- if (!seg) continue;
39
- if (path.length > 0) {
40
- const pathTrailing = path[path.length - 1] === "/";
41
- const segLeading = seg[0] === "/";
42
- if (pathTrailing && segLeading) path += seg.slice(1);
43
- else path += pathTrailing || segLeading ? seg : `/${seg}`;
44
- } else path += seg;
45
- }
46
- return normalize(path);
47
- };
48
15
  function cwd() {
49
16
  if (typeof process !== "undefined" && typeof process.cwd === "function") return process.cwd().replace(/\\/g, "/");
50
17
  return "/";
@@ -140,15 +107,31 @@ const COMPONENT_EXTENSIONS = [
140
107
  ".ts",
141
108
  ".js"
142
109
  ];
110
+ const ISLAND_IMPORT_RE = /import\s+([$_A-Za-z][$_\w]*)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*,?\s*\}/g;
111
+ function findIslandImports(code) {
112
+ const imports = [];
113
+ let match;
114
+ ISLAND_IMPORT_RE.lastIndex = 0;
115
+ while ((match = ISLAND_IMPORT_RE.exec(code)) !== null) {
116
+ const specifierRaw = match[2];
117
+ imports.push({
118
+ statement: match[0],
119
+ importName: match[1],
120
+ specifier: specifierRaw.slice(1, -1),
121
+ hydrate: match[3].slice(1, -1)
122
+ });
123
+ }
124
+ return imports;
125
+ }
143
126
  /**
144
127
  * Prefix for virtual island wrapper modules. Must NOT use the `\0` prefix
145
128
  * because `@sveltejs/vite-plugin-svelte` skips `\0`-prefixed modules in
146
129
  * its transform filter.
147
130
  */
148
131
  const WRAPPER_PREFIX = "/@void-island-wrap/";
149
- function islandsPlugin(pagesDir) {
132
+ function islandsPlugin(pagesDir, options) {
150
133
  let pageScan = null;
151
- const root = dirname(pagesDir);
134
+ const root = options?.root ?? dirname(pagesDir);
152
135
  const islandManifest = /* @__PURE__ */ new Map();
153
136
  const islandCssFiles = /* @__PURE__ */ new Set();
154
137
  let isDev = false;
@@ -173,8 +156,7 @@ function islandsPlugin(pagesDir) {
173
156
  },
174
157
  async buildStart() {
175
158
  if (existsSync(pagesDir)) {
176
- pageScan = await scanPages(root);
177
- const importAttrRe = /import\s+\w+\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
159
+ pageScan = await scanPages(root, { pagesDir });
178
160
  const filesToScan = [
179
161
  ...pageScan.pages.map((page) => ({
180
162
  filePath: page.componentPath,
@@ -194,11 +176,9 @@ function islandsPlugin(pagesDir) {
194
176
  const filePath = resolve(pagesDir, entry.filePath);
195
177
  const code = readFileSync(filePath, "utf-8");
196
178
  const importerDir = dirname(filePath);
197
- importAttrRe.lastIndex = 0;
198
- let importMatch;
199
- while ((importMatch = importAttrRe.exec(code)) !== null) {
200
- const spec = importMatch[1].slice(1, -1);
201
- const strategy = importMatch[2].slice(1, -1);
179
+ for (const islandImport of findIslandImports(code)) {
180
+ const spec = islandImport.specifier;
181
+ const strategy = islandImport.hydrate;
202
182
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
203
183
  if (absPath) {
204
184
  const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
@@ -258,16 +238,12 @@ function islandsPlugin(pagesDir) {
258
238
  filter: { id: /\.island\.svelte$/ },
259
239
  handler(code, id) {
260
240
  if (/import\s+\{[^}]*\buseForm\b/.test(code)) this.warn(`islands: useForm cannot be imported in island page '${id}'. Use useIslandForm instead.`);
261
- const importAttrRe = /import\s+(\w+)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
262
241
  const importerDir = dirname(id);
263
242
  let transformed = code;
264
- let match;
265
- while ((match = importAttrRe.exec(code)) !== null) {
266
- const name = match[1];
267
- const specRaw = match[2];
268
- const strategyRaw = match[3];
269
- const spec = specRaw.slice(1, -1);
270
- const strategy = strategyRaw.slice(1, -1);
243
+ for (const islandImport of findIslandImports(code)) {
244
+ const name = islandImport.importName;
245
+ const spec = islandImport.specifier;
246
+ const strategy = islandImport.hydrate;
271
247
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
272
248
  if (absPath) {
273
249
  const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
@@ -280,7 +256,7 @@ function islandsPlugin(pagesDir) {
280
256
  const islandId = absPath ? normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "") : spec;
281
257
  const wrapperUrl = WRAPPER_PREFIX + encodeURIComponent(islandId) + ".svelte?src=" + encodeURIComponent(absPath || spec) + "&island=" + encodeURIComponent(islandId) + "&hydrate=" + encodeURIComponent(strategy);
282
258
  const replacement = `import ${name} from ${JSON.stringify(wrapperUrl)}`;
283
- transformed = transformed.replace(match[0], replacement);
259
+ transformed = transformed.replace(islandImport.statement, replacement);
284
260
  }
285
261
  if (transformed !== code) return {
286
262
  code: transformed,
@@ -459,11 +435,28 @@ function pagesPlugin(pagesDir, options) {
459
435
  let pageScan = null;
460
436
  let isDev = false;
461
437
  let viteConfig;
462
- const root = dirname(pagesDir);
438
+ const root = options?.root ?? dirname(pagesDir);
439
+ const normalizedPagesDir = normalizePath(pagesDir);
440
+ let pendingRescan = null;
441
+ const rescanPages = async () => {
442
+ pageScan = existsSync(pagesDir) ? await scanPages(root, { pagesDir }) : null;
443
+ };
444
+ const runRescan = () => {
445
+ pendingRescan ??= rescanPages().finally(() => {
446
+ pendingRescan = null;
447
+ });
448
+ return pendingRescan;
449
+ };
450
+ const invalidatePageModules = (server) => {
451
+ for (const env of Object.values(server.environments)) for (const id of ["\0" + RENDERER_ID, "\0" + CLIENT_ENTRY_ID]) {
452
+ const mod = env.moduleGraph.getModuleById(id);
453
+ if (mod) env.moduleGraph.invalidateModule(mod);
454
+ }
455
+ };
463
456
  return {
464
457
  name: "void-svelte:pages",
465
458
  async buildStart() {
466
- if (existsSync(pagesDir)) pageScan = await scanPages(root);
459
+ await runRescan();
467
460
  },
468
461
  resolveId: {
469
462
  filter: { id: /^virtual:void-pages-(renderer|client)$/ },
@@ -473,7 +466,9 @@ function pagesPlugin(pagesDir, options) {
473
466
  },
474
467
  load: {
475
468
  filter: { id: /^\0virtual:void-pages-(renderer|client)$/ },
476
- handler(id) {
469
+ async handler(id) {
470
+ if (isDev && pendingRescan) await pendingRescan;
471
+ if (!pageScan && existsSync(pagesDir)) await runRescan();
477
472
  if (id === "\0" + RENDERER_ID) {
478
473
  if (!pageScan) return "export function renderPage() { throw new Error('No pages found'); }";
479
474
  return generateRendererModule(pageScan, pagesDir, isDev);
@@ -484,6 +479,18 @@ function pagesPlugin(pagesDir, options) {
484
479
  }
485
480
  }
486
481
  },
482
+ configureServer(server) {
483
+ const handlePagesChange = async (path, event) => {
484
+ const normalizedPath = normalizePath(path);
485
+ if (normalizedPath !== normalizedPagesDir && !normalizedPath.startsWith(normalizedPagesDir + "/")) return;
486
+ if (event === "change" && !normalizedPath.endsWith(".md")) return;
487
+ await runRescan();
488
+ invalidatePageModules(server);
489
+ };
490
+ server.watcher.on("add", (path) => handlePagesChange(path, "add"));
491
+ server.watcher.on("change", (path) => handlePagesChange(path, "change"));
492
+ server.watcher.on("unlink", (path) => handlePagesChange(path, "unlink"));
493
+ },
487
494
  configResolved(config) {
488
495
  viteConfig = config;
489
496
  },
@@ -661,14 +668,16 @@ start();
661
668
  //#endregion
662
669
  //#region src/plugin.ts
663
670
  function voidSvelte(options) {
664
- const pagesDir = join(process.cwd(), "pages");
671
+ const root = process.cwd();
672
+ const pagesDir = resolvePagesDir(root);
665
673
  return [
666
674
  ...svelte(options?.svelte),
667
675
  pagesPlugin(pagesDir, {
676
+ root,
668
677
  viewTransitions: options?.viewTransitions,
669
678
  prefetch: options?.prefetch
670
679
  }),
671
- ...islandsPlugin(pagesDir)
680
+ ...islandsPlugin(pagesDir, { root })
672
681
  ];
673
682
  }
674
683
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/svelte",
3
- "version": "0.7.12",
3
+ "version": "0.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -44,12 +44,12 @@
44
44
  "svelte": "^5.55.5",
45
45
  "vite": "npm:@voidzero-dev/vite-plus-core@0.1.20",
46
46
  "vite-plus": "^0.1.20",
47
- "void": "0.7.12"
47
+ "void": "0.8.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "svelte": "^5.0.0",
51
51
  "vite": "^8.0.0",
52
- "void": "0.7.12"
52
+ "void": "0.8.0"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "vp pack",