@void/solid 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 VoidSolidOptions = Readonly<{
11
11
  viewTransitions?: boolean;
12
12
  prefetch?: PrefetchConfig;
13
13
  }>;
14
- declare function voidSolid(options?: VoidSolidOptions): ReadonlyArray<Plugin>;
14
+ declare function voidSolid(options?: VoidSolidOptions): Array<Plugin>;
15
15
  //#endregion
16
16
  export { VoidSolidOptions, voidSolid };
package/dist/plugin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import solid from "vite-plugin-solid";
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 "/";
@@ -141,6 +108,22 @@ const COMPONENT_EXTENSIONS = [
141
108
  ".ts",
142
109
  ".js"
143
110
  ];
111
+ const ISLAND_IMPORT_RE = /import\s+([$_A-Za-z][$_\w]*)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*,?\s*\}/g;
112
+ function findIslandImports(code) {
113
+ const imports = [];
114
+ let match;
115
+ ISLAND_IMPORT_RE.lastIndex = 0;
116
+ while ((match = ISLAND_IMPORT_RE.exec(code)) !== null) {
117
+ const specifierRaw = match[2];
118
+ imports.push({
119
+ statement: match[0],
120
+ importName: match[1],
121
+ specifier: specifierRaw.slice(1, -1),
122
+ hydrate: match[3].slice(1, -1)
123
+ });
124
+ }
125
+ return imports;
126
+ }
144
127
  /**
145
128
  * Prefix for virtual island wrapper modules. Must NOT use the `\0` prefix
146
129
  * because `vite-plugin-solid` skips `\0`-prefixed modules in its transform
@@ -148,10 +131,10 @@ const COMPONENT_EXTENSIONS = [
148
131
  * detect the language.
149
132
  */
150
133
  const WRAPPER_PREFIX = "/@void-island-wrap/";
