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,24 @@
|
|
|
1
|
+
import debug from "debug";
|
|
2
|
+
const log = debug("rwsdk:vite:dev-server-timing-plugin");
|
|
3
|
+
export const devServerTimingPlugin = () => {
|
|
4
|
+
const startTime = Date.now();
|
|
5
|
+
let hasLoggedFirstResponse = false;
|
|
6
|
+
return {
|
|
7
|
+
name: "rwsdk:dev-server-timing",
|
|
8
|
+
configureServer(server) {
|
|
9
|
+
server.middlewares.use((_req, res, next) => {
|
|
10
|
+
if (!hasLoggedFirstResponse) {
|
|
11
|
+
res.on("finish", () => {
|
|
12
|
+
if (!hasLoggedFirstResponse) {
|
|
13
|
+
hasLoggedFirstResponse = true;
|
|
14
|
+
const endTime = Date.now();
|
|
15
|
+
const duration = endTime - startTime;
|
|
16
|
+
log(`🚀 Dev server first response completed in ${duration}ms`);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
next();
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -4,6 +4,7 @@ import debug from "debug";
|
|
|
4
4
|
import { transformClientComponents } from "./transformClientComponents.mjs";
|
|
5
5
|
import { transformServerFunctions } from "./transformServerFunctions.mjs";
|
|
6
6
|
import { normalizeModulePath } from "./normalizeModulePath.mjs";
|
|
7
|
+
import { invalidateModule } from "./invalidateModule.mjs";
|
|
7
8
|
const log = debug("rwsdk:vite:rsc-directives-plugin");
|
|
8
9
|
const verboseLog = debug("verbose:rwsdk:vite:rsc-directives-plugin");
|
|
9
10
|
const getLoader = (filePath) => {
|
|
@@ -26,75 +27,172 @@ const getLoader = (filePath) => {
|
|
|
26
27
|
return "js";
|
|
27
28
|
}
|
|
28
29
|
};
|
|
29
|
-
export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, }) =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, }) => {
|
|
31
|
+
let devServer;
|
|
32
|
+
let isAfterFirstResponse = false;
|
|
33
|
+
const addModule = (kind, environment, id) => {
|
|
34
|
+
const files = kind === "client" ? clientFiles : serverFiles;
|
|
35
|
+
const rawId = id.split("?")[0];
|
|
36
|
+
const resolvedId = rawId;
|
|
37
|
+
const relativePath = rawId.slice("/".length);
|
|
38
|
+
const fullPath = path.resolve(projectRootDir, relativePath);
|
|
39
|
+
const isNodeModule = id.includes("node_modules");
|
|
40
|
+
const hadFile = files.has(id);
|
|
41
|
+
log("Adding %s module to %s and invalidating cache: id=%s", kind, files, resolvedId);
|
|
42
|
+
files.add(resolvedId);
|
|
43
|
+
if (devServer && isNodeModule) {
|
|
44
|
+
const lookupModule = kind === "client"
|
|
45
|
+
? "virtual:use-client-lookup"
|
|
46
|
+
: "virtual:use-server-lookup";
|
|
47
|
+
log("Registering missing import for %s module resolvedId=%s in environment %s, fullPath=%s", kind, resolvedId, environment, fullPath);
|
|
48
|
+
devServer.environments[environment].depsOptimizer?.registerMissingImport(resolvedId, fullPath);
|
|
49
|
+
if (isAfterFirstResponse && !hadFile) {
|
|
50
|
+
log("Invalidating cache for lookup module %s after adding module id=%s", lookupModule, id);
|
|
51
|
+
invalidateModule(devServer, environment, `virtual:use-${kind}-lookup`);
|
|
52
|
+
}
|
|
44
53
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
};
|
|
55
|
+
const addClientModule = (environment, id) => {
|
|
56
|
+
addModule("client", environment, id);
|
|
57
|
+
};
|
|
58
|
+
const addServerModule = (environment, id) => {
|
|
59
|
+
addModule("server", environment, id);
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
name: "rwsdk:rsc-directives",
|
|
63
|
+
configureServer(server) {
|
|
64
|
+
devServer = server;
|
|
65
|
+
devServer.middlewares.use((_req, res, next) => {
|
|
66
|
+
// context(justinvdm, 15 Jun 2025): We want to watch for new client and server modules
|
|
67
|
+
// and invalidate their respective module lookups when this happens, but
|
|
68
|
+
// we only want to do this after the first render
|
|
69
|
+
if (!isAfterFirstResponse) {
|
|
70
|
+
res.on("finish", () => {
|
|
71
|
+
if (!isAfterFirstResponse) {
|
|
72
|
+
isAfterFirstResponse = true;
|
|
73
|
+
log("Dev server first response completed");
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
next();
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
async transform(code, id) {
|
|
81
|
+
verboseLog("Transform called for id=%s, environment=%s", id, this.environment.name);
|
|
82
|
+
const normalizedId = normalizeModulePath(projectRootDir, id);
|
|
83
|
+
const clientResult = await transformClientComponents(code, normalizedId, {
|
|
84
|
+
environmentName: this.environment.name,
|
|
85
|
+
clientFiles,
|
|
86
|
+
addClientModule,
|
|
87
|
+
});
|
|
88
|
+
if (clientResult) {
|
|
89
|
+
log("Client component transformation successful for id=%s", id);
|
|
90
|
+
return {
|
|
91
|
+
code: clientResult.code,
|
|
92
|
+
map: clientResult.map,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const serverResult = transformServerFunctions(code, normalizedId, this.environment.name, serverFiles, addServerModule);
|
|
96
|
+
if (serverResult) {
|
|
97
|
+
log("Server function transformation successful for id=%s", id);
|
|
98
|
+
return {
|
|
99
|
+
code: serverResult.code,
|
|
100
|
+
map: serverResult.map,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
verboseLog("No transformation applied for id=%s", id);
|
|
104
|
+
},
|
|
105
|
+
configEnvironment(env, config) {
|
|
106
|
+
log("Configuring environment: env=%s", env);
|
|
107
|
+
config.optimizeDeps ??= {};
|
|
108
|
+
config.optimizeDeps.esbuildOptions ??= {};
|
|
109
|
+
config.optimizeDeps.esbuildOptions.plugins ??= [];
|
|
110
|
+
config.optimizeDeps.esbuildOptions.plugins.push({
|
|
111
|
+
name: "rsc-directives-esbuild-transform",
|
|
112
|
+
setup(build) {
|
|
113
|
+
log("Setting up esbuild plugin for environment: %s", env);
|
|
114
|
+
build.onLoad({ filter: /\.(js|ts|jsx|tsx|mts|mjs|cjs)$/ }, async (args) => {
|
|
115
|
+
verboseLog("Esbuild onLoad called for environment=%s, path=%s", env, args.path);
|
|
116
|
+
const normalizedPath = normalizeModulePath(projectRootDir, args.path);
|
|
117
|
+
// context(justinvdm,2025-06-15): If we're in app code,
|
|
118
|
+
// we will be doing the transform work in the vite plugin hooks,
|
|
119
|
+
// the only reason we're in esbuild land for app code is for
|
|
120
|
+
// dependency discovery, so we can skip transform work
|
|
121
|
+
// and use heuristics instead - see below inside if block
|
|
122
|
+
if (!args.path.includes("node_modules")) {
|
|
123
|
+
log("Esbuild onLoad found app code, path=%s", args.path);
|
|
124
|
+
if (clientFiles.has(normalizedPath)) {
|
|
125
|
+
// context(justinvdm,2025-06-15): If this is a client file:
|
|
126
|
+
// * for ssr and client envs we can skip so esbuild looks at the
|
|
127
|
+
// original source code to discovery dependencies
|
|
128
|
+
// * for worker env, the transform would have just created
|
|
129
|
+
// references and dropped all imports, so we can just return empty code
|
|
130
|
+
if (env === "client" || env === "ssr") {
|
|
131
|
+
log("Esbuild onLoad skipping client module in app code for client or ssr env, path=%s", args.path);
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
log("Esbuild onLoad returning empty code for server module in app code for worker env, path=%s to bypass esbuild dependency discovery", args.path);
|
|
136
|
+
return {
|
|
137
|
+
contents: "",
|
|
138
|
+
loader: "js",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (serverFiles.has(normalizedPath)) {
|
|
143
|
+
// context(justinvdm,2025-06-15): If this is a server file:
|
|
144
|
+
// * for worker env, we can skip so esbuild looks at the
|
|
145
|
+
// original source code to discovery dependencies
|
|
146
|
+
// * for ssr and client envs, the transform would have just created
|
|
147
|
+
// references and dropped all imports, so we can just return empty code
|
|
148
|
+
if (env === "worker") {
|
|
149
|
+
log("Esbuild onLoad skipping server module in app code for worker env, path=%s", args.path);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
else if (env === "ssr" || env === "client") {
|
|
153
|
+
log("Esbuild onLoad returning empty code for server module in app code for ssr or client env, path=%s", args.path);
|
|
154
|
+
return {
|
|
155
|
+
contents: "",
|
|
156
|
+
loader: "js",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
let code;
|
|
162
|
+
try {
|
|
163
|
+
code = await fs.readFile(args.path, "utf-8");
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
verboseLog("Failed to read file: %s, environment=%s", args.path, env);
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
const clientResult = await transformClientComponents(code, normalizedPath, {
|
|
170
|
+
environmentName: env,
|
|
171
|
+
clientFiles,
|
|
172
|
+
isEsbuild: true,
|
|
173
|
+
addClientModule,
|
|
174
|
+
});
|
|
175
|
+
if (clientResult) {
|
|
176
|
+
log("Esbuild client component transformation successful for environment=%s, path=%s", env, args.path);
|
|
177
|
+
verboseLog("Esbuild client component transformation for environment=%s, path=%s, code: %j", env, args.path, clientResult.code);
|
|
178
|
+
return {
|
|
179
|
+
contents: clientResult.code,
|
|
180
|
+
loader: getLoader(args.path),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const serverResult = transformServerFunctions(code, normalizedPath, env, serverFiles, addServerModule);
|
|
184
|
+
if (serverResult) {
|
|
185
|
+
log("Esbuild server function transformation successful for environment=%s, path=%s", env, args.path);
|
|
186
|
+
return {
|
|
187
|
+
contents: serverResult.code,
|
|
188
|
+
loader: getLoader(args.path),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
verboseLog("Esbuild no transformation applied for environment=%s, path=%s", env, args.path);
|
|
78
192
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
const serverResult = transformServerFunctions(code, normalizeModulePath(projectRootDir, args.path), env, serverFiles);
|
|
87
|
-
if (serverResult) {
|
|
88
|
-
log("Esbuild server function transformation successful for path=%s", args.path);
|
|
89
|
-
return {
|
|
90
|
-
contents: serverResult.code,
|
|
91
|
-
loader: getLoader(args.path),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
verboseLog("Esbuild no transformation applied for path=%s", args.path);
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
log("Environment configuration complete for env=%s", env);
|
|
99
|
-
},
|
|
100
|
-
});
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
log("Environment configuration complete for env=%s", env);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
};
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export declare const IMPORT_PATTERNS: string[];
|
|
2
|
+
export declare const EXPORT_PATTERNS: string[];
|
|
3
|
+
export interface ExportInfo {
|
|
4
|
+
name: string;
|
|
5
|
+
isDefault: boolean;
|
|
6
|
+
alias?: string;
|
|
7
|
+
isReExport?: boolean;
|
|
8
|
+
moduleSpecifier?: string;
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* Finds import specifiers and their positions in the code using the provided patterns.
|
|
4
12
|
* @param code The code to search for import specifiers.
|
|
@@ -12,3 +20,11 @@ export declare function findImportSpecifiers(id: string, code: string, ignoredIm
|
|
|
12
20
|
e: number;
|
|
13
21
|
raw: string;
|
|
14
22
|
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Finds export information in the code using ast-grep patterns.
|
|
25
|
+
* @param id The file identifier for language detection.
|
|
26
|
+
* @param code The code to search for exports.
|
|
27
|
+
* @param log Optional logger function for debug output.
|
|
28
|
+
* @returns Array of export information objects.
|
|
29
|
+
*/
|
|
30
|
+
export declare function findExports(id: string, code: string, log?: (...args: any[]) => void): ExportInfo[];
|
|
@@ -24,6 +24,23 @@ export const IMPORT_PATTERNS = [
|
|
|
24
24
|
"require('$MODULE')",
|
|
25
25
|
"require(`$MODULE`)",
|
|
26
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
|
+
];
|
|
27
44
|
/**
|
|
28
45
|
* Finds import specifiers and their positions in the code using the provided patterns.
|
|
29
46
|
* @param code The code to search for import specifiers.
|
|
@@ -74,3 +91,138 @@ export function findImportSpecifiers(id, code, ignoredImportPatterns, log) {
|
|
|
74
91
|
}
|
|
75
92
|
return results;
|
|
76
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
|
+
isReExport: true,
|
|
161
|
+
moduleSpecifier,
|
|
162
|
+
});
|
|
163
|
+
logger("Found re-export: %s from %s", exportName, moduleSpecifier);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (matchText.startsWith("export default")) {
|
|
168
|
+
// Default export
|
|
169
|
+
const name = nameCapture?.text() || "default";
|
|
170
|
+
const key = `${name}:true:default`;
|
|
171
|
+
if (seen.has(key))
|
|
172
|
+
continue;
|
|
173
|
+
seen.add(key);
|
|
174
|
+
results.push({
|
|
175
|
+
name,
|
|
176
|
+
isDefault: true,
|
|
177
|
+
});
|
|
178
|
+
logger("Found default export: %s", name);
|
|
179
|
+
}
|
|
180
|
+
else if (matchText.includes("export {")) {
|
|
181
|
+
// Local export declaration
|
|
182
|
+
const exportListMatch = matchText.match(/export\s*\{\s*([^}]+)\s*\}/);
|
|
183
|
+
if (exportListMatch) {
|
|
184
|
+
const exportList = exportListMatch[1];
|
|
185
|
+
const exports = exportList.split(",").map((e) => e.trim());
|
|
186
|
+
for (const exp of exports) {
|
|
187
|
+
const [originalName, alias] = exp.includes(" as ")
|
|
188
|
+
? exp.split(" as ").map((s) => s.trim())
|
|
189
|
+
: [exp.trim(), undefined];
|
|
190
|
+
const exportName = alias || originalName;
|
|
191
|
+
const key = `${exportName}:${originalName === "default"}:local`;
|
|
192
|
+
if (seen.has(key))
|
|
193
|
+
continue;
|
|
194
|
+
seen.add(key);
|
|
195
|
+
results.push({
|
|
196
|
+
name: exportName,
|
|
197
|
+
isDefault: originalName === "default",
|
|
198
|
+
alias: alias !== originalName ? alias : undefined,
|
|
199
|
+
});
|
|
200
|
+
logger("Found local export: %s", exportName);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (nameCapture) {
|
|
205
|
+
// Named export (function, const, etc.)
|
|
206
|
+
const name = nameCapture.text();
|
|
207
|
+
const key = `${name}:false:named`;
|
|
208
|
+
if (seen.has(key))
|
|
209
|
+
continue;
|
|
210
|
+
seen.add(key);
|
|
211
|
+
results.push({
|
|
212
|
+
name,
|
|
213
|
+
isDefault: false,
|
|
214
|
+
});
|
|
215
|
+
logger("Found named export: %s", name);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
logger("Error processing export pattern %s: %O", pattern, err);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
logger("Error parsing code for exports: %O", err);
|
|
226
|
+
}
|
|
227
|
+
return results;
|
|
228
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { findExports } from "./findImportSpecifiers.mjs";
|
|
3
|
+
describe("findExports", () => {
|
|
4
|
+
it("finds named exports", () => {
|
|
5
|
+
const code = `
|
|
6
|
+
export const Component = () => {};
|
|
7
|
+
export function helper() {}
|
|
8
|
+
export let data = {};
|
|
9
|
+
`;
|
|
10
|
+
const exports = findExports("/test.tsx", code);
|
|
11
|
+
// Just check that we found the exports, order doesn't matter for our use case
|
|
12
|
+
const names = exports.map((e) => e.name).sort();
|
|
13
|
+
expect(names).toEqual(["Component", "data", "helper"]);
|
|
14
|
+
// Check that none are default
|
|
15
|
+
expect(exports.every((e) => !e.isDefault)).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it("finds default exports", () => {
|
|
18
|
+
const code = `
|
|
19
|
+
export default function Component() {}
|
|
20
|
+
`;
|
|
21
|
+
const exports = findExports("/test.tsx", code);
|
|
22
|
+
// Should find at least one default export
|
|
23
|
+
const defaultExports = exports.filter((e) => e.isDefault);
|
|
24
|
+
expect(defaultExports.length).toBeGreaterThan(0);
|
|
25
|
+
expect(defaultExports.some((e) => e.name === "Component")).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it("finds export declarations", () => {
|
|
28
|
+
const code = `
|
|
29
|
+
const First = () => {};
|
|
30
|
+
const Second = () => {};
|
|
31
|
+
export { First, Second };
|
|
32
|
+
`;
|
|
33
|
+
const exports = findExports("/test.tsx", code);
|
|
34
|
+
const names = exports.map((e) => e.name).sort();
|
|
35
|
+
expect(names).toEqual(["First", "Second"]);
|
|
36
|
+
});
|
|
37
|
+
it("finds export declarations with aliases", () => {
|
|
38
|
+
const code = `
|
|
39
|
+
const Component = () => {};
|
|
40
|
+
export { Component as MyComponent };
|
|
41
|
+
`;
|
|
42
|
+
const exports = findExports("/test.tsx", code);
|
|
43
|
+
expect(exports.some((e) => e.name === "MyComponent")).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it("finds re-exports", () => {
|
|
46
|
+
const code = `
|
|
47
|
+
export { sum } from './math';
|
|
48
|
+
export { default as multiply } from './multiply';
|
|
49
|
+
`;
|
|
50
|
+
const exports = findExports("/test.tsx", code);
|
|
51
|
+
// Should find re-exports
|
|
52
|
+
const reExports = exports.filter((e) => e.isReExport);
|
|
53
|
+
expect(reExports.length).toBeGreaterThan(0);
|
|
54
|
+
const names = reExports.map((e) => e.name).sort();
|
|
55
|
+
expect(names).toContain("sum");
|
|
56
|
+
expect(names).toContain("multiply");
|
|
57
|
+
});
|
|
58
|
+
it("handles mixed export styles", () => {
|
|
59
|
+
const code = `
|
|
60
|
+
export const First = () => {};
|
|
61
|
+
const Second = () => {};
|
|
62
|
+
export default function Main() {}
|
|
63
|
+
export { Second };
|
|
64
|
+
`;
|
|
65
|
+
const exports = findExports("/test.tsx", code);
|
|
66
|
+
const names = exports.map((e) => e.name);
|
|
67
|
+
expect(names).toContain("First");
|
|
68
|
+
expect(names).toContain("Second");
|
|
69
|
+
// Should have at least one default export
|
|
70
|
+
const defaultExports = exports.filter((e) => e.isDefault);
|
|
71
|
+
expect(defaultExports.length).toBeGreaterThan(0);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const IMPORT_PATTERNS: string[];
|
|
2
|
+
export declare const EXPORT_PATTERNS: string[];
|
|
3
|
+
export interface ExportInfo {
|
|
4
|
+
name: string;
|
|
5
|
+
isDefault: boolean;
|
|
6
|
+
alias?: string;
|
|
7
|
+
originalName?: string;
|
|
8
|
+
isReExport?: boolean;
|
|
9
|
+
moduleSpecifier?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Finds import specifiers and their positions in the code using the provided patterns.
|
|
13
|
+
* @param code The code to search for import specifiers.
|
|
14
|
+
* @param lang The language parser to use (TypeScript or Tsx).
|
|
15
|
+
* @param ignoredImportPatterns Array of regex patterns to ignore.
|
|
16
|
+
* @param log Optional logger function for debug output.
|
|
17
|
+
* @returns Array of objects with start, end, and raw import string.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findImportSpecifiers(id: string, code: string, ignoredImportPatterns: RegExp[], log?: (...args: any[]) => void): Array<{
|
|
20
|
+
s: number;
|
|
21
|
+
e: number;
|
|
22
|
+
raw: string;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Finds export information in the code using ast-grep patterns.
|
|
26
|
+
* @param id The file identifier for language detection.
|
|
27
|
+
* @param code The code to search for exports.
|
|
28
|
+
* @param log Optional logger function for debug output.
|
|
29
|
+
* @returns Array of export information objects.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findExports(id: string, code: string, log?: (...args: any[]) => void): ExportInfo[];
|