rwsdk 0.0.83 → 0.0.84
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/runtime/lib/router.d.ts +10 -1
- package/dist/runtime/lib/router.js +47 -13
- package/dist/runtime/lib/router.test.d.ts +1 -0
- package/dist/runtime/lib/router.test.js +58 -0
- package/dist/runtime/render/__rwsdk_ssr_bridge.d.ts +10 -0
- package/dist/runtime/render/__rwsdk_ssr_bridge.js +9 -0
- package/dist/runtime/render/__rwsdkssr_render.d.ts +9 -0
- package/dist/runtime/render/__rwsdkssr_render.js +13 -0
- package/dist/runtime/render/injectRSCPayload.js +8 -6
- package/dist/runtime/render/ssrBridge.d.ts +2 -0
- package/dist/runtime/render/ssrBridge.js +2 -0
- package/dist/runtime/render/ssrRenderToReadableStream.d.ts +2 -0
- package/dist/runtime/render/ssrRenderToReadableStream.js +2 -0
- package/dist/runtime/requestInfo/__rwsdknossr_worker.d.ts +5 -0
- package/dist/runtime/requestInfo/__rwsdknossr_worker.js +33 -0
- package/dist/runtime/worker.js +7 -3
- package/dist/vite/aliasedModuleResolver.d.mts +9 -0
- package/dist/vite/aliasedModuleResolver.mjs +62 -0
- package/dist/vite/aliasedSSRResolver.d.mts +5 -0
- package/dist/vite/aliasedSSRResolver.mjs +74 -0
- package/dist/vite/ensureConfigArrays.d.mts +1 -0
- package/dist/vite/ensureConfigArrays.mjs +12 -0
- package/dist/vite/findImportSpecifiers.d.mts +14 -0
- package/dist/vite/findImportSpecifiers.mjs +76 -0
- package/dist/vite/isBareImport.d.mts +1 -0
- package/dist/vite/isBareImport.mjs +5 -0
- package/dist/vite/moduleResolver.d.mts +10 -0
- package/dist/vite/moduleResolver.mjs +74 -0
- package/dist/vite/transformClientComponents.d.mts +11 -0
- package/dist/vite/transformClientComponents.mjs +171 -0
- package/dist/vite/transformServerReferences.d.mts +11 -0
- package/dist/vite/transformServerReferences.mjs +74 -0
- package/dist/vite/useClientPlugin.mjs +7 -3
- package/dist/vite/useClientPlugin.test.mjs +91 -1
- package/dist/vite/useServerPlugin.d.mts +3 -0
- package/dist/vite/useServerPlugin.mjs +134 -37
- package/dist/vite/useServerPlugin.test.d.mts +1 -0
- package/dist/vite/useServerPlugin.test.mjs +99 -0
- package/dist/vite/virtualizedSSRPlugin.d.mts +56 -0
- package/dist/vite/virtualizedSSRPlugin.mjs +464 -0
- package/package.json +1 -2
|
@@ -6,6 +6,7 @@ export type DocumentProps = RequestInfo & {
|
|
|
6
6
|
export type RwContext = {
|
|
7
7
|
nonce: string;
|
|
8
8
|
Document: React.FC<DocumentProps>;
|
|
9
|
+
rscPayload: boolean;
|
|
9
10
|
};
|
|
10
11
|
export type RouteMiddleware = (requestInfo: RequestInfo) => Response | Promise<Response> | void | Promise<void> | Promise<Response | void>;
|
|
11
12
|
type RouteFunction = (requestInfo: RequestInfo) => Response | Promise<Response>;
|
|
@@ -17,6 +18,7 @@ export type RouteDefinition = {
|
|
|
17
18
|
path: string;
|
|
18
19
|
handler: RouteHandler;
|
|
19
20
|
};
|
|
21
|
+
export declare function matchPath(routePath: string, requestPath: string): RequestInfo["params"] | null;
|
|
20
22
|
export declare function defineRoutes(routes: Route[]): {
|
|
21
23
|
routes: Route[];
|
|
22
24
|
handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, }: {
|
|
@@ -30,5 +32,12 @@ export declare function defineRoutes(routes: Route[]): {
|
|
|
30
32
|
export declare function route(path: string, handler: RouteHandler): RouteDefinition;
|
|
31
33
|
export declare function index(handler: RouteHandler): RouteDefinition;
|
|
32
34
|
export declare function prefix(prefix: string, routes: ReturnType<typeof route>[]): RouteDefinition[];
|
|
33
|
-
export declare function render(Document: React.FC<DocumentProps>, routes: Route[]
|
|
35
|
+
export declare function render(Document: React.FC<DocumentProps>, routes: Route[],
|
|
36
|
+
/**
|
|
37
|
+
* @param options - Configuration options for rendering.
|
|
38
|
+
* @param options.rscPayload - Toggle the RSC payload that's appended to the Document. Disabling this will mean that interactivity no longer works.
|
|
39
|
+
*/
|
|
40
|
+
options?: {
|
|
41
|
+
rscPayload: boolean;
|
|
42
|
+
}): Route[];
|
|
34
43
|
export {};
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { isValidElementType } from "react-is";
|
|
2
|
-
function matchPath(routePath, requestPath) {
|
|
2
|
+
export function matchPath(routePath, requestPath) {
|
|
3
|
+
// Check for invalid pattern: multiple colons in a segment (e.g., /:param1:param2/)
|
|
4
|
+
if (routePath.includes(":")) {
|
|
5
|
+
const segments = routePath.split("/");
|
|
6
|
+
for (const segment of segments) {
|
|
7
|
+
if ((segment.match(/:/g) || []).length > 1) {
|
|
8
|
+
throw new Error(`Invalid route pattern: segment "${segment}" in "${routePath}" contains multiple colons.`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
// Check for invalid pattern: double wildcard (e.g., /**/)
|
|
13
|
+
if (routePath.indexOf("**") !== -1) {
|
|
14
|
+
throw new Error(`Invalid route pattern: "${routePath}" contains "**". Use "*" for a single wildcard segment.`);
|
|
15
|
+
}
|
|
3
16
|
const pattern = routePath
|
|
4
17
|
.replace(/:[a-zA-Z0-9]+/g, "([^/]+)") // Convert :param to capture group
|
|
5
18
|
.replace(/\*/g, "(.*)"); // Convert * to wildcard capture group
|
|
@@ -8,18 +21,33 @@ function matchPath(routePath, requestPath) {
|
|
|
8
21
|
if (!matches) {
|
|
9
22
|
return null;
|
|
10
23
|
}
|
|
11
|
-
//
|
|
24
|
+
// Revised parameter extraction:
|
|
12
25
|
const params = {};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
let currentMatchIndex = 1; // Regex matches are 1-indexed
|
|
27
|
+
// This regex finds either a named parameter token (e.g., ":id") or a wildcard star token ("*").
|
|
28
|
+
const tokenRegex = /:([a-zA-Z0-9_]+)|\*/g;
|
|
29
|
+
let matchToken;
|
|
30
|
+
let wildcardCounter = 0;
|
|
31
|
+
// Ensure regex starts from the beginning of the routePath for each call if it's stateful (it is with /g)
|
|
32
|
+
tokenRegex.lastIndex = 0;
|
|
33
|
+
while ((matchToken = tokenRegex.exec(routePath)) !== null) {
|
|
34
|
+
// Ensure we have a corresponding match from the regex execution
|
|
35
|
+
if (matches[currentMatchIndex] === undefined) {
|
|
36
|
+
// This case should ideally not be hit if routePath and pattern generation are correct
|
|
37
|
+
// and all parts of the regex matched.
|
|
38
|
+
// Consider logging a warning or throwing an error if critical.
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (matchToken[1]) {
|
|
42
|
+
// This token is a named parameter (e.g., matchToken[1] is "id" for ":id")
|
|
43
|
+
params[matchToken[1]] = matches[currentMatchIndex];
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// This token is a wildcard "*"
|
|
47
|
+
params[`$${wildcardCounter}`] = matches[currentMatchIndex];
|
|
48
|
+
wildcardCounter++;
|
|
49
|
+
}
|
|
50
|
+
currentMatchIndex++;
|
|
23
51
|
}
|
|
24
52
|
return params;
|
|
25
53
|
}
|
|
@@ -104,9 +132,15 @@ export function prefix(prefix, routes) {
|
|
|
104
132
|
};
|
|
105
133
|
});
|
|
106
134
|
}
|
|
107
|
-
export function render(Document, routes
|
|
135
|
+
export function render(Document, routes,
|
|
136
|
+
/**
|
|
137
|
+
* @param options - Configuration options for rendering.
|
|
138
|
+
* @param options.rscPayload - Toggle the RSC payload that's appended to the Document. Disabling this will mean that interactivity no longer works.
|
|
139
|
+
*/
|
|
140
|
+
options = { rscPayload: true }) {
|
|
108
141
|
const documentMiddleware = ({ rw }) => {
|
|
109
142
|
rw.Document = Document;
|
|
143
|
+
rw.rscPayload = options.rscPayload;
|
|
110
144
|
};
|
|
111
145
|
return [documentMiddleware, ...routes];
|
|
112
146
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { matchPath } from "./router";
|
|
3
|
+
describe("matchPath", () => {
|
|
4
|
+
// Test case 1: Static paths
|
|
5
|
+
it("should match static paths", () => {
|
|
6
|
+
expect(matchPath("/about/", "/about/")).toEqual({});
|
|
7
|
+
expect(matchPath("/contact/", "/contact/")).toEqual({});
|
|
8
|
+
});
|
|
9
|
+
it("should not match different static paths", () => {
|
|
10
|
+
expect(matchPath("/about/", "/service/")).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
// Test case 2: Paths with parameters
|
|
13
|
+
it("should match paths with parameters and extract them", () => {
|
|
14
|
+
expect(matchPath("/users/:id/", "/users/123/")).toEqual({ id: "123" });
|
|
15
|
+
expect(matchPath("/posts/:category/:slug/", "/posts/tech/my-first-post/")).toEqual({ category: "tech", slug: "my-first-post" });
|
|
16
|
+
});
|
|
17
|
+
it("should not match if parameter is missing", () => {
|
|
18
|
+
expect(matchPath("/users/:id/", "/users/")).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
// Test case 3: Paths with wildcards
|
|
21
|
+
it("should match paths with wildcards and extract them", () => {
|
|
22
|
+
expect(matchPath("/files/*/", "/files/document.pdf/")).toEqual({
|
|
23
|
+
$0: "document.pdf",
|
|
24
|
+
});
|
|
25
|
+
expect(matchPath("/data/*\/content/", "/data/archive/content/")).toEqual({
|
|
26
|
+
$0: "archive",
|
|
27
|
+
});
|
|
28
|
+
expect(matchPath("/assets/*/*/", "/assets/images/pic.png/")).toEqual({
|
|
29
|
+
$0: "images",
|
|
30
|
+
$1: "pic.png",
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it("should match empty wildcard", () => {
|
|
34
|
+
expect(matchPath("/files/*/", "/files//")).toEqual({ $0: "" });
|
|
35
|
+
});
|
|
36
|
+
// Test case 4: Paths with both parameters and wildcards
|
|
37
|
+
it("should match paths with both parameters and wildcards", () => {
|
|
38
|
+
expect(matchPath("/products/:productId/*/", "/products/abc/details/more/")).toEqual({ productId: "abc", $0: "details/more" });
|
|
39
|
+
});
|
|
40
|
+
// Test case 5: Paths that don't match
|
|
41
|
+
it("should return null for non-matching paths", () => {
|
|
42
|
+
expect(matchPath("/specific/path/", "/a/different/path/")).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
// Test case 6: Edge cases
|
|
45
|
+
it("should handle trailing slashes correctly", () => {
|
|
46
|
+
// Current implementation in defineRoutes adds a trailing slash if missing,
|
|
47
|
+
// and route() function also enforces it. matchPath itself doesn't normalize.
|
|
48
|
+
expect(matchPath("/path/", "/path")).toBeNull(); // Path to match must end with /
|
|
49
|
+
expect(matchPath("/path/", "/path/")).toEqual({});
|
|
50
|
+
});
|
|
51
|
+
it("should handle paths with multiple parameters and wildcards interspersed", () => {
|
|
52
|
+
expect(matchPath("/type/:typeId/item/*/:itemId/*/", "/type/a/item/image/b/thumb/")).toEqual({ typeId: "a", $0: "image", itemId: "b", $1: "thumb" });
|
|
53
|
+
});
|
|
54
|
+
it("should not allow named parameters or wildcards in the same path", () => {
|
|
55
|
+
expect(() => matchPath("/type/:typeId:is:broken", "/type/a-thumb-drive")).toThrow();
|
|
56
|
+
expect(() => matchPath("/type/**", "/type/a-thumb-drive")).toThrow();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactServerDom from "react-dom/server.edge";
|
|
3
|
+
import { DocumentProps } from "../lib/router";
|
|
4
|
+
import { RequestInfo } from "../requestInfo/types";
|
|
5
|
+
export declare const renderRscThenableToHtmlStream: ({ thenable, Document, requestInfo, nonce, }: {
|
|
6
|
+
thenable: any;
|
|
7
|
+
Document: React.FC<DocumentProps>;
|
|
8
|
+
requestInfo: RequestInfo;
|
|
9
|
+
nonce?: string;
|
|
10
|
+
}) => Promise<ReactServerDom.ReactDOMServerReadableStream>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import ReactServerDom from "react-dom/server.edge";
|
|
4
|
+
const { use } = React;
|
|
5
|
+
const { renderToReadableStream } = ReactServerDom;
|
|
6
|
+
export const renderRscThenableToHtmlStream = ({ thenable, Document, requestInfo, nonce, }) => {
|
|
7
|
+
const Component = () => (_jsx(Document, { ...requestInfo, children: use(thenable).node }));
|
|
8
|
+
return renderToReadableStream(_jsx(Component, {}), { nonce });
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactServerDom from "react-dom/server.edge";
|
|
3
|
+
import { type DocumentProps } from "../lib/router";
|
|
4
|
+
import { type RequestInfo } from "../requestInfo/types";
|
|
5
|
+
export declare const renderRscThenableToHtmlStream: ({ thenable, Document, requestInfo, }: {
|
|
6
|
+
thenable: any;
|
|
7
|
+
Document: React.FC<DocumentProps>;
|
|
8
|
+
requestInfo: RequestInfo;
|
|
9
|
+
}) => Promise<ReactServerDom.ReactDOMServerReadableStream>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import ReactServerDom from "react-dom/server.edge";
|
|
4
|
+
const { use } = React;
|
|
5
|
+
const { renderToReadableStream } = ReactServerDom;
|
|
6
|
+
export const renderRscThenableToHtmlStream = async ({ thenable, Document, requestInfo, }) => {
|
|
7
|
+
const Component = () => {
|
|
8
|
+
return (_jsx(Document, { ...requestInfo, children: use(thenable).node }));
|
|
9
|
+
};
|
|
10
|
+
return await renderToReadableStream(_jsx(Component, {}), {
|
|
11
|
+
nonce: requestInfo.rw.nonce,
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const encoder = new TextEncoder();
|
|
4
4
|
const trailer = "</body></html>";
|
|
5
5
|
export function injectRSCPayload(rscStream, { nonce }) {
|
|
6
|
-
|
|
6
|
+
let decoder = new TextDecoder();
|
|
7
7
|
let resolveFlightDataPromise;
|
|
8
8
|
let flightDataPromise = new Promise((resolve) => (resolveFlightDataPromise = resolve));
|
|
9
9
|
let startedRSC = false;
|
|
@@ -13,9 +13,12 @@ export function injectRSCPayload(rscStream, { nonce }) {
|
|
|
13
13
|
let buffered = [];
|
|
14
14
|
let timeout = null;
|
|
15
15
|
function flushBufferedChunks(controller) {
|
|
16
|
-
for (
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
for (let chunk of buffered) {
|
|
17
|
+
let buf = decoder.decode(chunk);
|
|
18
|
+
if (buf.endsWith(trailer)) {
|
|
19
|
+
buf = buf.slice(0, -trailer.length);
|
|
20
|
+
}
|
|
21
|
+
controller.enqueue(encoder.encode(buf));
|
|
19
22
|
}
|
|
20
23
|
buffered.length = 0;
|
|
21
24
|
timeout = null;
|
|
@@ -42,8 +45,7 @@ export function injectRSCPayload(rscStream, { nonce }) {
|
|
|
42
45
|
clearTimeout(timeout);
|
|
43
46
|
flushBufferedChunks(controller);
|
|
44
47
|
}
|
|
45
|
-
controller.enqueue(encoder.encode(
|
|
46
|
-
controller.enqueue(encoder.encode(trailer));
|
|
48
|
+
controller.enqueue(encoder.encode("</body></html>"));
|
|
47
49
|
},
|
|
48
50
|
});
|
|
49
51
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RequestInfo, DefaultAppContext } from "./types";
|
|
2
|
+
export declare const requestInfo: RequestInfo<DefaultAppContext>;
|
|
3
|
+
export declare function getRequestInfo(): RequestInfo;
|
|
4
|
+
export declare function runWithRequestInfo<Result>(context: Record<string, any>, fn: () => Result): Result;
|
|
5
|
+
export declare function runWithRequestInfoOverrides<Result>(overrides: Record<string, any>, fn: () => Result): Result;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
const requestInfoStore = new AsyncLocalStorage();
|
|
3
|
+
const requestInfoBase = {};
|
|
4
|
+
const REQUEST_INFO_KEYS = ["request", "params", "ctx", "headers", "rw", "cf"];
|
|
5
|
+
REQUEST_INFO_KEYS.forEach((key) => {
|
|
6
|
+
Object.defineProperty(requestInfoBase, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: false,
|
|
9
|
+
get: function () {
|
|
10
|
+
const store = requestInfoStore.getStore();
|
|
11
|
+
return store ? store[key] : undefined;
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
export const requestInfo = Object.freeze(requestInfoBase);
|
|
16
|
+
export function getRequestInfo() {
|
|
17
|
+
const store = requestInfoStore.getStore();
|
|
18
|
+
if (!store) {
|
|
19
|
+
throw new Error("Request context not found");
|
|
20
|
+
}
|
|
21
|
+
return store;
|
|
22
|
+
}
|
|
23
|
+
export function runWithRequestInfo(context, fn) {
|
|
24
|
+
return requestInfoStore.run(context, fn);
|
|
25
|
+
}
|
|
26
|
+
export function runWithRequestInfoOverrides(overrides, fn) {
|
|
27
|
+
const requestInfo = requestInfoStore.getStore();
|
|
28
|
+
const newRequestInfo = {
|
|
29
|
+
...requestInfo,
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
return requestInfoStore.run(newRequestInfo, fn);
|
|
33
|
+
}
|
package/dist/runtime/worker.js
CHANGED
|
@@ -37,6 +37,7 @@ export const defineApp = (routes) => {
|
|
|
37
37
|
const rw = {
|
|
38
38
|
Document: DefaultDocument,
|
|
39
39
|
nonce: generateNonce(),
|
|
40
|
+
rscPayload: true,
|
|
40
41
|
};
|
|
41
42
|
const outerRequestInfo = {
|
|
42
43
|
request,
|
|
@@ -104,9 +105,12 @@ export const defineApp = (routes) => {
|
|
|
104
105
|
Parent: ({ children }) => (_jsx(rw.Document, { ...requestInfo, children: children })),
|
|
105
106
|
nonce: rw.nonce,
|
|
106
107
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
let html = htmlStream;
|
|
109
|
+
if (rw.rscPayload) {
|
|
110
|
+
html = htmlStream.pipeThrough(injectRSCPayload(rscPayloadStream2, {
|
|
111
|
+
nonce: rw.nonce,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
110
114
|
return new Response(html, {
|
|
111
115
|
headers: {
|
|
112
116
|
"content-type": "text/html; charset=utf-8",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function createAliasedModuleResolver({ getAliases, roots, conditionNames, name, }: {
|
|
2
|
+
getAliases?: () => Array<{
|
|
3
|
+
find: string | RegExp;
|
|
4
|
+
replacement: string;
|
|
5
|
+
}>;
|
|
6
|
+
roots: string[];
|
|
7
|
+
conditionNames?: string[];
|
|
8
|
+
name?: string;
|
|
9
|
+
}): (request: string, importer: string) => string | false;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import enhancedResolve from "enhanced-resolve";
|
|
3
|
+
import debug from "debug";
|
|
4
|
+
function applyAlias(request, aliasEntries, name) {
|
|
5
|
+
if (!aliasEntries)
|
|
6
|
+
return request;
|
|
7
|
+
const logPrefix = `[${name}]`;
|
|
8
|
+
// Support both array and object forms
|
|
9
|
+
const entries = Array.isArray(aliasEntries)
|
|
10
|
+
? aliasEntries
|
|
11
|
+
: Object.entries(aliasEntries).map(([find, replacement]) => ({
|
|
12
|
+
find,
|
|
13
|
+
replacement,
|
|
14
|
+
}));
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const { find, replacement } = entry;
|
|
17
|
+
if (typeof find === "string") {
|
|
18
|
+
if (request === find || request.startsWith(find + "/")) {
|
|
19
|
+
debug("rwsdk:vite:aliased-module-resolver")("%s [applyAlias] Matched string alias: '%s' -> '%s' for request '%s'", logPrefix, find, replacement, request);
|
|
20
|
+
return replacement + request.slice(find.length);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (find instanceof RegExp) {
|
|
24
|
+
if (find.test(request)) {
|
|
25
|
+
debug("rwsdk:vite:aliased-module-resolver")("%s [applyAlias] Matched RegExp alias: %O -> '%s' for request '%s'", logPrefix, find, replacement, request);
|
|
26
|
+
return request.replace(find, replacement);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return request;
|
|
31
|
+
}
|
|
32
|
+
export function createAliasedModuleResolver({ getAliases, roots, conditionNames = ["workerd", "edge", "import", "default"], name = "aliasedModuleResolver", }) {
|
|
33
|
+
const log = debug("rwsdk:vite:aliased-module-resolver");
|
|
34
|
+
const logPrefix = `[${name}]`;
|
|
35
|
+
// Create a resolver instance with the provided conditionNames
|
|
36
|
+
const baseModuleResolver = enhancedResolve.create.sync({
|
|
37
|
+
conditionNames,
|
|
38
|
+
});
|
|
39
|
+
return function resolveModule(request, importer) {
|
|
40
|
+
log("%s Called with request: '%s', importer: '%s'", logPrefix, request, importer);
|
|
41
|
+
const aliasEntries = getAliases ? getAliases() : [];
|
|
42
|
+
log("%s Alias entries: %O", logPrefix, aliasEntries);
|
|
43
|
+
const normalized = applyAlias(request, aliasEntries, name);
|
|
44
|
+
log("%s After aliasing: '%s'", logPrefix, normalized);
|
|
45
|
+
const result = baseModuleResolver(normalized, path.dirname(importer));
|
|
46
|
+
if (result) {
|
|
47
|
+
log("%s Resolved %s relative to: '%s'", logPrefix, result, importer);
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
for (const root of roots) {
|
|
51
|
+
try {
|
|
52
|
+
const result = baseModuleResolver(root, normalized);
|
|
53
|
+
log("%s Resolved %s to: '%s' with root '%s'", logPrefix, normalized, result, root);
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
log("%s Resolution failed for '%s' from root '%s': %O", logPrefix, normalized, root, err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import enhancedResolve from "enhanced-resolve";
|
|
3
|
+
import debug from "debug";
|
|
4
|
+
const baseSSRResolver = enhancedResolve.create.sync({
|
|
5
|
+
conditionNames: ["workerd", "edge", "import", "default"],
|
|
6
|
+
});
|
|
7
|
+
function applyAlias(request, aliasEntries, name) {
|
|
8
|
+
if (!aliasEntries)
|
|
9
|
+
return request;
|
|
10
|
+
const logPrefix = `[${name}]`;
|
|
11
|
+
// Support both array and object forms
|
|
12
|
+
const entries = Array.isArray(aliasEntries)
|
|
13
|
+
? aliasEntries
|
|
14
|
+
: Object.entries(aliasEntries).map(([find, replacement]) => ({
|
|
15
|
+
find,
|
|
16
|
+
replacement,
|
|
17
|
+
}));
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const { find, replacement } = entry;
|
|
20
|
+
if (typeof find === "string") {
|
|
21
|
+
if (request === find || request.startsWith(find + "/")) {
|
|
22
|
+
debug("rwsdk:vite:aliased-ssr-resolver")("%s [applyAlias] Matched string alias: '%s' -> '%s' for request '%s'", logPrefix, find, replacement, request);
|
|
23
|
+
return replacement + request.slice(find.length);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (find instanceof RegExp) {
|
|
27
|
+
if (find.test(request)) {
|
|
28
|
+
debug("rwsdk:vite:aliased-ssr-resolver")("%s [applyAlias] Matched RegExp alias: %O -> '%s' for request '%s'", logPrefix, find, replacement, request);
|
|
29
|
+
return request.replace(find, replacement);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return request;
|
|
34
|
+
}
|
|
35
|
+
export function createAliasedSSRResolver({ getResolveConfig, roots, name = "aliasedSSRResolver", }) {
|
|
36
|
+
const log = debug("rwsdk:vite:aliased-ssr-resolver");
|
|
37
|
+
const logPrefix = `[${name}]`;
|
|
38
|
+
return function resolveModule(request, importer) {
|
|
39
|
+
log("%s Called with request: '%s', importer: '%s'", logPrefix, request, importer);
|
|
40
|
+
let normalized = request;
|
|
41
|
+
const resolveConfig = getResolveConfig?.() || {};
|
|
42
|
+
const aliasEntries = resolveConfig.alias;
|
|
43
|
+
log("%s Alias entries: %O", logPrefix, aliasEntries);
|
|
44
|
+
normalized = applyAlias(normalized, aliasEntries, name);
|
|
45
|
+
log("%s After aliasing: '%s'", logPrefix, normalized);
|
|
46
|
+
let rootsToTry = roots && roots.length > 0 ? roots : [];
|
|
47
|
+
// If leading slash, treat as first root-rooted (for compatibility)
|
|
48
|
+
if (normalized.startsWith("/")) {
|
|
49
|
+
if (rootsToTry.length > 0) {
|
|
50
|
+
const rooted = path.join(rootsToTry[0], normalized);
|
|
51
|
+
log("%s Leading slash detected, resolving as root[0]-rooted: '%s'", logPrefix, rooted);
|
|
52
|
+
normalized = rooted;
|
|
53
|
+
rootsToTry = [rootsToTry[0]];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const isAbsolute = path.isAbsolute(normalized);
|
|
57
|
+
if (isAbsolute) {
|
|
58
|
+
log("%s Resolving absolute path: '%s'", logPrefix, normalized);
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
for (const root of rootsToTry) {
|
|
62
|
+
try {
|
|
63
|
+
log("%s Trying root: '%s'", logPrefix, root);
|
|
64
|
+
const result = baseSSRResolver(root, normalized);
|
|
65
|
+
log("%s Resolved to: '%s' with root '%s'", logPrefix, result, root);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
log("%s Resolution failed for '%s' from root '%s': %O", logPrefix, normalized, root, err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ensureConfigArrays: (config: any) => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const ensureConfigArrays = (config) => {
|
|
2
|
+
config.optimizeDeps ??= {};
|
|
3
|
+
config.optimizeDeps.include ??= [];
|
|
4
|
+
config.optimizeDeps.esbuildOptions ??= {};
|
|
5
|
+
config.optimizeDeps.esbuildOptions.plugins ??= [];
|
|
6
|
+
config.resolve ??= {};
|
|
7
|
+
config.resolve.alias ??= [];
|
|
8
|
+
if (!Array.isArray(config.resolve.alias)) {
|
|
9
|
+
const aliasObj = config.resolve.alias;
|
|
10
|
+
config.resolve.alias = Object.entries(aliasObj).map(([find, replacement]) => ({ find, replacement }));
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const IMPORT_PATTERNS: string[];
|
|
2
|
+
/**
|
|
3
|
+
* Finds import specifiers and their positions in the code using the provided patterns.
|
|
4
|
+
* @param code The code to search for import specifiers.
|
|
5
|
+
* @param lang The language parser to use (TypeScript or Tsx).
|
|
6
|
+
* @param ignoredImportPatterns Array of regex patterns to ignore.
|
|
7
|
+
* @param log Optional logger function for debug output.
|
|
8
|
+
* @returns Array of objects with start, end, and raw import string.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findImportSpecifiers(id: string, code: string, ignoredImportPatterns: RegExp[], log?: (...args: any[]) => void): Array<{
|
|
11
|
+
s: number;
|
|
12
|
+
e: number;
|
|
13
|
+
raw: string;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { parse as sgParse, Lang as SgLang, Lang } from "@ast-grep/napi";
|
|
2
|
+
import path from "path";
|
|
3
|
+
// These patterns are used to match import statements in code for SSR transformations.
|
|
4
|
+
export const IMPORT_PATTERNS = [
|
|
5
|
+
'import { $$$ } from "$MODULE"',
|
|
6
|
+
"import { $$$ } from '$MODULE'",
|
|
7
|
+
'import $DEFAULT from "$MODULE"',
|
|
8
|
+
"import $DEFAULT from '$MODULE'",
|
|
9
|
+
'import * as $NS from "$MODULE"',
|
|
10
|
+
"import * as $NS from '$MODULE'",
|
|
11
|
+
'import "$MODULE"',
|
|
12
|
+
"import '$MODULE'",
|
|
13
|
+
// Static Re-exports
|
|
14
|
+
'export { $$$ } from "$MODULE"',
|
|
15
|
+
"export { $$$ } from '$MODULE'",
|
|
16
|
+
'export * from "$MODULE"',
|
|
17
|
+
"export * from '$MODULE'",
|
|
18
|
+
// Dynamic Imports
|
|
19
|
+
'import("$MODULE")',
|
|
20
|
+
"import('$MODULE')",
|
|
21
|
+
"import(`$MODULE`)",
|
|
22
|
+
// CommonJS require
|
|
23
|
+
'require("$MODULE")',
|
|
24
|
+
"require('$MODULE')",
|
|
25
|
+
"require(`$MODULE`)",
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Finds import specifiers and their positions in the code using the provided patterns.
|
|
29
|
+
* @param code The code to search for import specifiers.
|
|
30
|
+
* @param lang The language parser to use (TypeScript or Tsx).
|
|
31
|
+
* @param ignoredImportPatterns Array of regex patterns to ignore.
|
|
32
|
+
* @param log Optional logger function for debug output.
|
|
33
|
+
* @returns Array of objects with start, end, and raw import string.
|
|
34
|
+
*/
|
|
35
|
+
export function findImportSpecifiers(id, code, ignoredImportPatterns, log) {
|
|
36
|
+
const ext = path.extname(id).toLowerCase();
|
|
37
|
+
const lang = ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : SgLang.TypeScript;
|
|
38
|
+
const logger = log ?? (() => { });
|
|
39
|
+
const results = [];
|
|
40
|
+
try {
|
|
41
|
+
// sgParse and lang must be provided by the consumer
|
|
42
|
+
const root = sgParse(lang, code);
|
|
43
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
44
|
+
try {
|
|
45
|
+
const matches = root.root().findAll(pattern);
|
|
46
|
+
for (const match of matches) {
|
|
47
|
+
const moduleCapture = match.getMatch("MODULE");
|
|
48
|
+
if (moduleCapture) {
|
|
49
|
+
const importPath = moduleCapture.text();
|
|
50
|
+
if (importPath.startsWith("virtual:")) {
|
|
51
|
+
logger(":findImportSpecifiersWithPositions: Ignoring import because it starts with 'virtual:': importPath=%s", importPath);
|
|
52
|
+
}
|
|
53
|
+
else if (importPath.includes("__rwsdknossr")) {
|
|
54
|
+
logger(":findImportSpecifiersWithPositions: Ignoring import because it includes '__rwsdknossr': importPath=%s", importPath);
|
|
55
|
+
}
|
|
56
|
+
else if (ignoredImportPatterns.some((pattern) => pattern.test(importPath))) {
|
|
57
|
+
logger(":findImportSpecifiersWithPositions: Ignoring import because it matches IGNORED_IMPORT_PATTERNS: importPath=%s", importPath);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const { start, end } = moduleCapture.range();
|
|
61
|
+
results.push({ s: start.index, e: end.index, raw: importPath });
|
|
62
|
+
logger(":findImportSpecifiersWithPositions: Including import specifier: importPath=%s, range=[%d, %d]", importPath, start.index, end.index);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
logger(":findImportSpecifiersWithPositions: Error processing pattern: %O", err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
logger(":findImportSpecifiersWithPositions: Error parsing content: %O", err);
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isBareImport(importPath: string): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ModuleResolver = ReturnType<typeof createModuleResolver>;
|
|
2
|
+
export declare function createModuleResolver({ getAliases, roots, conditionNames, name, }: {
|
|
3
|
+
getAliases?: () => Array<{
|
|
4
|
+
find: string | RegExp;
|
|
5
|
+
replacement: string;
|
|
6
|
+
}>;
|
|
7
|
+
roots: string[];
|
|
8
|
+
conditionNames?: string[];
|
|
9
|
+
name: string;
|
|
10
|
+
}): (request: string, importer?: string) => string | false;
|