@plumeria/compiler 0.24.5 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/extract.js +272 -171
  2. package/package.json +7 -7
package/dist/extract.js CHANGED
@@ -7,6 +7,7 @@ exports.generatedTsMap = void 0;
7
7
  exports.extractTSFile = extractTSFile;
8
8
  exports.restoreAllOriginals = restoreAllOriginals;
9
9
  exports.extractVueAndSvelte = extractVueAndSvelte;
10
+ const core_1 = require("@swc/core");
10
11
  const fs_1 = __importDefault(require("fs"));
11
12
  const path_1 = __importDefault(require("path"));
12
13
  const generatedTsMap = new Map();
@@ -115,171 +116,267 @@ function isInHtmlText(code, position) {
115
116
  }
116
117
  return lastCloseTag > lastOpenTag && nextOpenTag > position;
117
118
  }
118
- function extractCssProps(code) {
119
- const propsMatches = [];
120
- const regex = /css\.props\s*\(/g;
121
- let match;
122
- while ((match = regex.exec(code))) {
123
- if (isInComment(code, match.index) ||
124
- isInString(code, match.index) ||
125
- isInHtmlText(code, match.index)) {
126
- continue;
127
- }
128
- const startIndex = match.index + match[0].length;
129
- let parenCount = 1;
130
- let currentIndex = startIndex;
131
- let args = '';
132
- while (parenCount > 0 && currentIndex < code.length) {
133
- const char = code[currentIndex];
134
- if (char === '(') {
135
- parenCount++;
136
- }
137
- else if (char === ')') {
138
- parenCount--;
119
+ function expressionToString(expr) {
120
+ switch (expr.type) {
121
+ case 'Identifier':
122
+ return expr.value;
123
+ case 'MemberExpression': {
124
+ const obj = expressionToString(expr.object);
125
+ if (obj && expr.property.type === 'Identifier') {
126
+ return `${obj}.${expr.property.value}`;
139
127
  }
140
- if (parenCount > 0 || char !== ')') {
141
- args += char;
128
+ break;
129
+ }
130
+ case 'CallExpression': {
131
+ if (expr.callee.type !== 'Super' && expr.callee.type !== 'Import') {
132
+ const callee = expressionToString(expr.callee);
133
+ if (callee) {
134
+ const args = expr.arguments
135
+ .map((arg) => expressionToString(arg.expression))
136
+ .join(', ');
137
+ return `${callee}(${args})`;
138
+ }
142
139
  }
143
- currentIndex++;
140
+ break;
144
141
  }
145
- if (parenCount === 0) {
146
- const originalArgs = args.split(/\s*,\s*(?![^(]*\))/);
147
- const staticArgs = [];
148
- const conditionalStyleObjects = [];
149
- for (const arg of originalArgs) {
150
- const trimmedArg = arg.trim();
151
- if (trimmedArg) {
152
- if (trimmedArg.includes('&&') || trimmedArg.includes('?')) {
153
- const styles = parseCssPropsArguments(trimmedArg);
154
- conditionalStyleObjects.push(...styles);
142
+ case 'ObjectExpression': {
143
+ const properties = expr.properties
144
+ .map((prop) => {
145
+ if ('key' in prop && prop.key && prop.key.type === 'Identifier') {
146
+ const key = prop.key.value;
147
+ const value = expressionToString(prop.value);
148
+ return `${key}: ${value}`;
149
+ }
150
+ return '[complex property]';
151
+ })
152
+ .join(', ');
153
+ console.warn(`css.props: Argument unsupported ${expr.type}: { ${properties} } Use css.create instead.`);
154
+ return '';
155
+ }
156
+ case 'StringLiteral':
157
+ return String(expr.value);
158
+ }
159
+ console.warn(`css.props: Argument unsupported ${expr.type}: Use css.create instead.`);
160
+ return '';
161
+ }
162
+ function extractCssProps(ast) {
163
+ const propsMatches = [];
164
+ try {
165
+ visit(ast, {
166
+ CallExpression: (node) => {
167
+ if (node.callee.type === 'MemberExpression' &&
168
+ node.callee.object.type === 'Identifier' &&
169
+ node.callee.object.value === 'css' &&
170
+ node.callee.property.type === 'Identifier' &&
171
+ node.callee.property.value === 'props') {
172
+ const staticArgs = [];
173
+ const conditionalStyleObjects = [];
174
+ for (const arg of node.arguments) {
175
+ if (arg.expression) {
176
+ if (arg.expression.type === 'ConditionalExpression' ||
177
+ (arg.expression.type === 'BinaryExpression' &&
178
+ arg.expression.operator === '&&')) {
179
+ const styles = extractStyleObjectsFromExpression(arg.expression);
180
+ conditionalStyleObjects.push(...styles);
181
+ }
182
+ else {
183
+ const argStr = expressionToString(arg.expression);
184
+ if (argStr) {
185
+ staticArgs.push(argStr);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ if (staticArgs.length > 0) {
191
+ propsMatches.push(`css.props(${staticArgs.join(', ')})`);
155
192
  }
156
- else {
157
- staticArgs.push(trimmedArg);
193
+ for (const styleObj of conditionalStyleObjects) {
194
+ if (styleObj &&
195
+ styleObj !== 'false' &&
196
+ styleObj !== 'null' &&
197
+ styleObj !== 'undefined') {
198
+ propsMatches.push(`css.props(${styleObj})`);
199
+ }
158
200
  }
159
201
  }
202
+ },
203
+ });
204
+ }
205
+ catch (e) {
206
+ console.error(`Failed to parse code to extract css.props: ${e}`);
207
+ }
208
+ return propsMatches;
209
+ }
210
+ function extractStyleObjectsFromExpression(expression) {
211
+ switch (expression.type) {
212
+ case 'BinaryExpression':
213
+ if (expression.operator === '&&') {
214
+ return extractStyleObjectsFromExpression(expression.right);
160
215
  }
161
- if (staticArgs.length > 0) {
162
- propsMatches.push(`css.props(${staticArgs.join(', ')})`);
163
- }
164
- for (const styleObj of conditionalStyleObjects) {
165
- if (styleObj &&
166
- styleObj !== 'false' &&
167
- styleObj !== 'null' &&
168
- styleObj !== 'undefined') {
169
- propsMatches.push(`css.props(${styleObj})`);
170
- }
216
+ break;
217
+ case 'ConditionalExpression':
218
+ return [
219
+ ...extractStyleObjectsFromExpression(expression.consequent),
220
+ ...extractStyleObjectsFromExpression(expression.alternate),
221
+ ];
222
+ case 'BooleanLiteral':
223
+ case 'NullLiteral':
224
+ return [];
225
+ case 'Identifier':
226
+ if (expression.value === 'undefined') {
227
+ return [];
171
228
  }
229
+ case 'ObjectExpression': {
230
+ const str = expressionToString(expression);
231
+ return str ? [str] : [];
172
232
  }
173
233
  }
174
- return propsMatches;
234
+ const str = expressionToString(expression);
235
+ if (str) {
236
+ return [str];
237
+ }
238
+ return [];
175
239
  }
176
- function extractStaticStringLiteralVariable(code) {
240
+ function extractStaticStringLiteralVariable(ast) {
177
241
  const matches = [];
178
- const regex = /\b(?:var|let|const)\s+[a-zA-Z_$][\w$]*\s*=\s*(['"])(.*?)\1\s*;?/gm;
179
- let match;
180
- while ((match = regex.exec(code))) {
181
- const index = match.index;
182
- if (isInComment(code, index) ||
183
- isInString(code, index) ||
184
- isInHtmlText(code, index)) {
185
- continue;
186
- }
187
- matches.push(match[0]);
242
+ try {
243
+ visit(ast, {
244
+ VariableDeclaration: (node) => {
245
+ const allStringLiterals = node.declarations.length > 0 &&
246
+ node.declarations.every((decl) => decl.init && decl.init.type === 'StringLiteral');
247
+ if (allStringLiterals) {
248
+ const { code: extractedCode } = (0, core_1.printSync)({
249
+ type: 'Module',
250
+ body: [node],
251
+ span: { start: 0, end: 0, ctxt: 0 },
252
+ });
253
+ matches.push(extractedCode.trim());
254
+ }
255
+ },
256
+ });
257
+ }
258
+ catch (e) {
259
+ console.error(`Failed to parse code to extract static string literal variables: ${e}`);
188
260
  }
189
261
  return matches.join('\n');
190
262
  }
191
- function extractCssCreate(code) {
192
- const cssCreateMatches = [];
193
- const regex = /(?:(?:\s*const\s+[a-zA-Z0-9_$]+\s*=\s*css\.create\([\s\S]*?\);\s*))/g;
263
+ function extractCssPropsFromTemplate(code) {
264
+ const matches = [];
265
+ const regex = /css\.props\(([^)]*)\)/g;
194
266
  let match;
195
- while ((match = regex.exec(code))) {
196
- if (isInComment(code, match.index) ||
197
- isInString(code, match.index) ||
198
- isInHtmlText(code, match.index)) {
267
+ while ((match = regex.exec(code)) !== null) {
268
+ const matchStart = match.index;
269
+ if (isInComment(code, matchStart) ||
270
+ isInString(code, matchStart) ||
271
+ isInHtmlText(code, matchStart)) {
199
272
  continue;
200
273
  }
201
- cssCreateMatches.push(match[0]);
202
- }
203
- return cssCreateMatches.join('\n');
204
- }
205
- function extractCssKeyframes(code) {
206
- const cssCreateMatches = [];
207
- const regex = /(?:(?:\s*const\s+[a-zA-Z0-9_$]+\s*=\s*css\.keyframes\([\s\S]*?\);\s*))/g;
208
- let match;
209
- while ((match = regex.exec(code))) {
210
- if (isInComment(code, match.index) ||
211
- isInString(code, match.index) ||
212
- isInHtmlText(code, match.index)) {
274
+ const scriptStartIndex = code.indexOf('<script');
275
+ const scriptEndIndex = code.indexOf('</script>');
276
+ if (scriptStartIndex !== -1 &&
277
+ scriptEndIndex !== -1 &&
278
+ match.index > scriptStartIndex &&
279
+ match.index < scriptEndIndex) {
213
280
  continue;
214
281
  }
215
- cssCreateMatches.push(match[0]);
282
+ const args = match[1];
283
+ if (args && !args.includes('{') && !args.includes('(')) {
284
+ matches.push(`css.props(${args})`);
285
+ }
216
286
  }
217
- return cssCreateMatches.join('\n');
287
+ return matches;
218
288
  }
219
- function extractCssViewTransition(code) {
220
- const cssCreateMatches = [];
221
- const regex = /(?:(?:\s*const\s+[a-zA-Z0-9_$]+\s*=\s*css\.viewTransition\([\s\S]*?\);\s*))/g;
222
- let match;
223
- while ((match = regex.exec(code))) {
224
- if (isInComment(code, match.index) ||
225
- isInString(code, match.index) ||
226
- isInHtmlText(code, match.index)) {
227
- continue;
289
+ function visit(node, visitor) {
290
+ if (!node)
291
+ return;
292
+ const visitorFunc = visitor[node.type];
293
+ if (visitorFunc) {
294
+ visitorFunc(node);
295
+ }
296
+ for (const key in node) {
297
+ if (typeof node[key] === 'object' && node[key] !== null) {
298
+ if (Array.isArray(node[key])) {
299
+ for (const child of node[key]) {
300
+ visit(child, visitor);
301
+ }
302
+ }
303
+ else {
304
+ visit(node[key], visitor);
305
+ }
228
306
  }
229
- cssCreateMatches.push(match[0]);
230
307
  }
231
- return cssCreateMatches.join('\n');
232
308
  }
233
- function extractCssDefineConsts(code) {
234
- const cssCreateMatches = [];
235
- const regex = /(?:(?:\s*const\s+[a-zA-Z0-9_$]+\s*=\s*css\.defineConsts\([\s\S]*?\);\s*))/g;
236
- let match;
237
- while ((match = regex.exec(code))) {
238
- if (isInComment(code, match.index) ||
239
- isInString(code, match.index) ||
240
- isInHtmlText(code, match.index)) {
241
- continue;
242
- }
243
- cssCreateMatches.push(match[0]);
309
+ function importDeclarationToString(node) {
310
+ const source = node.source.value;
311
+ const defaultImport = node.specifiers.find((s) => s.type === 'ImportDefaultSpecifier');
312
+ const namespaceImport = node.specifiers.find((s) => s.type === 'ImportNamespaceSpecifier');
313
+ const namedImports = node.specifiers.filter((s) => s.type === 'ImportSpecifier');
314
+ let importClause = '';
315
+ if (defaultImport) {
316
+ importClause += defaultImport.local.value;
244
317
  }
245
- return cssCreateMatches.join('\n');
318
+ if (namespaceImport) {
319
+ if (importClause)
320
+ importClause += ', ';
321
+ importClause += `* as ${namespaceImport.local.value}`;
322
+ }
323
+ if (namedImports.length > 0) {
324
+ if (importClause)
325
+ importClause += ', ';
326
+ const namedParts = namedImports.map((spec) => {
327
+ if (spec.imported && spec.imported.value !== spec.local.value) {
328
+ return `${spec.imported.value} as ${spec.local.value}`;
329
+ }
330
+ return spec.local.value;
331
+ });
332
+ importClause += `{ ${namedParts.join(', ')} }`;
333
+ }
334
+ if (importClause) {
335
+ return `import ${importClause} from '${source}';`;
336
+ }
337
+ return `import '${source}';`;
246
338
  }
247
- function extractCssDefineTokens(code) {
248
- const cssCreateMatches = [];
249
- const regex = /(?:(?:\s*const\s+[a-zA-Z0-9_$]+\s*=\s*css\.defineTokens\([\s\S]*?\);\s*))/g;
250
- let match;
251
- while ((match = regex.exec(code))) {
252
- if (isInComment(code, match.index) ||
253
- isInString(code, match.index) ||
254
- isInHtmlText(code, match.index)) {
255
- continue;
256
- }
257
- cssCreateMatches.push(match[0]);
339
+ function extractImportDeclarations(ast) {
340
+ const importDeclarations = [];
341
+ try {
342
+ visit(ast, {
343
+ ImportDeclaration: (node) => {
344
+ importDeclarations.push(importDeclarationToString(node));
345
+ },
346
+ });
347
+ }
348
+ catch (e) {
349
+ console.error(`Failed to parse code to extract import declarations: ${e}`);
258
350
  }
259
- return cssCreateMatches.join('\n');
351
+ return importDeclarations.join('\n');
260
352
  }
261
- function parseCssPropsArguments(args) {
262
- const results = [];
263
- const splitArgs = args.split(/\s*,\s*(?![^(]*\))/);
264
- for (const arg of splitArgs) {
265
- if (arg.includes('&&')) {
266
- const match = arg.match(/&&\s*([^\s,]+)/);
267
- if (match) {
268
- results.push(match[1]);
269
- continue;
270
- }
271
- }
272
- if (arg.includes('?')) {
273
- const match = arg.match(/([^?]+)\?([^:]+):(.+)$/);
274
- if (match) {
275
- results.push(match[2].trim());
276
- results.push(match[3].trim());
277
- continue;
278
- }
279
- }
280
- results.push(arg.trim());
353
+ function extractCssMethod(ast, methodName) {
354
+ const matches = [];
355
+ try {
356
+ visit(ast, {
357
+ VariableDeclaration: (node) => {
358
+ const containsCssMethod = node.declarations.some((decl) => decl.init &&
359
+ decl.init.type === 'CallExpression' &&
360
+ decl.init.callee.type === 'MemberExpression' &&
361
+ decl.init.callee.object.type === 'Identifier' &&
362
+ decl.init.callee.object.value === 'css' &&
363
+ decl.init.callee.property.type === 'Identifier' &&
364
+ decl.init.callee.property.value === methodName);
365
+ if (containsCssMethod && node.span) {
366
+ const { code: extractedCode } = (0, core_1.printSync)({
367
+ type: 'Module',
368
+ body: [node],
369
+ span: { start: 0, end: 0, ctxt: 0 },
370
+ });
371
+ matches.push(extractedCode.trim());
372
+ }
373
+ },
374
+ });
281
375
  }
282
- return results;
376
+ catch (e) {
377
+ console.error(`Failed to parse code to extract css.${methodName}: ${e}`);
378
+ }
379
+ return matches.join('\n');
283
380
  }
284
381
  async function extractVueAndSvelte(filePath) {
285
382
  const ext = path_1.default.extname(filePath);
@@ -304,45 +401,47 @@ async function extractVueAndSvelte(filePath) {
304
401
  }
305
402
  }
306
403
  const tsCode = contentLines.join('\n');
307
- const propsMatches = [...extractCssProps(tsCode), ...extractCssProps(code)];
404
+ if (!tsCode.trim()) {
405
+ const tsPath = filePath.replace(ext, '.ts');
406
+ fs_1.default.writeFileSync(tsPath, '', 'utf8');
407
+ generatedTsMap.set(filePath, tsPath);
408
+ return tsPath;
409
+ }
410
+ const ast = (0, core_1.parseSync)(tsCode, {
411
+ syntax: 'typescript',
412
+ tsx: true,
413
+ });
414
+ const propsFromScript = extractCssProps(ast);
415
+ const propsFromTemplate = extractCssPropsFromTemplate(code);
416
+ const propsMatches = [...new Set([...propsFromScript, ...propsFromTemplate])];
308
417
  const calls = propsMatches
309
418
  .filter(Boolean)
310
419
  .map((call) => `${call};`)
311
420
  .join('\n');
312
- const importRegex = /^(\s*import\s[^;]+;\s*)+/m;
313
- const importMatch = tsCode.match(importRegex);
314
- const importSection = importMatch ? importMatch[0] : '';
315
- const staticVariableSection = extractStaticStringLiteralVariable(tsCode);
316
- const cssCreateSection = extractCssCreate(tsCode);
317
- const cssKeyframesSection = extractCssKeyframes(tsCode);
318
- const cssViewTransitionSection = extractCssViewTransition(tsCode);
319
- const cssDefineConstsSection = extractCssDefineConsts(tsCode);
320
- const cssDefineTokensSection = extractCssDefineTokens(tsCode);
421
+ const importSection = extractImportDeclarations(ast);
422
+ const staticVariableSection = extractStaticStringLiteralVariable(ast);
423
+ const cssCreateSection = extractCssMethod(ast, 'create');
424
+ const cssKeyframesSection = extractCssMethod(ast, 'keyframes');
425
+ const cssViewTransitionSection = extractCssMethod(ast, 'viewTransition');
426
+ const cssDefineConstsSection = extractCssMethod(ast, 'defineConsts');
427
+ const cssDefineTokensSection = extractCssMethod(ast, 'defineTokens');
321
428
  let finalCode = '';
322
- if (importSection) {
429
+ if (importSection)
323
430
  finalCode += importSection + '\n';
324
- }
325
- if (staticVariableSection) {
431
+ if (staticVariableSection)
326
432
  finalCode += staticVariableSection + '\n';
327
- }
328
- if (cssKeyframesSection) {
433
+ if (cssKeyframesSection)
329
434
  finalCode += cssKeyframesSection + '\n';
330
- }
331
- if (cssViewTransitionSection) {
435
+ if (cssViewTransitionSection)
332
436
  finalCode += cssViewTransitionSection + '\n';
333
- }
334
- if (cssDefineConstsSection) {
437
+ if (cssDefineConstsSection)
335
438
  finalCode += cssDefineConstsSection + '\n';
336
- }
337
- if (cssDefineTokensSection) {
439
+ if (cssDefineTokensSection)
338
440
  finalCode += cssDefineTokensSection + '\n';
339
- }
340
- if (cssCreateSection) {
441
+ if (cssCreateSection)
341
442
  finalCode += cssCreateSection + '\n';
342
- }
343
- if (calls) {
443
+ if (calls)
344
444
  finalCode += calls + '\n';
345
- }
346
445
  const tsPath = filePath.replace(ext, '.ts');
347
446
  fs_1.default.writeFileSync(tsPath, finalCode, 'utf8');
348
447
  generatedTsMap.set(filePath, tsPath);
@@ -350,16 +449,18 @@ async function extractVueAndSvelte(filePath) {
350
449
  }
351
450
  async function extractTSFile(filePath) {
352
451
  const code = fs_1.default.readFileSync(filePath, 'utf8');
353
- const importRegex = /^(?:\s*import\s[^;]+;\s*)+/m;
354
- const importMatch = code.match(importRegex);
355
- const importSection = importMatch ? importMatch[0] : '';
356
- const staticVariableSection = extractStaticStringLiteralVariable(code);
357
- const cssCreateSection = extractCssCreate(code);
358
- const cssKeyframesSection = extractCssKeyframes(code);
359
- const cssViewTransitionSection = extractCssViewTransition(code);
360
- const cssDefineConstsSection = extractCssDefineConsts(code);
361
- const cssDefineTokensSection = extractCssDefineTokens(code);
362
- const propsMatches = extractCssProps(code);
452
+ const ast = (0, core_1.parseSync)(code, {
453
+ syntax: 'typescript',
454
+ tsx: true,
455
+ });
456
+ const importSection = extractImportDeclarations(ast);
457
+ const staticVariableSection = extractStaticStringLiteralVariable(ast);
458
+ const cssCreateSection = extractCssMethod(ast, 'create');
459
+ const cssKeyframesSection = extractCssMethod(ast, 'keyframes');
460
+ const cssViewTransitionSection = extractCssMethod(ast, 'viewTransition');
461
+ const cssDefineConstsSection = extractCssMethod(ast, 'defineConsts');
462
+ const cssDefineTokensSection = extractCssMethod(ast, 'defineTokens');
463
+ const propsMatches = extractCssProps(ast);
363
464
  const calls = propsMatches
364
465
  .filter(Boolean)
365
466
  .map((call) => `${call};`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/compiler",
3
- "version": "0.24.5",
3
+ "version": "0.25.1",
4
4
  "description": "Plumeria Rust-based compiler",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,12 +16,12 @@
16
16
  "css": "./bin/css.js"
17
17
  },
18
18
  "dependencies": {
19
- "@swc/core": "1.14.0",
20
- "find-up": "^7.0.0",
21
- "lightningcss": "^1.29.3",
22
- "postcss": "^8.5.3",
23
- "postcss-combine-media-query": "^2.0.0",
24
- "rscute": "^0.2.12"
19
+ "@swc/core": "1.15.0",
20
+ "find-up": "^8.0.0",
21
+ "lightningcss": "^1.30.2",
22
+ "postcss": "^8.5.6",
23
+ "postcss-combine-media-query": "^2.1.0",
24
+ "rscute": "^1.0.0"
25
25
  },
26
26
  "publishConfig": {
27
27
  "access": "public"