rwsdk 1.0.0-beta.4 → 1.0.0-beta.6

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.
@@ -1,6 +1,7 @@
1
1
  import "./setWebpackRequire";
2
2
  export { default as React } from "react";
3
3
  export { ClientOnly } from "./ClientOnly.js";
4
+ export { initClientNavigation, navigate } from "./navigation.js";
4
5
  import type { HydrationOptions, Transport } from "./types";
5
6
  export declare const fetchTransport: Transport;
6
7
  export declare const initClient: ({ transport, hydrateRootOptions, handleResponse, }?: {
@@ -11,6 +11,7 @@ import { createFromFetch, createFromReadableStream, encodeReply, } from "react-s
11
11
  import { rscStream } from "rsc-html-stream/client";
12
12
  export { default as React } from "react";
13
13
  export { ClientOnly } from "./ClientOnly.js";
14
+ export { initClientNavigation, navigate } from "./navigation.js";
14
15
  export const fetchTransport = (transportContext) => {
15
16
  const fetchCallServer = async (id, args) => {
16
17
  const url = new URL(window.location.href);
@@ -4,6 +4,14 @@ export interface ClientNavigationOptions {
4
4
  scrollBehavior?: "auto" | "smooth" | "instant";
5
5
  }
6
6
  export declare function validateClickEvent(event: MouseEvent, target: HTMLElement): boolean;
7
+ export interface NavigateOptions {
8
+ history?: "push" | "replace";
9
+ info?: {
10
+ scrollToTop?: boolean;
11
+ scrollBehavior?: "auto" | "smooth" | "instant";
12
+ };
13
+ }
14
+ export declare function navigate(href: string, options?: NavigateOptions): Promise<void>;
7
15
  export declare function initClientNavigation(opts?: ClientNavigationOptions): {
8
16
  handleResponse: (response: Response) => boolean;
9
17
  };
@@ -1,10 +1,3 @@
1
- function saveScrollPosition(x, y) {
2
- window.history.replaceState({
3
- ...window.history.state,
4
- scrollX: x,
5
- scrollY: y,
6
- }, "", window.location.href);
7
- }
8
1
  export function validateClickEvent(event, target) {
9
2
  // should this only work for left click?
10
3
  if (event.button !== 0) {
@@ -37,19 +30,43 @@ export function validateClickEvent(event, target) {
37
30
  }
38
31
  return true;
39
32
  }
33
+ let IS_CLIENT_NAVIGATION = false;
34
+ export async function navigate(href, options = { history: "push" }) {
35
+ if (!IS_CLIENT_NAVIGATION) {
36
+ window.location.href = href;
37
+ return;
38
+ }
39
+ saveScrollPosition(window.scrollX, window.scrollY);
40
+ const url = window.location.origin + href;
41
+ if (options.history === "push") {
42
+ window.history.pushState({ path: href }, "", url);
43
+ }
44
+ else {
45
+ window.history.replaceState({ path: href }, "", url);
46
+ }
47
+ await globalThis.__rsc_callServer;
48
+ const scrollToTop = options.info?.scrollToTop ?? true;
49
+ const scrollBehavior = options.info?.scrollBehavior ?? "instant";
50
+ if (scrollToTop && history.scrollRestoration === "auto") {
51
+ window.scrollTo({
52
+ top: 0,
53
+ left: 0,
54
+ behavior: scrollBehavior,
55
+ });
56
+ saveScrollPosition(0, 0);
57
+ }
58
+ }
59
+ function saveScrollPosition(x, y) {
60
+ window.history.replaceState({
61
+ ...window.history.state,
62
+ scrollX: x,
63
+ scrollY: y,
64
+ }, "", window.location.href);
65
+ }
40
66
  export function initClientNavigation(opts = {}) {
41
- const options = {
42
- onNavigate: async function onNavigate() {
43
- // @ts-expect-error
44
- await globalThis.__rsc_callServer();
45
- },
46
- scrollToTop: true,
47
- scrollBehavior: "instant",
48
- ...opts,
49
- };
67
+ IS_CLIENT_NAVIGATION = true;
50
68
  history.scrollRestoration = "auto";
51
69
  document.addEventListener("click", async function handleClickEvent(event) {
52
- // Prevent default navigation
53
70
  if (!validateClickEvent(event, event.target)) {
54
71
  return;
55
72
  }
@@ -57,28 +74,18 @@ export function initClientNavigation(opts = {}) {
57
74
  const el = event.target;
58
75
  const a = el.closest("a");
59
76
  const href = a?.getAttribute("href");
60
- saveScrollPosition(window.scrollX, window.scrollY);
61
- window.history.pushState({ path: href }, "", window.location.origin + href);
62
- await options.onNavigate();
63
- if (options.scrollToTop && history.scrollRestoration === "auto") {
64
- window.scrollTo({
65
- top: 0,
66
- left: 0,
67
- behavior: options.scrollBehavior,
68
- });
69
- saveScrollPosition(0, 0);
70
- }
71
- history.scrollRestoration = "auto";
77
+ navigate(href);
72
78
  }, true);
73
79
  window.addEventListener("popstate", async function handlePopState() {
74
- saveScrollPosition(window.scrollX, window.scrollY);
75
- await options.onNavigate();
80
+ // @ts-expect-error
81
+ await globalThis.__rsc_callServer();
76
82
  });
77
83
  // Return a handleResponse function for use with initClient
78
84
  return {
79
85
  handleResponse: function handleResponse(response) {
80
86
  if (!response.ok) {
81
87
  // Redirect to the current page (window.location) to show the error
88
+ // This means the page that produced the error is called twice.
82
89
  window.location.href = window.location.href;
83
90
  return false;
84
91
  }
@@ -1,2 +1,3 @@
1
1
  import "./types/ssr";
2
2
  export * from "../lib/streams/consumeEventStream";
3
+ export declare const navigate: () => void;
@@ -1,2 +1,5 @@
1
1
  import "./types/ssr";
2
2
  export * from "../lib/streams/consumeEventStream";
3
+ export const navigate = () => {
4
+ /* stub */
5
+ };
@@ -8,4 +8,4 @@ export interface ManifestChunk {
8
8
  css?: string[];
9
9
  assets?: string[];
10
10
  }
11
- export declare const getManifest: () => Promise<Manifest>;
11
+ export declare function getManifest(): Promise<Manifest>;
@@ -1,14 +1,17 @@
1
1
  let manifest;
2
- export const getManifest = async () => {
2
+ export async function getManifest() {
3
3
  if (manifest) {
4
4
  return manifest;
5
5
  }
6
6
  if (import.meta.env.VITE_IS_DEV_SERVER) {
7
+ // In dev, there's no manifest, so we can use an empty object.
7
8
  manifest = {};
8
9
  }
9
10
  else {
10
- const { default: prodManifest } = await import("virtual:rwsdk:manifest.js");
11
- manifest = prodManifest;
11
+ // context(justinvdm, 2 Oct 2025): In production, the manifest is a
12
+ // placeholder string that will be replaced by the linker plugin with the
13
+ // actual manifest JSON object.
14
+ manifest = "__RWSDK_MANIFEST_PLACEHOLDER__";
12
15
  }
13
16
  return manifest;
14
- };
17
+ }
@@ -1,4 +1,5 @@
1
1
  import debug from "debug";
2
+ import { rm } from "node:fs/promises";
2
3
  import { resolve } from "node:path";
3
4
  import { runDirectivesScan } from "./runDirectivesScan.mjs";
4
5
  const log = debug("rwsdk:vite:build-app");
@@ -10,6 +11,7 @@ const log = debug("rwsdk:vite:build-app");
10
11
  * @see docs/architecture/productionBuildProcess.md
11
12
  */
12
13
  export async function buildApp({ builder, clientEntryPoints, clientFiles, serverFiles, projectRootDir, workerEntryPathname, }) {
14
+ await rm(resolve(projectRootDir, "dist"), { recursive: true, force: true });
13
15
  const workerEnv = builder.environments.worker;
14
16
  await runDirectivesScan({
15
17
  rootConfig: builder.config,
@@ -9,7 +9,7 @@ export function linkWorkerBundle({ code, manifestContent, projectRootDir, }) {
9
9
  const manifest = JSON.parse(manifestContent);
10
10
  // 1. Replace the manifest placeholder with the actual manifest content.
11
11
  log("Injecting manifest into worker bundle");
12
- newCode = newCode.replace('"__RWSDK_MANIFEST_PLACEHOLDER__"', manifestContent);
12
+ newCode = newCode.replace(/['"]__RWSDK_MANIFEST_PLACEHOLDER__['"]/, manifestContent);
13
13
  // 2. Replace asset placeholders with their final hashed paths.
14
14
  log("Replacing asset placeholders in final worker bundle");
15
15
  for (const [key, value] of Object.entries(manifest)) {
@@ -18,7 +18,6 @@ import { directivesPlugin } from "./directivesPlugin.mjs";
18
18
  import { injectVitePreamble } from "./injectVitePreamblePlugin.mjs";
19
19
  import { knownDepsResolverPlugin } from "./knownDepsResolverPlugin.mjs";
20
20
  import { linkerPlugin } from "./linkerPlugin.mjs";
21
- import { manifestPlugin } from "./manifestPlugin.mjs";
22
21
  import { miniflareHMRPlugin } from "./miniflareHMRPlugin.mjs";
23
22
  import { moveStaticAssetsPlugin } from "./moveStaticAssetsPlugin.mjs";
24
23
  import { prismaPlugin } from "./prismaPlugin.mjs";
@@ -144,9 +143,6 @@ export const redwoodPlugin = async (options = {}) => {
144
143
  clientEntryPoints,
145
144
  projectRootDir,
146
145
  }),
147
- manifestPlugin({
148
- projectRootDir,
149
- }),
150
146
  moveStaticAssetsPlugin({ rootDir: projectRootDir }),
151
147
  prismaPlugin({ projectRootDir }),
152
148
  linkerPlugin({ projectRootDir }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +0,0 @@
1
- import { type Plugin } from "vite";
2
- export declare const manifestPlugin: ({ projectRootDir, }: {
3
- projectRootDir: string;
4
- }) => Plugin;
@@ -1,63 +0,0 @@
1
- import debug from "debug";
2
- const log = debug("rwsdk:vite:manifest-plugin");
3
- const virtualModuleId = "virtual:rwsdk:manifest.js";
4
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
5
- export const manifestPlugin = ({ projectRootDir, }) => {
6
- let isBuild = false;
7
- return {
8
- name: "rwsdk:vite:manifest-plugin",
9
- enforce: "pre",
10
- configResolved(config) {
11
- isBuild = config.command === "build";
12
- },
13
- resolveId(id) {
14
- // Skip during directive scanning to avoid performance issues
15
- if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
16
- return;
17
- }
18
- if (id === virtualModuleId) {
19
- return resolvedVirtualModuleId;
20
- }
21
- },
22
- async load(id) {
23
- // Skip during directive scanning to avoid performance issues
24
- if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
25
- return;
26
- }
27
- if (id === resolvedVirtualModuleId) {
28
- if (isBuild) {
29
- // context(justinvdm, 28 Aug 2025): During the build, we don't have
30
- // the manifest yet. We insert a placeholder that the linker plugin
31
- // will replace in the final phase.
32
- log("Returning manifest placeholder for build");
33
- return `export default "__RWSDK_MANIFEST_PLACEHOLDER__"`;
34
- }
35
- // In dev, we can return an empty object.
36
- log("Not a build, returning empty manifest");
37
- return `export default {}`;
38
- }
39
- },
40
- configEnvironment(name, config) {
41
- if (name !== "worker" && name !== "ssr") {
42
- return;
43
- }
44
- log("Configuring environment: name=%s", name);
45
- config.optimizeDeps ??= {};
46
- config.optimizeDeps.esbuildOptions ??= {};
47
- config.optimizeDeps.esbuildOptions.plugins ??= [];
48
- config.optimizeDeps.esbuildOptions.plugins.push({
49
- name: "rwsdk:manifest:esbuild",
50
- setup(build) {
51
- log("Setting up esbuild plugin for environment: %s", name);
52
- build.onResolve({ filter: /^virtual:rwsdk:manifest\.js$/ }, () => {
53
- log("Resolving virtual manifest module in esbuild");
54
- return {
55
- path: "virtual:rwsdk:manifest.js",
56
- external: true,
57
- };
58
- });
59
- },
60
- });
61
- },
62
- };
63
- };