rwsdk 1.2.10 → 1.2.11
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.
|
@@ -34,6 +34,13 @@ export function validateClickEvent(event, target) {
|
|
|
34
34
|
}
|
|
35
35
|
let IS_CLIENT_NAVIGATION = false;
|
|
36
36
|
let scrollRestoration = null;
|
|
37
|
+
let currentPathKey = null;
|
|
38
|
+
function getLocationPathKey() {
|
|
39
|
+
return `${window.location.pathname ?? ""}${window.location.search ?? ""}`;
|
|
40
|
+
}
|
|
41
|
+
function getUrlPathKey(url) {
|
|
42
|
+
return `${url.pathname ?? ""}${url.search ?? ""}` || getLocationPathKey();
|
|
43
|
+
}
|
|
37
44
|
export async function navigate(href, options = { history: "push" }) {
|
|
38
45
|
if (!IS_CLIENT_NAVIGATION) {
|
|
39
46
|
window.location.href = href;
|
|
@@ -52,6 +59,7 @@ export async function navigate(href, options = { history: "push" }) {
|
|
|
52
59
|
else {
|
|
53
60
|
scrollRestoration?.replaceEntry(href, url, nextScrollPosition);
|
|
54
61
|
}
|
|
62
|
+
currentPathKey = getUrlPathKey(url);
|
|
55
63
|
if (scrollToTop) {
|
|
56
64
|
scrollRestoration?.setPendingScroll({
|
|
57
65
|
...nextScrollPosition,
|
|
@@ -109,6 +117,7 @@ export function initClientNavigation(opts = {}) {
|
|
|
109
117
|
IS_CLIENT_NAVIGATION = true;
|
|
110
118
|
scrollRestoration = createScrollRestoration();
|
|
111
119
|
scrollRestoration.initialize();
|
|
120
|
+
currentPathKey = getLocationPathKey();
|
|
112
121
|
document.addEventListener("click", async function handleClickEvent(event) {
|
|
113
122
|
if (!validateClickEvent(event, event.target)) {
|
|
114
123
|
return;
|
|
@@ -120,6 +129,12 @@ export function initClientNavigation(opts = {}) {
|
|
|
120
129
|
await navigate(href, { history: "push", onNavigate: opts.onNavigate });
|
|
121
130
|
}, true);
|
|
122
131
|
window.addEventListener("popstate", async function handlePopState() {
|
|
132
|
+
const nextPathKey = getLocationPathKey();
|
|
133
|
+
const isHashOnlyChange = nextPathKey === currentPathKey;
|
|
134
|
+
currentPathKey = nextPathKey;
|
|
135
|
+
if (isHashOnlyChange) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
123
138
|
scrollRestoration?.restorePopStateScroll();
|
|
124
139
|
await opts.onNavigate?.();
|
|
125
140
|
await globalThis.__rsc_callServer(null, null, "navigation");
|
|
@@ -103,7 +103,7 @@ describe("onNavigate callback (issue #1123 regression)", () => {
|
|
|
103
103
|
}),
|
|
104
104
|
});
|
|
105
105
|
vi.stubGlobal("window", {
|
|
106
|
-
location: { href: "http://localhost/" },
|
|
106
|
+
location: { href: "http://localhost/", pathname: "/", search: "" },
|
|
107
107
|
addEventListener: vi.fn((event, handler) => {
|
|
108
108
|
if (event === "popstate")
|
|
109
109
|
capturedPopstateHandler = handler;
|
|
@@ -167,6 +167,7 @@ describe("onNavigate callback (issue #1123 regression)", () => {
|
|
|
167
167
|
const onNavigate = vi.fn();
|
|
168
168
|
initClientNavigation({ onNavigate });
|
|
169
169
|
expect(capturedPopstateHandler).not.toBeNull();
|
|
170
|
+
window.location.pathname = "/about";
|
|
170
171
|
await capturedPopstateHandler();
|
|
171
172
|
expect(onNavigate).toHaveBeenCalled();
|
|
172
173
|
});
|
|
@@ -208,11 +209,13 @@ describe("initClientNavigation", () => {
|
|
|
208
209
|
let capturedScrollHandler = null;
|
|
209
210
|
let capturedPagehideHandler = null;
|
|
210
211
|
let capturedVisibilityChangeHandler = null;
|
|
212
|
+
let capturedPopstateHandler = null;
|
|
211
213
|
beforeEach(() => {
|
|
212
214
|
historyState = {};
|
|
213
215
|
capturedScrollHandler = null;
|
|
214
216
|
capturedPagehideHandler = null;
|
|
215
217
|
capturedVisibilityChangeHandler = null;
|
|
218
|
+
capturedPopstateHandler = null;
|
|
216
219
|
vi.clearAllMocks();
|
|
217
220
|
const mockHistory = {
|
|
218
221
|
scrollRestoration: "auto",
|
|
@@ -236,7 +239,7 @@ describe("initClientNavigation", () => {
|
|
|
236
239
|
}),
|
|
237
240
|
});
|
|
238
241
|
vi.stubGlobal("window", {
|
|
239
|
-
location: { href: "http://localhost/" },
|
|
242
|
+
location: { href: "http://localhost/", pathname: "/", search: "" },
|
|
240
243
|
addEventListener: vi.fn((event, handler) => {
|
|
241
244
|
if (event === "scroll") {
|
|
242
245
|
capturedScrollHandler = handler;
|
|
@@ -244,6 +247,9 @@ describe("initClientNavigation", () => {
|
|
|
244
247
|
if (event === "pagehide") {
|
|
245
248
|
capturedPagehideHandler = handler;
|
|
246
249
|
}
|
|
250
|
+
if (event === "popstate") {
|
|
251
|
+
capturedPopstateHandler = handler;
|
|
252
|
+
}
|
|
247
253
|
}),
|
|
248
254
|
history: mockHistory,
|
|
249
255
|
fetch: vi.fn(),
|
|
@@ -279,6 +285,22 @@ describe("initClientNavigation", () => {
|
|
|
279
285
|
initClientNavigation();
|
|
280
286
|
expect(history.scrollRestoration).toBe("manual");
|
|
281
287
|
});
|
|
288
|
+
it("ignores hash-only popstate events so anchor links keep their native scroll", async () => {
|
|
289
|
+
const onNavigate = vi.fn();
|
|
290
|
+
globalThis.__rsc_callServer = vi.fn().mockResolvedValue(undefined);
|
|
291
|
+
const { onHydrated } = initClientNavigation({ onNavigate });
|
|
292
|
+
expect(capturedPopstateHandler).not.toBeNull();
|
|
293
|
+
window.location.hash =
|
|
294
|
+
"#heading";
|
|
295
|
+
window.location.href =
|
|
296
|
+
"http://localhost/#heading";
|
|
297
|
+
window.scrollY = 500;
|
|
298
|
+
await capturedPopstateHandler();
|
|
299
|
+
onHydrated();
|
|
300
|
+
expect(onNavigate).not.toHaveBeenCalled();
|
|
301
|
+
expect(globalThis.__rsc_callServer).not.toHaveBeenCalled();
|
|
302
|
+
expect(window.scrollTo).not.toHaveBeenCalled();
|
|
303
|
+
});
|
|
282
304
|
it("does not write to history state on scroll", () => {
|
|
283
305
|
initClientNavigation();
|
|
284
306
|
expect(capturedScrollHandler).not.toBeNull();
|
|
@@ -94,10 +94,8 @@ export const transformServerFunctions = (code, normalizedId, environment, server
|
|
|
94
94
|
process.env.VERBOSE &&
|
|
95
95
|
log(`Transforming for ${environment} environment: normalizedId=%s`, normalizedId);
|
|
96
96
|
const exportInfo = findExportInfo(code, normalizedId);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
...exportInfo.reExports.map((r) => r.localName),
|
|
100
|
-
]);
|
|
97
|
+
const reExportNames = new Set(exportInfo.reExports.map((r) => r.localName));
|
|
98
|
+
const allExports = new Set(Array.from(exportInfo.localFunctions).filter((name) => !reExportNames.has(name)));
|
|
101
99
|
// Check for default function exports that should also be named exports
|
|
102
100
|
const defaultFunctionName = findDefaultFunctionName(code, normalizedId);
|
|
103
101
|
if (defaultFunctionName) {
|
|
@@ -105,11 +103,26 @@ export const transformServerFunctions = (code, normalizedId, environment, server
|
|
|
105
103
|
}
|
|
106
104
|
// Generate completely new code for SSR
|
|
107
105
|
const s = new MagicString("");
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
const hasDefExport = hasDefaultExport(code, normalizedId);
|
|
107
|
+
if (allExports.size > 0 || hasDefExport) {
|
|
108
|
+
if (environment === "ssr") {
|
|
109
|
+
s.append('import { createServerReference } from "rwsdk/__ssr";\n\n');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
s.append('import { createServerReference } from "rwsdk/client";\n\n');
|
|
113
|
+
}
|
|
110
114
|
}
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
for (const reExport of exportInfo.reExports) {
|
|
116
|
+
const reExportStatement = reExport.originalName === "default"
|
|
117
|
+
? `export { default as ${reExport.localName} } from ${JSON.stringify(reExport.moduleSpecifier)};\n`
|
|
118
|
+
: reExport.originalName === reExport.localName
|
|
119
|
+
? `export { ${reExport.originalName} } from ${JSON.stringify(reExport.moduleSpecifier)};\n`
|
|
120
|
+
: `export { ${reExport.originalName} as ${reExport.localName} } from ${JSON.stringify(reExport.moduleSpecifier)};\n`;
|
|
121
|
+
s.append(reExportStatement);
|
|
122
|
+
log(`Preserved ${environment} re-export for function: %s (original: %s) from %s in normalizedId=%s`, reExport.localName, reExport.originalName, reExport.moduleSpecifier, normalizedId);
|
|
123
|
+
}
|
|
124
|
+
if (exportInfo.reExports.length > 0 && allExports.size > 0) {
|
|
125
|
+
s.append("\n");
|
|
113
126
|
}
|
|
114
127
|
const ext = path.extname(normalizedId).toLowerCase();
|
|
115
128
|
const lang = ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : SgLang.TypeScript;
|
|
@@ -151,7 +164,7 @@ export const transformServerFunctions = (code, normalizedId, environment, server
|
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
// Check for default export in the actual module (not re-exports)
|
|
154
|
-
if (
|
|
167
|
+
if (hasDefExport) {
|
|
155
168
|
let method;
|
|
156
169
|
let source = "action";
|
|
157
170
|
const patterns = [
|
|
@@ -169,6 +169,17 @@ export const getProject = serverQuery([
|
|
|
169
169
|
SERVER_QUERY_ARRAY_POST_CODE,
|
|
170
170
|
};
|
|
171
171
|
describe("TRANSFORMS", () => {
|
|
172
|
+
it("preserves client re-exports so serverQuery metadata comes from the defining module", () => {
|
|
173
|
+
const barrelResult = transformServerFunctions(`
|
|
174
|
+
"use server";
|
|
175
|
+
|
|
176
|
+
export { getProject } from "./queries";
|
|
177
|
+
`, "/actions.ts", "client", new Set());
|
|
178
|
+
expect(barrelResult?.code).toContain(`export { getProject } from "./queries";`);
|
|
179
|
+
expect(barrelResult?.code).not.toContain(`createServerReference("/actions.ts", "getProject")`);
|
|
180
|
+
const queryResult = transformServerFunctions(SERVER_QUERY_GET_CODE, "/queries.ts", "client", new Set());
|
|
181
|
+
expect(queryResult?.code).toContain(`createServerReference("/queries.ts", "getProject", "GET", "query")`);
|
|
182
|
+
});
|
|
172
183
|
for (const [key, CODE] of Object.entries(TEST_CASES)) {
|
|
173
184
|
describe(key, () => {
|
|
174
185
|
it(`CLIENT`, () => {
|