rwsdk 0.1.0-alpha.9 → 0.1.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.
- package/dist/runtime/client.d.ts +3 -1
- package/dist/runtime/client.js +15 -11
- package/dist/runtime/clientNavigation.d.ts +3 -0
- package/dist/runtime/clientNavigation.js +43 -0
- package/dist/runtime/entries/client.d.ts +1 -0
- package/dist/runtime/entries/client.js +1 -0
- package/dist/runtime/entries/worker.d.ts +2 -0
- package/dist/runtime/entries/worker.js +2 -0
- package/dist/runtime/imports/ClientOnly.d.ts +3 -0
- package/dist/runtime/imports/ClientOnly.js +8 -0
- package/dist/runtime/imports/NoSSRStub.d.ts +1 -0
- package/dist/runtime/imports/NoSSRStub.js +4 -0
- package/dist/runtime/imports/client.js +15 -2
- package/dist/runtime/imports/worker.d.ts +1 -1
- package/dist/runtime/imports/worker.js +7 -5
- package/dist/runtime/lib/db/DOWorkerDialect.d.ts +29 -0
- package/dist/runtime/lib/db/DOWorkerDialect.js +66 -0
- package/dist/runtime/lib/db/SqliteDurableObject.d.ts +14 -0
- package/dist/runtime/lib/db/SqliteDurableObject.js +42 -0
- package/dist/runtime/lib/db/create.d.ts +3 -0
- package/dist/runtime/lib/db/create.js +36 -0
- package/dist/runtime/lib/db/createDb.d.ts +2 -0
- package/dist/runtime/lib/db/createDb.js +33 -0
- package/dist/runtime/lib/db/index.d.ts +3 -0
- package/dist/runtime/lib/db/index.js +3 -0
- package/dist/runtime/lib/db/logger.d.ts +2 -0
- package/dist/runtime/lib/db/logger.js +41 -0
- package/dist/runtime/lib/db/migrations.d.ts +23 -0
- package/dist/runtime/lib/db/migrations.js +34 -0
- package/dist/runtime/lib/db/types.d.ts +0 -0
- package/dist/runtime/lib/db/types.js +1 -0
- package/dist/runtime/lib/debug.d.ts +2 -0
- package/dist/runtime/lib/debug.js +36 -0
- package/dist/runtime/lib/router.d.ts +6 -1
- package/dist/runtime/lib/router.js +9 -2
- package/dist/runtime/register/ssr.d.ts +2 -0
- package/dist/runtime/register/ssr.js +14 -1
- package/dist/runtime/register/worker.d.ts +1 -1
- package/dist/runtime/register/worker.js +5 -2
- package/dist/runtime/render/renderRscThenableToHtmlStream.d.ts +2 -1
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +17 -3
- package/dist/runtime/render/renderToStream.d.ts +9 -0
- package/dist/runtime/render/renderToStream.js +26 -0
- package/dist/runtime/render/renderToString.d.ts +7 -0
- package/dist/runtime/render/renderToString.js +26 -0
- package/dist/runtime/render/transformRscToHtmlStream.js +1 -0
- package/dist/runtime/worker.js +17 -10
- package/dist/scripts/debug-sync.mjs +8 -6
- package/dist/scripts/worker-run.mjs +1 -0
- package/dist/vite/configPlugin.d.mts +2 -2
- package/dist/vite/configPlugin.mjs +10 -21
- package/dist/vite/createDirectiveLookupPlugin.d.mts +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +88 -57
- package/dist/vite/devServerTimingPlugin.d.mts +2 -0
- package/dist/vite/devServerTimingPlugin.mjs +24 -0
- package/dist/vite/directivesPlugin.mjs +168 -70
- package/dist/vite/findImportSpecifiers.d.mts +16 -0
- package/dist/vite/findImportSpecifiers.mjs +152 -0
- package/dist/vite/findImportSpecifiers.test.d.mts +1 -0
- package/dist/vite/findImportSpecifiers.test.mjs +73 -0
- package/dist/vite/findSpecifiers.d.mts +31 -0
- package/dist/vite/findSpecifiers.mjs +230 -0
- package/dist/vite/hasDirective.d.mts +7 -0
- package/dist/vite/hasDirective.mjs +54 -0
- package/dist/vite/hasOwnCloudflareVitePlugin.d.mts +3 -0
- package/dist/vite/hasOwnCloudflareVitePlugin.mjs +14 -0
- package/dist/vite/injectVitePreamblePlugin.d.mts +2 -2
- package/dist/vite/injectVitePreamblePlugin.mjs +5 -2
- package/dist/vite/invalidateModule.d.mts +2 -0
- package/dist/vite/invalidateModule.mjs +14 -0
- package/dist/vite/miniflareHMRPlugin.d.mts +8 -0
- package/dist/vite/miniflareHMRPlugin.mjs +133 -0
- package/dist/vite/normalizeModulePath.mjs +12 -1
- package/dist/vite/reactConditionsResolverPlugin.d.mts +1 -1
- package/dist/vite/reactConditionsResolverPlugin.mjs +64 -59
- package/dist/vite/redwoodPlugin.d.mts +2 -1
- package/dist/vite/redwoodPlugin.mjs +24 -7
- package/dist/vite/resolveModuleId.d.mts +6 -0
- package/dist/vite/resolveModuleId.mjs +14 -0
- package/dist/vite/ssrBridgePlugin.d.mts +5 -1
- package/dist/vite/ssrBridgePlugin.mjs +4 -43
- package/dist/vite/transformClientComponents.d.mts +1 -0
- package/dist/vite/transformClientComponents.mjs +61 -125
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +14 -3
- package/dist/vite/transformServerFunctions.d.mts +11 -3
- package/dist/vite/transformServerFunctions.mjs +256 -171
- package/dist/vite/transformServerFunctions.test.mjs +22 -3
- package/dist/vite/useClientLookupPlugin.mjs +1 -0
- package/dist/vite/useServerLookupPlugin.mjs +1 -0
- package/dist/vite/useServerPlugin.d.mts +1 -1
- package/dist/vite/useServerPlugin.mjs +1 -1
- package/package.json +14 -3
|
@@ -0,0 +1,230 @@
|
|
|
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
|
+
// These patterns are used to match export statements for client/server component transformations
|
|
28
|
+
export const EXPORT_PATTERNS = [
|
|
29
|
+
// Named exports
|
|
30
|
+
"export const $NAME = $$$",
|
|
31
|
+
"export let $NAME = $$$",
|
|
32
|
+
"export var $NAME = $$$",
|
|
33
|
+
"export function $NAME($$$) { $$$ }",
|
|
34
|
+
"export async function $NAME($$$) { $$$ }",
|
|
35
|
+
// Default exports
|
|
36
|
+
"export default function $NAME($$$) { $$$ }",
|
|
37
|
+
"export default function($$$) { $$$ }",
|
|
38
|
+
"export default $$$",
|
|
39
|
+
// Export declarations
|
|
40
|
+
"export { $$$ }",
|
|
41
|
+
'export { $$$ } from "$MODULE"',
|
|
42
|
+
"export { $$$ } from '$MODULE'",
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Finds import specifiers and their positions in the code using the provided patterns.
|
|
46
|
+
* @param code The code to search for import specifiers.
|
|
47
|
+
* @param lang The language parser to use (TypeScript or Tsx).
|
|
48
|
+
* @param ignoredImportPatterns Array of regex patterns to ignore.
|
|
49
|
+
* @param log Optional logger function for debug output.
|
|
50
|
+
* @returns Array of objects with start, end, and raw import string.
|
|
51
|
+
*/
|
|
52
|
+
export function findImportSpecifiers(id, code, ignoredImportPatterns, log) {
|
|
53
|
+
const ext = path.extname(id).toLowerCase();
|
|
54
|
+
const lang = ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : SgLang.TypeScript;
|
|
55
|
+
const logger = log ?? (() => { });
|
|
56
|
+
const results = [];
|
|
57
|
+
try {
|
|
58
|
+
// sgParse and lang must be provided by the consumer
|
|
59
|
+
const root = sgParse(lang, code);
|
|
60
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
61
|
+
try {
|
|
62
|
+
const matches = root.root().findAll(pattern);
|
|
63
|
+
for (const match of matches) {
|
|
64
|
+
const moduleCapture = match.getMatch("MODULE");
|
|
65
|
+
if (moduleCapture) {
|
|
66
|
+
const importPath = moduleCapture.text();
|
|
67
|
+
if (importPath.startsWith("virtual:")) {
|
|
68
|
+
logger(":findImportSpecifiersWithPositions: Ignoring import because it starts with 'virtual:': importPath=%s", importPath);
|
|
69
|
+
}
|
|
70
|
+
else if (importPath.includes("__rwsdknossr")) {
|
|
71
|
+
logger(":findImportSpecifiersWithPositions: Ignoring import because it includes '__rwsdknossr': importPath=%s", importPath);
|
|
72
|
+
}
|
|
73
|
+
else if (ignoredImportPatterns.some((pattern) => pattern.test(importPath))) {
|
|
74
|
+
logger(":findImportSpecifiersWithPositions: Ignoring import because it matches IGNORED_IMPORT_PATTERNS: importPath=%s", importPath);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const { start, end } = moduleCapture.range();
|
|
78
|
+
results.push({ s: start.index, e: end.index, raw: importPath });
|
|
79
|
+
logger(":findImportSpecifiersWithPositions: Including import specifier: importPath=%s, range=[%d, %d]", importPath, start.index, end.index);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
logger(":findImportSpecifiersWithPositions: Error processing pattern: %O", err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
logger(":findImportSpecifiersWithPositions: Error parsing content: %O", err);
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Finds export information in the code using ast-grep patterns.
|
|
96
|
+
* @param id The file identifier for language detection.
|
|
97
|
+
* @param code The code to search for exports.
|
|
98
|
+
* @param log Optional logger function for debug output.
|
|
99
|
+
* @returns Array of export information objects.
|
|
100
|
+
*/
|
|
101
|
+
export function findExports(id, code, log) {
|
|
102
|
+
const ext = path.extname(id).toLowerCase();
|
|
103
|
+
const lang = ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : SgLang.TypeScript;
|
|
104
|
+
const logger = log ?? (() => { });
|
|
105
|
+
const results = [];
|
|
106
|
+
const seen = new Set(); // Track seen exports to avoid duplicates
|
|
107
|
+
try {
|
|
108
|
+
const root = sgParse(lang, code);
|
|
109
|
+
// Use the existing EXPORT_PATTERNS in a specific order to avoid duplicates
|
|
110
|
+
const orderedPatterns = [
|
|
111
|
+
// Handle re-exports first (most specific)
|
|
112
|
+
...EXPORT_PATTERNS.filter((p) => p.includes('from "$MODULE"') || p.includes("from '$MODULE'")),
|
|
113
|
+
// Then named exports
|
|
114
|
+
...EXPORT_PATTERNS.filter((p) => p.startsWith("export const") ||
|
|
115
|
+
p.startsWith("export let") ||
|
|
116
|
+
p.startsWith("export var") ||
|
|
117
|
+
p.startsWith("export function") ||
|
|
118
|
+
p.startsWith("export async function")),
|
|
119
|
+
// Then default exports
|
|
120
|
+
...EXPORT_PATTERNS.filter((p) => p.startsWith("export default")),
|
|
121
|
+
// Finally export declarations
|
|
122
|
+
...EXPORT_PATTERNS.filter((p) => p === "export { $$$ }"),
|
|
123
|
+
];
|
|
124
|
+
for (const pattern of orderedPatterns) {
|
|
125
|
+
try {
|
|
126
|
+
const matches = root.root().findAll(pattern);
|
|
127
|
+
for (const match of matches) {
|
|
128
|
+
const nameCapture = match.getMatch("NAME");
|
|
129
|
+
const moduleCapture = match.getMatch("MODULE");
|
|
130
|
+
const matchText = match.text();
|
|
131
|
+
if (pattern.includes('from "$MODULE"') ||
|
|
132
|
+
pattern.includes("from '$MODULE'")) {
|
|
133
|
+
// Re-export from module
|
|
134
|
+
const moduleSpecifier = moduleCapture?.text();
|
|
135
|
+
if (!moduleSpecifier)
|
|
136
|
+
continue;
|
|
137
|
+
if (pattern.includes("export *")) {
|
|
138
|
+
// Skip export * for now - too complex
|
|
139
|
+
logger("Skipping export * from %s", moduleSpecifier);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Parse the export list
|
|
143
|
+
const exportListMatch = matchText.match(/export\s*\{\s*([^}]+)\s*\}/);
|
|
144
|
+
if (exportListMatch) {
|
|
145
|
+
const exportList = exportListMatch[1];
|
|
146
|
+
const exports = exportList.split(",").map((e) => e.trim());
|
|
147
|
+
for (const exp of exports) {
|
|
148
|
+
const [originalName, alias] = exp.includes(" as ")
|
|
149
|
+
? exp.split(" as ").map((s) => s.trim())
|
|
150
|
+
: [exp.trim(), undefined];
|
|
151
|
+
const exportName = alias || originalName;
|
|
152
|
+
const key = `${exportName}:${originalName === "default"}:reexport:${moduleSpecifier}`;
|
|
153
|
+
if (seen.has(key))
|
|
154
|
+
continue;
|
|
155
|
+
seen.add(key);
|
|
156
|
+
results.push({
|
|
157
|
+
name: exportName,
|
|
158
|
+
isDefault: originalName === "default",
|
|
159
|
+
alias: alias !== originalName ? alias : undefined,
|
|
160
|
+
originalName: originalName,
|
|
161
|
+
isReExport: true,
|
|
162
|
+
moduleSpecifier,
|
|
163
|
+
});
|
|
164
|
+
logger("Found re-export: %s from %s", exportName, moduleSpecifier);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (matchText.startsWith("export default")) {
|
|
169
|
+
// Default export
|
|
170
|
+
const name = nameCapture?.text() || "default";
|
|
171
|
+
const key = `${name}:true:default`;
|
|
172
|
+
if (seen.has(key))
|
|
173
|
+
continue;
|
|
174
|
+
seen.add(key);
|
|
175
|
+
results.push({
|
|
176
|
+
name,
|
|
177
|
+
isDefault: true,
|
|
178
|
+
});
|
|
179
|
+
logger("Found default export: %s", name);
|
|
180
|
+
}
|
|
181
|
+
else if (matchText.includes("export {")) {
|
|
182
|
+
// Local export declaration
|
|
183
|
+
const exportListMatch = matchText.match(/export\s*\{\s*([^}]+)\s*\}/);
|
|
184
|
+
if (exportListMatch) {
|
|
185
|
+
const exportList = exportListMatch[1];
|
|
186
|
+
const exports = exportList.split(",").map((e) => e.trim());
|
|
187
|
+
for (const exp of exports) {
|
|
188
|
+
const [originalName, alias] = exp.includes(" as ")
|
|
189
|
+
? exp.split(" as ").map((s) => s.trim())
|
|
190
|
+
: [exp.trim(), undefined];
|
|
191
|
+
const exportName = alias || originalName;
|
|
192
|
+
const key = `${exportName}:${originalName === "default"}:local`;
|
|
193
|
+
if (seen.has(key))
|
|
194
|
+
continue;
|
|
195
|
+
seen.add(key);
|
|
196
|
+
results.push({
|
|
197
|
+
name: exportName,
|
|
198
|
+
isDefault: originalName === "default",
|
|
199
|
+
alias: alias !== originalName ? alias : undefined,
|
|
200
|
+
originalName: originalName,
|
|
201
|
+
});
|
|
202
|
+
logger("Found local export: %s", exportName);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (nameCapture) {
|
|
207
|
+
// Named export (function, const, etc.)
|
|
208
|
+
const name = nameCapture.text();
|
|
209
|
+
const key = `${name}:false:named`;
|
|
210
|
+
if (seen.has(key))
|
|
211
|
+
continue;
|
|
212
|
+
seen.add(key);
|
|
213
|
+
results.push({
|
|
214
|
+
name,
|
|
215
|
+
isDefault: false,
|
|
216
|
+
});
|
|
217
|
+
logger("Found named export: %s", name);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
logger("Error processing export pattern %s: %O", pattern, err);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
logger("Error parsing code for exports: %O", err);
|
|
228
|
+
}
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Efficiently checks if a React directive (e.g., "use server", "use client")
|
|
3
|
+
* is present in the code. Optimized for performance with a two-step approach:
|
|
4
|
+
* 1. Quick string search to see if directive exists anywhere
|
|
5
|
+
* 2. Line-by-line check only if the directive might be present
|
|
6
|
+
*/
|
|
7
|
+
export declare function hasDirective(code: string, directive: string): boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Efficiently checks if a React directive (e.g., "use server", "use client")
|
|
3
|
+
* is present in the code. Optimized for performance with a two-step approach:
|
|
4
|
+
* 1. Quick string search to see if directive exists anywhere
|
|
5
|
+
* 2. Line-by-line check only if the directive might be present
|
|
6
|
+
*/
|
|
7
|
+
export function hasDirective(code, directive) {
|
|
8
|
+
// Quick performance check: if directive doesn't exist anywhere, skip line checking
|
|
9
|
+
const singleQuoteDirective = `'${directive}'`;
|
|
10
|
+
const doubleQuoteDirective = `"${directive}"`;
|
|
11
|
+
if (!code.includes(singleQuoteDirective) &&
|
|
12
|
+
!code.includes(doubleQuoteDirective)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
// Split into lines and check each one
|
|
16
|
+
const lines = code.split("\n");
|
|
17
|
+
let inMultiLineComment = false;
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
const trimmedLine = line.trim();
|
|
20
|
+
// Skip empty lines
|
|
21
|
+
if (trimmedLine.length === 0) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Handle multi-line comments
|
|
25
|
+
if (trimmedLine.startsWith("/*")) {
|
|
26
|
+
inMultiLineComment = true;
|
|
27
|
+
// Check if the comment ends on the same line
|
|
28
|
+
if (trimmedLine.includes("*/")) {
|
|
29
|
+
inMultiLineComment = false;
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (inMultiLineComment) {
|
|
34
|
+
// Check if this line ends the multi-line comment
|
|
35
|
+
if (trimmedLine.includes("*/")) {
|
|
36
|
+
inMultiLineComment = false;
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Skip single-line comments
|
|
41
|
+
if (trimmedLine.startsWith("//")) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Check if this line starts with the directive
|
|
45
|
+
if (trimmedLine.startsWith(doubleQuoteDirective) ||
|
|
46
|
+
trimmedLine.startsWith(singleQuoteDirective)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// If we hit a non-empty, non-comment line that's not a directive, we can stop
|
|
50
|
+
// (directives must be at the top of the file/scope, after comments)
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export async function hasOwnCloudflareVitePlugin({ rootProjectDir, }) {
|
|
4
|
+
const packageJsonPath = path.join(rootProjectDir, "package.json");
|
|
5
|
+
try {
|
|
6
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
|
|
7
|
+
return !!(packageJson.dependencies?.["@cloudflare/vite-plugin"] ||
|
|
8
|
+
packageJson.devDependencies?.["@cloudflare/vite-plugin"]);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
console.error("Error reading package.json:", error);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Plugin } from "vite";
|
|
2
|
-
export declare const injectVitePreamble: ({
|
|
3
|
-
|
|
2
|
+
export declare const injectVitePreamble: ({ clientEntryPathnames, mode, }: {
|
|
3
|
+
clientEntryPathnames: string[];
|
|
4
4
|
mode: "development" | "production";
|
|
5
5
|
}) => Plugin;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import MagicString from "magic-string";
|
|
2
|
-
export const injectVitePreamble = ({
|
|
2
|
+
export const injectVitePreamble = ({ clientEntryPathnames, mode, }) => ({
|
|
3
3
|
name: "rwsdk:inject-vite-preamble",
|
|
4
4
|
apply: "serve",
|
|
5
5
|
transform(code, id) {
|
|
6
|
-
if (
|
|
6
|
+
if (this.environment.name !== "client") {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!clientEntryPathnames.includes(id)) {
|
|
7
10
|
return;
|
|
8
11
|
}
|
|
9
12
|
// Only inject preamble in development mode
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import debug from "debug";
|
|
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);
|
|
8
|
+
if (moduleNode) {
|
|
9
|
+
devServer?.environments[environment]?.moduleGraph.invalidateModule(moduleNode);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
verboseLog("Module not found: id=%s, environment=%s", id, environment);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import colors from "picocolors";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { getShortName } from "../lib/getShortName.mjs";
|
|
5
|
+
import { pathExists } from "fs-extra";
|
|
6
|
+
const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => {
|
|
7
|
+
// Prevent infinite recursion
|
|
8
|
+
if (seen.has(module))
|
|
9
|
+
return false;
|
|
10
|
+
seen.add(module);
|
|
11
|
+
// Check direct importers
|
|
12
|
+
for (const importer of module.importers) {
|
|
13
|
+
if (importer.file === entryFile)
|
|
14
|
+
return true;
|
|
15
|
+
// Recursively check importers
|
|
16
|
+
if (hasEntryAsAncestor(importer, entryFile, seen))
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
// Cache for "use client" status results
|
|
22
|
+
const useClientCache = new Map();
|
|
23
|
+
// Function to invalidate cache for a file
|
|
24
|
+
const invalidateUseClientCache = (file) => {
|
|
25
|
+
useClientCache.delete(file);
|
|
26
|
+
};
|
|
27
|
+
const isUseClientModule = async (ctx, file, seen = new Set()) => {
|
|
28
|
+
// Prevent infinite recursion
|
|
29
|
+
if (seen.has(file))
|
|
30
|
+
return false;
|
|
31
|
+
seen.add(file);
|
|
32
|
+
try {
|
|
33
|
+
// Check cache first
|
|
34
|
+
if (useClientCache.has(file)) {
|
|
35
|
+
return useClientCache.get(file);
|
|
36
|
+
}
|
|
37
|
+
// Read and check the file
|
|
38
|
+
const content = (await pathExists(file))
|
|
39
|
+
? await readFile(file, "utf-8")
|
|
40
|
+
: "";
|
|
41
|
+
const hasUseClient = content.includes("'use client'") || content.includes('"use client"');
|
|
42
|
+
if (hasUseClient) {
|
|
43
|
+
useClientCache.set(file, true);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// Get the module from the module graph to find importers
|
|
47
|
+
const module = ctx.server.moduleGraph.getModuleById(file);
|
|
48
|
+
if (!module) {
|
|
49
|
+
useClientCache.set(file, false);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Check all importers recursively
|
|
53
|
+
for (const importer of module.importers) {
|
|
54
|
+
if (await isUseClientModule(ctx, importer.url, seen)) {
|
|
55
|
+
useClientCache.set(file, true);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
useClientCache.set(file, false);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
useClientCache.set(file, false);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
export const miniflareHMRPlugin = (givenOptions) => [
|
|
68
|
+
{
|
|
69
|
+
name: "rwsdk:miniflare-hmr",
|
|
70
|
+
async hotUpdate(ctx) {
|
|
71
|
+
const environment = givenOptions.viteEnvironment.name;
|
|
72
|
+
const entry = givenOptions.workerEntryPathname;
|
|
73
|
+
if (!["client", environment].includes(this.environment.name)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// todo(justinvdm, 12 Dec 2024): Skip client references
|
|
77
|
+
const modules = Array.from(ctx.server.environments[environment].moduleGraph.getModulesByFile(ctx.file) ?? []);
|
|
78
|
+
const isWorkerUpdate = ctx.file === entry ||
|
|
79
|
+
modules.some((module) => hasEntryAsAncestor(module, entry));
|
|
80
|
+
// The worker doesnt need an update
|
|
81
|
+
// => Short circuit HMR
|
|
82
|
+
if (!isWorkerUpdate) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
// The worker needs an update, but this is the client environment
|
|
86
|
+
// => Notify for HMR update of any css files imported by in worker, that are also in the client module graph
|
|
87
|
+
// Why: There may have been changes to css classes referenced, which might css modules to change
|
|
88
|
+
if (this.environment.name === "client") {
|
|
89
|
+
const cssModules = [];
|
|
90
|
+
for (const [_, module] of ctx.server.environments[environment]
|
|
91
|
+
.moduleGraph.idToModuleMap) {
|
|
92
|
+
// todo(justinvdm, 13 Dec 2024): We check+update _all_ css files in worker module graph,
|
|
93
|
+
// but it could just be a subset of css files that are actually affected, depending
|
|
94
|
+
// on the importers and imports of the changed file. We should be smarter about this.
|
|
95
|
+
if (module.file && module.file.endsWith(".css")) {
|
|
96
|
+
const clientModules = ctx.server.environments.client.moduleGraph.getModulesByFile(module.file);
|
|
97
|
+
if (clientModules) {
|
|
98
|
+
cssModules.push(...clientModules.values());
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
invalidateUseClientCache(ctx.file);
|
|
103
|
+
return (await isUseClientModule(ctx, ctx.file))
|
|
104
|
+
? [...ctx.modules, ...cssModules]
|
|
105
|
+
: cssModules;
|
|
106
|
+
}
|
|
107
|
+
// The worker needs an update, and the hot check is for the worker environment
|
|
108
|
+
// => Notify for custom RSC-based HMR update, then short circuit HMR
|
|
109
|
+
if (isWorkerUpdate && this.environment.name === environment) {
|
|
110
|
+
const shortName = getShortName(ctx.file, ctx.server.config.root);
|
|
111
|
+
this.environment.logger.info(`${colors.green(`worker update`)} ${colors.dim(shortName)}`, {
|
|
112
|
+
clear: true,
|
|
113
|
+
timestamp: true,
|
|
114
|
+
});
|
|
115
|
+
const m = ctx.server.environments.client.moduleGraph
|
|
116
|
+
.getModulesByFile(resolve(givenOptions.rootDir, "src", "app", "style.css"))
|
|
117
|
+
?.values()
|
|
118
|
+
.next().value;
|
|
119
|
+
if (m) {
|
|
120
|
+
ctx.server.environments.client.moduleGraph.invalidateModule(m, new Set(), ctx.timestamp, true);
|
|
121
|
+
}
|
|
122
|
+
ctx.server.environments.client.hot.send({
|
|
123
|
+
type: "custom",
|
|
124
|
+
event: "rsc:update",
|
|
125
|
+
data: {
|
|
126
|
+
file: ctx.file,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
];
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
export const normalizeModulePath = (projectRootDir, modulePath) =>
|
|
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
|
+
};
|
|
@@ -13,4 +13,4 @@ export declare const ENV_RESOLVERS: {
|
|
|
13
13
|
export declare const ENV_IMPORT_MAPPINGS: {
|
|
14
14
|
[k: string]: Map<string, string>;
|
|
15
15
|
};
|
|
16
|
-
export declare const reactConditionsResolverPlugin: () =>
|
|
16
|
+
export declare const reactConditionsResolverPlugin: () => Plugin[];
|
|
@@ -95,71 +95,76 @@ function createEsbuildResolverPlugin(envName) {
|
|
|
95
95
|
},
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
-
export const reactConditionsResolverPlugin =
|
|
98
|
+
export const reactConditionsResolverPlugin = () => {
|
|
99
99
|
log("Initializing react conditions resolver plugin");
|
|
100
100
|
let isBuild = false;
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
name: "rwsdk:react-conditions-resolver:config",
|
|
104
|
+
enforce: "post",
|
|
105
|
+
config(config, { command }) {
|
|
106
|
+
isBuild = command === "build";
|
|
107
|
+
log("Configuring plugin for command=%s", command);
|
|
108
|
+
},
|
|
109
|
+
configResolved(config) {
|
|
110
|
+
log("Setting up resolve aliases and optimizeDeps for each environment");
|
|
111
|
+
// Set up aliases and optimizeDeps for each environment
|
|
112
|
+
for (const [envName, mappings] of Object.entries(ENV_IMPORT_MAPPINGS)) {
|
|
113
|
+
const reactImports = ENV_REACT_IMPORTS[envName];
|
|
114
|
+
// Ensure environment config exists
|
|
115
|
+
if (!config.environments) {
|
|
116
|
+
config.environments = {};
|
|
117
|
+
}
|
|
118
|
+
if (!config.environments[envName]) {
|
|
119
|
+
config.environments[envName] = {};
|
|
120
|
+
}
|
|
121
|
+
const envConfig = config.environments[envName];
|
|
122
|
+
const esbuildPlugin = createEsbuildResolverPlugin(envName);
|
|
123
|
+
if (esbuildPlugin && mappings) {
|
|
124
|
+
envConfig.optimizeDeps ??= {};
|
|
125
|
+
envConfig.optimizeDeps.esbuildOptions ??= {};
|
|
126
|
+
envConfig.optimizeDeps.esbuildOptions.define ??= {};
|
|
127
|
+
envConfig.optimizeDeps.esbuildOptions.define["process.env.NODE_ENV"] = JSON.stringify(process.env.NODE_ENV ?? "production");
|
|
128
|
+
envConfig.optimizeDeps.esbuildOptions.plugins ??= [];
|
|
129
|
+
envConfig.optimizeDeps.esbuildOptions.plugins.push(esbuildPlugin);
|
|
130
|
+
envConfig.optimizeDeps.include ??= [];
|
|
131
|
+
envConfig.optimizeDeps.include.push(...reactImports);
|
|
132
|
+
log("Added esbuild plugin and optimizeDeps includes for environment: %s", envName);
|
|
133
|
+
}
|
|
134
|
+
const aliases = ensureAliasArray(envConfig);
|
|
135
|
+
for (const [find, replacement] of mappings) {
|
|
136
|
+
const findRegex = new RegExp(`^${find.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")}$`);
|
|
137
|
+
aliases.push({ find: findRegex, replacement });
|
|
138
|
+
log("Added alias for env=%s: %s -> %s", envName, find, replacement);
|
|
139
|
+
}
|
|
140
|
+
log("Environment %s configured with %d aliases and %d optimizeDeps includes", envName, mappings.size, reactImports.length);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
107
143
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (!config.environments) {
|
|
115
|
-
config.environments = {};
|
|
144
|
+
{
|
|
145
|
+
name: "rwsdk:react-conditions-resolver:resolveId",
|
|
146
|
+
enforce: "pre",
|
|
147
|
+
async resolveId(id, importer) {
|
|
148
|
+
if (!isBuild) {
|
|
149
|
+
return;
|
|
116
150
|
}
|
|
117
|
-
|
|
118
|
-
|
|
151
|
+
const envName = this.environment?.name;
|
|
152
|
+
if (!envName) {
|
|
153
|
+
return;
|
|
119
154
|
}
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
envConfig.optimizeDeps.esbuildOptions.define ??= {};
|
|
126
|
-
envConfig.optimizeDeps.esbuildOptions.define["process.env.NODE_ENV"] =
|
|
127
|
-
JSON.stringify(process.env.NODE_ENV ?? "production");
|
|
128
|
-
envConfig.optimizeDeps.esbuildOptions.plugins ??= [];
|
|
129
|
-
envConfig.optimizeDeps.esbuildOptions.plugins.push(esbuildPlugin);
|
|
130
|
-
envConfig.optimizeDeps.include ??= [];
|
|
131
|
-
envConfig.optimizeDeps.include.push(...reactImports);
|
|
132
|
-
log("Added esbuild plugin and optimizeDeps includes for environment: %s", envName);
|
|
155
|
+
verboseLog("Resolving id=%s, environment=%s, importer=%s", id, envName, importer);
|
|
156
|
+
const mappings = ENV_IMPORT_MAPPINGS[envName];
|
|
157
|
+
if (!mappings) {
|
|
158
|
+
verboseLog("No mappings found for environment: %s", envName);
|
|
159
|
+
return;
|
|
133
160
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
log("Added alias for env=%s: %s -> %s", envName, find, replacement);
|
|
161
|
+
const resolved = mappings.get(id);
|
|
162
|
+
if (resolved) {
|
|
163
|
+
log("Resolved %s -> %s for env=%s", id, resolved, envName);
|
|
164
|
+
return resolved;
|
|
139
165
|
}
|
|
140
|
-
|
|
141
|
-
}
|
|
166
|
+
verboseLog("No resolution found for id=%s in env=%s", id, envName);
|
|
167
|
+
},
|
|
142
168
|
},
|
|
143
|
-
|
|
144
|
-
if (!isBuild) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const envName = this.environment?.name;
|
|
148
|
-
if (!envName) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
verboseLog("Resolving id=%s, environment=%s, importer=%s", id, envName, importer);
|
|
152
|
-
const mappings = ENV_IMPORT_MAPPINGS[envName];
|
|
153
|
-
if (!mappings) {
|
|
154
|
-
verboseLog("No mappings found for environment: %s", envName);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const resolved = mappings.get(id);
|
|
158
|
-
if (resolved) {
|
|
159
|
-
log("Resolved %s -> %s for env=%s", id, resolved, envName);
|
|
160
|
-
return resolved;
|
|
161
|
-
}
|
|
162
|
-
verboseLog("No resolution found for id=%s in env=%s", id, envName);
|
|
163
|
-
},
|
|
164
|
-
};
|
|
169
|
+
];
|
|
165
170
|
};
|