@rsc-kit/core 0.20.13 → 0.21.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.
Files changed (56) hide show
  1. package/dist/apiPrerender.js +1 -1
  2. package/dist/apiPrerender.js.map +1 -1
  3. package/dist/appAssets.d.ts +6 -0
  4. package/dist/appAssets.js +34 -0
  5. package/dist/appAssets.js.map +1 -1
  6. package/dist/appleSplash.d.ts +10 -0
  7. package/dist/appleSplash.js +74 -0
  8. package/dist/appleSplash.js.map +1 -0
  9. package/dist/earlyHints.js +7 -2
  10. package/dist/earlyHints.js.map +1 -1
  11. package/dist/export.js +3 -1
  12. package/dist/export.js.map +1 -1
  13. package/dist/files.d.ts +0 -8
  14. package/dist/files.js +6 -0
  15. package/dist/files.js.map +1 -1
  16. package/dist/headers.d.ts +8 -0
  17. package/dist/headers.js +8 -0
  18. package/dist/headers.js.map +1 -1
  19. package/dist/host.d.ts +9 -0
  20. package/dist/host.js +109 -29
  21. package/dist/host.js.map +1 -1
  22. package/dist/js/Form.d.ts +47 -54
  23. package/dist/js/Form.js +41 -48
  24. package/dist/js/Form.js.map +1 -1
  25. package/dist/js/chunkPreload.d.ts +26 -0
  26. package/dist/js/chunkPreload.js +66 -0
  27. package/dist/js/chunkPreload.js.map +1 -0
  28. package/dist/js/installCapture.d.ts +15 -0
  29. package/dist/js/installCapture.js +17 -0
  30. package/dist/js/installCapture.js.map +1 -0
  31. package/dist/js/navigate.js +4 -0
  32. package/dist/js/navigate.js.map +1 -1
  33. package/dist/js/useInstall.d.ts +15 -0
  34. package/dist/js/useInstall.js +102 -0
  35. package/dist/js/useInstall.js.map +1 -0
  36. package/dist/manifest.d.ts +2 -0
  37. package/dist/manifest.js.map +1 -1
  38. package/dist/metadata.d.ts +31 -0
  39. package/dist/metadata.js.map +1 -1
  40. package/dist/prerender.d.ts +43 -5
  41. package/dist/prerender.js +138 -19
  42. package/dist/prerender.js.map +1 -1
  43. package/dist/request.js +3 -2
  44. package/dist/request.js.map +1 -1
  45. package/dist/shellHead.js +4 -2
  46. package/dist/shellHead.js.map +1 -1
  47. package/dist/stableNames.d.ts +52 -0
  48. package/dist/stableNames.js +91 -0
  49. package/dist/stableNames.js.map +1 -0
  50. package/dist/vite.d.ts +5 -3
  51. package/dist/vite.js +200 -13
  52. package/dist/vite.js.map +1 -1
  53. package/dist/webManifest.d.ts +37 -0
  54. package/dist/webManifest.js +51 -6
  55. package/dist/webManifest.js.map +1 -1
  56. package/package.json +8 -4
@@ -1,5 +1,21 @@
1
+ /** A jump into the app from its icon - a long press on Android, a right click on desktop. */
2
+ export interface ManifestShortcut {
3
+ name: string;
4
+ shortName?: string;
5
+ description?: string;
6
+ url: string;
7
+ /** Paths, like `icons`: sizes read from the filename. */
8
+ icons?: string[];
9
+ }
1
10
  /** What `rscKit({ manifest })` takes. */
