rwsdk 0.1.13 → 0.1.14

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,2 +1,6 @@
1
- import type { ViteDevServer } from "vite";
2
- export declare const invalidateModule: (devServer: ViteDevServer, environment: string, id: string) => void;
1
+ import type { EnvironmentModuleNode, ViteDevServer } from "vite";
2
+ interface InvalidatableModuleOptions {
3
+ invalidateImportersRecursively?: boolean;
4
+ }
5
+ export declare const invalidateModule: (devServer: ViteDevServer, environment: string, target: string | EnvironmentModuleNode, options?: InvalidatableModuleOptions, seen?: Set<EnvironmentModuleNode>) => void;
6
+ export {};
@@ -1,14 +1,30 @@
1
1
  import debug from "debug";
2
2
  const log = debug("rwsdk:vite:invalidate-module");
3
- const verboseLog = debug("verbose:rwsdk:vite:invalidate-module");
4
- export const invalidateModule = (devServer, environment, id) => {
5
- const [rawId, _query] = id.split("?");
6
- log("Invalidating module: id=%s, environment=%s", id, environment);
7
- const moduleNode = devServer?.environments[environment]?.moduleGraph.idToModuleMap.get(rawId);
3
+ export const invalidateModule = (devServer, environment, target, options = {}, seen = new Set()) => {
4
+ let moduleNode;
5
+ if (typeof target === "string") {
6
+ const id = target;
7
+ const [rawId, _query] = id.split("?");
8
+ moduleNode =
9
+ devServer?.environments[environment]?.moduleGraph.idToModuleMap.get(rawId);
10
+ }
11
+ else {
12
+ moduleNode = target;
13
+ }
8
14
  if (moduleNode) {
9
- devServer?.environments[environment]?.moduleGraph.invalidateModule(moduleNode);
15
+ if (seen.has(moduleNode)) {
16
+ return;
17
+ }
18
+ seen.add(moduleNode);
19
+ devServer.environments[environment]?.moduleGraph.invalidateModule(moduleNode, seen);
20
+ log("Invalidating module: id=%s, environment=%s", moduleNode.id, environment);
21
+ if (options.invalidateImportersRecursively) {
22
+ for (const importer of moduleNode.importers) {
23
+ invalidateModule(devServer, environment, importer, options, seen);
24
+ }
25
+ }
10
26
  }
11
27
  else {
12
- verboseLog("Module not found: id=%s, environment=%s", id, environment);
28
+ log("Module not found: id=%s, environment=%s", typeof target === "string" ? target : target.id, environment);
13
29
  }
14
30
  };
@@ -0,0 +1 @@
1
+ export declare function isJsFile(filepath: string): boolean;
@@ -0,0 +1 @@
1
+ export declare function isJsFile(filepath: string): boolean;
@@ -0,0 +1,3 @@
1
+ export function isJsFile(filepath) {
2
+ return /\.(m|c)?(j|t)s(x)?$/.test(filepath);
3
+ }
@@ -0,0 +1,3 @@
1
+ export function isJsFile(filepath) {
2
+ return /\.(m|c)?(j|t)s(x)?$/.test(filepath);
3
+ }
@@ -1,5 +1,7 @@
1
1
  import { Plugin } from "vite";
2
2
  export declare const miniflareHMRPlugin: (givenOptions: {
3
+ clientFiles: Set<string>;
4
+ serverFiles: Set<string>;
3
5
  rootDir: string;
4
6
  viteEnvironment: {
5
7
  name: string;
@@ -4,13 +4,23 @@ import { readFile } from "node:fs/promises";
4
4
  import debug from "debug";
5
5
  import { VIRTUAL_SSR_PREFIX } from "./ssrBridgePlugin.mjs";
6
6
  import { normalizeModulePath } from "./normalizeModulePath.mjs";
7
+ import { hasDirective as sourceHasDirective } from "./hasDirective.mjs";
8
+ import { isJsFile } from "./isJsFile.mjs";
9
+ import { invalidateModule } from "./invalidateModule.mjs";
7
10
  import { getShortName } from "../lib/getShortName.mjs";
8
- import { pathExists } from "fs-extra";
9
11
  const verboseLog = debug("verbose:rwsdk:vite:hmr-plugin");
12
+ const hasDirective = async (filepath, directive) => {
13
+ if (!isJsFile(filepath)) {
14
+ return false;
15
+ }
16
+ const content = await readFile(filepath, "utf-8");
17
+ return sourceHasDirective(content, directive);
18
+ };
10
19
  const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => {
11
20
  // Prevent infinite recursion
12
- if (seen.has(module))
21
+ if (seen.has(module)) {
13
22
  return false;
23
+ }
14
24
  seen.add(module);
15
25
  // Check direct importers
16
26
  for (const importer of module.importers) {
@@ -22,62 +32,47 @@ const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => {
22
32
  }
23
33
  return false;
24
34
  };
25
- // Cache for "use client" status results
26
- const useClientCache = new Map();
27
- // Function to invalidate cache for a file
28
- const invalidateUseClientCache = (file) => {
29
- useClientCache.delete(file);
30
- };
31
- const isUseClientModule = async (ctx, file, seen = new Set()) => {
32
- // Prevent infinite recursion
33
- if (seen.has(file))
34
- return false;
35
- seen.add(file);
36
- try {
37
- // Check cache first
38
- if (useClientCache.has(file)) {
39
- return useClientCache.get(file);
40
- }
41
- // Read and check the file
42
- const content = (await pathExists(file))
43
- ? await readFile(file, "utf-8")
44
- : "";
45
- const hasUseClient = content.includes("'use client'") || content.includes('"use client"');
46
- if (hasUseClient) {
47
- useClientCache.set(file, true);
48
- return true;
49
- }
50
- // Get the module from the module graph to find importers
51
- const module = ctx.server.moduleGraph.getModuleById(file);
52
- if (!module) {
53
- useClientCache.set(file, false);
54
- return false;
55
- }
56
- // Check all importers recursively
57
- for (const importer of module.importers) {
58
- if (await isUseClientModule(ctx, importer.url, seen)) {
59
- useClientCache.set(file, true);
60
- return true;
61
- }
62
- }
63
- useClientCache.set(file, false);
64
- return false;
65
- }
66
- catch (error) {
67
- useClientCache.set(file, false);
68
- return false;
69
- }
70
- };
71
35
  export const miniflareHMRPlugin = (givenOptions) => [
72
36
  {
73
37
  name: "rwsdk:miniflare-hmr",
74
38
  async hotUpdate(ctx) {
39
+ const { clientFiles, serverFiles, viteEnvironment: { name: environment }, workerEntryPathname: entry, } = givenOptions;
75
40
  verboseLog("Hot update: (env=%s) %s\nModule graph:\n\n%s", this.environment.name, ctx.file, dumpFullModuleGraph(ctx.server, this.environment.name));
76
- const environment = givenOptions.viteEnvironment.name;
77
- const entry = givenOptions.workerEntryPathname;
78
41
  if (!["client", environment].includes(this.environment.name)) {
79
42
  return [];
80
43
  }
44
+ const hasClientDirective = await hasDirective(ctx.file, "use client");
45
+ const hasServerDirective = !hasClientDirective && (await hasDirective(ctx.file, "use server"));
46
+ let clientDirectiveChanged = false;
47
+ let serverDirectiveChanged = false;
48
+ if (!clientFiles.has(ctx.file) && hasClientDirective) {
49
+ clientFiles.add(ctx.file);
50
+ clientDirectiveChanged = true;
51
+ }
52
+ else if (clientFiles.has(ctx.file) && !hasClientDirective) {
53
+ clientFiles.delete(ctx.file);
54
+ clientDirectiveChanged = true;
55
+ }
56
+ if (!serverFiles.has(ctx.file) && hasServerDirective) {
57
+ serverFiles.add(ctx.file);
58
+ serverDirectiveChanged = true;
59
+ }
60
+ else if (serverFiles.has(ctx.file) && !hasServerDirective) {
61
+ serverFiles.delete(ctx.file);
62
+ serverDirectiveChanged = true;
63
+ }
64
+ if (clientDirectiveChanged) {
65
+ ["client", "ssr", environment].forEach((environment) => {
66
+ invalidateModule(ctx.server, environment, "virtual:use-client-lookup.js");
67
+ });
68
+ invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX + "/@id/virtual:use-client-lookup.js");
69
+ }
70
+ if (serverDirectiveChanged) {
71
+ ["client", "ssr", environment].forEach((environment) => {
72
+ invalidateModule(ctx.server, environment, "virtual:use-server-lookup.js");
73
+ });
74
+ invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX + "/@id/virtual:use-server-lookup.js");
75
+ }
81
76
  // todo(justinvdm, 12 Dec 2024): Skip client references
82
77
  const modules = Array.from(ctx.server.environments[environment].moduleGraph.getModulesByFile(ctx.file) ?? []);
83
78
  const isWorkerUpdate = ctx.file === entry ||
@@ -91,7 +86,6 @@ export const miniflareHMRPlugin = (givenOptions) => [
91
86
  // => Notify for HMR update of any css files imported by in worker, that are also in the client module graph
92
87
  // Why: There may have been changes to css classes referenced, which might css modules to change
93
88
  if (this.environment.name === "client") {
94
- const cssModules = [];
95
89
  for (const [_, module] of ctx.server.environments[environment]
96
90
  .moduleGraph.idToModuleMap) {
97
91
  // todo(justinvdm, 13 Dec 2024): We check+update _all_ css files in worker module graph,
@@ -99,15 +93,15 @@ export const miniflareHMRPlugin = (givenOptions) => [
99
93
  // on the importers and imports of the changed file. We should be smarter about this.
100
94
  if (module.file && module.file.endsWith(".css")) {
101
95
  const clientModules = ctx.server.environments.client.moduleGraph.getModulesByFile(module.file);
102
- if (clientModules) {
103
- cssModules.push(...clientModules.values());
96
+ for (const clientModule of clientModules ?? []) {
97
+ invalidateModule(ctx.server, "client", clientModule);
104
98
  }
105
99
  }
106
100
  }
107
- invalidateUseClientCache(ctx.file);
108
- return (await isUseClientModule(ctx, ctx.file))
109
- ? [...ctx.modules, ...cssModules]
110
- : cssModules;
101
+ // context(justinvdm, 10 Jul 2025): If this isn't a file with a client
102
+ // directive or a css file, we shouldn't invalidate anything else to
103
+ // avoid full page reload
104
+ return hasClientDirective || ctx.file.endsWith(".css") ? undefined : [];
111
105
  }
112
106
  // The worker needs an update, and the hot check is for the worker environment
113
107
  // => Notify for custom RSC-based HMR update, then short circuit HMR
@@ -122,12 +116,12 @@ export const miniflareHMRPlugin = (givenOptions) => [
122
116
  ?.values()
123
117
  .next().value;
124
118
  if (m) {
125
- ctx.server.environments.client.moduleGraph.invalidateModule(m, new Set(), ctx.timestamp, true);
119
+ invalidateModule(ctx.server, environment, m);
126
120
  }
127
121
  const virtualSSRModule = ctx.server.environments[environment].moduleGraph.idToModuleMap.get(VIRTUAL_SSR_PREFIX +
128
122
  normalizeModulePath(givenOptions.rootDir, ctx.file));
129
123
  if (virtualSSRModule) {
130
- ctx.server.environments.worker.moduleGraph.invalidateModule(virtualSSRModule, new Set(), ctx.timestamp, true);
124
+ invalidateModule(ctx.server, environment, virtualSSRModule);
131
125
  }
132
126
  ctx.server.environments.client.hot.send({
133
127
  type: "custom",
@@ -77,6 +77,8 @@ export const redwoodPlugin = async (options = {}) => {
77
77
  })
78
78
  : [],
79
79
  miniflareHMRPlugin({
80
+ clientFiles,
81
+ serverFiles,
80
82
  rootDir: projectRootDir,
81
83
  viteEnvironment: { name: "worker" },
82
84
  workerEntryPathname,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {