@wsxjs/wsx-vite-plugin 0.0.7 → 0.0.8
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.mts +6 -26
- package/dist/index.d.ts +6 -26
- package/dist/index.js +367 -43
- package/dist/index.mjs +357 -43
- package/package.json +14 -6
- package/src/babel-plugin-wsx-state.ts +260 -0
- package/src/babel-plugin-wsx-style.ts +149 -0
- package/src/index.ts +2 -1
- package/src/vite-plugin-wsx-babel.ts +218 -0
- package/src/vite-plugin-wsx.ts +0 -166
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Babel plugin to transform @state decorator to compile-time Proxy creation
|
|
3
|
+
*
|
|
4
|
+
* Transforms:
|
|
5
|
+
* @state private state = { count: 0 };
|
|
6
|
+
*
|
|
7
|
+
* To:
|
|
8
|
+
* private state;
|
|
9
|
+
* constructor() {
|
|
10
|
+
* super();
|
|
11
|
+
* this.state = this.reactive({ count: 0 });
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { PluginObj, PluginPass } from "@babel/core";
|
|
16
|
+
import type * as t from "@babel/types";
|
|
17
|
+
import * as tModule from "@babel/types";
|
|
18
|
+
|
|
19
|
+
interface WSXStatePluginPass extends PluginPass {
|
|
20
|
+
stateProperties: Array<{
|
|
21
|
+
key: string;
|
|
22
|
+
initialValue: t.Expression;
|
|
23
|
+
isObject: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
reactiveMethodName: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function babelPluginWSXState(): PluginObj<WSXStatePluginPass> {
|
|
29
|
+
const t = tModule;
|
|
30
|
+
return {
|
|
31
|
+
name: "babel-plugin-wsx-state",
|
|
32
|
+
visitor: {
|
|
33
|
+
ClassDeclaration(path) {
|
|
34
|
+
const classBody = path.node.body;
|
|
35
|
+
const stateProperties: Array<{
|
|
36
|
+
key: string;
|
|
37
|
+
initialValue: t.Expression;
|
|
38
|
+
isObject: boolean;
|
|
39
|
+
}> = [];
|
|
40
|
+
|
|
41
|
+
// Find all @state decorated properties
|
|
42
|
+
// Debug: log all class members
|
|
43
|
+
console.info(
|
|
44
|
+
`[Babel Plugin WSX State] Processing class ${path.node.id?.name || "anonymous"}, members: ${classBody.body.length}`
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
for (const member of classBody.body) {
|
|
48
|
+
// Debug: log member type
|
|
49
|
+
console.info(
|
|
50
|
+
` - Member type: ${member.type}, key: ${member.type === "ClassProperty" || member.type === "ClassPrivateProperty" ? (member.key as any)?.name : "N/A"}`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Check both ClassProperty and ClassPrivateProperty
|
|
54
|
+
// @babel/plugin-proposal-class-properties might convert them
|
|
55
|
+
if (
|
|
56
|
+
(member.type === "ClassProperty" ||
|
|
57
|
+
member.type === "ClassPrivateProperty") &&
|
|
58
|
+
member.key.type === "Identifier"
|
|
59
|
+
) {
|
|
60
|
+
// Debug: log all class properties
|
|
61
|
+
console.info(
|
|
62
|
+
` - Property: ${member.key.name}, decorators: ${member.decorators?.length || 0}, hasValue: ${!!member.value}`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (member.decorators && member.decorators.length > 0) {
|
|
66
|
+
// Debug: log decorator names
|
|
67
|
+
member.decorators.forEach((decorator) => {
|
|
68
|
+
if (decorator.expression.type === "Identifier") {
|
|
69
|
+
console.info(` Decorator: ${decorator.expression.name}`);
|
|
70
|
+
} else if (
|
|
71
|
+
decorator.expression.type === "CallExpression" &&
|
|
72
|
+
decorator.expression.callee.type === "Identifier"
|
|
73
|
+
) {
|
|
74
|
+
console.debug(
|
|
75
|
+
` Decorator: ${decorator.expression.callee.name}()`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check if has @state decorator
|
|
82
|
+
const hasStateDecorator = member.decorators?.some(
|
|
83
|
+
(decorator: t.Decorator) => {
|
|
84
|
+
if (
|
|
85
|
+
decorator.expression.type === "Identifier" &&
|
|
86
|
+
decorator.expression.name === "state"
|
|
87
|
+
) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
decorator.expression.type === "CallExpression" &&
|
|
92
|
+
decorator.expression.callee.type === "Identifier" &&
|
|
93
|
+
decorator.expression.callee.name === "state"
|
|
94
|
+
) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (hasStateDecorator && member.value) {
|
|
102
|
+
const key = member.key.name;
|
|
103
|
+
const initialValue = member.value as t.Expression;
|
|
104
|
+
|
|
105
|
+
// Determine if it's an object/array
|
|
106
|
+
const isObject =
|
|
107
|
+
initialValue.type === "ObjectExpression" ||
|
|
108
|
+
initialValue.type === "ArrayExpression";
|
|
109
|
+
|
|
110
|
+
stateProperties.push({
|
|
111
|
+
key,
|
|
112
|
+
initialValue,
|
|
113
|
+
isObject,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Remove @state decorator - but keep other decorators
|
|
117
|
+
if (member.decorators) {
|
|
118
|
+
member.decorators = member.decorators.filter(
|
|
119
|
+
(decorator: t.Decorator) => {
|
|
120
|
+
if (
|
|
121
|
+
decorator.expression.type === "Identifier" &&
|
|
122
|
+
decorator.expression.name === "state"
|
|
123
|
+
) {
|
|
124
|
+
return false; // Remove @state decorator
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
decorator.expression.type === "CallExpression" &&
|
|
128
|
+
decorator.expression.callee.type === "Identifier" &&
|
|
129
|
+
decorator.expression.callee.name === "state"
|
|
130
|
+
) {
|
|
131
|
+
return false; // Remove @state() decorator
|
|
132
|
+
}
|
|
133
|
+
return true; // Keep other decorators
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Remove initial value - it will be set in constructor via this.reactive()
|
|
139
|
+
// Keep the property declaration but without initial value
|
|
140
|
+
member.value = undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (stateProperties.length === 0) {
|
|
146
|
+
return; // No @state properties found
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Find or create constructor
|
|
150
|
+
let constructor = classBody.body.find(
|
|
151
|
+
(member: t.ClassBody["body"][number]): member is t.ClassMethod =>
|
|
152
|
+
member.type === "ClassMethod" && member.kind === "constructor"
|
|
153
|
+
) as t.ClassMethod | undefined;
|
|
154
|
+
|
|
155
|
+
if (!constructor) {
|
|
156
|
+
// Create constructor if it doesn't exist
|
|
157
|
+
constructor = t.classMethod(
|
|
158
|
+
"constructor",
|
|
159
|
+
t.identifier("constructor"),
|
|
160
|
+
[],
|
|
161
|
+
t.blockStatement([])
|
|
162
|
+
);
|
|
163
|
+
classBody.body.unshift(constructor);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add initialization code to constructor
|
|
167
|
+
const statements: t.Statement[] = [];
|
|
168
|
+
|
|
169
|
+
// Add super() call if not present
|
|
170
|
+
const hasSuper = constructor.body.body.some(
|
|
171
|
+
(stmt: t.Statement) =>
|
|
172
|
+
stmt.type === "ExpressionStatement" &&
|
|
173
|
+
stmt.expression.type === "CallExpression" &&
|
|
174
|
+
stmt.expression.callee.type === "Super"
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (!hasSuper) {
|
|
178
|
+
statements.push(t.expressionStatement(t.callExpression(t.super(), [])));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// CRITICAL: Add state property initialization AFTER all existing constructor code
|
|
182
|
+
// WebComponent already has reactive() and useState() methods
|
|
183
|
+
// We'll insert these statements at the END of constructor, not right after super()
|
|
184
|
+
for (const { key, initialValue, isObject } of stateProperties) {
|
|
185
|
+
if (isObject) {
|
|
186
|
+
// For objects/arrays: this.state = this.reactive({ count: 0 });
|
|
187
|
+
statements.push(
|
|
188
|
+
t.expressionStatement(
|
|
189
|
+
t.assignmentExpression(
|
|
190
|
+
"=",
|
|
191
|
+
t.memberExpression(t.thisExpression(), t.identifier(key)),
|
|
192
|
+
t.callExpression(
|
|
193
|
+
t.memberExpression(
|
|
194
|
+
t.thisExpression(),
|
|
195
|
+
t.identifier("reactive")
|
|
196
|
+
),
|
|
197
|
+
[initialValue]
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
// For primitives: use useState
|
|
204
|
+
// const [getState, setState] = this.useState("state", initialValue);
|
|
205
|
+
// Object.defineProperty(this, "state", { get: getState, set: setState });
|
|
206
|
+
const getterId = t.identifier(`_get${key}`);
|
|
207
|
+
const setterId = t.identifier(`_set${key}`);
|
|
208
|
+
|
|
209
|
+
statements.push(
|
|
210
|
+
t.variableDeclaration("const", [
|
|
211
|
+
t.variableDeclarator(
|
|
212
|
+
t.arrayPattern([getterId, setterId]),
|
|
213
|
+
t.callExpression(
|
|
214
|
+
t.memberExpression(
|
|
215
|
+
t.thisExpression(),
|
|
216
|
+
t.identifier("useState")
|
|
217
|
+
),
|
|
218
|
+
[t.stringLiteral(key), initialValue]
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
])
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
statements.push(
|
|
225
|
+
t.expressionStatement(
|
|
226
|
+
t.callExpression(
|
|
227
|
+
t.memberExpression(
|
|
228
|
+
t.identifier("Object"),
|
|
229
|
+
t.identifier("defineProperty")
|
|
230
|
+
),
|
|
231
|
+
[
|
|
232
|
+
t.thisExpression(),
|
|
233
|
+
t.stringLiteral(key),
|
|
234
|
+
t.objectExpression([
|
|
235
|
+
t.objectProperty(t.identifier("get"), getterId),
|
|
236
|
+
t.objectProperty(t.identifier("set"), setterId),
|
|
237
|
+
t.objectProperty(
|
|
238
|
+
t.identifier("enumerable"),
|
|
239
|
+
t.booleanLiteral(true)
|
|
240
|
+
),
|
|
241
|
+
t.objectProperty(
|
|
242
|
+
t.identifier("configurable"),
|
|
243
|
+
t.booleanLiteral(true)
|
|
244
|
+
),
|
|
245
|
+
]),
|
|
246
|
+
]
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// CRITICAL: Insert statements at the END of constructor
|
|
254
|
+
// WebComponent already has reactive() and useState() methods
|
|
255
|
+
// Inserting at the end ensures all constructor code has run
|
|
256
|
+
constructor.body.body.push(...statements);
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Babel plugin to automatically inject CSS styles for WSX components
|
|
3
|
+
*
|
|
4
|
+
* Transforms:
|
|
5
|
+
* // Button.wsx (if Button.css exists)
|
|
6
|
+
* export default class Button extends WebComponent { ... }
|
|
7
|
+
*
|
|
8
|
+
* To:
|
|
9
|
+
* import styles from "./Button.css?inline";
|
|
10
|
+
* export default class Button extends WebComponent {
|
|
11
|
+
* private _autoStyles = styles;
|
|
12
|
+
* ...
|
|
13
|
+
* }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { PluginObj, PluginPass } from "@babel/core";
|
|
17
|
+
import type * as t from "@babel/types";
|
|
18
|
+
import * as tModule from "@babel/types";
|
|
19
|
+
|
|
20
|
+
interface WSXStylePluginPass extends PluginPass {
|
|
21
|
+
cssFileExists: boolean;
|
|
22
|
+
cssFilePath: string;
|
|
23
|
+
componentName: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if styles are already imported in the file
|
|
28
|
+
*/
|
|
29
|
+
function hasStylesImport(program: t.Program): boolean {
|
|
30
|
+
for (const node of program.body) {
|
|
31
|
+
if (node.type === "ImportDeclaration") {
|
|
32
|
+
const source = node.source.value;
|
|
33
|
+
// Check if it's a CSS import with ?inline
|
|
34
|
+
if (
|
|
35
|
+
typeof source === "string" &&
|
|
36
|
+
(source.endsWith(".css?inline") || source.endsWith(".css"))
|
|
37
|
+
) {
|
|
38
|
+
// Check if it's imported as "styles"
|
|
39
|
+
const defaultSpecifier = node.specifiers.find(
|
|
40
|
+
(spec) => spec.type === "ImportDefaultSpecifier"
|
|
41
|
+
);
|
|
42
|
+
if (defaultSpecifier) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if _autoStyles property already exists in the class
|
|
53
|
+
*/
|
|
54
|
+
function hasAutoStylesProperty(classBody: t.ClassBody): boolean {
|
|
55
|
+
for (const member of classBody.body) {
|
|
56
|
+
if (
|
|
57
|
+
(member.type === "ClassProperty" || member.type === "ClassPrivateProperty") &&
|
|
58
|
+
member.key.type === "Identifier" &&
|
|
59
|
+
member.key.name === "_autoStyles"
|
|
60
|
+
) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default function babelPluginWSXStyle(): PluginObj<WSXStylePluginPass> {
|
|
68
|
+
const t = tModule;
|
|
69
|
+
return {
|
|
70
|
+
name: "babel-plugin-wsx-style",
|
|
71
|
+
visitor: {
|
|
72
|
+
Program(path, state) {
|
|
73
|
+
const { cssFileExists, cssFilePath, componentName } =
|
|
74
|
+
state.opts as WSXStylePluginPass;
|
|
75
|
+
|
|
76
|
+
// Skip if CSS file doesn't exist
|
|
77
|
+
if (!cssFileExists) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check if styles are already manually imported
|
|
82
|
+
if (hasStylesImport(path.node)) {
|
|
83
|
+
console.info(
|
|
84
|
+
`[Babel Plugin WSX Style] Skipping ${componentName}: styles already manually imported`
|
|
85
|
+
);
|
|
86
|
+
return; // Skip auto-injection if manual import exists
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.info(
|
|
90
|
+
`[Babel Plugin WSX Style] Injecting CSS import for ${componentName}: ${cssFilePath}`
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Add CSS import at the top of the file
|
|
94
|
+
const importStatement = t.importDeclaration(
|
|
95
|
+
[t.importDefaultSpecifier(t.identifier("styles"))],
|
|
96
|
+
t.stringLiteral(cssFilePath)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Insert after existing imports (if any)
|
|
100
|
+
let insertIndex = 0;
|
|
101
|
+
for (let i = 0; i < path.node.body.length; i++) {
|
|
102
|
+
const node = path.node.body[i];
|
|
103
|
+
if (node.type === "ImportDeclaration") {
|
|
104
|
+
insertIndex = i + 1;
|
|
105
|
+
} else {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
path.node.body.splice(insertIndex, 0, importStatement);
|
|
111
|
+
},
|
|
112
|
+
ClassDeclaration(path, state) {
|
|
113
|
+
const { cssFileExists } = state.opts as WSXStylePluginPass;
|
|
114
|
+
|
|
115
|
+
// Skip if CSS file doesn't exist
|
|
116
|
+
if (!cssFileExists) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const classBody = path.node.body;
|
|
121
|
+
|
|
122
|
+
// Check if _autoStyles property already exists
|
|
123
|
+
if (hasAutoStylesProperty(classBody)) {
|
|
124
|
+
return; // Skip if already exists
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Note: We don't check for manual imports here because:
|
|
128
|
+
// 1. Program visitor already handled that and skipped if manual import exists
|
|
129
|
+
// 2. If we reach here, it means Program visitor added the import (or it was already there)
|
|
130
|
+
// 3. We should add the class property regardless
|
|
131
|
+
|
|
132
|
+
// Add class property: private _autoStyles = styles;
|
|
133
|
+
// Use classProperty (not classPrivateProperty) to create TypeScript private property
|
|
134
|
+
// TypeScript private is compile-time only and becomes a regular property at runtime
|
|
135
|
+
const autoStylesProperty = t.classProperty(
|
|
136
|
+
t.identifier("_autoStyles"),
|
|
137
|
+
t.identifier("styles"),
|
|
138
|
+
null, // typeAnnotation
|
|
139
|
+
[], // decorators
|
|
140
|
+
false, // computed
|
|
141
|
+
false // static
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Insert at the beginning of class body (before methods and other properties)
|
|
145
|
+
classBody.body.unshift(autoStylesProperty);
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
// Export the Babel-based plugin (with compile-time decorator support)
|
|
2
|
+
export { vitePluginWSXWithBabel as wsx } from "./vite-plugin-wsx-babel";
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/**
|
|
3
|
+
* Vite Plugin for WSX with Babel decorator support
|
|
4
|
+
*
|
|
5
|
+
* Uses Babel to preprocess decorators before esbuild transformation
|
|
6
|
+
* This ensures decorators work correctly even with esbuild's limitations
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Plugin } from "vite";
|
|
10
|
+
import { transform } from "esbuild";
|
|
11
|
+
import { transformSync } from "@babel/core";
|
|
12
|
+
import { existsSync } from "fs";
|
|
13
|
+
import { dirname, join, basename } from "path";
|
|
14
|
+
import babelPluginWSXState from "./babel-plugin-wsx-state";
|
|
15
|
+
import babelPluginWSXStyle from "./babel-plugin-wsx-style";
|
|
16
|
+
|
|
17
|
+
export interface WSXPluginOptions {
|
|
18
|
+
jsxFactory?: string;
|
|
19
|
+
jsxFragment?: string;
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
extensions?: string[];
|
|
22
|
+
autoStyleInjection?: boolean; // Default: true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getJSXFactoryImportPath(_options: WSXPluginOptions): string {
|
|
26
|
+
return "@wsxjs/wsx-core";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function vitePluginWSXWithBabel(options: WSXPluginOptions = {}): Plugin {
|
|
30
|
+
const {
|
|
31
|
+
jsxFactory = "h",
|
|
32
|
+
jsxFragment = "Fragment",
|
|
33
|
+
debug = false,
|
|
34
|
+
extensions = [".wsx"],
|
|
35
|
+
autoStyleInjection = true,
|
|
36
|
+
} = options;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
name: "vite-plugin-wsx-babel",
|
|
40
|
+
enforce: "pre",
|
|
41
|
+
|
|
42
|
+
async transform(code: string, id: string) {
|
|
43
|
+
const isWSXFile = extensions.some((ext) => id.endsWith(ext));
|
|
44
|
+
|
|
45
|
+
if (!isWSXFile) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (debug) {
|
|
50
|
+
console.log(`[WSX Plugin Babel] Processing: ${id}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if corresponding CSS file exists (for auto style injection)
|
|
54
|
+
let cssFileExists = false;
|
|
55
|
+
let cssFilePath = "";
|
|
56
|
+
let componentName = "";
|
|
57
|
+
|
|
58
|
+
if (autoStyleInjection) {
|
|
59
|
+
const fileDir = dirname(id);
|
|
60
|
+
const fileName = basename(id, extensions.find((ext) => id.endsWith(ext)) || "");
|
|
61
|
+
const cssFilePathWithoutQuery = join(fileDir, `${fileName}.css`);
|
|
62
|
+
cssFileExists = existsSync(cssFilePathWithoutQuery);
|
|
63
|
+
componentName = fileName;
|
|
64
|
+
|
|
65
|
+
// Generate relative path for import (e.g., "./Button.css?inline")
|
|
66
|
+
// Use relative path from the file's directory
|
|
67
|
+
if (cssFileExists) {
|
|
68
|
+
// For import statement, use relative path with ?inline query
|
|
69
|
+
cssFilePath = `./${fileName}.css?inline`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (cssFileExists) {
|
|
73
|
+
console.log(
|
|
74
|
+
`[WSX Plugin Babel] Found CSS file for auto-injection: ${cssFilePathWithoutQuery}, will inject: ${cssFilePath}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let transformedCode = code;
|
|
80
|
+
|
|
81
|
+
// 1. Add JSX imports if needed
|
|
82
|
+
// Check for actual import statement
|
|
83
|
+
const hasWSXCoreImport = code.includes('from "@wsxjs/wsx-core"');
|
|
84
|
+
// Check if JSX factory functions are already imported
|
|
85
|
+
const hasJSXInImport =
|
|
86
|
+
hasWSXCoreImport &&
|
|
87
|
+
(new RegExp(`[{,]\\s*${jsxFactory}\\s*[},]`).test(code) ||
|
|
88
|
+
new RegExp(`[{,]\\s*${jsxFragment}\\s*[},]`).test(code));
|
|
89
|
+
|
|
90
|
+
// If file has JSX syntax but no import, add it
|
|
91
|
+
// Note: @jsxImportSource pragma is just a hint, we need actual import for esbuild
|
|
92
|
+
if ((code.includes("<") || code.includes("Fragment")) && !hasJSXInImport) {
|
|
93
|
+
const importPath = getJSXFactoryImportPath(options);
|
|
94
|
+
const importStatement = `import { ${jsxFactory}, ${jsxFragment} } from "${importPath}";\n`;
|
|
95
|
+
transformedCode = importStatement + transformedCode;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 2. Use Babel to preprocess decorators
|
|
99
|
+
try {
|
|
100
|
+
const babelResult = transformSync(transformedCode, {
|
|
101
|
+
filename: id, // Pass the actual filename so Babel knows it's .wsx
|
|
102
|
+
presets: [
|
|
103
|
+
[
|
|
104
|
+
"@babel/preset-typescript",
|
|
105
|
+
{
|
|
106
|
+
isTSX: true, // Enable JSX syntax
|
|
107
|
+
allExtensions: true, // Process all extensions, including .wsx
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
],
|
|
111
|
+
plugins: [
|
|
112
|
+
// CRITICAL: Style injection plugin must run FIRST
|
|
113
|
+
// This ensures _autoStyles property exists before state transformations
|
|
114
|
+
...(autoStyleInjection && cssFileExists
|
|
115
|
+
? [
|
|
116
|
+
[
|
|
117
|
+
babelPluginWSXStyle,
|
|
118
|
+
{
|
|
119
|
+
cssFileExists,
|
|
120
|
+
cssFilePath,
|
|
121
|
+
componentName,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
]
|
|
125
|
+
: []),
|
|
126
|
+
// State decorator transformation runs after style injection
|
|
127
|
+
babelPluginWSXState,
|
|
128
|
+
[
|
|
129
|
+
"@babel/plugin-proposal-decorators",
|
|
130
|
+
{
|
|
131
|
+
version: "2023-05",
|
|
132
|
+
decoratorsBeforeExport: true,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
[
|
|
136
|
+
"@babel/plugin-proposal-class-properties",
|
|
137
|
+
{
|
|
138
|
+
loose: false,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
"@babel/plugin-transform-class-static-block", // Support static class blocks
|
|
142
|
+
],
|
|
143
|
+
// parserOpts not needed - @babel/preset-typescript and plugins handle it
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (babelResult && babelResult.code) {
|
|
147
|
+
transformedCode = babelResult.code;
|
|
148
|
+
if (debug) {
|
|
149
|
+
console.log(`[WSX Plugin Babel] Decorators preprocessed: ${id}`);
|
|
150
|
+
// Log generated code for debugging
|
|
151
|
+
if (
|
|
152
|
+
transformedCode.includes("this.reactive") ||
|
|
153
|
+
transformedCode.includes("this.useState")
|
|
154
|
+
) {
|
|
155
|
+
console.log(
|
|
156
|
+
`[WSX Plugin Babel] Generated reactive code found in: ${id}\n` +
|
|
157
|
+
transformedCode
|
|
158
|
+
.split("\n")
|
|
159
|
+
.filter(
|
|
160
|
+
(line) =>
|
|
161
|
+
line.includes("this.reactive") ||
|
|
162
|
+
line.includes("this.useState")
|
|
163
|
+
)
|
|
164
|
+
.join("\n")
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.warn(
|
|
171
|
+
`[WSX Plugin Babel] Babel transform failed for ${id}, falling back to esbuild only:`,
|
|
172
|
+
error
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 2.5. Ensure JSX imports still exist after Babel transformation
|
|
177
|
+
// Babel might have removed or modified imports, so we check again
|
|
178
|
+
const hasJSXAfterBabel =
|
|
179
|
+
transformedCode.includes('from "@wsxjs/wsx-core"') &&
|
|
180
|
+
(new RegExp(`[{,]\\s*${jsxFactory}\\s*[},]`).test(transformedCode) ||
|
|
181
|
+
new RegExp(`[{,]\\s*${jsxFragment}\\s*[},]`).test(transformedCode));
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
(transformedCode.includes("<") || transformedCode.includes("Fragment")) &&
|
|
185
|
+
!hasJSXAfterBabel
|
|
186
|
+
) {
|
|
187
|
+
const importPath = getJSXFactoryImportPath(options);
|
|
188
|
+
const importStatement = `import { ${jsxFactory}, ${jsxFragment} } from "${importPath}";\n`;
|
|
189
|
+
transformedCode = importStatement + transformedCode;
|
|
190
|
+
if (debug) {
|
|
191
|
+
console.log(
|
|
192
|
+
`[WSX Plugin Babel] Re-added JSX imports after Babel transform: ${id}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 3. Use esbuild for JSX transformation
|
|
198
|
+
try {
|
|
199
|
+
const result = await transform(transformedCode, {
|
|
200
|
+
loader: "jsx", // Already TypeScript-transformed by Babel
|
|
201
|
+
jsx: "transform",
|
|
202
|
+
jsxFactory: jsxFactory,
|
|
203
|
+
jsxFragment: jsxFragment,
|
|
204
|
+
target: "es2020",
|
|
205
|
+
format: "esm",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
code: result.code,
|
|
210
|
+
map: null,
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`[WSX Plugin Babel] Transform error for ${id}:`, error);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|