151
- function islandsPlugin(pagesDir) {
134
+ function islandsPlugin(pagesDir, options) {
152
135
  let pageScan = null;
153
136
  let isDev = false;
154
- const root = dirname(pagesDir);
137
+ const root = options?.root ?? dirname(pagesDir);
155
138
  const islandManifest = /* @__PURE__ */ new Map();
156
139
  const islandCssFiles = /* @__PURE__ */ new Set();
157
140
  const wrapperMeta = /* @__PURE__ */ new Map();
@@ -177,8 +160,7 @@ function islandsPlugin(pagesDir) {
177
160
  },
178
161
  async buildStart() {
179
162
  if (existsSync(pagesDir)) {
180
- pageScan = await scanPages(root);
181
- const importAttrRe = /import\s+\w+\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
163
+ pageScan = await scanPages(root, { pagesDir });
182
164
  const filesToScan = [
183
165
  ...pageScan.pages.map((page) => ({
184
166
  filePath: page.componentPath,
@@ -198,11 +180,9 @@ function islandsPlugin(pagesDir) {
198
180
  const filePath = resolve(pagesDir, entry.filePath);
199
181
  const code = readFileSync(filePath, "utf-8");
200
182
  const importerDir = dirname(filePath);
201
- importAttrRe.lastIndex = 0;
202
- let importMatch;
203
- while ((importMatch = importAttrRe.exec(code)) !== null) {
204
- const spec = importMatch[1].slice(1, -1);
205
- const strategy = importMatch[2].slice(1, -1);
183
+ for (const islandImport of findIslandImports(code)) {
184
+ const spec = islandImport.specifier;
185
+ const strategy = islandImport.hydrate;
206
186
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
207
187
  if (absPath) {
208
188
  const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
@@ -265,16 +245,12 @@ function islandsPlugin(pagesDir) {
265
245
  filter: { id: /\.island\.(tsx|jsx)$/ },
266
246
  handler(code, id) {
267
247
  if (/import\s+\{[^}]*\buseForm\b/.test(code)) this.warn(`islands: useForm cannot be imported in island page '${id}'. Use useIslandForm instead.`);
268
- const importAttrRe = /import\s+(\w+)\s+from\s+("[^"]+"|'[^']+')\s+with\s*\{\s*island\s*:\s*("[^"]+"|'[^']+')\s*\}/g;
269
248
  const importerDir = dirname(id);
270
249
  let transformed = code;
271
- let match;
272
- while ((match = importAttrRe.exec(code)) !== null) {
273
- const name = match[1];
274
- const specRaw = match[2];
275
- const strategyRaw = match[3];
276
- const spec = specRaw.slice(1, -1);
277
- const strategy = strategyRaw.slice(1, -1);
250
+ for (const islandImport of findIslandImports(code)) {
251
+ const name = islandImport.importName;
252
+ const spec = islandImport.specifier;
253
+ const strategy = islandImport.hydrate;
278
254
  const absPath = resolveSpecifier(spec, importerDir, COMPONENT_EXTENSIONS);
279
255
  if (absPath) {
280
256
  const islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
@@ -292,7 +268,7 @@ function islandsPlugin(pagesDir) {
292
268
  hydrate: strategy
293
269
  });
294
270
  const replacement = `import ${name} from ${JSON.stringify(wrapperUrl)}`;
295
- transformed = transformed.replace(match[0], replacement);
271
+ transformed = transformed.replace(islandImport.statement, replacement);
296
272
  }
297
273
  if (transformed !== code) return {
298
274
  code: transformed,
@@ -442,12 +418,30 @@ function hasNonIslandPages(dir) {
442
418
  }
443
419
  function pagesPlugin(pagesDir, options) {
444
420
  let pageScan = null;
421
+ let isDev = false;
445
422
  let viteConfig;
446
- const root = dirname(pagesDir);
423
+ const root = options?.root ?? dirname(pagesDir);
424
+ const normalizedPagesDir = normalizePath(pagesDir);
425
+ let pendingRescan = null;
426
+ const rescanPages = async () => {
427
+ pageScan = existsSync(pagesDir) ? await scanPages(root, { pagesDir }) : null;
428
+ };
429
+ const runRescan = () => {
430
+ pendingRescan ??= rescanPages().finally(() => {
431
+ pendingRescan = null;
432
+ });
433
+ return pendingRescan;
434
+ };
435
+ const invalidatePageModules = (server) => {
436
+ for (const env of Object.values(server.environments)) for (const id of ["\0" + RENDERER_ID, "\0" + CLIENT_ENTRY_ID]) {
437
+ const mod = env.moduleGraph.getModuleById(id);
438
+ if (mod) env.moduleGraph.invalidateModule(mod);
439
+ }
440
+ };
447
441
  return {
448
442
  name: "void-solid:pages",
449
443
  async buildStart() {
450
- if (existsSync(pagesDir)) pageScan = await scanPages(root);
444
+ await runRescan();
451
445
  },
452
446
  resolveId: {
453
447
  filter: { id: /^virtual:void-pages-(renderer|client)$/ },
@@ -457,7 +451,9 @@ function pagesPlugin(pagesDir, options) {
457
451
  },
458
452
  load: {
459
453
  filter: { id: /^\0virtual:void-pages-(renderer|client)$/ },
460
- handler(id) {
454
+ async handler(id) {
455
+ if (isDev && pendingRescan) await pendingRescan;
456
+ if (!pageScan && existsSync(pagesDir)) await runRescan();
461
457
  if (id === "\0" + RENDERER_ID) {
462
458
  if (!pageScan) return "export function renderPage() { throw new Error('No pages found'); }";
463
459
  return generateRendererModule(pageScan, pagesDir);
@@ -468,19 +464,34 @@ function pagesPlugin(pagesDir, options) {
468
464
  }
469
465
  }
470
466
  },
467
+ configureServer(server) {
468
+ const handlePagesChange = async (path, event) => {
469
+ const normalizedPath = normalizePath(path);
470
+ if (normalizedPath !== normalizedPagesDir && !normalizedPath.startsWith(normalizedPagesDir + "/")) return;
471
+ if (event === "change" && !normalizedPath.endsWith(".md")) return;
472
+ await runRescan();
473
+ invalidatePageModules(server);
474
+ };
475
+ server.watcher.on("add", (path) => handlePagesChange(path, "add"));
476
+ server.watcher.on("change", (path) => handlePagesChange(path, "change"));
477
+ server.watcher.on("unlink", (path) => handlePagesChange(path, "unlink"));
478
+ },
471
479
  configResolved(config) {
472
480
  viteConfig = config;
473
481
  },
474
482
  config(_, env) {
475
483
  if (!existsSync(pagesDir)) return;
476
- if (env.command === "serve") return { optimizeDeps: {
477
- exclude: ["@void/solid"],
478
- include: [
479
- "solid-js",
480
- "solid-js/web",
481
- "solid-js/store"
482
- ]
483
- } };
484
+ if (env.command === "serve") {
485
+ isDev = true;
486
+ return { optimizeDeps: {
487
+ exclude: ["@void/solid"],
488
+ include: [
489
+ "solid-js",
490
+ "solid-js/web",
491
+ "solid-js/store"
492
+ ]
493
+ } };
494
+ }
484
495
  if (!hasNonIslandPages(pagesDir)) return;
485
496
  return { build: {
486
497
  manifest: true,
@@ -699,7 +710,8 @@ function configPlugin() {
699
710
  };
700
711
  }
701
712
  function voidSolid(options) {
702
- const pagesDir = join(process.cwd(), "pages");
713
+ const root = process.cwd();
714
+ const pagesDir = resolvePagesDir(root);
703
715
  return [
704
716
  configPlugin(),
705
717
  solid({
@@ -707,10 +719,11 @@ function voidSolid(options) {
707
719
  ...options?.solid
708
720
  }),
709
721
  pagesPlugin(pagesDir, {
722
+ root,
710
723
  viewTransitions: options?.viewTransitions,
711
724
  prefetch: options?.prefetch
712
725
  }),
713
- ...islandsPlugin(pagesDir)
726
+ ...islandsPlugin(pagesDir, { root })
714
727
  ];
715
728
  }
716
729
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void/solid",
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",
@@ -43,12 +43,12 @@
43
43
  "solid-js": "^1.9.12",
44
44
  "vite": "npm:@voidzero-dev/vite-plus-core@0.1.20",
45
45
  "vite-plus": "^0.1.20",
46
- "void": "0.7.12"
46
+ "void": "0.8.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "solid-js": "^1.9.0",
50
50
  "vite": "^8.0.0",
51
- "void": "0.7.12"
51
+ "void": "0.8.0"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "vp pack",