@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.mjs CHANGED
@@ -1,92 +1,406 @@
1
- // src/vite-plugin-wsx.ts
1
+ // src/vite-plugin-wsx-babel.ts
2
2
  import { transform } from "esbuild";
3
+ import { transformSync } from "@babel/core";
4
+ import { existsSync } from "fs";
5
+ import { dirname, join, basename } from "path";
6
+
7
+ // src/babel-plugin-wsx-state.ts
8
+ import * as tModule from "@babel/types";
9
+ function babelPluginWSXState() {
10
+ const t = tModule;
11
+ return {
12
+ name: "babel-plugin-wsx-state",
13
+ visitor: {
14
+ ClassDeclaration(path) {
15
+ const classBody = path.node.body;
16
+ const stateProperties = [];
17
+ console.info(
18
+ `[Babel Plugin WSX State] Processing class ${path.node.id?.name || "anonymous"}, members: ${classBody.body.length}`
19
+ );
20
+ for (const member of classBody.body) {
21
+ console.info(
22
+ ` - Member type: ${member.type}, key: ${member.type === "ClassProperty" || member.type === "ClassPrivateProperty" ? member.key?.name : "N/A"}`
23
+ );
24
+ if ((member.type === "ClassProperty" || member.type === "ClassPrivateProperty") && member.key.type === "Identifier") {
25
+ console.info(
26
+ ` - Property: ${member.key.name}, decorators: ${member.decorators?.length || 0}, hasValue: ${!!member.value}`
27
+ );
28
+ if (member.decorators && member.decorators.length > 0) {
29
+ member.decorators.forEach((decorator) => {
30
+ if (decorator.expression.type === "Identifier") {
31
+ console.info(` Decorator: ${decorator.expression.name}`);
32
+ } else if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier") {
33
+ console.debug(
34
+ ` Decorator: ${decorator.expression.callee.name}()`
35
+ );
36
+ }
37
+ });
38
+ }
39
+ const hasStateDecorator = member.decorators?.some(
40
+ (decorator) => {
41
+ if (decorator.expression.type === "Identifier" && decorator.expression.name === "state") {
42
+ return true;
43
+ }
44
+ if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "state") {
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ );
50
+ if (hasStateDecorator && member.value) {
51
+ const key = member.key.name;
52
+ const initialValue = member.value;
53
+ const isObject = initialValue.type === "ObjectExpression" || initialValue.type === "ArrayExpression";
54
+ stateProperties.push({
55
+ key,
56
+ initialValue,
57
+ isObject
58
+ });
59
+ if (member.decorators) {
60
+ member.decorators = member.decorators.filter(
61
+ (decorator) => {
62
+ if (decorator.expression.type === "Identifier" && decorator.expression.name === "state") {
63
+ return false;
64
+ }
65
+ if (decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "state") {
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ );
71
+ }
72
+ member.value = void 0;
73
+ }
74
+ }
75
+ }
76
+ if (stateProperties.length === 0) {
77
+ return;
78
+ }
79
+ let constructor = classBody.body.find(
80
+ (member) => member.type === "ClassMethod" && member.kind === "constructor"
81
+ );
82
+ if (!constructor) {
83
+ constructor = t.classMethod(
84
+ "constructor",
85
+ t.identifier("constructor"),
86
+ [],
87
+ t.blockStatement([])
88
+ );
89
+ classBody.body.unshift(constructor);
90
+ }
91
+ const statements = [];
92
+ const hasSuper = constructor.body.body.some(
93
+ (stmt) => stmt.type === "ExpressionStatement" && stmt.expression.type === "CallExpression" && stmt.expression.callee.type === "Super"
94
+ );
95
+ if (!hasSuper) {
96
+ statements.push(t.expressionStatement(t.callExpression(t.super(), [])));
97
+ }
98
+ for (const { key, initialValue, isObject } of stateProperties) {
99
+ if (isObject) {
100
+ statements.push(
101
+ t.expressionStatement(
102
+ t.assignmentExpression(
103
+ "=",
104
+ t.memberExpression(t.thisExpression(), t.identifier(key)),
105
+ t.callExpression(
106
+ t.memberExpression(
107
+ t.thisExpression(),
108
+ t.identifier("reactive")
109
+ ),
110
+ [initialValue]
111
+ )
112
+ )
113
+ )
114
+ );
115
+ } else {
116
+ const getterId = t.identifier(`_get${key}`);
117
+ const setterId = t.identifier(`_set${key}`);
118
+ statements.push(
119
+ t.variableDeclaration("const", [
120
+ t.variableDeclarator(
121
+ t.arrayPattern([getterId, setterId]),
122
+ t.callExpression(
123
+ t.memberExpression(
124
+ t.thisExpression(),
125
+ t.identifier("useState")
126
+ ),
127
+ [t.stringLiteral(key), initialValue]
128
+ )
129
+ )
130
+ ])
131
+ );
132
+ statements.push(
133
+ t.expressionStatement(
134
+ t.callExpression(
135
+ t.memberExpression(
136
+ t.identifier("Object"),
137
+ t.identifier("defineProperty")
138
+ ),
139
+ [
140
+ t.thisExpression(),
141
+ t.stringLiteral(key),
142
+ t.objectExpression([
143
+ t.objectProperty(t.identifier("get"), getterId),
144
+ t.objectProperty(t.identifier("set"), setterId),
145
+ t.objectProperty(
146
+ t.identifier("enumerable"),
147
+ t.booleanLiteral(true)
148
+ ),
149
+ t.objectProperty(
150
+ t.identifier("configurable"),
151
+ t.booleanLiteral(true)
152
+ )
153
+ ])
154
+ ]
155
+ )
156
+ )
157
+ );
158
+ }
159
+ }
160
+ constructor.body.body.push(...statements);
161
+ }
162
+ }
163
+ };
164
+ }
165
+
166
+ // src/babel-plugin-wsx-style.ts
167
+ import * as tModule2 from "@babel/types";
168
+ function hasStylesImport(program) {
169
+ for (const node of program.body) {
170
+ if (node.type === "ImportDeclaration") {
171
+ const source = node.source.value;
172
+ if (typeof source === "string" && (source.endsWith(".css?inline") || source.endsWith(".css"))) {
173
+ const defaultSpecifier = node.specifiers.find(
174
+ (spec) => spec.type === "ImportDefaultSpecifier"
175
+ );
176
+ if (defaultSpecifier) {
177
+ return true;
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return false;
183
+ }
184
+ function hasAutoStylesProperty(classBody) {
185
+ for (const member of classBody.body) {
186
+ if ((member.type === "ClassProperty" || member.type === "ClassPrivateProperty") && member.key.type === "Identifier" && member.key.name === "_autoStyles") {
187
+ return true;
188
+ }
189
+ }
190
+ return false;
191
+ }
192
+ function babelPluginWSXStyle() {
193
+ const t = tModule2;
194
+ return {
195
+ name: "babel-plugin-wsx-style",
196
+ visitor: {
197
+ Program(path, state) {
198
+ const { cssFileExists, cssFilePath, componentName } = state.opts;
199
+ if (!cssFileExists) {
200
+ return;
201
+ }
202
+ if (hasStylesImport(path.node)) {
203
+ console.info(
204
+ `[Babel Plugin WSX Style] Skipping ${componentName}: styles already manually imported`
205
+ );
206
+ return;
207
+ }
208
+ console.info(
209
+ `[Babel Plugin WSX Style] Injecting CSS import for ${componentName}: ${cssFilePath}`
210
+ );
211
+ const importStatement = t.importDeclaration(
212
+ [t.importDefaultSpecifier(t.identifier("styles"))],
213
+ t.stringLiteral(cssFilePath)
214
+ );
215
+ let insertIndex = 0;
216
+ for (let i = 0; i < path.node.body.length; i++) {
217
+ const node = path.node.body[i];
218
+ if (node.type === "ImportDeclaration") {
219
+ insertIndex = i + 1;
220
+ } else {
221
+ break;
222
+ }
223
+ }
224
+ path.node.body.splice(insertIndex, 0, importStatement);
225
+ },
226
+ ClassDeclaration(path, state) {
227
+ const { cssFileExists } = state.opts;
228
+ if (!cssFileExists) {
229
+ return;
230
+ }
231
+ const classBody = path.node.body;
232
+ if (hasAutoStylesProperty(classBody)) {
233
+ return;
234
+ }
235
+ const autoStylesProperty = t.classProperty(
236
+ t.identifier("_autoStyles"),
237
+ t.identifier("styles"),
238
+ null,
239
+ // typeAnnotation
240
+ [],
241
+ // decorators
242
+ false,
243
+ // computed
244
+ false
245
+ // static
246
+ );
247
+ classBody.body.unshift(autoStylesProperty);
248
+ }
249
+ }
250
+ };
251
+ }
252
+
253
+ // src/vite-plugin-wsx-babel.ts
3
254
  function getJSXFactoryImportPath(_options) {
4
255
  return "@wsxjs/wsx-core";
5
256
  }
6
- function vitePluginWSX(options = {}) {
257
+ function vitePluginWSXWithBabel(options = {}) {
7
258
  const {
8
259
  jsxFactory = "h",
9
260
  jsxFragment = "Fragment",
10
261
  debug = false,
11
- extensions = [".wsx"]
262
+ extensions = [".wsx"],
263
+ autoStyleInjection = true
12
264
  } = options;
13
265
  return {
14
- name: "vite-plugin-wsx",
266
+ name: "vite-plugin-wsx-babel",
15
267
  enforce: "pre",
16
- // 确保在 React 插件之前执行
17
- // 处理 .wsx 文件加载
18
- load(id) {
19
- const isWSXFile = extensions.some((ext) => id.endsWith(ext));
20
- if (!isWSXFile) {
21
- return null;
22
- }
23
- if (debug) {
24
- console.log(`[WSX Plugin] Loading: ${id}`);
25
- }
26
- return null;
27
- },
28
- // 在transform阶段处理文件
29
268
  async transform(code, id) {
30
269
  const isWSXFile = extensions.some((ext) => id.endsWith(ext));
31
270
  if (!isWSXFile) {
32
271
  return null;
33
272
  }
34
273
  if (debug) {
35
- console.log(`[WSX Plugin] Processing: ${id}`);
274
+ console.log(`[WSX Plugin Babel] Processing: ${id}`);
275
+ }
276
+ let cssFileExists = false;
277
+ let cssFilePath = "";
278
+ let componentName = "";
279
+ if (autoStyleInjection) {
280
+ const fileDir = dirname(id);
281
+ const fileName = basename(id, extensions.find((ext) => id.endsWith(ext)) || "");
282
+ const cssFilePathWithoutQuery = join(fileDir, `${fileName}.css`);
283
+ cssFileExists = existsSync(cssFilePathWithoutQuery);
284
+ componentName = fileName;
285
+ if (cssFileExists) {
286
+ cssFilePath = `./${fileName}.css?inline`;
287
+ }
288
+ if (cssFileExists) {
289
+ console.log(
290
+ `[WSX Plugin Babel] Found CSS file for auto-injection: ${cssFilePathWithoutQuery}, will inject: ${cssFilePath}`
291
+ );
292
+ }
36
293
  }
37
294
  let transformedCode = code;
38
295
  const hasWSXCoreImport = code.includes('from "@wsxjs/wsx-core"');
39
296
  const hasJSXInImport = hasWSXCoreImport && (new RegExp(`[{,]\\s*${jsxFactory}\\s*[},]`).test(code) || new RegExp(`[{,]\\s*${jsxFragment}\\s*[},]`).test(code));
40
- if (debug) {
41
- console.log(`[WSX Plugin] Checking JSX imports for: ${id}`);
42
- console.log(` - hasWSXCoreImport: ${hasWSXCoreImport}`);
43
- console.log(` - hasJSXInImport: ${hasJSXInImport}`);
44
- console.log(` - has < character: ${code.includes("<")}`);
45
- console.log(` - has Fragment: ${code.includes("Fragment")}`);
46
- }
47
297
  if ((code.includes("<") || code.includes("Fragment")) && !hasJSXInImport) {
48
298
  const importPath = getJSXFactoryImportPath(options);
49
299
  const importStatement = `import { ${jsxFactory}, ${jsxFragment} } from "${importPath}";
300
+ `;
301
+ transformedCode = importStatement + transformedCode;
302
+ }
303
+ try {
304
+ const babelResult = transformSync(transformedCode, {
305
+ filename: id,
306
+ // Pass the actual filename so Babel knows it's .wsx
307
+ presets: [
308
+ [
309
+ "@babel/preset-typescript",
310
+ {
311
+ isTSX: true,
312
+ // Enable JSX syntax
313
+ allExtensions: true
314
+ // Process all extensions, including .wsx
315
+ }
316
+ ]
317
+ ],
318
+ plugins: [
319
+ // CRITICAL: Style injection plugin must run FIRST
320
+ // This ensures _autoStyles property exists before state transformations
321
+ ...autoStyleInjection && cssFileExists ? [
322
+ [
323
+ babelPluginWSXStyle,
324
+ {
325
+ cssFileExists,
326
+ cssFilePath,
327
+ componentName
328
+ }
329
+ ]
330
+ ] : [],
331
+ // State decorator transformation runs after style injection
332
+ babelPluginWSXState,
333
+ [
334
+ "@babel/plugin-proposal-decorators",
335
+ {
336
+ version: "2023-05",
337
+ decoratorsBeforeExport: true
338
+ }
339
+ ],
340
+ [
341
+ "@babel/plugin-proposal-class-properties",
342
+ {
343
+ loose: false
344
+ }
345
+ ],
346
+ "@babel/plugin-transform-class-static-block"
347
+ // Support static class blocks
348
+ ]
349
+ // parserOpts not needed - @babel/preset-typescript and plugins handle it
350
+ });
351
+ if (babelResult && babelResult.code) {
352
+ transformedCode = babelResult.code;
353
+ if (debug) {
354
+ console.log(`[WSX Plugin Babel] Decorators preprocessed: ${id}`);
355
+ if (transformedCode.includes("this.reactive") || transformedCode.includes("this.useState")) {
356
+ console.log(
357
+ `[WSX Plugin Babel] Generated reactive code found in: ${id}
358
+ ` + transformedCode.split("\n").filter(
359
+ (line) => line.includes("this.reactive") || line.includes("this.useState")
360
+ ).join("\n")
361
+ );
362
+ }
363
+ }
364
+ }
365
+ } catch (error) {
366
+ console.warn(
367
+ `[WSX Plugin Babel] Babel transform failed for ${id}, falling back to esbuild only:`,
368
+ error
369
+ );
370
+ }
371
+ const hasJSXAfterBabel = transformedCode.includes('from "@wsxjs/wsx-core"') && (new RegExp(`[{,]\\s*${jsxFactory}\\s*[},]`).test(transformedCode) || new RegExp(`[{,]\\s*${jsxFragment}\\s*[},]`).test(transformedCode));
372
+ if ((transformedCode.includes("<") || transformedCode.includes("Fragment")) && !hasJSXAfterBabel) {
373
+ const importPath = getJSXFactoryImportPath(options);
374
+ const importStatement = `import { ${jsxFactory}, ${jsxFragment} } from "${importPath}";
50
375
  `;
51
376
  transformedCode = importStatement + transformedCode;
52
377
  if (debug) {
53
- console.log(`[WSX Plugin] Added JSX factory import to: ${id}`);
378
+ console.log(
379
+ `[WSX Plugin Babel] Re-added JSX imports after Babel transform: ${id}`
380
+ );
54
381
  }
55
382
  }
56
383
  try {
57
384
  const result = await transform(transformedCode, {
58
- loader: "tsx",
385
+ loader: "jsx",
386
+ // Already TypeScript-transformed by Babel
59
387
  jsx: "transform",
60
388
  jsxFactory,
61
389
  jsxFragment,
62
390
  target: "es2020",
63
391
  format: "esm"
64
- // Esbuild supports decorators natively with tsx loader
65
392
  });
66
- if (debug) {
67
- console.log(`[WSX Plugin] JSX transformed: ${id}`);
68
- }
69
393
  return {
70
394
  code: result.code,
71
395
  map: null
72
396
  };
73
397
  } catch (error) {
74
- console.error(`[WSX Plugin] Transform error for ${id}:`, error);
398
+ console.error(`[WSX Plugin Babel] Transform error for ${id}:`, error);
75
399
  throw error;
76
400
  }
77
- },
78
- // We handle JSX transformation directly in the transform hook
79
- // No need to modify global esbuild config
80
- // 构建开始时的日志
81
- buildStart() {
82
- if (debug) {
83
- console.log(`[WSX Plugin] Build started with extensions: ${extensions.join(", ")}`);
84
- console.log(`[WSX Plugin] JSX Factory: ${jsxFactory}, Fragment: ${jsxFragment}`);
85
- }
86
401
  }
87
402
  };
88
403
  }
89
- var vite_plugin_wsx_default = vitePluginWSX;
90
404
  export {
91
- vite_plugin_wsx_default as wsx
405
+ vitePluginWSXWithBabel as wsx
92
406
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-vite-plugin",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Vite plugin for WSX Framework",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -25,9 +25,17 @@
25
25
  "web-components"
26
26
  ],
27
27
  "dependencies": {
28
- "@wsxjs/wsx-core": "0.0.7"
28
+ "@babel/core": "^7.28.0",
29
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
30
+ "@babel/plugin-proposal-decorators": "^7.28.0",
31
+ "@babel/plugin-transform-class-static-block": "^7.28.0",
32
+ "@babel/preset-typescript": "^7.28.5",
33
+ "@babel/types": "^7.28.1",
34
+ "@wsxjs/wsx-core": "0.0.8"
29
35
  },
30
36
  "devDependencies": {
37
+ "@babel/traverse": "^7.28.5",
38
+ "@types/babel__core": "^7.20.5",
31
39
  "@types/jest": "^29.0.0",
32
40
  "@types/node": "^20.0.0",
33
41
  "jest": "^29.0.0",
@@ -37,12 +45,12 @@
37
45
  "vite": "^5.0.0"
38
46
  },
39
47
  "peerDependencies": {
40
- "vite": ">=4.0.0",
41
- "esbuild": ">=0.25.0"
48
+ "esbuild": ">=0.25.0",
49
+ "vite": ">=4.0.0"
42
50
  },
43
51
  "scripts": {
44
- "build": "tsup src/index.ts --format cjs,esm --dts --external esbuild",
45
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch --external esbuild",
52
+ "build": "tsup src/index.ts --format cjs,esm --dts --external esbuild --external @babel/core --external @babel/preset-typescript --external @babel/plugin-proposal-decorators --external @babel/plugin-proposal-class-properties --external @babel/plugin-transform-class-static-block --external @babel/types",
53
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch --external esbuild --external @babel/core --external @babel/preset-typescript --external @babel/plugin-proposal-decorators --external @babel/plugin-proposal-class-properties --external @babel/plugin-transform-class-static-block --external @babel/types",
46
54
  "test": "jest",
47
55
  "test:watch": "jest --watch",
48
56
  "test:coverage": "jest --coverage",