rwsdk 0.2.0 → 0.3.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 (63) hide show
  1. package/dist/lib/constants.d.mts +6 -1
  2. package/dist/lib/constants.mjs +6 -1
  3. package/dist/lib/smokeTests/browser.mjs +5 -21
  4. package/dist/lib/smokeTests/codeUpdates.d.mts +1 -1
  5. package/dist/lib/smokeTests/codeUpdates.mjs +41 -5
  6. package/dist/lib/smokeTests/development.d.mts +1 -1
  7. package/dist/lib/smokeTests/development.mjs +4 -10
  8. package/dist/lib/smokeTests/release.d.mts +1 -1
  9. package/dist/lib/smokeTests/release.mjs +4 -9
  10. package/dist/lib/smokeTests/runSmokeTests.mjs +2 -2
  11. package/dist/lib/smokeTests/templates/SmokeTest.template.js +3 -2
  12. package/dist/lib/testUtils/stubEnvVars.d.mts +2 -0
  13. package/dist/lib/testUtils/stubEnvVars.mjs +11 -0
  14. package/dist/runtime/imports/client.js +4 -9
  15. package/dist/runtime/imports/worker.js +2 -1
  16. package/dist/runtime/register/ssr.js +2 -1
  17. package/dist/runtime/requestInfo/worker.js +9 -1
  18. package/dist/runtime/worker.d.ts +0 -3
  19. package/dist/runtime/worker.js +1 -10
  20. package/dist/scripts/debug-sync.mjs +0 -23
  21. package/dist/scripts/smoke-test.mjs +0 -10
  22. package/dist/vite/buildApp.d.mts +15 -0
  23. package/dist/vite/buildApp.mjs +53 -0
  24. package/dist/vite/configPlugin.d.mts +4 -4
  25. package/dist/vite/configPlugin.mjs +70 -76
  26. package/dist/vite/constants.d.mts +2 -0
  27. package/dist/vite/constants.mjs +12 -0
  28. package/dist/vite/createDirectiveLookupPlugin.d.mts +0 -6
  29. package/dist/vite/createDirectiveLookupPlugin.mjs +69 -145
  30. package/dist/vite/createViteAwareResolver.d.mts +4 -0
  31. package/dist/vite/createViteAwareResolver.mjs +208 -0
  32. package/dist/vite/directiveModulesDevPlugin.d.mts +8 -0
  33. package/dist/vite/directiveModulesDevPlugin.mjs +87 -0
  34. package/dist/vite/directivesFilteringPlugin.d.mts +6 -0
  35. package/dist/vite/directivesFilteringPlugin.mjs +31 -0
  36. package/dist/vite/directivesPlugin.mjs +32 -42
  37. package/dist/vite/getViteEsbuild.d.mts +1 -0
  38. package/dist/vite/getViteEsbuild.mjs +12 -0
  39. package/dist/vite/injectVitePreamblePlugin.d.mts +3 -2
  40. package/dist/vite/injectVitePreamblePlugin.mjs +8 -2
  41. package/dist/vite/linkerPlugin.d.mts +4 -0
  42. package/dist/vite/linkerPlugin.mjs +41 -0
  43. package/dist/vite/manifestPlugin.d.mts +2 -2
  44. package/dist/vite/manifestPlugin.mjs +20 -37
  45. package/dist/vite/moveStaticAssetsPlugin.mjs +2 -1
  46. package/dist/vite/prismaPlugin.mjs +1 -1
  47. package/dist/vite/reactConditionsResolverPlugin.mjs +15 -16
  48. package/dist/vite/redwoodPlugin.d.mts +0 -1
  49. package/dist/vite/redwoodPlugin.mjs +27 -9
  50. package/dist/vite/runDirectivesScan.d.mts +7 -0
  51. package/dist/vite/runDirectivesScan.mjs +156 -0
  52. package/dist/vite/ssrBridgePlugin.mjs +21 -14
  53. package/dist/vite/transformClientComponents.d.mts +0 -1
  54. package/dist/vite/transformClientComponents.mjs +1 -9
  55. package/dist/vite/transformJsxScriptTagsPlugin.d.mts +4 -3
  56. package/dist/vite/transformJsxScriptTagsPlugin.mjs +66 -84
  57. package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +67 -41
  58. package/dist/vite/transformServerFunctions.d.mts +1 -1
  59. package/dist/vite/transformServerFunctions.mjs +11 -12
  60. package/dist/vite/virtualPlugin.mjs +8 -0
  61. package/package.json +7 -1
  62. package/dist/runtime/clientNavigation.d.ts +0 -9
  63. package/dist/runtime/clientNavigation.js +0 -88
