@wsxjs/wsx-vite-plugin 0.0.13 → 0.0.15

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.js CHANGED
@@ -47,68 +47,255 @@ function babelPluginWSXState() {
47
47
  return {
48
48
  name: "babel-plugin-wsx-state",
49
49
  visitor: {
50
+ Program: {
51
+ enter(_path, state) {
52
+ console.info(
53
+ `[Babel Plugin WSX State] DEBUG: state.opts keys = ${Object.keys(state.opts || {}).join(", ")}`
54
+ );
55
+ console.info(
56
+ `[Babel Plugin WSX State] DEBUG: state.opts = ${JSON.stringify(state.opts).substring(0, 200)}`
57
+ );
58
+ const opts = state.opts;
59
+ if (opts?.originalSource) {
60
+ state.originalSource = opts.originalSource;
61
+ console.info(
62
+ `[Babel Plugin WSX State] \u2705 Stored original source from options (length: ${String(opts.originalSource).length})`
63
+ );
64
+ } else {
65
+ console.warn(
66
+ `[Babel Plugin WSX State] \u26A0\uFE0F state.opts.originalSource not found, trying fallback...`
67
+ );
68
+ const file = state.file;
69
+ if (file) {
70
+ const sourceCode = file.code;
71
+ if (sourceCode) {
72
+ state.originalSource = sourceCode;
73
+ console.info(
74
+ `[Babel Plugin WSX State] \u2705 Stored original source from file (length: ${sourceCode.length})`
75
+ );
76
+ } else {
77
+ console.error(
78
+ `[Babel Plugin WSX State] \u274C ERROR: Could not get original source code from state.opts or state.file!`
79
+ );
80
+ }
81
+ } else {
82
+ console.error(
83
+ `[Babel Plugin WSX State] \u274C ERROR: state.file is undefined!`
84
+ );
85
+ }
86
+ }
87
+ }
88
+ },
50
89
  ClassDeclaration(path) {
51
90
  const classBody = path.node.body;
52
91
  const stateProperties = [];
53
92
  console.info(
54
93
  `[Babel Plugin WSX State] Processing class ${path.node.id?.name || "anonymous"}, members: ${classBody.body.length}`
55
94
  );
95
+ if (path.node.decorators && path.node.decorators.length > 0) {
96
+ console.info(
97
+ `[Babel Plugin WSX State] Class-level decorators: ${path.node.decorators.length}`
98
+ );
99
+ }
56
100
  for (const member of classBody.body) {
57
101
  console.info(
58
102
  ` - Member type: ${member.type}, key: ${member.type === "ClassProperty" || member.type === "ClassPrivateProperty" ? member.key?.name : "N/A"}`
59
103
  );
60
104
  if ((member.type === "ClassProperty" || member.type === "ClassPrivateProperty") && member.key.type === "Identifier") {
105
+ const propertyName = member.key.name;
106
+ const decoratorCount = member.decorators?.length || 0;
107
+ const hasValue = !!member.value && !(member.value.type === "Identifier" && member.value.name === "undefined") && !(member.value.type === "UnaryExpression" && member.value.operator === "void");
108
+ const isUndefined = !!(member.value && (member.value.type === "Identifier" && member.value.name === "undefined" || member.value.type === "UnaryExpression" && member.value.operator === "void"));
109
+ const valueType = member.value ? member.value.type : "none";
110
+ const valueName = member.value && member.value.type === "Identifier" ? member.value.name : "none";
61
111
  console.info(
62
- ` - Property: ${member.key.name}, decorators: ${member.decorators?.length || 0}, hasValue: ${!!member.value}`
112
+ ` - Property: ${propertyName}, decorators: ${decoratorCount}, hasValue: ${hasValue}, isUndefined: ${isUndefined}, value type: ${valueType}, value name: ${valueName}`
63
113
  );
64
- if (member.decorators && member.decorators.length > 0) {
65
- member.decorators.forEach((decorator) => {
66
- if (decorator.expression.type === "Identifier") {
67
- console.info(` Decorator: ${decorator.expression.name}`);
68
- } else if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier") {
69
- console.debug(
70
- ` Decorator: ${decorator.expression.callee.name}()`
114
+ try {
115
+ if (isUndefined) {
116
+ const originalSource2 = path.state?.originalSource;
117
+ if (originalSource2) {
118
+ const propertyPattern = new RegExp(
119
+ `@state\\s+(?:private|protected|public)?\\s+${propertyName}\\s*[?;]`,
120
+ "m"
71
121
  );
122
+ if (propertyPattern.test(originalSource2)) {
123
+ console.error(
124
+ `[Babel Plugin WSX State] ERROR: Found @state decorator in source for property '${propertyName}' but value is undefined (from optional property syntax)!`
125
+ );
126
+ const error = path.buildCodeFrameError(
127
+ `@state decorator on property '${propertyName}' requires an initial value.
128
+
129
+ The property has undefined value (from optional property syntax '${propertyName}?'), but @state decorator needs a real value to decide whether to use useState (primitive) or reactive (object/array).
130
+
131
+ Examples:
132
+ @state private ${propertyName} = ""; // for string
133
+ @state private ${propertyName} = 0; // for number
134
+ @state private ${propertyName} = {}; // for object
135
+ @state private ${propertyName} = []; // for array
136
+ @state private ${propertyName} = undefined; // explicitly undefined
137
+
138
+ Current code: @state private ${propertyName}?;
139
+
140
+ Fix: Change to '@state private ${propertyName} = undefined;' or provide a real initial value.`
141
+ );
142
+ console.error(
143
+ `[Babel Plugin WSX State] ERROR: ${error.message}`
144
+ );
145
+ throw error;
146
+ }
72
147
  }
73
- });
74
- }
75
- const hasStateDecorator = member.decorators?.some(
76
- (decorator) => {
77
- if (decorator.expression.type === "Identifier" && decorator.expression.name === "state") {
78
- return true;
148
+ }
149
+ if (propertyName === "count") {
150
+ console.info(
151
+ `[Babel Plugin WSX State] DEBUG path.state keys for '${propertyName}': ${path.state ? Object.keys(path.state).join(", ") : "null"}`
152
+ );
153
+ console.info(
154
+ `[Babel Plugin WSX State] DEBUG path.state.originalSource type: ${path.state ? typeof path.state.originalSource : "path.state is null"}`
155
+ );
156
+ }
157
+ const originalSource = path.state?.originalSource;
158
+ if (originalSource) {
159
+ const propertyPattern = new RegExp(
160
+ `@state\\s+(?:private|protected|public)?\\s+${propertyName}\\s*[?;]`,
161
+ "m"
162
+ );
163
+ const hasStateInSource = propertyPattern.test(originalSource);
164
+ if (hasStateInSource) {
165
+ console.info(
166
+ `[Babel Plugin WSX State] Found @state in source for property '${propertyName}' (decorators array was empty: ${decoratorCount})`
167
+ );
168
+ const hasInitialValueInSource = new RegExp(
169
+ `@state\\s+(?:private|protected|public)?\\s+${propertyName}\\s*=\\s*[^;]+`,
170
+ "m"
171
+ ).test(originalSource);
172
+ if (!hasInitialValueInSource) {
173
+ console.error(
174
+ `[Babel Plugin WSX State] ERROR: Found @state decorator in source for property '${propertyName}' but no initial value!`
175
+ );
176
+ const error = path.buildCodeFrameError(
177
+ `@state decorator on property '${propertyName}' requires an initial value.
178
+
179
+ The @state decorator needs a real value to decide whether to use useState (primitive) or reactive (object/array).
180
+
181
+ Examples:
182
+ @state private ${propertyName} = ""; // for string
183
+ @state private ${propertyName} = 0; // for number
184
+ @state private ${propertyName} = {}; // for object
185
+ @state private ${propertyName} = []; // for array
186
+ @state private ${propertyName} = undefined; // explicitly undefined
187
+
188
+ Current code: @state private ${propertyName}?;
189
+
190
+ Fix: Change to '@state private ${propertyName} = undefined;' or provide a real initial value.`
191
+ );
192
+ console.error(
193
+ `[Babel Plugin WSX State] ERROR: ${error.message}`
194
+ );
195
+ throw error;
196
+ }
79
197
  }
80
- if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "state") {
81
- return true;
198
+ } else {
199
+ if (propertyName === "count") {
200
+ console.warn(
201
+ `[Babel Plugin WSX State] WARNING: originalSource not available for property '${propertyName}'`
202
+ );
82
203
  }
83
- return false;
84
204
  }
85
- );
86
- if (hasStateDecorator && member.value) {
87
- const key = member.key.name;
88
- const initialValue = member.value;
89
- const isObject = initialValue.type === "ObjectExpression" || initialValue.type === "ArrayExpression";
90
- const isArray = initialValue.type === "ArrayExpression";
91
- stateProperties.push({
92
- key,
93
- initialValue,
94
- isObject,
95
- isArray
96
- // Add isArray flag
97
- });
98
- if (member.decorators) {
99
- member.decorators = member.decorators.filter(
205
+ if (member.decorators && member.decorators.length > 0) {
206
+ member.decorators.forEach((decorator) => {
207
+ if (decorator.expression.type === "Identifier") {
208
+ console.info(` Decorator: ${decorator.expression.name}`);
209
+ } else if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier") {
210
+ console.info(
211
+ ` Decorator: ${decorator.expression.callee.name}()`
212
+ );
213
+ } else {
214
+ console.info(` Decorator: ${decorator.expression.type}`);
215
+ }
216
+ });
217
+ } else {
218
+ }
219
+ let hasStateDecorator = false;
220
+ try {
221
+ hasStateDecorator = member.decorators?.some(
100
222
  (decorator) => {
101
223
  if (decorator.expression.type === "Identifier" && decorator.expression.name === "state") {
102
- return false;
224
+ return true;
103
225
  }
104
226
  if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "state") {
105
- return false;
227
+ return true;
106
228
  }
107
- return true;
229
+ return false;
108
230
  }
231
+ ) || false;
232
+ } catch (error) {
233
+ console.error(
234
+ `[Babel Plugin WSX State] ERROR in hasStateDecorator check for ${propertyName}: ${error}`
109
235
  );
236
+ throw error;
110
237
  }
111
- member.value = void 0;
238
+ if (hasStateDecorator) {
239
+ const key = member.key.name;
240
+ const hasInitialValue = !!(member.value && !(member.value.type === "Identifier" && member.value.name === "undefined") && !(member.value.type === "UnaryExpression" && member.value.operator === "void"));
241
+ if (key === "count") {
242
+ console.error(
243
+ `[Babel Plugin WSX State] DEBUG: hasInitialValue for 'count' = ${hasInitialValue}, type = ${typeof hasInitialValue}`
244
+ );
245
+ console.error(
246
+ `[Babel Plugin WSX State] DEBUG: member.value = ${member.value}, !hasInitialValue = ${!hasInitialValue}`
247
+ );
248
+ }
249
+ if (!hasInitialValue) {
250
+ const error = path.buildCodeFrameError(
251
+ `@state decorator on property '${key}' requires an initial value.
252
+
253
+ Examples:
254
+ @state private ${key} = ""; // for string
255
+ @state private ${key} = 0; // for number
256
+ @state private ${key} = {}; // for object
257
+ @state private ${key} = []; // for array
258
+ @state private ${key} = undefined; // for optional
259
+
260
+ Current code: @state private ${key};
261
+
262
+ This error should be caught during build time. If you see this at runtime, it means the Babel plugin did not process this file.`
263
+ );
264
+ console.error(`[Babel Plugin WSX State] ERROR: ${error.message}`);
265
+ throw error;
266
+ }
267
+ const initialValue = member.value;
268
+ const isObject = initialValue.type === "ObjectExpression" || initialValue.type === "ArrayExpression";
269
+ const isArray = initialValue.type === "ArrayExpression";
270
+ stateProperties.push({
271
+ key,
272
+ initialValue,
273
+ isObject,
274
+ isArray
275
+ // Add isArray flag
276
+ });
277
+ if (member.decorators) {
278
+ member.decorators = member.decorators.filter(
279
+ (decorator) => {
280
+ if (decorator.expression.type === "Identifier" && decorator.expression.name === "state") {
281
+ return false;
282
+ }
283
+ if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "state") {
284
+ return false;
285
+ }
286
+ return true;
287
+ }
288
+ );
289
+ }
290
+ member.value = void 0;
291
+ }
292
+ } catch (error) {
293
+ console.error(
294
+ `[Babel Plugin WSX State] CRASH in member processing for '${propertyName}':`,
295
+ error
296
+ );
297
+ console.error(`Stack trace:`, error.stack);
298
+ throw error;
112
299
  }
113
300
  }
114
301
  }
@@ -572,18 +759,39 @@ function vitePluginWSXWithBabel(options = {}) {
572
759
  const babelResult = (0, import_core.transformSync)(transformedCode, {
573
760
  filename: id,
574
761
  // Pass the actual filename so Babel knows it's .wsx
762
+ // CRITICAL: Configure parser to preserve decorators
763
+ // @babel/preset-typescript should preserve decorators by default,
764
+ // but we explicitly enable decorator parsing to be sure
765
+ parserOpts: {
766
+ plugins: [
767
+ ["decorators", { decoratorsBeforeExport: true }],
768
+ // Stage 3 decorators
769
+ "typescript",
770
+ "jsx"
771
+ ]
772
+ },
575
773
  presets: [
576
774
  [
577
775
  "@babel/preset-typescript",
578
776
  {
579
777
  isTSX: true,
580
778
  // Enable JSX syntax
581
- allExtensions: true
779
+ allExtensions: true,
582
780
  // Process all extensions, including .wsx
781
+ // CRITICAL: onlyRemoveTypeImports only affects import statements
782
+ // Decorators are preserved by default in @babel/preset-typescript
783
+ // They are only removed if we explicitly configure it
784
+ onlyRemoveTypeImports: false
785
+ // Remove all type-only imports
583
786
  }
584
787
  ]
585
788
  ],
586
789
  plugins: [
790
+ // CRITICAL: Decorator plugin must run FIRST to parse decorators correctly
791
+ // This ensures decorators are properly parsed before our custom plugins try to process them
792
+ // However, we need to use a custom visitor that doesn't transform decorators yet
793
+ // Actually, we should NOT run decorator plugin first because it transforms decorators
794
+ // Instead, we rely on TypeScript preset to parse but not transform decorators
587
795
  // CRITICAL: Style injection plugin must run FIRST
588
796
  // This ensures _autoStyles property exists before state transformations
589
797
  ...autoStyleInjection && cssFileExists ? [
@@ -599,8 +807,20 @@ function vitePluginWSXWithBabel(options = {}) {
599
807
  // Focus key generation plugin runs early to add data-wsx-key attributes
600
808
  // This must run before JSX is transformed to h() calls
601
809
  babelPluginWSXFocus,
602
- // State decorator transformation runs after style injection
603
- babelPluginWSXState,
810
+ // CRITICAL: State decorator transformation must run BEFORE @babel/plugin-proposal-decorators
811
+ // This allows the plugin to detect @state decorators in their original form and throw errors if needed
812
+ // The plugin removes @state decorators after processing, so the decorator plugin won't see them
813
+ [
814
+ babelPluginWSXState,
815
+ {
816
+ // Pass ORIGINAL source code (before JSX import injection) to plugin
817
+ // This ensures we can detect @state decorators even if they're removed by TypeScript preset
818
+ originalSource: code
819
+ // Use original code, not transformedCode
820
+ }
821
+ ],
822
+ // Decorator plugin runs after our custom plugins
823
+ // This transforms remaining decorators (like @autoRegister) to runtime calls
604
824
  [
605
825
  "@babel/plugin-proposal-decorators",
606
826
  {
@@ -621,8 +841,13 @@ function vitePluginWSXWithBabel(options = {}) {
621
841
  });
622
842
  if (babelResult && babelResult.code) {
623
843
  transformedCode = babelResult.code;
844
+ } else {
845
+ throw new Error(
846
+ `[WSX Plugin] Babel transform returned no code for ${id}. @state decorators will NOT be processed and will cause runtime errors. Please check Babel configuration and plugin setup.`
847
+ );
624
848
  }
625
- } catch {
849
+ } catch (error) {
850
+ throw error;
626
851
  }
627
852
  const hasJSXAfterBabel = transformedCode.includes('from "@wsxjs/wsx-core"') && (new RegExp(`[{,]\\s*${jsxFactory}\\s*[},]`).test(transformedCode) || new RegExp(`[{,]\\s*${jsxFragment}\\s*[},]`).test(transformedCode));
628
853
  if ((transformedCode.includes("<") || transformedCode.includes("Fragment")) && !hasJSXAfterBabel) {