rwsdk 0.1.24 → 0.1.25

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.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Normalize a module path to a consistent form.
3
+ * Returns slash-prefixed paths for files within project root,
4
+ * or absolute paths for external files.
5
+ *
6
+ * Examples:
7
+ * /Users/justin/my-app/src/page.ts → /src/page.ts
8
+ * ../shared/utils.ts → /Users/justin/shared/utils.ts
9
+ * /src/page.ts (Vite-style) → /src/page.ts
10
+ * node_modules/foo/index.js → /node_modules/foo/index.js
11
+ *
12
+ * With { absolute: true }:
13
+ * /Users/justin/my-app/src/page.ts → /Users/justin/my-app/src/page.ts
14
+ *
15
+ * With { isViteStyle: false }:
16
+ * /opt/tools/logger.ts → /opt/tools/logger.ts (treated as external)
17
+ * /src/page.tsx → /src/page.tsx (treated as external)
18
+ *
19
+ * With { isViteStyle: true }:
20
+ * /opt/tools/logger.ts → /opt/tools/logger.ts (resolved as Vite-style)
21
+ * /src/page.tsx, { absolute: true } → /Users/justin/my-app/src/page.tsx
22
+ */
23
+ export declare function normalizeModulePath(modulePath: string, projectRootDir: string, options?: {
24
+ absolute?: boolean;
25
+ isViteStyle?: boolean;
26
+ }): string;
@@ -0,0 +1,101 @@
1
+ import * as path from "node:path";
2
+ import { normalizePath as normalizePathSeparators } from "vite";
3
+ /**
4
+ * Find the number of common ancestor segments between two absolute paths.
5
+ * Returns the count of shared directory segments from the root.
6
+ */
7
+ function findCommonAncestorDepth(path1, path2) {
8
+ const segments1 = path1.split("/").filter(Boolean);
9
+ const segments2 = path2.split("/").filter(Boolean);
10
+ let commonLength = 0;
11
+ const minLength = Math.min(segments1.length, segments2.length);
12
+ for (let i = 0; i < minLength; i++) {
13
+ if (segments1[i] === segments2[i]) {
14
+ commonLength++;
15
+ }
16
+ else {
17
+ break;
18
+ }
19
+ }
20
+ return commonLength;
21
+ }
22
+ /**
23
+ * Normalize a module path to a consistent form.
24
+ * Returns slash-prefixed paths for files within project root,
25
+ * or absolute paths for external files.
26
+ *
27
+ * Examples:
28
+ * /Users/justin/my-app/src/page.ts → /src/page.ts
29
+ * ../shared/utils.ts → /Users/justin/shared/utils.ts
30
+ * /src/page.ts (Vite-style) → /src/page.ts
31
+ * node_modules/foo/index.js → /node_modules/foo/index.js
32
+ *
33
+ * With { absolute: true }:
34
+ * /Users/justin/my-app/src/page.ts → /Users/justin/my-app/src/page.ts
35
+ *
36
+ * With { isViteStyle: false }:
37
+ * /opt/tools/logger.ts → /opt/tools/logger.ts (treated as external)
38
+ * /src/page.tsx → /src/page.tsx (treated as external)
39
+ *
40
+ * With { isViteStyle: true }:
41
+ * /opt/tools/logger.ts → /opt/tools/logger.ts (resolved as Vite-style)
42
+ * /src/page.tsx, { absolute: true } → /Users/justin/my-app/src/page.tsx
43
+ */
44
+ export function normalizeModulePath(modulePath, projectRootDir, options = {}) {
45
+ modulePath = normalizePathSeparators(modulePath);
46
+ projectRootDir = normalizePathSeparators(path.resolve(projectRootDir));
47
+ // Handle empty string or current directory
48
+ if (modulePath === "" || modulePath === ".") {
49
+ return options.absolute ? projectRootDir : "/";
50
+ }
51
+ // For relative paths, resolve them first
52
+ let resolved;
53
+ if (path.isAbsolute(modulePath)) {
54
+ if (modulePath.startsWith(projectRootDir + "/") ||
55
+ modulePath === projectRootDir) {
56
+ // Path starts with project root - it's a real absolute path inside project
57
+ resolved = modulePath;
58
+ }
59
+ else {
60
+ // Check how the path relates to the project root
61
+ if (options.isViteStyle !== undefined) {
62
+ // User explicitly specified whether this should be treated as Vite-style
63
+ if (options.isViteStyle) {
64
+ resolved = path.resolve(projectRootDir, modulePath.slice(1));
65
+ }
66
+ else {
67
+ resolved = modulePath;
68
+ }
69
+ }
70
+ else {
71
+ // Fall back to heuristics using common ancestor depth
72
+ const commonDepth = findCommonAncestorDepth(modulePath, projectRootDir);
73
+ if (commonDepth > 0) {
74
+ // Paths share meaningful common ancestor - treat as real absolute path
75
+ resolved = modulePath;
76
+ }
77
+ else {
78
+ // No meaningful common ancestor - assume Vite-style path within project
79
+ resolved = path.resolve(projectRootDir, modulePath.slice(1));
80
+ }
81
+ }
82
+ }
83
+ }
84
+ else {
85
+ resolved = path.resolve(projectRootDir, modulePath);
86
+ }
87
+ resolved = normalizePathSeparators(resolved);
88
+ // If absolute option is set, always return absolute paths
89
+ if (options.absolute) {
90
+ return resolved;
91
+ }
92
+ // Check if the resolved path is within the project root
93
+ const relative = path.relative(projectRootDir, resolved);
94
+ // If the path goes outside the project root (starts with ..), return absolute
95
+ if (relative.startsWith("..")) {
96
+ return resolved;
97
+ }
98
+ // Path is within project root, return as Vite-style relative path
99
+ const cleanRelative = relative === "." ? "" : relative;
100
+ return "/" + normalizePathSeparators(cleanRelative);
101
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,203 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { normalizeModulePath } from "./normalizeModulePath.mjs";
3
+ describe("normalizeModulePath", () => {
4
+ describe("1. Project-local paths", () => {
5
+ it("Relative file", () => {
6
+ expect(normalizeModulePath("src/page.tsx", "/Users/name/code/my-app")).toBe("/src/page.tsx");
7
+ });
8
+ it("Relative file in subdir", () => {
9
+ expect(normalizeModulePath("src/utils/index.ts", "/Users/name/code/my-app")).toBe("/src/utils/index.ts");
10
+ });
11
+ it("Relative file with ./", () => {
12
+ expect(normalizeModulePath("./src/page.tsx", "/Users/name/code/my-app")).toBe("/src/page.tsx");
13
+ });
14
+ it("Relative file with ../ (external)", () => {
15
+ expect(normalizeModulePath("../shared/foo.ts", "/Users/name/code/my-app")).toBe("/Users/name/code/shared/foo.ts");
16
+ });
17
+ });
18
+ describe("2. Vite-style absolute paths", () => {
19
+ it("Vite-style root import", () => {
20
+ expect(normalizeModulePath("/src/page.tsx", "/Users/name/code/my-app")).toBe("/src/page.tsx");
21
+ });
22
+ it("Vite-style node_modules", () => {
23
+ expect(normalizeModulePath("/node_modules/foo.js", "/Users/name/code/my-app")).toBe("/node_modules/foo.js");
24
+ });
25
+ });
26
+ describe("3. Real absolute paths inside project", () => {
27
+ it("Real abs path (inside)", () => {
28
+ expect(normalizeModulePath("/Users/name/code/my-app/src/page.tsx", "/Users/name/code/my-app")).toBe("/src/page.tsx");
29
+ });
30
+ it("Real abs path (deep inside)", () => {
31
+ expect(normalizeModulePath("/Users/name/code/my-app/src/features/auth.ts", "/Users/name/code/my-app")).toBe("/src/features/auth.ts");
32
+ });
33
+ });
34
+ describe("4. Real absolute paths outside project", () => {
35
+ it("External shared pkg", () => {
36
+ expect(normalizeModulePath("/Users/name/code/my-monorepo/packages/shared/utils.ts", "/Users/name/code/my-monorepo/packages/app")).toBe("/Users/name/code/my-monorepo/packages/shared/utils.ts");
37
+ });
38
+ it("External node_modules", () => {
39
+ expect(normalizeModulePath("/Users/name/code/my-monorepo/node_modules/foo/index.js", "/Users/name/code/my-monorepo/packages/app")).toBe("/Users/name/code/my-monorepo/node_modules/foo/index.js");
40
+ });
41
+ it("Completely external path", () => {
42
+ expect(normalizeModulePath("/opt/tools/logger.ts", "/Users/name/code/my-app")).toBe("/opt/tools/logger.ts");
43
+ });
44
+ });
45
+ describe("5. Absolute option", () => {
46
+ describe("Project-local paths with absolute option", () => {
47
+ it("Relative file", () => {
48
+ expect(normalizeModulePath("src/page.tsx", "/Users/name/code/my-app", {
49
+ absolute: true,
50
+ })).toBe("/Users/name/code/my-app/src/page.tsx");
51
+ });
52
+ it("Relative file in subdir", () => {
53
+ expect(normalizeModulePath("src/utils/index.ts", "/Users/name/code/my-app", {
54
+ absolute: true,
55
+ })).toBe("/Users/name/code/my-app/src/utils/index.ts");
56
+ });
57
+ it("Relative file with ./", () => {
58
+ expect(normalizeModulePath("./src/page.tsx", "/Users/name/code/my-app", {
59
+ absolute: true,
60
+ })).toBe("/Users/name/code/my-app/src/page.tsx");
61
+ });
62
+ it("Relative file with ../ (external)", () => {
63
+ expect(normalizeModulePath("../shared/foo.ts", "/Users/name/code/my-app", {
64
+ absolute: true,
65
+ })).toBe("/Users/name/code/shared/foo.ts");
66
+ });
67
+ });
68
+ describe("Vite-style absolute paths with absolute option", () => {
69
+ it("Vite-style root import", () => {
70
+ expect(normalizeModulePath("/src/page.tsx", "/Users/name/code/my-app", {
71
+ absolute: true,
72
+ })).toBe("/Users/name/code/my-app/src/page.tsx");
73
+ });
74
+ it("Vite-style node_modules", () => {
75
+ expect(normalizeModulePath("/node_modules/foo.js", "/Users/name/code/my-app", { absolute: true })).toBe("/Users/name/code/my-app/node_modules/foo.js");
76
+ });
77
+ });
78
+ describe("Real absolute paths with absolute option", () => {
79
+ it("Real abs path (inside)", () => {
80
+ expect(normalizeModulePath("/Users/name/code/my-app/src/page.tsx", "/Users/name/code/my-app", { absolute: true })).toBe("/Users/name/code/my-app/src/page.tsx");
81
+ });
82
+ it("Real abs path (deep inside)", () => {
83
+ expect(normalizeModulePath("/Users/name/code/my-app/src/features/auth.ts", "/Users/name/code/my-app", { absolute: true })).toBe("/Users/name/code/my-app/src/features/auth.ts");
84
+ });
85
+ it("External shared pkg", () => {
86
+ expect(normalizeModulePath("/Users/name/code/my-monorepo/packages/shared/utils.ts", "/Users/name/code/my-monorepo/packages/app", { absolute: true })).toBe("/Users/name/code/my-monorepo/packages/shared/utils.ts");
87
+ });
88
+ it("External node_modules", () => {
89
+ expect(normalizeModulePath("/Users/name/code/my-monorepo/node_modules/foo/index.js", "/Users/name/code/my-monorepo/packages/app", { absolute: true })).toBe("/Users/name/code/my-monorepo/node_modules/foo/index.js");
90
+ });
91
+ it("Completely external path", () => {
92
+ expect(normalizeModulePath("/opt/tools/logger.ts", "/Users/name/code/my-app", {
93
+ absolute: true,
94
+ isViteStyle: false,
95
+ })).toBe("/opt/tools/logger.ts");
96
+ });
97
+ });
98
+ describe("Edge cases with absolute option", () => {
99
+ it("Empty string", () => {
100
+ expect(normalizeModulePath("", "/Users/name/code/my-app", {
101
+ absolute: true,
102
+ })).toBe("/Users/name/code/my-app");
103
+ });
104
+ it("Dot current dir", () => {
105
+ expect(normalizeModulePath(".", "/Users/name/code/my-app", {
106
+ absolute: true,
107
+ })).toBe("/Users/name/code/my-app");
108
+ });
109
+ it("Dot parent dir", () => {
110
+ expect(normalizeModulePath("..", "/Users/name/code/my-app", {
111
+ absolute: true,
112
+ })).toBe("/Users/name/code");
113
+ });
114
+ it("Trailing slash", () => {
115
+ expect(normalizeModulePath("src/", "/Users/name/code/my-app", {
116
+ absolute: true,
117
+ })).toBe("/Users/name/code/my-app/src");
118
+ });
119
+ it("Leading and trailing slashes", () => {
120
+ expect(normalizeModulePath("/src/", "/Users/name/code/my-app", {
121
+ absolute: true,
122
+ })).toBe("/Users/name/code/my-app/src");
123
+ });
124
+ });
125
+ describe("Project root is / with absolute option", () => {
126
+ it("Root-based path", () => {
127
+ expect(normalizeModulePath("/src/index.ts", "/", { absolute: true })).toBe("/src/index.ts");
128
+ });
129
+ it("System path", () => {
130
+ expect(normalizeModulePath("/etc/hosts", "/", { absolute: true })).toBe("/etc/hosts");
131
+ });
132
+ });
133
+ });
134
+ describe("6. Edge and weird cases", () => {
135
+ it("Empty string", () => {
136
+ expect(normalizeModulePath("", "/Users/name/code/my-app")).toBe("/");
137
+ });
138
+ it("Dot current dir", () => {
139
+ expect(normalizeModulePath(".", "/Users/name/code/my-app")).toBe("/");
140
+ });
141
+ it("Dot parent dir", () => {
142
+ expect(normalizeModulePath("..", "/Users/name/code/my-app")).toBe("/Users/name/code");
143
+ });
144
+ it("Trailing slash", () => {
145
+ expect(normalizeModulePath("src/", "/Users/name/code/my-app")).toBe("/src");
146
+ });
147
+ it("Leading and trailing slashes", () => {
148
+ expect(normalizeModulePath("/src/", "/Users/name/code/my-app")).toBe("/src");
149
+ });
150
+ });
151
+ describe("7. Project root is /", () => {
152
+ it("Root-based path", () => {
153
+ expect(normalizeModulePath("/src/index.ts", "/")).toBe("/src/index.ts");
154
+ });
155
+ it("System path", () => {
156
+ expect(normalizeModulePath("/etc/hosts", "/")).toBe("/etc/hosts");
157
+ });
158
+ });
159
+ describe("8. isViteStyle option", () => {
160
+ describe("isViteStyle: false (treat as external)", () => {
161
+ it("System path that would normally be Vite-style", () => {
162
+ expect(normalizeModulePath("/opt/tools/logger.ts", "/Users/name/code/my-app", {
163
+ isViteStyle: false,
164
+ })).toBe("/opt/tools/logger.ts");
165
+ });
166
+ it("Src path with isViteStyle: false", () => {
167
+ expect(normalizeModulePath("/src/page.tsx", "/Users/name/code/my-app", {
168
+ isViteStyle: false,
169
+ })).toBe("/src/page.tsx");
170
+ });
171
+ it("With absolute option", () => {
172
+ expect(normalizeModulePath("/opt/tools/logger.ts", "/Users/name/code/my-app", {
173
+ absolute: true,
174
+ isViteStyle: false,
175
+ })).toBe("/opt/tools/logger.ts");
176
+ });
177
+ });
178
+ describe("isViteStyle: true (force Vite-style)", () => {
179
+ it("System path forced to Vite-style", () => {
180
+ expect(normalizeModulePath("/opt/tools/logger.ts", "/Users/name/code/my-app", {
181
+ isViteStyle: true,
182
+ })).toBe("/opt/tools/logger.ts");
183
+ });
184
+ it("Src path with isViteStyle: true", () => {
185
+ expect(normalizeModulePath("/src/page.tsx", "/Users/name/code/my-app", {
186
+ isViteStyle: true,
187
+ })).toBe("/src/page.tsx");
188
+ });
189
+ it("With absolute option", () => {
190
+ expect(normalizeModulePath("/src/page.tsx", "/Users/name/code/my-app", {
191
+ absolute: true,
192
+ isViteStyle: true,
193
+ })).toBe("/Users/name/code/my-app/src/page.tsx");
194
+ });
195
+ it("System path forced to Vite-style with absolute option", () => {
196
+ expect(normalizeModulePath("/opt/tools/logger.ts", "/Users/name/code/my-app", {
197
+ absolute: true,
198
+ isViteStyle: true,
199
+ })).toBe("/Users/name/code/my-app/opt/tools/logger.ts");
200
+ });
201
+ });
202
+ });
203
+ });
@@ -2,7 +2,7 @@ import MagicString from "magic-string";
2
2
  import path from "path";
3
3
  import { readFile } from "fs/promises";
4
4
  import debug from "debug";
5
- import { normalizeModulePath } from "./normalizeModulePath.mjs";
5
+ import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
6
6
  import { pathExists } from "fs-extra";
7
7
  import { stat } from "fs/promises";
8
8
  import { getSrcPaths } from "../lib/getSrcPaths.js";
@@ -22,7 +22,7 @@ export const findFilesContainingDirective = async ({ projectRootDir, files, dire
22
22
  process.env.VERBOSE && log("Scanning file: %s", file);
23
23
  const content = await readFile(file, "utf-8");
24
24
  if (hasDirective(content, directive)) {
25
- const normalizedPath = normalizeModulePath(projectRootDir, file);
25
+ const normalizedPath = normalizeModulePath(file, projectRootDir);
26
26
  log("Found '%s' directive in file: %s -> %s", directive, file, normalizedPath);
27
27
  files.add(normalizedPath);
28
28
  }
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
3
3
  import debug from "debug";
4
4
  import { transformClientComponents } from "./transformClientComponents.mjs";
5
5
  import { transformServerFunctions } from "./transformServerFunctions.mjs";
6
- import { normalizeModulePath } from "./normalizeModulePath.mjs";
6
+ import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
7
7
  const log = debug("rwsdk:vite:rsc-directives-plugin");
8
8
  const getLoader = (filePath) => {
9
9
  const ext = path.extname(filePath).slice(1);
@@ -31,9 +31,10 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
31
31
  const addModule = (kind, environment, id) => {
32
32
  const files = kind === "client" ? clientFiles : serverFiles;
33
33
  const rawId = id.split("?")[0];
34
- const resolvedId = rawId;
35
- const relativePath = rawId.slice("/".length);
36
- const fullPath = path.resolve(projectRootDir, relativePath);
34
+ const resolvedId = normalizeModulePath(rawId, projectRootDir);
35
+ const fullPath = normalizeModulePath(rawId, projectRootDir, {
36
+ absolute: true,
37
+ });
37
38
  const isNodeModule = id.includes("node_modules");
38
39
  const hadFile = files.has(id);
39
40
  log("Adding %s module to %s and invalidating cache: id=%s", kind, files, resolvedId);
@@ -77,7 +78,7 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
77
78
  async transform(code, id) {
78
79
  process.env.VERBOSE &&
79
80
  log("Transform called for id=%s, environment=%s", id, this.environment.name);
80
- const normalizedId = normalizeModulePath(projectRootDir, id);
81
+ const normalizedId = normalizeModulePath(id, projectRootDir);
81
82
  const clientResult = await transformClientComponents(code, normalizedId, {
82
83
  environmentName: this.environment.name,
83
84
  clientFiles,
@@ -112,7 +113,7 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
112
113
  build.onLoad({ filter: /\.(js|ts|jsx|tsx|mts|mjs|cjs)$/ }, async (args) => {
113
114
  process.env.VERBOSE &&
114
115
  log("Esbuild onLoad called for environment=%s, path=%s", env, args.path);
115
- const normalizedPath = normalizeModulePath(projectRootDir, args.path);
116
+ const normalizedPath = normalizeModulePath(args.path, projectRootDir);
116
117
  // context(justinvdm,2025-06-15): If we're in app code,
117
118
  // we will be doing the transform work in the vite plugin hooks,
118
119
  // the only reason we're in esbuild land for app code is for
@@ -3,7 +3,7 @@ import colors from "picocolors";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import debug from "debug";
5
5
  import { VIRTUAL_SSR_PREFIX } from "./ssrBridgePlugin.mjs";
6
- import { normalizeModulePath } from "./normalizeModulePath.mjs";
6
+ import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
7
7
  import { hasDirective as sourceHasDirective } from "./hasDirective.mjs";
8
8
  import { isJsFile } from "./isJsFile.mjs";
9
9
  import { invalidateModule } from "./invalidateModule.mjs";
@@ -33,7 +33,7 @@ const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => {
33
33
  return false;
34
34
  };
35
35
  const isInUseClientGraph = ({ file, clientFiles, server, }) => {
36
- const id = normalizeModulePath(server.config.root, file);
36
+ const id = normalizeModulePath(file, server.config.root);
37
37
  if (clientFiles.has(id)) {
38
38
  return true;
39
39
  }
@@ -74,7 +74,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
74
74
  }
75
75
  invalidateModule(ctx.server, "ssr", ctx.file);
76
76
  invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX +
77
- normalizeModulePath(givenOptions.rootDir, ctx.file));
77
+ normalizeModulePath(ctx.file, givenOptions.rootDir));
78
78
  return [];
79
79
  }
80
80
  if (!["client", environment].includes(this.environment.name)) {
@@ -159,7 +159,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
159
159
  invalidateModule(ctx.server, environment, m);
160
160
  }
161
161
  const virtualSSRModule = ctx.server.environments[environment].moduleGraph.idToModuleMap.get(VIRTUAL_SSR_PREFIX +
162
- normalizeModulePath(givenOptions.rootDir, ctx.file));
162
+ normalizeModulePath(ctx.file, givenOptions.rootDir));
163
163
  if (virtualSSRModule) {
164
164
  invalidateModule(ctx.server, environment, virtualSSRModule);
165
165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- export declare const normalizeModulePath: (projectRootDir: string, modulePath: string) => string;
@@ -1,13 +0,0 @@
1
- import path from "node:path";
2
- export const normalizeModulePath = (projectRootDir, modulePath) => {
3
- // /Users/path/to/project/src/foo/bar.ts -> /src/foo/bar.ts
4
- if (modulePath.startsWith(projectRootDir)) {
5
- return "/" + path.relative(projectRootDir, modulePath);
6
- }
7
- // /src/foo/bar.ts -> /src/foo/bar.ts
8
- if (modulePath.startsWith("/")) {
9
- return modulePath;
10
- }
11
- // src/foo/bar.ts -> /src/foo/bar.ts
12
- return "/" + modulePath;
13
- };