@@ -1,57 +1,40 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import debug from "debug";
3
- import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
4
2
  const log = debug("rwsdk:vite:manifest-plugin");
5
3
  const virtualModuleId = "virtual:rwsdk:manifest.js";
6
4
  const resolvedVirtualModuleId = "\0" + virtualModuleId;
7
- export const manifestPlugin = ({ manifestPath, }) => {
5
+ export const manifestPlugin = ({ projectRootDir, }) => {
8
6
  let isBuild = false;
9
- let root;
10
7
  return {
11
- name: "rwsdk:manifest",
8
+ name: "rwsdk:vite:manifest-plugin",
9
+ enforce: "pre",
12
10
  configResolved(config) {
13
- log("Config resolved, command=%s", config.command);
14
11
  isBuild = config.command === "build";
15
- root = config.root;
16
12
  },
17
13
  resolveId(id) {
14
+ // Skip during directive scanning to avoid performance issues
15
+ if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
16
+ return;
17
+ }
18
18
  if (id === virtualModuleId) {
19
- process.env.VERBOSE && log("Resolving virtual module id=%s", id);
20
19
  return resolvedVirtualModuleId;
21
20
  }
22
21
  },
23
22
  async load(id) {
23
+ // Skip during directive scanning to avoid performance issues
24
+ if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
25
+ return;
26
+ }
24
27
  if (id === resolvedVirtualModuleId) {
25
- process.env.VERBOSE && log("Loading virtual module id=%s", id);
26
- if (!isBuild) {
27
- process.env.VERBOSE && log("Not a build, returning empty manifest");
28
- return `export default {}`;
29
- }
30
- log("Reading manifest from %s", manifestPath);
31
- const manifestContent = await readFile(manifestPath, "utf-8");
32
- const manifest = JSON.parse(manifestContent);
33
- const normalizedManifest = {};
34
- for (const key in manifest) {
35
- const normalizedKey = normalizeModulePath(key, root, {
36
- isViteStyle: false,
37
- });
38
- const entry = manifest[key];
39
- delete manifest[key];
40
- normalizedManifest[normalizedKey] = entry;
41
- entry.file = normalizeModulePath(entry.file, root, {
42
- isViteStyle: false,
43
- });
44
- const normalizedCss = [];
45
- if (entry.css) {
46
- for (const css of entry.css) {
47
- normalizedCss.push(normalizeModulePath(css, root, {
48
- isViteStyle: false,
49
- }));
50
- }
51
- entry.css = normalizedCss;
52
- }
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__"`;
53
34
  }
54
- return `export default ${JSON.stringify(normalizedManifest)}`;
35
+ // In dev, we can return an empty object.
36
+ log("Not a build, returning empty manifest");
37
+ return `export default {}`;
55
38
  }
56
39
  },
57
40
  configEnvironment(name, config) {
@@ -3,7 +3,8 @@ export const moveStaticAssetsPlugin = ({ rootDir, }) => ({
3
3
  name: "rwsdk:move-static-assets",
4
4
  apply: "build",
5
5
  async closeBundle() {
6
- if (this.environment.name === "worker") {
6
+ if (this.environment.name === "worker" &&
7
+ process.env.RWSDK_BUILD_PASS === "linker") {
7
8
  await $sh({
8
9
  cwd: rootDir,
9
10
  }) `mv dist/worker/assets/*.css dist/client/assets/ || true`;
@@ -17,7 +17,7 @@ export const prismaPlugin = async ({ projectRootDir, }) => {
17
17
  return {
18
18
  name: "rwsdk:prisma",
19
19
  configEnvironment(name, config) {
20
- if (name !== "worker") {
20
+ if (name !== "worker" || !process.env.VITE_IS_DEV_SERVER) {
21
21
  return;
22
22
  }
23
23
  const wasmPath = resolve(projectRootDir, "node_modules/.prisma/client/wasm.js");
@@ -79,10 +79,12 @@ function resolveEnvImportMappings(env, projectRootDir) {
79
79
  const resolved = resolveReactImport(importRequest, env, projectRootDir, true);
80
80
  if (resolved) {
81
81
  mappings.set(importRequest, resolved);
82
- log("Added mapping for %s -> %s in env=%s", importRequest, resolved, env);
82
+ process.env.VERBOSE &&
83
+ log("Added mapping for %s -> %s in env=%s", importRequest, resolved, env);
83
84
  }
84
85
  }
85
- log("Environment import mappings complete for env=%s: %d mappings", env, mappings.size);
86
+ process.env.VERBOSE &&
87
+ log("Environment import mappings complete for env=%s: %d mappings", env, mappings.size);
86
88
  return mappings;
87
89
  }
88
90
  export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
@@ -92,6 +94,9 @@ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
92
94
  env,
93
95
  resolveEnvImportMappings(env, projectRootDir),
94
96
  ]));
97
+ // Log a clean summary instead of all the individual mappings
98
+ const totalMappings = Object.values(ENV_IMPORT_MAPPINGS).reduce((sum, mappings) => sum + mappings.size, 0);
99
+ log("React conditions resolver configured with %d total mappings across %d environments", totalMappings, Object.keys(ENV_IMPORT_MAPPINGS).length);
95
100
  function createEsbuildResolverPlugin(envName, mappings) {
96
101
  if (!mappings) {
97
102
  return null;
@@ -100,15 +105,11 @@ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
100
105
  name: `rwsdk:react-conditions-resolver-esbuild-${envName}`,
101
106
  setup(build) {
102
107
  build.onResolve({ filter: /.*/ }, (args) => {
103
- process.env.VERBOSE &&
104
- log("ESBuild resolving %s for env=%s, args=%O", args.path, envName, args);
105
108
  let resolved = mappings.get(args.path);
106
109
  if (!resolved) {
107
110
  resolved = resolveReactImport(args.path, envName, projectRootDir);
108
111
  }
109
112
  if (resolved && args.importer !== "") {
110
- process.env.VERBOSE &&
111
- log("ESBuild resolving %s -> %s for env=%s", args.path, resolved, envName);
112
113
  if (args.path === "react-server-dom-webpack/client.edge") {
113
114
  return;
114
115
  }
@@ -116,10 +117,6 @@ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
116
117
  path: resolved,
117
118
  };
118
119
  }
119
- else {
120
- process.env.VERBOSE &&
121
- log("ESBuild no resolution found for %s for env=%s", args.path, envName);
122
- }
123
120
  });
124
121
  },
125
122
  };
@@ -161,7 +158,8 @@ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
161
158
  for (const [find, replacement] of mappings) {
162
159
  const findRegex = new RegExp(`^${find.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")}$`);
163
160
  aliases.push({ find: findRegex, replacement });
164
- log("Added alias for env=%s: %s -> %s", envName, find, replacement);
161
+ process.env.VERBOSE &&
162
+ log("Added alias for env=%s: %s -> %s", envName, find, replacement);
165
163
  }
166
164
  log("Environment %s configured with %d aliases and %d optimizeDeps includes", envName, mappings.size, reactImports.length);
167
165
  }
@@ -171,6 +169,10 @@ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
171
169
  name: "rwsdk:react-conditions-resolver:resolveId",
172
170
  enforce: "pre",
173
171
  async resolveId(id, importer) {
172
+ // Skip during directive scanning to avoid performance issues
173
+ if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
174
+ return;
175
+ }
174
176
  if (!isBuild) {
175
177
  return;
176
178
  }
@@ -178,8 +180,6 @@ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
178
180
  if (!envName) {
179
181
  return;
180
182
  }
181
- process.env.VERBOSE &&
182
- log("Resolving id=%s, environment=%s, importer=%s", id, envName, importer);
183
183
  const mappings = ENV_IMPORT_MAPPINGS[envName];
184
184
  if (!mappings) {
185
185
  process.env.VERBOSE &&
@@ -191,11 +191,10 @@ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
191
191
  resolved = resolveReactImport(id, envName, projectRootDir);
192
192
  }
193
193
  if (resolved) {
194
- log("Resolved %s -> %s for env=%s", id, resolved, envName);
194
+ process.env.VERBOSE &&
195
+ log("Resolved %s -> %s for env=%s", id, resolved, envName);
195
196
  return resolved;
196
197
  }
197
- process.env.VERBOSE &&
198
- log("No resolution found for id=%s in env=%s", id, envName);
199
198
  },
200
199
  },
201
200
  ];
@@ -6,7 +6,6 @@ export type RedwoodPluginOptions = {
6
6
  includeReactPlugin?: boolean;
7
7
  configPath?: string;
8
8
  entry?: {
9
- client?: string | string[];
10
9
  worker?: string;
11
10
  };
12
11
  };
@@ -24,6 +24,9 @@ import { ssrBridgePlugin } from "./ssrBridgePlugin.mjs";
24
24
  import { hasPkgScript } from "../lib/hasPkgScript.mjs";
25
25
  import { devServerTimingPlugin } from "./devServerTimingPlugin.mjs";
26
26
  import { manifestPlugin } from "./manifestPlugin.mjs";
27
+ import { linkerPlugin } from "./linkerPlugin.mjs";
28
+ import { directiveModulesDevPlugin } from "./directiveModulesDevPlugin.mjs";
29
+ import { directivesFilteringPlugin } from "./directivesFilteringPlugin.mjs";
27
30
  const determineWorkerEntryPathname = async (projectRootDir, workerConfigPath, options) => {
28
31
  if (options.entry?.worker) {
29
32
  return resolve(projectRootDir, options.entry.worker);
@@ -31,6 +34,9 @@ const determineWorkerEntryPathname = async (projectRootDir, workerConfigPath, op
31
34
  const workerConfig = unstable_readConfig({ config: workerConfigPath });
32
35
  return resolve(projectRootDir, workerConfig.main ?? "src/worker.tsx");
33
36
  };
37
+ const clientFiles = new Set();
38
+ const serverFiles = new Set();
39
+ const clientEntryPoints = new Set();
34
40
  export const redwoodPlugin = async (options = {}) => {
35
41
  const projectRootDir = process.cwd();
36
42
  const workerConfigPath = options.configPath ??
@@ -38,11 +44,6 @@ export const redwoodPlugin = async (options = {}) => {
38
44
  ? resolve(projectRootDir, process.env.RWSDK_WRANGLER_CONFIG)
39
45
  : await findWranglerConfig(projectRootDir));
40
46
  const workerEntryPathname = await determineWorkerEntryPathname(projectRootDir, workerConfigPath, options);
41
- const clientEntryPathnames = (Array.isArray(options.entry?.client)
42
- ? options.entry.client
43
- : [options.entry?.client ?? "src/client.tsx"]).map((entry) => resolve(projectRootDir, entry));
44
- const clientFiles = new Set();
45
- const serverFiles = new Set();
46
47
  const shouldIncludeCloudflarePlugin = options.includeCloudflarePlugin ??
47
48
  !(await hasOwnCloudflareVitePlugin({ rootProjectDir: projectRootDir }));
48
49
  const shouldIncludeReactPlugin = options.includeReactPlugin ??
@@ -63,11 +64,18 @@ export const redwoodPlugin = async (options = {}) => {
63
64
  return [
64
65
  devServerTimingPlugin(),
65
66
  devServerConstantPlugin(),
67
+ directiveModulesDevPlugin({
68
+ clientFiles,
69
+ serverFiles,
70
+ projectRootDir,
71
+ }),
66
72
  configPlugin({
67
73
  silent: options.silent ?? false,
68
74
  projectRootDir,
69
- clientEntryPathnames,
70
75
  workerEntryPathname,
76
+ clientFiles,
77
+ serverFiles,
78
+ clientEntryPoints,
71
79
  }),
72
80
  ssrBridgePlugin({
73
81
  clientFiles,
@@ -96,7 +104,10 @@ export const redwoodPlugin = async (options = {}) => {
96
104
  serverFiles,
97
105
  }),
98
106
  vitePreamblePlugin(),
99
- injectVitePreamble({ clientEntryPathnames }),
107
+ injectVitePreamble({
108
+ clientEntryPoints,
109
+ projectRootDir,
110
+ }),
100
111
  useClientLookupPlugin({
101
112
  projectRootDir,
102
113
  clientFiles,
@@ -106,12 +117,19 @@ export const redwoodPlugin = async (options = {}) => {
106
117
  serverFiles,
107
118
  }),
108
119
  transformJsxScriptTagsPlugin({
109
- manifestPath: resolve(projectRootDir, "dist", "client", ".vite", "manifest.json"),
120
+ clientEntryPoints,
121
+ projectRootDir,
110
122
  }),
111
123
  manifestPlugin({
112
- manifestPath: resolve(projectRootDir, "dist", "client", ".vite", "manifest.json"),
124
+ projectRootDir,
113
125
  }),
114
126
  moveStaticAssetsPlugin({ rootDir: projectRootDir }),
115
127
  prismaPlugin({ projectRootDir }),
128
+ linkerPlugin({ projectRootDir }),
129
+ directivesFilteringPlugin({
130
+ clientFiles,
131
+ serverFiles,
132
+ projectRootDir,
133
+ }),
116
134
  ];
117
135
  };
@@ -0,0 +1,7 @@
1
+ import { Environment, ResolvedConfig } from "vite";
2
+ export declare const runDirectivesScan: ({ rootConfig, environment, clientFiles, serverFiles, }: {
3
+ rootConfig: ResolvedConfig;
4
+ environment: Environment;
5
+ clientFiles: Set<string>;
6
+ serverFiles: Set<string>;
7
+ }) => Promise<void>;
@@ -0,0 +1,156 @@
1
+ import fsp from "node:fs/promises";
2
+ import { hasDirective } from "./hasDirective.mjs";
3
+ import path from "node:path";
4
+ import debug from "debug";
5
+ import { getViteEsbuild } from "./getViteEsbuild.mjs";
6
+ import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
7
+ import { externalModules } from "./constants.mjs";
8
+ import { createViteAwareResolver } from "./createViteAwareResolver.mjs";
9
+ const log = debug("rwsdk:vite:run-directives-scan");
10
+ // Copied from Vite's source code.
11
+ // https://github.com/vitejs/vite/blob/main/packages/vite/src/shared/utils.ts
12
+ const isObject = (value) => Object.prototype.toString.call(value) === "[object Object]";
13
+ // Copied from Vite's source code.
14
+ // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/utils.ts
15
+ const externalRE = /^(https?:)?\/\//;
16
+ const isExternalUrl = (url) => externalRE.test(url);
17
+ export const runDirectivesScan = async ({ rootConfig, environment, clientFiles, serverFiles, }) => {
18
+ console.log("\n🔍 Scanning for 'use client' and 'use server' directives...");
19
+ // Set environment variable to indicate scanning is in progress
20
+ process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE = "true";
21
+ try {
22
+ const esbuild = await getViteEsbuild(rootConfig.root);
23
+ const input = environment.config.build.rollupOptions?.input;
24
+ let entries;
25
+ if (Array.isArray(input)) {
26
+ entries = input;
27
+ }
28
+ else if (typeof input === "string") {
29
+ entries = [input];
30
+ }
31
+ else if (isObject(input)) {
32
+ entries = Object.values(input);
33
+ }
34
+ else {
35
+ entries = [];
36
+ }
37
+ if (entries.length === 0) {
38
+ log("No entries found for directives scan in environment '%s', skipping.", environment.name);
39
+ return;
40
+ }
41
+ const absoluteEntries = entries.map((entry) => path.resolve(rootConfig.root, entry));
42
+ log("Starting directives scan for environment '%s' with entries:", environment.name, absoluteEntries);
43
+ // Use enhanced-resolve with Vite plugin integration for full compatibility
44
+ const resolver = createViteAwareResolver(rootConfig, environment.name, environment);
45
+ const resolveId = async (id, importer) => {
46
+ return new Promise((resolve) => {
47
+ resolver({}, importer || rootConfig.root, id, {}, (err, result) => {
48
+ if (!err && result) {
49
+ resolve({ id: result });
50
+ }
51
+ else {
52
+ if (err) {
53
+ // Handle specific enhanced-resolve errors gracefully
54
+ const errorMessage = err.message || String(err);
55
+ if (errorMessage.includes("Package path . is not exported")) {
56
+ log("Package exports error for %s, marking as external", id);
57
+ }
58
+ else {
59
+ log("Resolution failed for %s: %s", id, errorMessage);
60
+ }
61
+ }
62
+ resolve(null);
63
+ }
64
+ });
65
+ });
66
+ };
67
+ const esbuildScanPlugin = {
68
+ name: "rwsdk:esbuild-scan-plugin",
69
+ setup(build) {
70
+ // Match Vite's behavior by externalizing assets and special queries.
71
+ // This prevents esbuild from trying to bundle them, which would fail.
72
+ const scriptFilter = /\.(c|m)?[jt]sx?$/;
73
+ const specialQueryFilter = /[?&](?:url|raw|worker|sharedworker|inline)\b/;
74
+ // This regex is used to identify if a path has any file extension.
75
+ const hasExtensionRegex = /\.[^/]+$/;
76
+ build.onResolve({ filter: specialQueryFilter }, (args) => {
77
+ log("Externalizing special query:", args.path);
78
+ return { external: true };
79
+ });
80
+ build.onResolve({ filter: /.*/, namespace: "file" }, (args) => {
81
+ // Externalize if the path has an extension AND that extension is not a
82
+ // script extension. Extensionless paths are assumed to be scripts and
83
+ // are allowed to pass through for resolution.
84
+ if (hasExtensionRegex.test(args.path) &&
85
+ !scriptFilter.test(args.path)) {
86
+ log("Externalizing non-script import:", args.path);
87
+ return { external: true };
88
+ }
89
+ });
90
+ build.onResolve({ filter: /.*/ }, async (args) => {
91
+ if (externalModules.includes(args.path)) {
92
+ return { external: true };
93
+ }
94
+ log("onResolve called for:", args.path, "from:", args.importer);
95
+ const resolved = await resolveId(args.path, args.importer);
96
+ log("Resolution result:", resolved);
97
+ const resolvedPath = resolved?.id;
98
+ if (resolvedPath && path.isAbsolute(resolvedPath)) {
99
+ // Normalize the path for esbuild compatibility
100
+ const normalizedPath = normalizeModulePath(resolvedPath, rootConfig.root, { absolute: true });
101
+ log("Normalized path:", normalizedPath);
102
+ return { path: normalizedPath };
103
+ }
104
+ log("Marking as external:", args.path, "resolved to:", resolvedPath);
105
+ return { external: true };
106
+ });
107
+ build.onLoad({ filter: /\.(m|c)?[jt]sx?$/ }, async (args) => {
108
+ log("onLoad called for:", args.path);
109
+ if (!args.path.startsWith("/") ||
110
+ args.path.includes("virtual:") ||
111
+ isExternalUrl(args.path)) {
112
+ log("Skipping file due to filter:", args.path, {
113
+ startsWithSlash: args.path.startsWith("/"),
114
+ hasVirtual: args.path.includes("virtual:"),
115
+ isExternal: isExternalUrl(args.path),
116
+ });
117
+ return null;
118
+ }
119
+ try {
120
+ const contents = await fsp.readFile(args.path, "utf-8");
121
+ if (hasDirective(contents, "use client")) {
122
+ log("Discovered 'use client' in:", args.path);
123
+ clientFiles.add(normalizeModulePath(args.path, rootConfig.root));
124
+ }
125
+ if (hasDirective(contents, "use server")) {
126
+ log("Discovered 'use server' in:", args.path);
127
+ serverFiles.add(normalizeModulePath(args.path, rootConfig.root));
128
+ }
129
+ return { contents, loader: "default" };
130
+ }
131
+ catch (e) {
132
+ log("Could not read file during scan, skipping:", args.path, e);
133
+ return null;
134
+ }
135
+ });
136
+ },
137
+ };
138
+ await esbuild.build({
139
+ entryPoints: absoluteEntries,
140
+ bundle: true,
141
+ write: false,
142
+ platform: "node",
143
+ format: "esm",
144
+ logLevel: "silent",
145
+ plugins: [esbuildScanPlugin],
146
+ });
147
+ }
148
+ catch (e) {
149
+ throw new Error(`RWSDK directive scan failed:\n${e.stack}`);
150
+ }
151
+ finally {
152
+ // Always clear the scanning flag when done
153
+ delete process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE;
154
+ console.log("✅ Scan complete.");
155
+ }
156
+ };
@@ -1,11 +1,10 @@
1
1
  import debug from "debug";
2
- import { SSR_BRIDGE_PATH } from "../lib/constants.mjs";
3
2
  import { findSsrImportCallSites } from "./findSsrSpecifiers.mjs";
3
+ import { INTERMEDIATE_SSR_BRIDGE_PATH } from "../lib/constants.mjs";
4
4
  import MagicString from "magic-string";
5
5
  const log = debug("rwsdk:vite:ssr-bridge-plugin");
6
6
  export const VIRTUAL_SSR_PREFIX = "virtual:rwsdk:ssr:";
7
7
  export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
8
- log("Initializing SSR bridge plugin with SSR_BRIDGE_PATH=%s", SSR_BRIDGE_PATH);
9
8
  let devServer;
10
9
  let isDev = false;
11
10
  const ssrBridgePlugin = {
@@ -49,8 +48,10 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
49
48
  }
50
49
  },
51
50
  async resolveId(id) {
52
- process.env.VERBOSE &&
53
- log("Resolving id=%s, environment=%s, isDev=%s", id, this.environment?.name, isDev);
51
+ // Skip during directive scanning to avoid performance issues
52
+ if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
53
+ return;
54
+ }
54
55
  if (isDev) {
55
56
  // context(justinvdm, 27 May 2025): In dev, we need to dynamically load
56
57
  // SSR modules, so we return the virtual id so that the dynamic loading
@@ -75,20 +76,27 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
75
76
  }
76
77
  }
77
78
  else {
78
- // context(justinvdm, 27 May 2025): In builds, since all SSR import chains
79
- // originate at SSR bridge module, we return the path to the already built
80
- // SSR bridge bundle - SSR env builds it, worker build tries to resolve it
81
- // here and uses it
79
+ // In build mode, the behavior depends on the build pass
82
80
  if (id === "rwsdk/__ssr_bridge" && this.environment.name === "worker") {
83
- log("Bridge module case (build): id=%s matches rwsdk/__ssr_bridge in worker environment, returning SSR_BRIDGE_PATH=%s", id, SSR_BRIDGE_PATH);
84
- return SSR_BRIDGE_PATH;
81
+ if (process.env.RWSDK_BUILD_PASS === "worker") {
82
+ // First pass: resolve to a temporary, external path
83
+ log("Bridge module case (build-worker pass): resolving to external path");
84
+ return { id: INTERMEDIATE_SSR_BRIDGE_PATH, external: true };
85
+ }
86
+ else if (process.env.RWSDK_BUILD_PASS === "linker") {
87
+ // Second pass (linker): resolve to the real intermediate build
88
+ // artifact so it can be bundled in.
89
+ log("Bridge module case (build-linker pass): resolving to bundleable path");
90
+ return { id: INTERMEDIATE_SSR_BRIDGE_PATH, external: false };
91
+ }
85
92
  }
86
93
  }
87
- process.env.VERBOSE && log("No resolution for id=%s", id);
88
94
  },
89
95
  async load(id) {
90
- process.env.VERBOSE &&
91
- log("Loading id=%s, isDev=%s, environment=%s", id, isDev, this.environment.name);
96
+ // Skip during directive scanning to avoid performance issues
97
+ if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
98
+ return;
99
+ }
92
100
  if (id.startsWith(VIRTUAL_SSR_PREFIX) &&
93
101
  this.environment.name === "worker") {
94
102
  const realId = id.slice(VIRTUAL_SSR_PREFIX.length);
@@ -132,7 +140,6 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
132
140
  return out;
133
141
  }
134
142
  }
135
- process.env.VERBOSE && log("No load handling for id=%s", id);
136
143
  },
137
144
  };
138
145
  return ssrBridgePlugin;
@@ -2,7 +2,6 @@ interface TransformContext {
2
2
  environmentName: string;
3
3
  clientFiles?: Set<string>;
4
4
  isEsbuild?: boolean;
5
- addClientModule?: (environment: string, id: string) => void;
6
5
  }
7
6
  interface TransformResult {
8
7
  code: string;
@@ -5,16 +5,10 @@ import { findExports } from "./findSpecifiers.mjs";
5
5
  const logVite = debug("rwsdk:vite:transform-client-components:vite");
6
6
  const logEsbuild = debug("rwsdk:vite:transform-client-components:esbuild");
7
7
  export async function transformClientComponents(code, normalizedId, ctx) {
8
- const log = ctx.isEsbuild ? logEsbuild : logVite;
9
- log("Called transformClientComponents for id: id=%s", normalizedId);
10
8
  if (!hasDirective(code, "use client")) {
11
- log("Skipping: no 'use client' directive in id=%s", normalizedId);
12
- process.env.VERBOSE &&
13
- log(":VERBOSE: Returning code unchanged for id=%s:\n%s", normalizedId, code);
14
9
  return;
15
10
  }
16
- log("Processing 'use client' module: id=%s", normalizedId);
17
- ctx.addClientModule?.(ctx.environmentName, normalizedId);
11
+ const log = ctx.isEsbuild ? logEsbuild : logVite;
18
12
  // Parse exports using the findExports helper
19
13
  const exportInfos = findExports(normalizedId, code, log);
20
14
  const processedExports = [];
@@ -85,7 +79,6 @@ export async function transformClientComponents(code, normalizedId, ctx) {
85
79
  const computedLocalNames = new Map(processedExports.map((info) => [getComputedLocalName(info), info]));
86
80
  // Add registerClientReference assignments for unique names
87
81
  for (const [computedLocalName, correspondingInfo] of computedLocalNames) {
88
- log(":isEsbuild=%s: Registering client reference for named export: %s as %s", !!ctx.isEsbuild, correspondingInfo.local, correspondingInfo.exported);
89
82
  s.append(`const ${computedLocalName} = registerClientReference("${normalizedId}", "${correspondingInfo.exported}");\n`);
90
83
  }
91
84
  // Add grouped export statement for named exports (preserving order and alias)
@@ -93,7 +86,6 @@ export async function transformClientComponents(code, normalizedId, ctx) {
93
86
  const exportNames = Array.from(computedLocalNames.entries()).map(([computedLocalName, correspondingInfo]) => correspondingInfo.local === correspondingInfo.exported
94
87
  ? computedLocalName
95
88
  : `${computedLocalName} as ${correspondingInfo.exported}`);
96
- log(":isEsbuild=%s: Exporting named exports: %O", !!ctx.isEsbuild, exportNames);
97
89
  s.append(`export { ${exportNames.join(", ")} };\n`);
98
90
  }
99
91
  // Add default export if present
@@ -1,8 +1,9 @@
1
1
  import { type Plugin } from "vite";
2
- export declare function transformJsxScriptTagsCode(code: string, manifest?: Record<string, any>): Promise<{
2
+ export declare function transformJsxScriptTagsCode(code: string, clientEntryPoints: Set<string>, manifest: Record<string, any> | undefined, projectRootDir: string): Promise<{
3
3
  code: string;
4
4
  map: null;
5
5
  } | undefined>;
6
- export declare const transformJsxScriptTagsPlugin: ({ manifestPath, }: {
7
- manifestPath: string;
6
+ export declare const transformJsxScriptTagsPlugin: ({ clientEntryPoints, projectRootDir, }: {
7
+ clientEntryPoints: Set<string>;
8
+ projectRootDir: string;
8
9
  }) => Plugin;