@jay-framework/compiler-jay-stack 0.9.0 → 0.11.0
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/index.d.ts +209 -4
- package/dist/index.js +600 -50
- package/package.json +7 -6
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,23 @@ import { Plugin } from 'vite';
|
|
|
2
2
|
import { JayRollupConfig } from '@jay-framework/vite-plugin';
|
|
3
3
|
export { JayRollupConfig } from '@jay-framework/vite-plugin';
|
|
4
4
|
|
|
5
|
+
interface ImportChainTrackerOptions {
|
|
6
|
+
/** Enable verbose logging */
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
/** Additional modules to treat as server-only */
|
|
9
|
+
additionalServerModules?: string[];
|
|
10
|
+
/** Additional package patterns to treat as server-only */
|
|
11
|
+
additionalServerPatterns?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates a Vite plugin that tracks import chains and logs when
|
|
15
|
+
* server-only modules are imported in client builds.
|
|
16
|
+
*
|
|
17
|
+
* This helps debug issues where server code is accidentally pulled
|
|
18
|
+
* into client bundles.
|
|
19
|
+
*/
|
|
20
|
+
declare function createImportChainTracker(options?: ImportChainTrackerOptions): Plugin;
|
|
21
|
+
|
|
5
22
|
type BuildEnvironment = 'client' | 'server';
|
|
6
23
|
/**
|
|
7
24
|
* Transform Jay Stack component builder chains to strip environment-specific code
|
|
@@ -16,12 +33,190 @@ declare function transformJayStackBuilder(code: string, filePath: string, enviro
|
|
|
16
33
|
map?: any;
|
|
17
34
|
};
|
|
18
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Transform action imports for client builds.
|
|
38
|
+
*
|
|
39
|
+
* On the server, action imports remain unchanged (handlers are executed directly).
|
|
40
|
+
* On the client, action imports are replaced with createActionCaller() calls.
|
|
41
|
+
*
|
|
42
|
+
* Example:
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // Source
|
|
45
|
+
* import { addToCart, searchProducts } from '../actions/cart.actions';
|
|
46
|
+
*
|
|
47
|
+
* // Client build output
|
|
48
|
+
* import { createActionCaller } from '@jay-framework/stack-client-runtime';
|
|
49
|
+
* const addToCart = createActionCaller('cart.addToCart', 'POST');
|
|
50
|
+
* const searchProducts = createActionCaller('products.search', 'GET');
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Metadata for a discovered action.
|
|
55
|
+
*/
|
|
56
|
+
interface ActionMetadata {
|
|
57
|
+
/** Unique action name (e.g., 'cart.addToCart') */
|
|
58
|
+
actionName: string;
|
|
59
|
+
/** HTTP method */
|
|
60
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
61
|
+
/** Export name in the source module */
|
|
62
|
+
exportName: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Result of extracting actions from a module.
|
|
66
|
+
*/
|
|
67
|
+
interface ExtractedActions {
|
|
68
|
+
/** Path to the action module */
|
|
69
|
+
modulePath: string;
|
|
70
|
+
/** Actions exported from this module */
|
|
71
|
+
actions: ActionMetadata[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clears the action metadata cache (useful for testing/dev reload).
|
|
75
|
+
*/
|
|
76
|
+
declare function clearActionMetadataCache(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Checks if an import source refers to an action module.
|
|
79
|
+
*/
|
|
80
|
+
declare function isActionImport(importSource: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Extracts action metadata from source code by parsing makeJayAction/makeJayQuery calls.
|
|
83
|
+
*
|
|
84
|
+
* @param sourceCode - The TypeScript source code
|
|
85
|
+
* @param filePath - Path to the file (for error messages)
|
|
86
|
+
* @returns Array of action metadata
|
|
87
|
+
*/
|
|
88
|
+
declare function extractActionsFromSource(sourceCode: string, filePath: string): ActionMetadata[];
|
|
89
|
+
/**
|
|
90
|
+
* Transform result with source map support.
|
|
91
|
+
*/
|
|
92
|
+
interface TransformResult {
|
|
93
|
+
code: string;
|
|
94
|
+
map?: any;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Transforms action imports in client builds.
|
|
98
|
+
*
|
|
99
|
+
* Replaces imports from action modules with createActionCaller() calls.
|
|
100
|
+
*
|
|
101
|
+
* @param code - Source code to transform
|
|
102
|
+
* @param id - Module ID (file path)
|
|
103
|
+
* @param resolveActionModule - Function to resolve and load action module source
|
|
104
|
+
* @returns Transformed code or null if no transform needed
|
|
105
|
+
*/
|
|
106
|
+
declare function transformActionImports(code: string, id: string, resolveActionModule: (importSource: string, importer: string) => Promise<{
|
|
107
|
+
path: string;
|
|
108
|
+
code: string;
|
|
109
|
+
} | null>): Promise<TransformResult | null>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Plugin Client Import Resolver
|
|
113
|
+
*
|
|
114
|
+
* Transforms imports from Jay plugin packages to use their /client subpath
|
|
115
|
+
* when in client build mode.
|
|
116
|
+
*
|
|
117
|
+
* This handles transitive plugin dependencies: when wix-stores imports from
|
|
118
|
+
* wix-server-client, the import should be rewritten to wix-server-client/client
|
|
119
|
+
* in client builds.
|
|
120
|
+
*
|
|
121
|
+
* Uses a `transform` hook instead of `resolveId` to ensure the rewrite happens
|
|
122
|
+
* before rollup's `external` option is evaluated.
|
|
123
|
+
*
|
|
124
|
+
* Detection:
|
|
125
|
+
* 1. Check if the imported package has a plugin.yaml (is a Jay plugin)
|
|
126
|
+
* 2. Check if the package exports a /client subpath
|
|
127
|
+
* 3. If both true, rewrite the import to use /client
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Interface for detecting if a package is a Jay plugin with /client export.
|
|
132
|
+
* Extracted to allow mocking in tests.
|
|
133
|
+
*/
|
|
134
|
+
interface PluginDetector {
|
|
135
|
+
/**
|
|
136
|
+
* Checks if a package is a Jay plugin with a /client export.
|
|
137
|
+
* @param packageName - The package name (e.g., '@jay-framework/wix-stores')
|
|
138
|
+
* @param projectRoot - The project root for resolution
|
|
139
|
+
* @returns true if the package should be rewritten to /client
|
|
140
|
+
*/
|
|
141
|
+
isJayPluginWithClientExport(packageName: string, projectRoot: string): boolean;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Default implementation using Node's require.resolve.
|
|
145
|
+
*/
|
|
146
|
+
declare function createDefaultPluginDetector(): PluginDetector;
|
|
147
|
+
/**
|
|
148
|
+
* Extracts the package name from an import source.
|
|
149
|
+
* Handles scoped packages like @jay-framework/wix-stores.
|
|
150
|
+
*/
|
|
151
|
+
declare function extractPackageName(source: string): string | null;
|
|
152
|
+
/**
|
|
153
|
+
* Checks if the import is already using a subpath (not just the main entry).
|
|
154
|
+
*/
|
|
155
|
+
declare function isSubpathImport(source: string, packageName: string): boolean;
|
|
156
|
+
interface TransformImportsOptions {
|
|
157
|
+
/** The source code to transform */
|
|
158
|
+
code: string;
|
|
159
|
+
/** Project root for plugin detection */
|
|
160
|
+
projectRoot: string;
|
|
161
|
+
/** File path for logging */
|
|
162
|
+
filePath: string;
|
|
163
|
+
/** Plugin detector (injectable for testing) */
|
|
164
|
+
pluginDetector: PluginDetector;
|
|
165
|
+
/** Enable verbose logging */
|
|
166
|
+
verbose?: boolean;
|
|
167
|
+
}
|
|
168
|
+
interface TransformImportsResult {
|
|
169
|
+
/** The transformed code */
|
|
170
|
+
code: string;
|
|
171
|
+
/** Whether any changes were made */
|
|
172
|
+
hasChanges: boolean;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Transforms import/export declarations in source code.
|
|
176
|
+
* Rewrites plugin package imports to use /client subpath.
|
|
177
|
+
*
|
|
178
|
+
* This is a pure function - all IO is handled by the pluginDetector.
|
|
179
|
+
*/
|
|
180
|
+
declare function transformImports(options: TransformImportsOptions): TransformImportsResult;
|
|
181
|
+
interface PluginClientImportResolverOptions {
|
|
182
|
+
/** Project root directory for resolution */
|
|
183
|
+
projectRoot?: string;
|
|
184
|
+
/** Enable verbose logging */
|
|
185
|
+
verbose?: boolean;
|
|
186
|
+
/** Custom plugin detector (for testing) */
|
|
187
|
+
pluginDetector?: PluginDetector;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Creates a Vite plugin that transforms plugin package imports to /client
|
|
191
|
+
* in client builds.
|
|
192
|
+
*
|
|
193
|
+
* Uses the `transform` hook to rewrite import declarations before rollup's
|
|
194
|
+
* external option is evaluated.
|
|
195
|
+
*/
|
|
196
|
+
declare function createPluginClientImportResolver(options?: PluginClientImportResolverOptions): Plugin;
|
|
197
|
+
|
|
198
|
+
interface JayStackCompilerOptions extends JayRollupConfig {
|
|
199
|
+
/**
|
|
200
|
+
* Enable import chain tracking for debugging server code leaking into client builds.
|
|
201
|
+
* When enabled, logs the full import chain when server-only modules are detected.
|
|
202
|
+
* @default false (but auto-enabled when DEBUG_IMPORTS=1 env var is set)
|
|
203
|
+
*/
|
|
204
|
+
trackImports?: boolean | ImportChainTrackerOptions;
|
|
205
|
+
}
|
|
19
206
|
/**
|
|
20
207
|
* Jay Stack Compiler - Handles both Jay runtime compilation and Jay Stack code splitting
|
|
21
208
|
*
|
|
22
209
|
* This plugin internally uses the jay:runtime plugin and adds Jay Stack-specific
|
|
23
210
|
* transformations for client/server code splitting.
|
|
24
211
|
*
|
|
212
|
+
* Environment detection is based on Vite's `options.ssr`:
|
|
213
|
+
* - `options.ssr = true` → server build (strip client code)
|
|
214
|
+
* - `options.ssr = false/undefined` → client build (strip server code)
|
|
215
|
+
*
|
|
216
|
+
* This works for both:
|
|
217
|
+
* - Dev server: SSR renders with server code, browser hydrates with client code
|
|
218
|
+
* - Package builds: Use `build.ssr = true/false` to control environment
|
|
219
|
+
*
|
|
25
220
|
* Usage:
|
|
26
221
|
* ```typescript
|
|
27
222
|
* import { jayStackCompiler } from '@jay-framework/compiler-jay-stack';
|
|
@@ -33,9 +228,19 @@ declare function transformJayStackBuilder(code: string, filePath: string, enviro
|
|
|
33
228
|
* });
|
|
34
229
|
* ```
|
|
35
230
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
231
|
+
* To debug import chain issues (server code leaking to client):
|
|
232
|
+
* ```bash
|
|
233
|
+
* DEBUG_IMPORTS=1 npm run build
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* Or enable in config:
|
|
237
|
+
* ```typescript
|
|
238
|
+
* ...jayStackCompiler({ trackImports: true })
|
|
239
|
+
* ```
|
|
240
|
+
*
|
|
241
|
+
* @param options - Configuration for Jay Stack compiler
|
|
242
|
+
* @returns Array of Vite plugins
|
|
38
243
|
*/
|
|
39
|
-
declare function jayStackCompiler(
|
|
244
|
+
declare function jayStackCompiler(options?: JayStackCompilerOptions): Plugin[];
|
|
40
245
|
|
|
41
|
-
export { type BuildEnvironment, jayStackCompiler, transformJayStackBuilder };
|
|
246
|
+
export { type ActionMetadata, type BuildEnvironment, type ExtractedActions, type ImportChainTrackerOptions, type JayStackCompilerOptions, type PluginClientImportResolverOptions, type PluginDetector, type TransformImportsOptions, type TransformImportsResult, clearActionMetadataCache, createDefaultPluginDetector, createImportChainTracker, createPluginClientImportResolver, extractActionsFromSource, extractPackageName, isActionImport, isSubpathImport, jayStackCompiler, transformActionImports, transformImports, transformJayStackBuilder };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { jayRuntime } from "@jay-framework/vite-plugin";
|
|
2
2
|
import tsBridge from "@jay-framework/typescript-bridge";
|
|
3
3
|
import { flattenVariable, isImportModuleVariableRoot, mkTransformer, SourceFileBindingResolver, areFlattenedAccessChainsEqual } from "@jay-framework/compiler";
|
|
4
|
-
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
const COMPONENT_SERVER_METHODS = /* @__PURE__ */ new Set([
|
|
5
8
|
"withServices",
|
|
6
9
|
"withLoadParams",
|
|
7
10
|
"withSlowlyRender",
|
|
8
11
|
"withFastRender"
|
|
9
12
|
]);
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
]);
|
|
13
|
+
const COMPONENT_CLIENT_METHODS = /* @__PURE__ */ new Set(["withInteractive", "withContexts"]);
|
|
14
|
+
const INIT_SERVER_METHODS = /* @__PURE__ */ new Set(["withServer"]);
|
|
15
|
+
const INIT_CLIENT_METHODS = /* @__PURE__ */ new Set(["withClient"]);
|
|
14
16
|
function shouldRemoveMethod(methodName, environment) {
|
|
15
|
-
|
|
17
|
+
if (environment === "client" && COMPONENT_SERVER_METHODS.has(methodName))
|
|
18
|
+
return true;
|
|
19
|
+
if (environment === "server" && COMPONENT_CLIENT_METHODS.has(methodName))
|
|
20
|
+
return true;
|
|
21
|
+
if (environment === "client" && INIT_SERVER_METHODS.has(methodName))
|
|
22
|
+
return true;
|
|
23
|
+
if (environment === "server" && INIT_CLIENT_METHODS.has(methodName))
|
|
24
|
+
return true;
|
|
25
|
+
return false;
|
|
16
26
|
}
|
|
17
27
|
const { isCallExpression: isCallExpression$1, isPropertyAccessExpression: isPropertyAccessExpression$1, isIdentifier: isIdentifier$2, isStringLiteral } = tsBridge;
|
|
18
28
|
function findBuilderMethodsToRemove(sourceFile, bindingResolver, environment) {
|
|
@@ -33,6 +43,7 @@ function findBuilderMethodsToRemove(sourceFile, bindingResolver, environment) {
|
|
|
33
43
|
sourceFile.forEachChild(visit);
|
|
34
44
|
return { callsToRemove, removedVariables };
|
|
35
45
|
}
|
|
46
|
+
const JAY_BUILDER_FUNCTIONS = /* @__PURE__ */ new Set(["makeJayStackComponent", "makeJayInit"]);
|
|
36
47
|
function isPartOfJayStackChain(callExpr, bindingResolver) {
|
|
37
48
|
let current = callExpr.expression;
|
|
38
49
|
while (true) {
|
|
@@ -42,7 +53,7 @@ function isPartOfJayStackChain(callExpr, bindingResolver) {
|
|
|
42
53
|
if (isIdentifier$2(current.expression)) {
|
|
43
54
|
const variable = bindingResolver.explain(current.expression);
|
|
44
55
|
const flattened = flattenVariable(variable);
|
|
45
|
-
if (flattened.path.length === 1 && flattened.path[0]
|
|
56
|
+
if (flattened.path.length === 1 && JAY_BUILDER_FUNCTIONS.has(flattened.path[0]) && isImportModuleVariableRoot(flattened.root) && isStringLiteral(flattened.root.module) && flattened.root.module.text === "@jay-framework/fullstack-component")
|
|
46
57
|
return true;
|
|
47
58
|
}
|
|
48
59
|
if (isPropertyAccessExpression$1(current.expression)) {
|
|
@@ -72,15 +83,15 @@ function collectVariablesFromArguments(args, bindingResolver, variables) {
|
|
|
72
83
|
const {
|
|
73
84
|
isIdentifier: isIdentifier$1,
|
|
74
85
|
isImportDeclaration: isImportDeclaration$1,
|
|
75
|
-
isFunctionDeclaration
|
|
76
|
-
isVariableStatement
|
|
77
|
-
isInterfaceDeclaration
|
|
78
|
-
isTypeAliasDeclaration
|
|
86
|
+
isFunctionDeclaration,
|
|
87
|
+
isVariableStatement,
|
|
88
|
+
isInterfaceDeclaration,
|
|
89
|
+
isTypeAliasDeclaration,
|
|
79
90
|
isClassDeclaration,
|
|
80
91
|
isEnumDeclaration,
|
|
81
92
|
SyntaxKind
|
|
82
93
|
} = tsBridge;
|
|
83
|
-
function analyzeUnusedStatements(sourceFile
|
|
94
|
+
function analyzeUnusedStatements(sourceFile) {
|
|
84
95
|
const statementsToRemove = /* @__PURE__ */ new Set();
|
|
85
96
|
const collectUsedIdentifiers = () => {
|
|
86
97
|
const used = /* @__PURE__ */ new Set();
|
|
@@ -141,19 +152,19 @@ function isExportStatement(statement) {
|
|
|
141
152
|
return false;
|
|
142
153
|
}
|
|
143
154
|
function getStatementDefinedName(statement) {
|
|
144
|
-
if (isFunctionDeclaration
|
|
155
|
+
if (isFunctionDeclaration(statement) && statement.name) {
|
|
145
156
|
return statement.name.text;
|
|
146
157
|
}
|
|
147
|
-
if (isVariableStatement
|
|
158
|
+
if (isVariableStatement(statement)) {
|
|
148
159
|
const firstDecl = statement.declarationList.declarations[0];
|
|
149
160
|
if (firstDecl && isIdentifier$1(firstDecl.name)) {
|
|
150
161
|
return firstDecl.name.text;
|
|
151
162
|
}
|
|
152
163
|
}
|
|
153
|
-
if (isInterfaceDeclaration
|
|
164
|
+
if (isInterfaceDeclaration(statement) && statement.name) {
|
|
154
165
|
return statement.name.text;
|
|
155
166
|
}
|
|
156
|
-
if (isTypeAliasDeclaration
|
|
167
|
+
if (isTypeAliasDeclaration(statement) && statement.name) {
|
|
157
168
|
return statement.name.text;
|
|
158
169
|
}
|
|
159
170
|
if (isClassDeclaration(statement) && statement.name) {
|
|
@@ -173,19 +184,10 @@ const {
|
|
|
173
184
|
isPropertyAccessExpression,
|
|
174
185
|
isImportDeclaration,
|
|
175
186
|
isNamedImports,
|
|
176
|
-
isIdentifier
|
|
177
|
-
isFunctionDeclaration,
|
|
178
|
-
isVariableStatement,
|
|
179
|
-
isInterfaceDeclaration,
|
|
180
|
-
isTypeAliasDeclaration
|
|
187
|
+
isIdentifier
|
|
181
188
|
} = tsBridge;
|
|
182
189
|
function transformJayStackBuilder(code, filePath, environment) {
|
|
183
|
-
const sourceFile = createSourceFile(
|
|
184
|
-
filePath,
|
|
185
|
-
code,
|
|
186
|
-
ScriptTarget.Latest,
|
|
187
|
-
true
|
|
188
|
-
);
|
|
190
|
+
const sourceFile = createSourceFile(filePath, code, ScriptTarget.Latest, true);
|
|
189
191
|
const transformers = [mkTransformer(mkJayStackCodeSplitTransformer, { environment })];
|
|
190
192
|
const printer = createPrinter();
|
|
191
193
|
const result = tsBridge.transform(sourceFile, transformers);
|
|
@@ -206,11 +208,7 @@ function mkJayStackCodeSplitTransformer({
|
|
|
206
208
|
environment
|
|
207
209
|
}) {
|
|
208
210
|
const bindingResolver = new SourceFileBindingResolver(sourceFile);
|
|
209
|
-
const { callsToRemove
|
|
210
|
-
sourceFile,
|
|
211
|
-
bindingResolver,
|
|
212
|
-
environment
|
|
213
|
-
);
|
|
211
|
+
const { callsToRemove } = findBuilderMethodsToRemove(sourceFile, bindingResolver, environment);
|
|
214
212
|
const transformVisitor = (node) => {
|
|
215
213
|
if (isCallExpression(node) && isPropertyAccessExpression(node.expression)) {
|
|
216
214
|
const variable = bindingResolver.explain(node.expression);
|
|
@@ -222,11 +220,12 @@ function mkJayStackCodeSplitTransformer({
|
|
|
222
220
|
}
|
|
223
221
|
return visitEachChild(node, transformVisitor, context);
|
|
224
222
|
};
|
|
225
|
-
let transformedSourceFile = visitEachChild(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
223
|
+
let transformedSourceFile = visitEachChild(
|
|
224
|
+
sourceFile,
|
|
225
|
+
transformVisitor,
|
|
226
|
+
context
|
|
229
227
|
);
|
|
228
|
+
const { statementsToRemove, unusedImports } = analyzeUnusedStatements(transformedSourceFile);
|
|
230
229
|
const transformedStatements = transformedSourceFile.statements.map((statement) => {
|
|
231
230
|
if (statementsToRemove.has(statement)) {
|
|
232
231
|
return void 0;
|
|
@@ -256,32 +255,490 @@ function filterImportDeclaration(statement, unusedImports, factory) {
|
|
|
256
255
|
importClause,
|
|
257
256
|
importClause.isTypeOnly,
|
|
258
257
|
importClause.name,
|
|
259
|
-
factory.updateNamedImports(
|
|
260
|
-
importClause.namedBindings,
|
|
261
|
-
usedElements
|
|
262
|
-
)
|
|
258
|
+
factory.updateNamedImports(importClause.namedBindings, usedElements)
|
|
263
259
|
),
|
|
264
260
|
statement.moduleSpecifier,
|
|
265
261
|
statement.assertClause
|
|
266
262
|
);
|
|
267
263
|
}
|
|
268
|
-
|
|
269
|
-
|
|
264
|
+
const actionMetadataCache = /* @__PURE__ */ new Map();
|
|
265
|
+
function clearActionMetadataCache() {
|
|
266
|
+
actionMetadataCache.clear();
|
|
267
|
+
}
|
|
268
|
+
function isActionImport(importSource) {
|
|
269
|
+
return importSource.includes(".actions") || importSource.includes("-actions") || importSource.includes("/actions/") || importSource.endsWith("/actions");
|
|
270
|
+
}
|
|
271
|
+
function extractActionsFromSource(sourceCode, filePath) {
|
|
272
|
+
const cached = actionMetadataCache.get(filePath);
|
|
273
|
+
if (cached) {
|
|
274
|
+
return cached;
|
|
275
|
+
}
|
|
276
|
+
const actions = [];
|
|
277
|
+
const sourceFile = tsBridge.createSourceFile(
|
|
278
|
+
filePath,
|
|
279
|
+
sourceCode,
|
|
280
|
+
tsBridge.ScriptTarget.Latest,
|
|
281
|
+
true
|
|
282
|
+
);
|
|
283
|
+
function visit(node) {
|
|
284
|
+
if (tsBridge.isVariableStatement(node)) {
|
|
285
|
+
const hasExport = node.modifiers?.some(
|
|
286
|
+
(m) => m.kind === tsBridge.SyntaxKind.ExportKeyword
|
|
287
|
+
);
|
|
288
|
+
if (!hasExport) {
|
|
289
|
+
tsBridge.forEachChild(node, visit);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
for (const decl of node.declarationList.declarations) {
|
|
293
|
+
if (!tsBridge.isIdentifier(decl.name) || !decl.initializer) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const exportName = decl.name.text;
|
|
297
|
+
const actionMeta = extractActionFromExpression(decl.initializer);
|
|
298
|
+
if (actionMeta) {
|
|
299
|
+
actions.push({
|
|
300
|
+
...actionMeta,
|
|
301
|
+
exportName
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
tsBridge.forEachChild(node, visit);
|
|
307
|
+
}
|
|
308
|
+
visit(sourceFile);
|
|
309
|
+
actionMetadataCache.set(filePath, actions);
|
|
310
|
+
return actions;
|
|
311
|
+
}
|
|
312
|
+
function extractActionFromExpression(node) {
|
|
313
|
+
let current = node;
|
|
314
|
+
let method = "POST";
|
|
315
|
+
let explicitMethod = null;
|
|
316
|
+
while (tsBridge.isCallExpression(current)) {
|
|
317
|
+
const expr = current.expression;
|
|
318
|
+
if (tsBridge.isPropertyAccessExpression(expr) && expr.name.text === "withMethod") {
|
|
319
|
+
const arg = current.arguments[0];
|
|
320
|
+
if (arg && tsBridge.isStringLiteral(arg)) {
|
|
321
|
+
explicitMethod = arg.text;
|
|
322
|
+
}
|
|
323
|
+
current = expr.expression;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (tsBridge.isPropertyAccessExpression(expr) && ["withServices", "withCaching", "withHandler", "withTimeout"].includes(expr.name.text)) {
|
|
327
|
+
current = expr.expression;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (tsBridge.isIdentifier(expr)) {
|
|
331
|
+
const funcName = expr.text;
|
|
332
|
+
if (funcName === "makeJayAction" || funcName === "makeJayQuery") {
|
|
333
|
+
const nameArg = current.arguments[0];
|
|
334
|
+
if (nameArg && tsBridge.isStringLiteral(nameArg)) {
|
|
335
|
+
method = funcName === "makeJayQuery" ? "GET" : "POST";
|
|
336
|
+
if (explicitMethod) {
|
|
337
|
+
method = explicitMethod;
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
actionName: nameArg.text,
|
|
341
|
+
method
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
async function transformActionImports(code, id, resolveActionModule) {
|
|
351
|
+
if (!code.includes("import")) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const sourceFile = tsBridge.createSourceFile(id, code, tsBridge.ScriptTarget.Latest, true);
|
|
355
|
+
const actionImports = [];
|
|
356
|
+
for (const statement of sourceFile.statements) {
|
|
357
|
+
if (!tsBridge.isImportDeclaration(statement)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
361
|
+
if (!tsBridge.isStringLiteral(moduleSpecifier)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const importSource = moduleSpecifier.text;
|
|
365
|
+
if (!isActionImport(importSource)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const importClause = statement.importClause;
|
|
369
|
+
if (!importClause?.namedBindings || !tsBridge.isNamedImports(importClause.namedBindings)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const namedImports = importClause.namedBindings.elements.map(
|
|
373
|
+
(el) => el.propertyName ? el.propertyName.text : el.name.text
|
|
374
|
+
);
|
|
375
|
+
actionImports.push({
|
|
376
|
+
importDecl: statement,
|
|
377
|
+
source: importSource,
|
|
378
|
+
namedImports,
|
|
379
|
+
start: statement.getStart(),
|
|
380
|
+
end: statement.getEnd()
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (actionImports.length === 0) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
const replacements = [];
|
|
387
|
+
let needsCreateActionCallerImport = false;
|
|
388
|
+
for (const imp of actionImports) {
|
|
389
|
+
const resolved = await resolveActionModule(imp.source, id);
|
|
390
|
+
if (!resolved) {
|
|
391
|
+
console.warn(`[action-transform] Could not resolve action module: ${imp.source}`);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const actions = extractActionsFromSource(resolved.code, resolved.path);
|
|
395
|
+
const callerDeclarations = [];
|
|
396
|
+
for (const importName of imp.namedImports) {
|
|
397
|
+
const action = actions.find((a) => a.exportName === importName);
|
|
398
|
+
if (action) {
|
|
399
|
+
callerDeclarations.push(
|
|
400
|
+
`const ${importName} = createActionCaller('${action.actionName}', '${action.method}');`
|
|
401
|
+
);
|
|
402
|
+
needsCreateActionCallerImport = true;
|
|
403
|
+
} else {
|
|
404
|
+
console.warn(
|
|
405
|
+
`[action-transform] Export '${importName}' from ${imp.source} is not a recognized action`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (callerDeclarations.length > 0) {
|
|
410
|
+
replacements.push({
|
|
411
|
+
start: imp.start,
|
|
412
|
+
end: imp.end,
|
|
413
|
+
replacement: callerDeclarations.join("\n")
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (replacements.length === 0) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
let result = code;
|
|
421
|
+
for (const rep of replacements.sort((a, b) => b.start - a.start)) {
|
|
422
|
+
result = result.slice(0, rep.start) + rep.replacement + result.slice(rep.end);
|
|
423
|
+
}
|
|
424
|
+
if (needsCreateActionCallerImport) {
|
|
425
|
+
const importStatement = `import { createActionCaller } from '@jay-framework/stack-client-runtime';
|
|
426
|
+
`;
|
|
427
|
+
result = importStatement + result;
|
|
428
|
+
}
|
|
429
|
+
return { code: result };
|
|
430
|
+
}
|
|
431
|
+
const SERVER_ONLY_MODULES = /* @__PURE__ */ new Set([
|
|
432
|
+
"module",
|
|
433
|
+
// createRequire
|
|
434
|
+
"fs",
|
|
435
|
+
"path",
|
|
436
|
+
"node:fs",
|
|
437
|
+
"node:path",
|
|
438
|
+
"node:module",
|
|
439
|
+
"child_process",
|
|
440
|
+
"node:child_process",
|
|
441
|
+
"crypto",
|
|
442
|
+
"node:crypto"
|
|
443
|
+
]);
|
|
444
|
+
const SERVER_ONLY_PACKAGE_PATTERNS = [
|
|
445
|
+
"@jay-framework/compiler-shared",
|
|
446
|
+
"@jay-framework/stack-server-runtime",
|
|
447
|
+
"yaml"
|
|
448
|
+
// Often used in server config
|
|
449
|
+
];
|
|
450
|
+
function createImportChainTracker(options = {}) {
|
|
451
|
+
const {
|
|
452
|
+
verbose = false,
|
|
453
|
+
additionalServerModules = [],
|
|
454
|
+
additionalServerPatterns = []
|
|
455
|
+
} = options;
|
|
456
|
+
const importChain = /* @__PURE__ */ new Map();
|
|
457
|
+
const detectedServerModules = /* @__PURE__ */ new Set();
|
|
458
|
+
const serverOnlyModules = /* @__PURE__ */ new Set([...SERVER_ONLY_MODULES, ...additionalServerModules]);
|
|
459
|
+
const serverOnlyPatterns = [...SERVER_ONLY_PACKAGE_PATTERNS, ...additionalServerPatterns];
|
|
460
|
+
function isServerOnlyModule(id) {
|
|
461
|
+
if (serverOnlyModules.has(id)) {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
for (const pattern of serverOnlyPatterns) {
|
|
465
|
+
if (id.includes(pattern)) {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
function buildImportChain(moduleId) {
|
|
472
|
+
const chain = [moduleId];
|
|
473
|
+
let current = moduleId;
|
|
474
|
+
for (let i = 0; i < 100; i++) {
|
|
475
|
+
const importer = importChain.get(current);
|
|
476
|
+
if (!importer)
|
|
477
|
+
break;
|
|
478
|
+
chain.push(importer);
|
|
479
|
+
current = importer;
|
|
480
|
+
}
|
|
481
|
+
return chain.reverse();
|
|
482
|
+
}
|
|
483
|
+
function formatChain(chain) {
|
|
484
|
+
return chain.map((id, idx) => {
|
|
485
|
+
const indent = " ".repeat(idx);
|
|
486
|
+
const shortId = shortenPath(id);
|
|
487
|
+
return `${indent}${idx === 0 ? "" : "↳ "}${shortId}`;
|
|
488
|
+
}).join("\n");
|
|
489
|
+
}
|
|
490
|
+
function shortenPath(id) {
|
|
491
|
+
if (id.includes("node_modules")) {
|
|
492
|
+
const parts = id.split("node_modules/");
|
|
493
|
+
return parts[parts.length - 1];
|
|
494
|
+
}
|
|
495
|
+
const cwd = process.cwd();
|
|
496
|
+
if (id.startsWith(cwd)) {
|
|
497
|
+
return id.slice(cwd.length + 1);
|
|
498
|
+
}
|
|
499
|
+
return id;
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
name: "jay-stack:import-chain-tracker",
|
|
503
|
+
enforce: "pre",
|
|
504
|
+
buildStart() {
|
|
505
|
+
importChain.clear();
|
|
506
|
+
detectedServerModules.clear();
|
|
507
|
+
if (verbose) {
|
|
508
|
+
console.log("[import-chain-tracker] Build started, tracking imports...");
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
resolveId(source, importer, options2) {
|
|
512
|
+
if (options2?.ssr) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
if (source.startsWith("\0")) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
if (importer) {
|
|
519
|
+
if (verbose) {
|
|
520
|
+
console.log(
|
|
521
|
+
`[import-chain-tracker] ${shortenPath(importer)} imports ${source}`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
},
|
|
527
|
+
load(id) {
|
|
528
|
+
return null;
|
|
529
|
+
},
|
|
530
|
+
transform(code, id, options2) {
|
|
531
|
+
if (options2?.ssr) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
if (isServerOnlyModule(id)) {
|
|
535
|
+
detectedServerModules.add(id);
|
|
536
|
+
const chain = buildImportChain(id);
|
|
537
|
+
console.error(
|
|
538
|
+
`
|
|
539
|
+
[import-chain-tracker] ⚠️ Server-only module detected in client build!`
|
|
540
|
+
);
|
|
541
|
+
console.error(`Module: ${shortenPath(id)}`);
|
|
542
|
+
console.error(`Import chain:`);
|
|
543
|
+
console.error(formatChain(chain));
|
|
544
|
+
console.error("");
|
|
545
|
+
}
|
|
546
|
+
const importRegex = /import\s+(?:(?:\{[^}]*\}|[^{}\s,]+)\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
547
|
+
let match;
|
|
548
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
549
|
+
const importedModule = match[1];
|
|
550
|
+
importChain.set(importedModule, id);
|
|
551
|
+
if (isServerOnlyModule(importedModule)) {
|
|
552
|
+
if (!detectedServerModules.has(importedModule)) {
|
|
553
|
+
detectedServerModules.add(importedModule);
|
|
554
|
+
console.error(
|
|
555
|
+
`
|
|
556
|
+
[import-chain-tracker] ⚠️ Server-only import detected in client build!`
|
|
557
|
+
);
|
|
558
|
+
console.error(`Module "${importedModule}" imported by: ${shortenPath(id)}`);
|
|
559
|
+
const chain = buildImportChain(id);
|
|
560
|
+
chain.push(importedModule);
|
|
561
|
+
console.error(`Import chain:`);
|
|
562
|
+
console.error(formatChain(chain));
|
|
563
|
+
console.error("");
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return null;
|
|
568
|
+
},
|
|
569
|
+
buildEnd() {
|
|
570
|
+
if (detectedServerModules.size > 0) {
|
|
571
|
+
console.warn(
|
|
572
|
+
`
|
|
573
|
+
[import-chain-tracker] ⚠️ ${detectedServerModules.size} server-only module(s) detected during transform:`
|
|
574
|
+
);
|
|
575
|
+
for (const mod of detectedServerModules) {
|
|
576
|
+
console.warn(` - ${mod}`);
|
|
577
|
+
}
|
|
578
|
+
console.warn(
|
|
579
|
+
"\nNote: These may be stripped by the code-split transform if only used in .withServer()."
|
|
580
|
+
);
|
|
581
|
+
console.warn(
|
|
582
|
+
'If build fails with "not exported" errors, check the import chains above.\n'
|
|
583
|
+
);
|
|
584
|
+
} else if (verbose) {
|
|
585
|
+
console.log(
|
|
586
|
+
"[import-chain-tracker] ✅ No server-only modules detected in client build"
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const require2 = createRequire(import.meta.url);
|
|
593
|
+
function createDefaultPluginDetector() {
|
|
594
|
+
const cache = /* @__PURE__ */ new Map();
|
|
595
|
+
return {
|
|
596
|
+
isJayPluginWithClientExport(packageName, projectRoot) {
|
|
597
|
+
const cacheKey = `${packageName}:${projectRoot}`;
|
|
598
|
+
if (cache.has(cacheKey)) {
|
|
599
|
+
return cache.get(cacheKey);
|
|
600
|
+
}
|
|
601
|
+
let result = false;
|
|
602
|
+
try {
|
|
603
|
+
require2.resolve(`${packageName}/plugin.yaml`, { paths: [projectRoot] });
|
|
604
|
+
try {
|
|
605
|
+
require2.resolve(`${packageName}/client`, { paths: [projectRoot] });
|
|
606
|
+
result = true;
|
|
607
|
+
} catch {
|
|
608
|
+
result = false;
|
|
609
|
+
}
|
|
610
|
+
} catch {
|
|
611
|
+
result = false;
|
|
612
|
+
}
|
|
613
|
+
cache.set(cacheKey, result);
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
function extractPackageName(source) {
|
|
619
|
+
if (source.startsWith(".") || source.startsWith("/")) {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
if (source.startsWith("@")) {
|
|
623
|
+
const parts2 = source.split("/");
|
|
624
|
+
if (parts2.length >= 2) {
|
|
625
|
+
return `${parts2[0]}/${parts2[1]}`;
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
const parts = source.split("/");
|
|
630
|
+
return parts[0];
|
|
631
|
+
}
|
|
632
|
+
function isSubpathImport(source, packageName) {
|
|
633
|
+
return source.length > packageName.length && source[packageName.length] === "/";
|
|
634
|
+
}
|
|
635
|
+
const IMPORT_REGEX = /import\s+(.+?)\s+from\s+(['"])([^'"]+)\2/g;
|
|
636
|
+
const EXPORT_FROM_REGEX = /export\s+(.+?)\s+from\s+(['"])([^'"]+)\2/g;
|
|
637
|
+
function transformImports(options) {
|
|
638
|
+
const { code, projectRoot, filePath, pluginDetector, verbose = false } = options;
|
|
639
|
+
let hasChanges = false;
|
|
640
|
+
let result = code;
|
|
641
|
+
result = result.replace(IMPORT_REGEX, (match, clause, quote, source) => {
|
|
642
|
+
const packageName = extractPackageName(source);
|
|
643
|
+
if (!packageName)
|
|
644
|
+
return match;
|
|
645
|
+
if (isSubpathImport(source, packageName))
|
|
646
|
+
return match;
|
|
647
|
+
if (!pluginDetector.isJayPluginWithClientExport(packageName, projectRoot))
|
|
648
|
+
return match;
|
|
649
|
+
hasChanges = true;
|
|
650
|
+
const newSource = `${packageName}/client`;
|
|
651
|
+
if (verbose) {
|
|
652
|
+
console.log(
|
|
653
|
+
`[plugin-client-import] Rewriting import ${source} -> ${newSource} (in ${path.basename(filePath)})`
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
return `import ${clause} from ${quote}${newSource}${quote}`;
|
|
657
|
+
});
|
|
658
|
+
result = result.replace(EXPORT_FROM_REGEX, (match, clause, quote, source) => {
|
|
659
|
+
const packageName = extractPackageName(source);
|
|
660
|
+
if (!packageName)
|
|
661
|
+
return match;
|
|
662
|
+
if (isSubpathImport(source, packageName))
|
|
663
|
+
return match;
|
|
664
|
+
if (!pluginDetector.isJayPluginWithClientExport(packageName, projectRoot))
|
|
665
|
+
return match;
|
|
666
|
+
hasChanges = true;
|
|
667
|
+
const newSource = `${packageName}/client`;
|
|
668
|
+
if (verbose) {
|
|
669
|
+
console.log(
|
|
670
|
+
`[plugin-client-import] Rewriting export ${source} -> ${newSource} (in ${path.basename(filePath)})`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
return `export ${clause} from ${quote}${newSource}${quote}`;
|
|
674
|
+
});
|
|
675
|
+
return { code: result, hasChanges };
|
|
676
|
+
}
|
|
677
|
+
function createPluginClientImportResolver(options = {}) {
|
|
678
|
+
const { verbose = false } = options;
|
|
679
|
+
let projectRoot = options.projectRoot || process.cwd();
|
|
680
|
+
let isSSRBuild = false;
|
|
681
|
+
const pluginDetector = options.pluginDetector || createDefaultPluginDetector();
|
|
682
|
+
return {
|
|
683
|
+
name: "jay-stack:plugin-client-import",
|
|
684
|
+
enforce: "pre",
|
|
685
|
+
configResolved(config) {
|
|
686
|
+
projectRoot = config.root || projectRoot;
|
|
687
|
+
isSSRBuild = !!config.build?.ssr;
|
|
688
|
+
},
|
|
689
|
+
transform(code, id, transformOptions) {
|
|
690
|
+
if (transformOptions?.ssr || isSSRBuild) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
if (!id.endsWith(".ts") && !id.endsWith(".js") && !id.includes(".ts?") && !id.includes(".js?")) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
if (id.includes("node_modules") && !id.includes("@jay-framework")) {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
if (!code.includes("@jay-framework/") && !code.includes("from '@") && !code.includes('from "@')) {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
const result = transformImports({
|
|
703
|
+
code,
|
|
704
|
+
projectRoot,
|
|
705
|
+
filePath: id,
|
|
706
|
+
pluginDetector,
|
|
707
|
+
verbose
|
|
708
|
+
});
|
|
709
|
+
if (!result.hasChanges) {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
return { code: result.code };
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function jayStackCompiler(options = {}) {
|
|
717
|
+
const { trackImports, ...jayOptions } = options;
|
|
718
|
+
const moduleCache = /* @__PURE__ */ new Map();
|
|
719
|
+
const shouldTrackImports = trackImports || process.env.DEBUG_IMPORTS === "1";
|
|
720
|
+
const trackerOptions = typeof trackImports === "object" ? trackImports : { verbose: process.env.DEBUG_IMPORTS === "1" };
|
|
721
|
+
const plugins = [];
|
|
722
|
+
if (shouldTrackImports) {
|
|
723
|
+
plugins.push(createImportChainTracker(trackerOptions));
|
|
724
|
+
}
|
|
725
|
+
plugins.push(createPluginClientImportResolver({ verbose: !!shouldTrackImports }));
|
|
726
|
+
plugins.push(
|
|
270
727
|
// First: Jay Stack code splitting transformation
|
|
271
728
|
{
|
|
272
729
|
name: "jay-stack:code-split",
|
|
273
730
|
enforce: "pre",
|
|
274
731
|
// Run before jay:runtime
|
|
275
|
-
transform(code, id) {
|
|
276
|
-
|
|
277
|
-
const isServerBuild = id.includes("?jay-server");
|
|
278
|
-
if (!isClientBuild && !isServerBuild) {
|
|
732
|
+
transform(code, id, options2) {
|
|
733
|
+
if (!id.endsWith(".ts") && !id.includes(".ts?")) {
|
|
279
734
|
return null;
|
|
280
735
|
}
|
|
281
|
-
const
|
|
282
|
-
|
|
736
|
+
const hasComponent = code.includes("makeJayStackComponent");
|
|
737
|
+
const hasInit = code.includes("makeJayInit");
|
|
738
|
+
if (!hasComponent && !hasInit) {
|
|
283
739
|
return null;
|
|
284
740
|
}
|
|
741
|
+
const environment = options2?.ssr ? "server" : "client";
|
|
285
742
|
try {
|
|
286
743
|
return transformJayStackBuilder(code, id, environment);
|
|
287
744
|
} catch (error) {
|
|
@@ -290,11 +747,104 @@ function jayStackCompiler(jayOptions = {}) {
|
|
|
290
747
|
}
|
|
291
748
|
}
|
|
292
749
|
},
|
|
293
|
-
// Second:
|
|
750
|
+
// Second: Action import transformation (client builds only)
|
|
751
|
+
// Uses resolveId + load to replace action modules with virtual modules
|
|
752
|
+
// containing createActionCaller calls BEFORE bundling happens.
|
|
753
|
+
(() => {
|
|
754
|
+
let isSSRBuild = false;
|
|
755
|
+
return {
|
|
756
|
+
name: "jay-stack:action-transform",
|
|
757
|
+
enforce: "pre",
|
|
758
|
+
// Track SSR mode from config
|
|
759
|
+
configResolved(config) {
|
|
760
|
+
isSSRBuild = config.build?.ssr ?? false;
|
|
761
|
+
},
|
|
762
|
+
buildStart() {
|
|
763
|
+
clearActionMetadataCache();
|
|
764
|
+
moduleCache.clear();
|
|
765
|
+
},
|
|
766
|
+
async resolveId(source, importer, options2) {
|
|
767
|
+
if (options2?.ssr || isSSRBuild) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
if (!isActionImport(source)) {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
if (!source.startsWith(".") || !importer) {
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
const importerDir = path.dirname(importer);
|
|
777
|
+
let resolvedPath = path.resolve(importerDir, source);
|
|
778
|
+
if (!resolvedPath.endsWith(".ts") && !resolvedPath.endsWith(".js")) {
|
|
779
|
+
if (fs.existsSync(resolvedPath + ".ts")) {
|
|
780
|
+
resolvedPath += ".ts";
|
|
781
|
+
} else if (fs.existsSync(resolvedPath + ".js")) {
|
|
782
|
+
resolvedPath += ".js";
|
|
783
|
+
} else {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
} else if (resolvedPath.endsWith(".js") && !fs.existsSync(resolvedPath)) {
|
|
787
|
+
const tsPath = resolvedPath.slice(0, -3) + ".ts";
|
|
788
|
+
if (fs.existsSync(tsPath)) {
|
|
789
|
+
resolvedPath = tsPath;
|
|
790
|
+
} else {
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return `\0jay-action:${resolvedPath}`;
|
|
795
|
+
},
|
|
796
|
+
async load(id) {
|
|
797
|
+
if (!id.startsWith("\0jay-action:")) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
const actualPath = id.slice("\0jay-action:".length);
|
|
801
|
+
let code;
|
|
802
|
+
try {
|
|
803
|
+
code = await fs.promises.readFile(actualPath, "utf-8");
|
|
804
|
+
} catch (err) {
|
|
805
|
+
console.error(`[action-transform] Could not read ${actualPath}:`, err);
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
const actions = extractActionsFromSource(code, actualPath);
|
|
809
|
+
if (actions.length === 0) {
|
|
810
|
+
console.warn(`[action-transform] No actions found in ${actualPath}`);
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
const lines = [
|
|
814
|
+
`import { createActionCaller } from '@jay-framework/stack-client-runtime';`,
|
|
815
|
+
""
|
|
816
|
+
];
|
|
817
|
+
for (const action of actions) {
|
|
818
|
+
lines.push(
|
|
819
|
+
`export const ${action.exportName} = createActionCaller('${action.actionName}', '${action.method}');`
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
if (code.includes("ActionError")) {
|
|
823
|
+
lines.push(
|
|
824
|
+
`export { ActionError } from '@jay-framework/stack-client-runtime';`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
const result = lines.join("\n");
|
|
828
|
+
return result;
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
})(),
|
|
832
|
+
// Third: Jay runtime compilation (existing plugin)
|
|
294
833
|
jayRuntime(jayOptions)
|
|
295
|
-
|
|
834
|
+
);
|
|
835
|
+
return plugins;
|
|
296
836
|
}
|
|
297
837
|
export {
|
|
838
|
+
clearActionMetadataCache,
|
|
839
|
+
createDefaultPluginDetector,
|
|
840
|
+
createImportChainTracker,
|
|
841
|
+
createPluginClientImportResolver,
|
|
842
|
+
extractActionsFromSource,
|
|
843
|
+
extractPackageName,
|
|
844
|
+
isActionImport,
|
|
845
|
+
isSubpathImport,
|
|
298
846
|
jayStackCompiler,
|
|
847
|
+
transformActionImports,
|
|
848
|
+
transformImports,
|
|
299
849
|
transformJayStackBuilder
|
|
300
850
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/compiler-jay-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,16 +27,17 @@
|
|
|
27
27
|
"test:watch": "vitest"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@jay-framework/compiler": "^0.
|
|
31
|
-
"@jay-framework/
|
|
32
|
-
"@jay-framework/
|
|
30
|
+
"@jay-framework/compiler": "^0.11.0",
|
|
31
|
+
"@jay-framework/compiler-shared": "^0.11.0",
|
|
32
|
+
"@jay-framework/typescript-bridge": "^0.6.0",
|
|
33
|
+
"@jay-framework/vite-plugin": "^0.11.0",
|
|
34
|
+
"typescript": "^5.3.3",
|
|
33
35
|
"vite": "^5.0.11"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
|
-
"@jay-framework/dev-environment": "^0.
|
|
38
|
+
"@jay-framework/dev-environment": "^0.11.0",
|
|
37
39
|
"rimraf": "^5.0.5",
|
|
38
40
|
"tsup": "^8.0.1",
|
|
39
|
-
"typescript": "^5.3.3",
|
|
40
41
|
"vitest": "^1.2.1"
|
|
41
42
|
}
|
|
42
43
|
}
|