2
11
  export interface WebManifestOptions {
12
+ /**
13
+ * The app's identity. Without it a browser derives one from `startUrl`, and
14
+ * changing that later leaves everyone who installed with an orphaned app -
15
+ * a different app, as far as their device is concerned. `'/'` is right for
16
+ * almost everyone, set once and never changed.
17
+ */
18
+ id?: string;
3
19
  /** The app's name, as an install prompt shows it. */
4
20
  name: string;
5
21
  /** The short name a home screen uses when there is no room for the full one. */
@@ -19,6 +35,20 @@ export interface WebManifestOptions {
19
35
  icons?: string[];
20
36
  /** Where an installed launch lands. Defaults to `/`. */
21
37
  startUrl?: string;
38
+ /** The urls that are the app; a link outside it opens in the browser. Defaults to the start url's directory. */
39
+ scope?: string;
40
+ /** Lock the installed app to a way of holding the device. */
41
+ orientation?: 'any' | 'natural' | 'portrait' | 'landscape' | 'portrait-primary' | 'landscape-primary';
42
+ /** What a store or an install sheet files it under: `['productivity']`. */
43
+ categories?: string[];
44
+ /** Jumps from the app's icon. */
45
+ shortcuts?: ManifestShortcut[];
46
+ /**
47
+ * What Chrome's richer install sheet shows. Rarely written here: an
48
+ * `app/screenshot-wide-1280x720.png` or `screenshot-narrow-…` is found by
49
+ * name, its size and form factor read from it - see the PWA guide.
50
+ */
51
+ screenshots?: string[];
22
52
  /** Defaults to `standalone`, which is the one that looks like an app. */
23
53
  display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
24
54
  /** The colour a browser paints its chrome. Also emitted as a meta tag. */
@@ -28,6 +58,13 @@ export interface WebManifestOptions {
28
58
  /** Anything else the spec allows, passed through untouched. */
29
59
  other?: Record<string, unknown>;
30
60
  }
61
+ /** An icon drawn for a launcher's mask: `icon-maskable-512.png`. */
62
+ export declare function isMaskable(file: string): boolean;
63
+ /** A screenshot's size, from `screenshot-wide-1280x720.png`; null when the name carries none. */
64
+ export declare function screenshotSize(file: string): {
65
+ width: number;
66
+ height: number;
67
+ } | null;
31
68
  /** Where the manifest is written and linked from. */
32
69
  export declare const MANIFEST_PATH = "/manifest.webmanifest";
33
70
  /**
@@ -9,6 +9,15 @@
9
9
  // Everything here is the web app manifest spec rather than an invention of
10
10
  // this package, so the names are its names and anything it accepts that is not
11
11
  // spelled out below can be passed through `other`.
12
+ /** An icon drawn for a launcher's mask: `icon-maskable-512.png`. */
13
+ export function isMaskable(file) {
14
+ return /(^|[/-])maskable\b/i.test(file);
15
+ }
16
+ /** A screenshot's size, from `screenshot-wide-1280x720.png`; null when the name carries none. */
17
+ export function screenshotSize(file) {
18
+ const match = /(\d{3,4})x(\d{3,4})\.(?:png|webp|jpe?g)$/i.exec(file);
19
+ return match ? { width: Number(match[1]), height: Number(match[2]) } : null;
20
+ }
12
21
  /** Where the manifest is written and linked from. */
13
22
  export const MANIFEST_PATH = '/manifest.webmanifest';
14
23
  /**
@@ -30,28 +39,54 @@ function typeOf(file) {
30
39
  ? 'image/jpeg'
31
40
  : `image/${extension}`;
32
41
  }
42
+ /** One icon, as the manifest spec writes it. */
43
+ function iconEntry(file) {
44
+ const size = sizeOf(file);
45
+ return {
46
+ src: file.startsWith('/') ? file : `/${file}`,
47
+ type: typeOf(file),
48
+ ...(size ? { sizes: `${size}x${size}` } : {}),
49
+ // One purpose per icon, from the name. A maskable icon is drawn with a
50
+ // safe zone - the middle 80% is all a launcher promises to show - so
51
+ // used as "any" it looks shrunk, and an ordinary icon used as
52
+ // "maskable" has its corners cut off. "any maskable" on one file was
53
+ // both at once, and the Android splash drew the shrunk one.
54
+ purpose: isMaskable(file) ? 'maskable' : 'any',
55
+ };
56
+ }
33
57
  /** The manifest's JSON, ready to write. */
34
58
  export function webManifest(options) {
35
- const icons = (options.icons ?? []).map((file) => {
36
- const size = sizeOf(file);
59
+ const icons = (options.icons ?? []).map(iconEntry);
60
+ const shortcuts = (options.shortcuts ?? []).map((shortcut) => ({
61
+ name: shortcut.name,
62
+ ...(shortcut.shortName ? { short_name: shortcut.shortName } : {}),
63
+ ...(shortcut.description ? { description: shortcut.description } : {}),
64
+ url: shortcut.url,
65
+ ...(shortcut.icons?.length ? { icons: shortcut.icons.map(iconEntry) } : {}),
66
+ }));
67
+ const screenshots = (options.screenshots ?? []).map((file) => {
68
+ const size = screenshotSize(file);
37
69
  return {
38
70
  src: file.startsWith('/') ? file : `/${file}`,
39
71
  type: typeOf(file),
40
- ...(size ? { sizes: `${size}x${size}` } : {}),
41
- // Maskable as well as any: without it Android crops a square icon into a
42
- // circle and takes the corners off whatever is in them.
43
- purpose: 'any maskable',
72
+ ...(size ? { sizes: `${size.width}x${size.height}`, form_factor: size.width > size.height ? 'wide' : 'narrow' } : {}),
44
73
  };
45
74
  });
46
75
  return (JSON.stringify({
76
+ ...(options.id ? { id: options.id } : {}),
47
77
  name: options.name,
48
78
  short_name: options.shortName ?? options.name,
49
79
  ...(options.description ? { description: options.description } : {}),
50
80
  start_url: options.startUrl ?? '/',
81
+ ...(options.scope ? { scope: options.scope } : {}),
51
82
  display: options.display ?? 'standalone',
83
+ ...(options.orientation ? { orientation: options.orientation } : {}),
84
+ ...(options.categories?.length ? { categories: options.categories } : {}),
52
85
  ...(options.themeColor ? { theme_color: options.themeColor } : {}),
53
86
  ...(options.backgroundColor ? { background_color: options.backgroundColor } : {}),
54
87
  ...(icons.length ? { icons } : {}),
88
+ ...(shortcuts.length ? { shortcuts } : {}),
89
+ ...(screenshots.length ? { screenshots } : {}),
55
90
  ...(options.other ?? {}),
56
91
  }, null, 2) + '\n');
57
92
  }
@@ -76,6 +111,16 @@ export function manifestWarning(options) {
76
111
  if (largest < 512) {
77
112
  return 'manifest: no 512px icon, so a splash screen will be upscaled from a smaller one.';
78
113
  }
114
+ // Android builds its splash from the name, the largest icon and this
115
+ // colour. Without it the splash is white, whatever the app looks like.
116
+ if (!options.backgroundColor) {
117
+ return 'manifest: no backgroundColor, so the Android splash screen is white. Set it to the colour the app starts on.';
118
+ }
119
+ // The one that bites later rather than now: an install keyed on startUrl
120
+ // is a different app the day startUrl changes.
121
+ if (!options.id) {
122
+ return "manifest: no id, so the app's identity is its startUrl - change that and everyone who installed has a different app. Set id: '/'.";
123
+ }
79
124
  return null;
80
125
  }
81
126
  //# sourceMappingURL=webManifest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webManifest.js","sourceRoot":"","sources":["../src/webManifest.ts"],"names":[],"mappings":"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","sourcesContent":["// The file that makes an app installable.\n//\n// A service worker makes an app work offline; this is what makes a browser\n// offer to put it on a home screen. They are separate decisions and separate\n// options, because an app can want either without the other — a site that\n// should survive a dead network is not necessarily one anybody wants as an\n// icon, and vice versa.\n//\n// Everything here is the web app manifest spec rather than an invention of\n// this package, so the names are its names and anything it accepts that is not\n// spelled out below can be passed through `other`.\n\n/** What `rscKit({ manifest })` takes. */\nexport interface WebManifestOptions {\n /** The app's name, as an install prompt shows it. */\n name: string\n /** The short name a home screen uses when there is no room for the full one. */\n shortName?: string\n description?: string\n /**\n * The icons a browser may install.\n *\n * Paths relative to the client output — a file in `public/` is served at its\n * own name, so `icon-192.png` is usually right. Sizes and types are read from\n * the filename rather than declared twice.\n *\n * A browser will not offer to install an app with no icon of at least 192px,\n * so the build says so rather than writing a manifest that quietly does\n * nothing.\n */\n icons?: string[]\n /** Where an installed launch lands. Defaults to `/`. */\n startUrl?: string\n /** Defaults to `standalone`, which is the one that looks like an app. */\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser'\n /** The colour a browser paints its chrome. Also emitted as a meta tag. */\n themeColor?: string\n /** The colour behind the app while it is starting. */\n backgroundColor?: string\n /** Anything else the spec allows, passed through untouched. */\n other?: Record<string, unknown>\n}\n\n/** Where the manifest is written and linked from. */\nexport const MANIFEST_PATH = '/manifest.webmanifest'\n\n/**\n * The size a filename claims, or null.\n *\n * `icon-192.png` and `icon-192x192.png` both read as 192. Declaring it in the\n * options as well would be the same number written twice, and the one that\n * drifts is the one nobody looks at.\n */\nexport function sizeOf(file: string): number | null {\n const match = /(\\d{2,4})(?:x\\d{2,4})?\\.(?:png|webp|jpg|jpeg|svg)$/i.exec(file)\n\n return match ? Number(match[1]) : null\n}\n\nfunction typeOf(file: string): string {\n const extension = file.slice(file.lastIndexOf('.') + 1).toLowerCase()\n\n return extension === 'svg'\n ? 'image/svg+xml'\n : extension === 'jpg' || extension === 'jpeg'\n ? 'image/jpeg'\n : `image/${extension}`\n}\n\n/** The manifest's JSON, ready to write. */\nexport function webManifest(options: WebManifestOptions): string {\n const icons = (options.icons ?? []).map((file) => {\n const size = sizeOf(file)\n\n return {\n src: file.startsWith('/') ? file : `/${file}`,\n type: typeOf(file),\n ...(size ? { sizes: `${size}x${size}` } : {}),\n // Maskable as well as any: without it Android crops a square icon into a\n // circle and takes the corners off whatever is in them.\n purpose: 'any maskable',\n }\n })\n\n return (\n JSON.stringify(\n {\n name: options.name,\n short_name: options.shortName ?? options.name,\n ...(options.description ? { description: options.description } : {}),\n start_url: options.startUrl ?? '/',\n display: options.display ?? 'standalone',\n ...(options.themeColor ? { theme_color: options.themeColor } : {}),\n ...(options.backgroundColor ? { background_color: options.backgroundColor } : {}),\n ...(icons.length ? { icons } : {}),\n ...(options.other ?? {}),\n },\n null,\n 2,\n ) + '\\n'\n )\n}\n\n/**\n * Whether these options produce something a browser would actually install.\n *\n * Returned as a sentence rather than thrown: a manifest with no icon is still\n * a valid manifest and still sets the theme colour, so refusing the build over\n * it would be refusing something that works. But an author who wrote\n * `manifest: {...}` meant \"installable\", and silence here is how they find out\n * months later that it never was.\n */\nexport function manifestWarning(options: WebManifestOptions): string | null {\n const icons = options.icons ?? []\n\n if (icons.length === 0) {\n return 'manifest: no icons, so no browser will offer to install this. Add a 192px and a 512px png.'\n }\n\n const largest = icons.reduce((best, file) => Math.max(best, sizeOf(file) ?? 0), 0)\n\n if (largest < 192) {\n return 'manifest: no icon of 192px or more, which is the smallest a browser will install. Sizes are read from the filename, so name it like icon-192.png.'\n }\n\n if (largest < 512) {\n return 'manifest: no 512px icon, so a splash screen will be upscaled from a smaller one.'\n }\n\n return null\n}\n\n/** The type a src/app/manifest.ts file satisfies. */\nexport type WebManifest = WebManifestOptions\n"]}
1
+ {"version":3,"file":"webManifest.js","sourceRoot":"","sources":["../src/webManifest.ts"],"names":[],"mappings":"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","sourcesContent":["// The file that makes an app installable.\n//\n// A service worker makes an app work offline; this is what makes a browser\n// offer to put it on a home screen. They are separate decisions and separate\n// options, because an app can want either without the other — a site that\n// should survive a dead network is not necessarily one anybody wants as an\n// icon, and vice versa.\n//\n// Everything here is the web app manifest spec rather than an invention of\n// this package, so the names are its names and anything it accepts that is not\n// spelled out below can be passed through `other`.\n\n/** A jump into the app from its icon - a long press on Android, a right click on desktop. */\nexport interface ManifestShortcut {\n name: string\n shortName?: string\n description?: string\n url: string\n /** Paths, like `icons`: sizes read from the filename. */\n icons?: string[]\n}\n\n/** What `rscKit({ manifest })` takes. */\nexport interface WebManifestOptions {\n /**\n * The app's identity. Without it a browser derives one from `startUrl`, and\n * changing that later leaves everyone who installed with an orphaned app -\n * a different app, as far as their device is concerned. `'/'` is right for\n * almost everyone, set once and never changed.\n */\n id?: string\n /** The app's name, as an install prompt shows it. */\n name: string\n /** The short name a home screen uses when there is no room for the full one. */\n shortName?: string\n description?: string\n /**\n * The icons a browser may install.\n *\n * Paths relative to the client output — a file in `public/` is served at its\n * own name, so `icon-192.png` is usually right. Sizes and types are read from\n * the filename rather than declared twice.\n *\n * A browser will not offer to install an app with no icon of at least 192px,\n * so the build says so rather than writing a manifest that quietly does\n * nothing.\n */\n icons?: string[]\n /** Where an installed launch lands. Defaults to `/`. */\n startUrl?: string\n /** The urls that are the app; a link outside it opens in the browser. Defaults to the start url's directory. */\n scope?: string\n /** Lock the installed app to a way of holding the device. */\n orientation?: 'any' | 'natural' | 'portrait' | 'landscape' | 'portrait-primary' | 'landscape-primary'\n /** What a store or an install sheet files it under: `['productivity']`. */\n categories?: string[]\n /** Jumps from the app's icon. */\n shortcuts?: ManifestShortcut[]\n /**\n * What Chrome's richer install sheet shows. Rarely written here: an\n * `app/screenshot-wide-1280x720.png` or `screenshot-narrow-…` is found by\n * name, its size and form factor read from it - see the PWA guide.\n */\n screenshots?: string[]\n /** Defaults to `standalone`, which is the one that looks like an app. */\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser'\n /** The colour a browser paints its chrome. Also emitted as a meta tag. */\n themeColor?: string\n /** The colour behind the app while it is starting. */\n backgroundColor?: string\n /** Anything else the spec allows, passed through untouched. */\n other?: Record<string, unknown>\n}\n\n/** An icon drawn for a launcher's mask: `icon-maskable-512.png`. */\nexport function isMaskable(file: string): boolean {\n return /(^|[/-])maskable\\b/i.test(file)\n}\n\n/** A screenshot's size, from `screenshot-wide-1280x720.png`; null when the name carries none. */\nexport function screenshotSize(file: string): { width: number; height: number } | null {\n const match = /(\\d{3,4})x(\\d{3,4})\\.(?:png|webp|jpe?g)$/i.exec(file)\n\n return match ? { width: Number(match[1]), height: Number(match[2]) } : null\n}\n\n/** Where the manifest is written and linked from. */\nexport const MANIFEST_PATH = '/manifest.webmanifest'\n\n/**\n * The size a filename claims, or null.\n *\n * `icon-192.png` and `icon-192x192.png` both read as 192. Declaring it in the\n * options as well would be the same number written twice, and the one that\n * drifts is the one nobody looks at.\n */\nexport function sizeOf(file: string): number | null {\n const match = /(\\d{2,4})(?:x\\d{2,4})?\\.(?:png|webp|jpg|jpeg|svg)$/i.exec(file)\n\n return match ? Number(match[1]) : null\n}\n\nfunction typeOf(file: string): string {\n const extension = file.slice(file.lastIndexOf('.') + 1).toLowerCase()\n\n return extension === 'svg'\n ? 'image/svg+xml'\n : extension === 'jpg' || extension === 'jpeg'\n ? 'image/jpeg'\n : `image/${extension}`\n}\n\n/** One icon, as the manifest spec writes it. */\nfunction iconEntry(file: string) {\n const size = sizeOf(file)\n\n return {\n src: file.startsWith('/') ? file : `/${file}`,\n type: typeOf(file),\n ...(size ? { sizes: `${size}x${size}` } : {}),\n // One purpose per icon, from the name. A maskable icon is drawn with a\n // safe zone - the middle 80% is all a launcher promises to show - so\n // used as \"any\" it looks shrunk, and an ordinary icon used as\n // \"maskable\" has its corners cut off. \"any maskable\" on one file was\n // both at once, and the Android splash drew the shrunk one.\n purpose: isMaskable(file) ? 'maskable' : 'any',\n }\n}\n\n/** The manifest's JSON, ready to write. */\nexport function webManifest(options: WebManifestOptions): string {\n const icons = (options.icons ?? []).map(iconEntry)\n\n const shortcuts = (options.shortcuts ?? []).map((shortcut) => ({\n name: shortcut.name,\n ...(shortcut.shortName ? { short_name: shortcut.shortName } : {}),\n ...(shortcut.description ? { description: shortcut.description } : {}),\n url: shortcut.url,\n ...(shortcut.icons?.length ? { icons: shortcut.icons.map(iconEntry) } : {}),\n }))\n\n const screenshots = (options.screenshots ?? []).map((file) => {\n const size = screenshotSize(file)\n\n return {\n src: file.startsWith('/') ? file : `/${file}`,\n type: typeOf(file),\n ...(size ? { sizes: `${size.width}x${size.height}`, form_factor: size.width > size.height ? 'wide' : 'narrow' } : {}),\n }\n })\n\n return (\n JSON.stringify(\n {\n ...(options.id ? { id: options.id } : {}),\n name: options.name,\n short_name: options.shortName ?? options.name,\n ...(options.description ? { description: options.description } : {}),\n start_url: options.startUrl ?? '/',\n ...(options.scope ? { scope: options.scope } : {}),\n display: options.display ?? 'standalone',\n ...(options.orientation ? { orientation: options.orientation } : {}),\n ...(options.categories?.length ? { categories: options.categories } : {}),\n ...(options.themeColor ? { theme_color: options.themeColor } : {}),\n ...(options.backgroundColor ? { background_color: options.backgroundColor } : {}),\n ...(icons.length ? { icons } : {}),\n ...(shortcuts.length ? { shortcuts } : {}),\n ...(screenshots.length ? { screenshots } : {}),\n ...(options.other ?? {}),\n },\n null,\n 2,\n ) + '\\n'\n )\n}\n\n/**\n * Whether these options produce something a browser would actually install.\n *\n * Returned as a sentence rather than thrown: a manifest with no icon is still\n * a valid manifest and still sets the theme colour, so refusing the build over\n * it would be refusing something that works. But an author who wrote\n * `manifest: {...}` meant \"installable\", and silence here is how they find out\n * months later that it never was.\n */\nexport function manifestWarning(options: WebManifestOptions): string | null {\n const icons = options.icons ?? []\n\n if (icons.length === 0) {\n return 'manifest: no icons, so no browser will offer to install this. Add a 192px and a 512px png.'\n }\n\n const largest = icons.reduce((best, file) => Math.max(best, sizeOf(file) ?? 0), 0)\n\n if (largest < 192) {\n return 'manifest: no icon of 192px or more, which is the smallest a browser will install. Sizes are read from the filename, so name it like icon-192.png.'\n }\n\n if (largest < 512) {\n return 'manifest: no 512px icon, so a splash screen will be upscaled from a smaller one.'\n }\n\n // Android builds its splash from the name, the largest icon and this\n // colour. Without it the splash is white, whatever the app looks like.\n if (!options.backgroundColor) {\n return 'manifest: no backgroundColor, so the Android splash screen is white. Set it to the colour the app starts on.'\n }\n\n // The one that bites later rather than now: an install keyed on startUrl\n // is a different app the day startUrl changes.\n if (!options.id) {\n return \"manifest: no id, so the app's identity is its startUrl - change that and everyone who installed has a different app. Set id: '/'.\"\n }\n\n return null\n}\n\n/** The type a src/app/manifest.ts file satisfies. */\nexport type WebManifest = WebManifestOptions\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsc-kit/core",
3
- "version": "0.20.13",
3
+ "version": "0.21.1",
4
4
  "description": "React Server Components as a Vite plugin. Routing, streaming, server actions, typed urls and build-time prerendering — deploy to Bun, Node, Workers, Vercel or Deno.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -85,6 +85,10 @@
85
85
  "types": "./dist/js/Form.d.ts",
86
86
  "default": "./dist/js/Form.js"
87
87
  },
88
+ "./install": {
89
+ "types": "./dist/js/useInstall.d.ts",
90
+ "default": "./dist/js/useInstall.js"
91
+ },
88
92
  "./section": {
89
93
  "types": "./dist/js/section.d.ts",
90
94
  "default": "./dist/js/section.js"
@@ -215,10 +219,10 @@
215
219
  }
216
220
  },
217
221
  "peerDependencies": {
218
- "react": "^19.2.0",
219
- "react-dom": "^19.2.0",
222
+ "react": "^19.2.3",
223
+ "react-dom": "^19.2.3",
220
224
  "vite": "^8.0.0",
221
- "@vitejs/plugin-rsc": "^0.5.0",
225
+ "@vitejs/plugin-rsc": "^0.5.34",
222
226
  "nuqs": ">=2.4.0"
223
227
  },
224
228
  "peerDependenciesMeta": {