@plumeria/unplugin 18.4.2 → 18.5.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 (3) hide show
  1. package/dist/core.js +244 -43
  2. package/dist/core.mjs +245 -44
  3. package/package.json +2 -2
package/dist/core.js CHANGED
@@ -87,6 +87,26 @@ const destructuredPropAliases = (fn) => {
87
87
  }
88
88
  return aliases;
89
89
  };
90
+ const spreadStyleMessage = (source) => `Plumeria: Spread elements in a style array are not supported: "...${source}". ` +
91
+ `List each style explicitly.`;
92
+ const resolveLocalStyleAlias = (aliases, expression) => {
93
+ let current = expression;
94
+ const seen = new Set();
95
+ while (utils_1.t.isIdentifier(current)) {
96
+ const name = current.value;
97
+ if (seen.has(name))
98
+ break;
99
+ seen.add(name);
100
+ const candidates = aliases[name];
101
+ if (!candidates || candidates.length === 0)
102
+ break;
103
+ const candidate = candidates.find((candidate) => candidate.context === current.ctxt);
104
+ if (!candidate)
105
+ break;
106
+ current = candidate.expression;
107
+ }
108
+ return current;
109
+ };
90
110
  const isStaticArgValue = (node) => node.type === 'StringLiteral' ||
91
111
  node.type === 'NumericLiteral' ||
92
112
  node.type === 'BooleanLiteral' ||
@@ -273,11 +293,32 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
273
293
  }
274
294
  const components = [];
275
295
  const propAliases = new Map();
296
+ const propDefaults = new Map();
297
+ const declaredProps = new Map();
276
298
  const addComponent = (name, fn) => {
277
299
  components.push({ name, node: fn });
300
+ const first = fn.params[0];
301
+ const pattern = unwrapPatternDefault(first?.pat ?? first);
302
+ const defaults = new Map();
303
+ for (const item of pattern?.properties ?? []) {
304
+ if (item.type === 'AssignmentPatternProperty' && item.value)
305
+ defaults.set(item.key.value, item.value);
306
+ if (item.type === 'KeyValuePatternProperty' &&
307
+ item.value?.type === 'AssignmentPattern')
308
+ defaults.set(item.key.value, item.value.right);
309
+ }
310
+ propDefaults.set(name, defaults);
278
311
  const aliases = destructuredPropAliases(fn);
279
312
  if (aliases.size > 0)
280
313
  propAliases.set(name, aliases);
314
+ const declared = new Set();
315
+ for (const item of pattern?.properties ?? []) {
316
+ const key = item.key;
317
+ if (utils_1.t.isIdentifier(key) || utils_1.t.isStringLiteral(key))
318
+ declared.add(String(key.value));
319
+ }
320
+ if (declared.size > 0)
321
+ declaredProps.set(name, declared);
281
322
  };
282
323
  for (const node of ast.body) {
283
324
  const statement = unwrapExport(node);
@@ -510,12 +551,15 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
510
551
  const checkStyleAliasAssignment = (decl) => {
511
552
  if (!utils_1.t.isIdentifier(decl.id) || !decl.init)
512
553
  return;
513
- const init = decl.init;
554
+ const init = (0, utils_1.unwrapExpression)(decl.init);
514
555
  if (utils_1.t.isMemberExpression(init) && utils_1.t.isIdentifier(init.object)) {
515
556
  const objName = init.object.value;
516
557
  if (localCreateStyles[objName] !== undefined ||
517
558
  mergedCreateTable[objName] !== undefined) {
518
- localStyleAliases[decl.id.value] = init;
559
+ (localStyleAliases[decl.id.value] ??= []).push({
560
+ expression: init,
561
+ context: decl.id.ctxt,
562
+ });
519
563
  }
520
564
  }
521
565
  };
@@ -525,9 +569,43 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
525
569
  const idSpans = new Set();
526
570
  const excludedSpans = new Set();
527
571
  const referenceIdents = (0, utils_1.collectReferenceIdentifiers)(ast);
572
+ const topLevelDeclarators = new Set();
573
+ for (const statement of ast.body) {
574
+ const declaration = unwrapExport(statement);
575
+ if (utils_1.t.isVariableDeclaration(declaration)) {
576
+ declaration.declarations.forEach((decl) => topLevelDeclarators.add(decl));
577
+ }
578
+ }
579
+ const unwrapStyleExpression = (node) => {
580
+ while (node &&
581
+ [
582
+ 'ParenthesisExpression',
583
+ 'TsAsExpression',
584
+ 'TsSatisfiesExpression',
585
+ 'TsNonNullExpression',
586
+ 'TsConstAssertion',
587
+ 'TsTypeAssertion',
588
+ ].includes(node.type))
589
+ node = node.expression;
590
+ return node;
591
+ };
592
+ const literalObjectArgument = (node, seen = new Set()) => {
593
+ node = unwrapStyleExpression(node);
594
+ if (utils_1.t.isObjectExpression(node))
595
+ return node;
596
+ if (!utils_1.t.isIdentifier(node) || seen.has(node.value))
597
+ return null;
598
+ seen.add(node.value);
599
+ const declaration = [...topLevelDeclarators].find((decl) => utils_1.t.isIdentifier(decl.id) &&
600
+ decl.id.value === node.value);
601
+ return declaration?.init
602
+ ? literalObjectArgument(declaration.init, seen)
603
+ : null;
604
+ };
605
+ const registeredStyleCalls = new Set();
528
606
  const registerStyle = (node, declSpan, isExported) => {
529
607
  let propName;
530
- const init = node.init;
608
+ const init = unwrapStyleExpression(node.init);
531
609
  if (utils_1.t.isIdentifier(node.id) &&
532
610
  init &&
533
611
  utils_1.t.isCallExpression(init) &&
@@ -552,6 +630,19 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
552
630
  }
553
631
  }
554
632
  if (propName && init && utils_1.t.isCallExpression(init)) {
633
+ if (['create', 'createTheme', 'createStatic'].includes(propName) &&
634
+ !topLevelDeclarators.has(node)) {
635
+ throwCompilationError(`Plumeria: css.${propName} must be assigned to a top-level variable so its styles can be resolved across files. Move this declaration outside the function or block.`, node);
636
+ }
637
+ if (['create', 'createTheme', 'createStatic'].includes(propName)) {
638
+ const argumentIndex = propName === 'createTheme' ? 1 : 0;
639
+ const argument = init.arguments[argumentIndex];
640
+ const object = argument && literalObjectArgument(argument.expression);
641
+ if (!object)
642
+ throwCompilationError(`Plumeria: css.${propName} needs a style object it can read at build time. Pass an object literal or a top-level constant containing one.`, init);
643
+ argument.expression = object;
644
+ registeredStyleCalls.add(init.span.start);
645
+ }
555
646
  if (propName === 'create' &&
556
647
  utils_1.t.isObjectExpression(init.arguments[0].expression)) {
557
648
  const obj = (0, utils_1.objectExpressionToObject)(init.arguments[0].expression, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
@@ -749,6 +840,10 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
749
840
  }
750
841
  }
751
842
  if (propName) {
843
+ if (['create', 'createTheme', 'createStatic'].includes(propName) &&
844
+ !registeredStyleCalls.has(node.span.start)) {
845
+ throwCompilationError(`Plumeria: css.${propName} must be assigned to a named top-level variable. Destructuring, assignment statements, and default-exported calls cannot be compiled.`, node);
846
+ }
752
847
  const args = node.arguments;
753
848
  if (propName === 'keyframes' && args.length > 0) {
754
849
  const expr = args[0].expression;
@@ -814,12 +909,17 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
814
909
  const jsxOpeningElementMap = new Map();
815
910
  const componentParamNames = new Set();
816
911
  const addFirstParamName = (fn) => {
817
- const p = fn.params[0];
912
+ const first = fn.params[0];
913
+ const p = unwrapPatternDefault(first?.pat ?? first);
818
914
  if (utils_1.t.isIdentifier(p)) {
819
915
  componentParamNames.add(p.value);
820
916
  }
821
- else if (p?.pat && utils_1.t.isIdentifier(p.pat)) {
822
- componentParamNames.add(p.pat.value);
917
+ else if (p?.type === 'ObjectPattern') {
918
+ for (const item of p.properties) {
919
+ if (item.type === 'RestElement' && utils_1.t.isIdentifier(item.argument)) {
920
+ componentParamNames.add(item.argument.value);
921
+ }
922
+ }
823
923
  }
824
924
  };
825
925
  for (const node of ast.body) {
@@ -856,6 +956,22 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
856
956
  return sourceBuffer.subarray(start, end).toString('utf-8');
857
957
  };
858
958
  const resolveStyleObject = (expr) => {
959
+ expr = (0, utils_1.unwrapExpression)(expr);
960
+ expr = resolveLocalStyleAlias(localStyleAliases, expr);
961
+ if (expr.type === 'ArrayExpression') {
962
+ let merged = {};
963
+ for (const element of expr.elements ?? []) {
964
+ if (!element)
965
+ continue;
966
+ if (element.spread)
967
+ throwCompilationError(spreadStyleMessage(getSource(element.expression)), element.expression);
968
+ const style = resolveStyleObject(element.expression);
969
+ if (!style)
970
+ return null;
971
+ merged = (0, utils_1.deepMerge)(merged, style);
972
+ }
973
+ return merged;
974
+ }
859
975
  if (utils_1.t.isObjectExpression(expr)) {
860
976
  return (0, utils_1.objectExpressionToObject)(expr, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
861
977
  }
@@ -915,6 +1031,26 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
915
1031
  return Boolean(localCreateStyles[callee.object.value]?.functions?.[callee.property.value] ??
916
1032
  createFunctionImportMap[callee.object.value]?.[callee.property.value]);
917
1033
  };
1034
+ const carriesStyleReference = (expression) => {
1035
+ const node = (0, utils_1.unwrapExpression)(expression);
1036
+ if (node.type === 'ArrayExpression')
1037
+ return node.elements.some((element) => !!element && carriesStyleReference(element.expression));
1038
+ if (isStyleFunctionCall(node))
1039
+ return true;
1040
+ if (utils_1.t.isObjectExpression(node))
1041
+ return false;
1042
+ return resolveStyleObject(node) !== null;
1043
+ };
1044
+ const assertNoStyleArraySpread = (expression) => {
1045
+ const node = (0, utils_1.unwrapExpression)(expression);
1046
+ if (node.type !== 'ArrayExpression')
1047
+ return;
1048
+ const spread = node.elements.find((element) => element?.spread);
1049
+ if (!spread)
1050
+ return;
1051
+ if (carriesStyleReference(node))
1052
+ throwCompilationError(spreadStyleMessage(getSource(spread.expression)), spread.expression);
1053
+ };
918
1054
  const resolveDynamicCall = (expr, forceRuntime = false) => {
919
1055
  if (!utils_1.t.isCallExpression(expr) || !utils_1.t.isMemberExpression(expr.callee))
920
1056
  return null;
@@ -930,8 +1066,14 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
930
1066
  if (callArgs.some((a) => a.spread))
931
1067
  return null;
932
1068
  const tempStaticTable = { ...mergedStaticTable };
1069
+ const providedParams = new Set();
933
1070
  const runtime = [];
934
- const resolveObjectArg = (argExpr) => (0, utils_1.objectExpressionToObject)(argExpr, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1071
+ const resolveObjectArg = (argExpr) => (0, utils_1.objectExpressionToObject)({
1072
+ ...argExpr,
1073
+ properties: argExpr.properties.filter((prop) => prop.type === 'Identifier' ||
1074
+ (prop.type === 'KeyValueProperty' &&
1075
+ isStaticArgValue(prop.value))),
1076
+ }, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
935
1077
  if (func.named) {
936
1078
  const argExpr = callArgs[0]?.expression;
937
1079
  if (callArgs.length > 1 ||
@@ -961,8 +1103,10 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
961
1103
  }
962
1104
  if (!forceRuntime &&
963
1105
  isStaticArgValue(source) &&
964
- argObj[key] !== undefined)
1106
+ argObj[key] !== undefined) {
965
1107
  tempStaticTable[local] = argObj[key];
1108
+ providedParams.add(local);
1109
+ }
966
1110
  else
967
1111
  runtime.push({ param: local, source });
968
1112
  });
@@ -973,8 +1117,10 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
973
1117
  return null;
974
1118
  const argObj = resolveObjectArg(callArgs[0].expression) ?? {};
975
1119
  func.params.forEach((p) => {
976
- if (argObj[p] !== undefined)
1120
+ if (argObj[p] !== undefined) {
977
1121
  tempStaticTable[p] = argObj[p];
1122
+ providedParams.add(p);
1123
+ }
978
1124
  });
979
1125
  }
980
1126
  else {
@@ -985,7 +1131,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
985
1131
  runtime.push({ param: p, source: callArg.expression });
986
1132
  });
987
1133
  }
988
- const resolved = (0, utils_1.resolveDynamicStyle)(func, runtime.map(({ param }) => param), tempStaticTable, dynamicStyleTables);
1134
+ const resolved = (0, utils_1.resolveDynamicStyle)(func, runtime.map(({ param }) => param), tempStaticTable, dynamicStyleTables, providedParams);
989
1135
  if (!resolved)
990
1136
  return null;
991
1137
  const { style, varGroups } = resolved;
@@ -1023,10 +1169,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1023
1169
  };
1024
1170
  const buildClassParts = (args, dynamicClassParts = [], existingClass = '', isStyleProp = false) => {
1025
1171
  args.forEach((arg) => {
1026
- const expr = arg.expression;
1027
- if (utils_1.t.isIdentifier(expr) && localStyleAliases[expr.value]) {
1028
- arg.expression = localStyleAliases[expr.value];
1029
- }
1172
+ arg.expression = resolveLocalStyleAlias(localStyleAliases, arg.expression);
1030
1173
  });
1031
1174
  const conditionals = [];
1032
1175
  const dynamicVars = [];
@@ -1150,6 +1293,8 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1150
1293
  return buildDecision(node, prefix, off, { n: 0 }, scoped);
1151
1294
  };
1152
1295
  const collectConditions = (node, currentTestStrings = [], argOrder) => {
1296
+ node = (0, utils_1.unwrapExpression)(node);
1297
+ node = resolveLocalStyleAlias(localStyleAliases, node);
1153
1298
  if (isNoOpStyle(node))
1154
1299
  return true;
1155
1300
  let branchStyle = resolveStyleObject(node);
@@ -1228,6 +1373,12 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1228
1373
  utils_1.t.isIdentifier(expr.property);
1229
1374
  if (!utils_1.t.isIdentifier(expr) && !isParamMember)
1230
1375
  return false;
1376
+ const paramObject = isParamMember
1377
+ ? expr.object.value
1378
+ : undefined;
1379
+ const isPropsMember = Boolean(paramObject &&
1380
+ localCreateStyles[paramObject] === undefined &&
1381
+ mergedCreateTable[paramObject] === undefined);
1231
1382
  const varName = utils_1.t.isIdentifier(expr)
1232
1383
  ? expr.value
1233
1384
  : expr.property.value;
@@ -1252,8 +1403,43 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1252
1403
  if (candidates.length > 0)
1253
1404
  possibilities = candidates;
1254
1405
  }
1255
- if (!possibilities || possibilities.length === 0)
1256
- return false;
1406
+ const fallback = owner && propDefaults.get(owner)?.get(propName);
1407
+ if (fallback) {
1408
+ const style = resolveStyleObject(fallback);
1409
+ if (style) {
1410
+ const atoms = {};
1411
+ (0, utils_1.getStyleRecords)(style).forEach((record) => {
1412
+ atoms[record.key] = record.hash;
1413
+ });
1414
+ const key = (0, zss_engine_1.genBase36Hash)(Object.values(atoms).join(' '), 1, 8);
1415
+ possibilities = [
1416
+ ...(possibilities ?? []),
1417
+ { key, styleObj: style },
1418
+ ];
1419
+ const span = fallback.span;
1420
+ {
1421
+ for (let i = replacements.length - 1; i >= 0; i--) {
1422
+ const replacement = replacements[i];
1423
+ if (replacement.start >= span.start - baseByteOffset &&
1424
+ replacement.end <= span.end - baseByteOffset)
1425
+ replacements.splice(i, 1);
1426
+ }
1427
+ excludeSubtreeSpans(fallback);
1428
+ replacements.push({
1429
+ start: span.start - baseByteOffset,
1430
+ end: span.end - baseByteOffset,
1431
+ content: JSON.stringify(key),
1432
+ });
1433
+ }
1434
+ }
1435
+ else {
1436
+ assertResolvable(fallback);
1437
+ }
1438
+ }
1439
+ if (!possibilities || possibilities.length === 0) {
1440
+ return Boolean(isPropsMember ||
1441
+ (owner && declaredProps.get(owner)?.has(propName)));
1442
+ }
1257
1443
  const carriesVars = possibilities.some((entry) => entry.hasVars);
1258
1444
  if (carriesVars && !isStyleProp) {
1259
1445
  throwCompilationError(`Plumeria: "${varName}" carries a dynamic function key, and css.use() returns only a class name. ` +
@@ -1969,50 +2155,61 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1969
2155
  }
1970
2156
  }
1971
2157
  if (compKey) {
2158
+ if (node.value?.type === 'JSXExpressionContainer')
2159
+ assertNoStyleArraySpread(node.value.expression);
1972
2160
  const list = scannedTables.componentPropsTable?.[compKey]?.[attrName];
1973
2161
  if (list &&
1974
2162
  node.value &&
1975
2163
  node.value.type === 'JSXExpressionContainer') {
1976
2164
  const expr = node.value.expression;
1977
- const carrierFor = (subNode) => {
2165
+ const carrierFor = (subNode, calls) => {
1978
2166
  const vars = [];
1979
2167
  let unresolved = false;
1980
- (0, utils_1.traverse)(subNode, {
1981
- CallExpression({ node: call }) {
1982
- if (!isStyleFunctionCall(call))
1983
- return;
1984
- const resolved = resolveDynamicCall(call, true);
1985
- if (!resolved) {
1986
- unresolved = true;
1987
- return;
1988
- }
1989
- vars.push(...resolved.vars);
1990
- },
1991
- });
2168
+ for (const expression of calls ?? [subNode])
2169
+ (0, utils_1.traverse)(expression, {
2170
+ CallExpression({ node: call }) {
2171
+ if (!isStyleFunctionCall(call))
2172
+ return;
2173
+ const resolved = resolveDynamicCall(call, true);
2174
+ if (!resolved) {
2175
+ unresolved = true;
2176
+ return;
2177
+ }
2178
+ vars.push(...resolved.vars);
2179
+ },
2180
+ });
1992
2181
  if (unresolved)
1993
2182
  return null;
1994
2183
  return `{ ${foldDynamicVars(vars).join(', ')} }`;
1995
2184
  };
1996
2185
  const replaceWithKey = (subNode) => {
1997
- const entry = list.find((x) => x.spanStart === subNode.span.start &&
1998
- x.filePath === resourcePath);
1999
- if (entry) {
2000
- let content = JSON.stringify(entry.key);
2186
+ const entries = list.filter((entry) => entry.spanStart === subNode.span.start &&
2187
+ entry.filePath === resourcePath);
2188
+ if (!entries.length)
2189
+ return false;
2190
+ let content = '""';
2191
+ for (const entry of [...entries].reverse()) {
2192
+ let value = JSON.stringify(entry.key);
2001
2193
  if (entry.hasVars) {
2002
- const carrier = carrierFor(subNode);
2194
+ const carrier = carrierFor(subNode, entry.dynamicCalls);
2003
2195
  if (!carrier)
2004
2196
  return false;
2005
- content = `{ key: ${content}, vars: ${carrier} }`;
2197
+ value = `{ key: ${value}, vars: ${carrier} }`;
2006
2198
  }
2007
- replacements.push({
2008
- start: subNode.span.start - baseByteOffset,
2009
- end: subNode.span.end - baseByteOffset,
2010
- content,
2011
- });
2199
+ const condition = entry.conditions
2200
+ ?.map(({ test, truthy }) => `${truthy ? '' : '!'}(${getSource(test)})`)
2201
+ .join(' && ');
2202
+ content = condition
2203
+ ? `((${condition}) ? ${value} : ${content})`
2204
+ : value;
2012
2205
  processStyleRecords(entry.styleObj);
2013
- return true;
2014
2206
  }
2015
- return false;
2207
+ replacements.push({
2208
+ start: subNode.span.start - baseByteOffset,
2209
+ end: subNode.span.end - baseByteOffset,
2210
+ content,
2211
+ });
2212
+ return true;
2016
2213
  };
2017
2214
  (0, utils_1.traverse)(expr, {
2018
2215
  MemberExpression({ node: subNode }) {
@@ -2041,7 +2238,11 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
2041
2238
  const args = expr.type === 'ArrayExpression'
2042
2239
  ? expr.elements
2043
2240
  .filter((el) => el !== undefined)
2044
- .map((el) => ({ expression: el.expression }))
2241
+ .map((el) => {
2242
+ if (el.spread)
2243
+ throwCompilationError(spreadStyleMessage(getSource(el.expression)), el.expression);
2244
+ return { expression: el.expression };
2245
+ })
2045
2246
  : [{ expression: expr }];
2046
2247
  const dynamicClassParts = [];
2047
2248
  const existingStyleParts = [];
package/dist/core.mjs CHANGED
@@ -2,7 +2,7 @@ import { createFilter } from '@rollup/pluginutils';
2
2
  import { parseSync } from '@swc/core';
3
3
  import * as path from 'path';
4
4
  import { applyCssValue, genBase36Hash, camelToKebabCase, isAtRule, } from 'zss-engine';
5
- import { traverse, resolvePropertyPolicy, assertPropertyPolicy, collectReferenceIdentifiers, getStyleRecords, getStateWeights, collectLocalConsts, objectExpressionToObject, t, getRootIdentifier, extractOndemandStyles, deepMerge, scanAll, resolveFileError, resolveImportPath, getLeadingCommentLength, optimizer, styleFunctionsOf, isUnitlessProp, resolveDynamicStyle, themeHashOf, getFileDependencies, resolveExport, resolveComponentKey, resolveThemeSelector, DEFAULT_STYLE_PROP, } from '@plumeria/utils';
5
+ import { traverse, resolvePropertyPolicy, assertPropertyPolicy, collectReferenceIdentifiers, getStyleRecords, getStateWeights, collectLocalConsts, objectExpressionToObject, t, getRootIdentifier, unwrapExpression, extractOndemandStyles, deepMerge, scanAll, resolveFileError, resolveImportPath, getLeadingCommentLength, optimizer, styleFunctionsOf, isUnitlessProp, resolveDynamicStyle, themeHashOf, getFileDependencies, resolveExport, resolveComponentKey, resolveThemeSelector, DEFAULT_STYLE_PROP, } from '@plumeria/utils';
6
6
  export const TARGET_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx'];
7
7
  export const EXTENSION_PATTERN = /\.(ts|tsx|js|jsx)$/;
8
8
  const unwrapExport = (node) => {
@@ -51,6 +51,26 @@ const destructuredPropAliases = (fn) => {
51
51
  }
52
52
  return aliases;
53
53
  };
54
+ const spreadStyleMessage = (source) => `Plumeria: Spread elements in a style array are not supported: "...${source}". ` +
55
+ `List each style explicitly.`;
56
+ const resolveLocalStyleAlias = (aliases, expression) => {
57
+ let current = expression;
58
+ const seen = new Set();
59
+ while (t.isIdentifier(current)) {
60
+ const name = current.value;
61
+ if (seen.has(name))
62
+ break;
63
+ seen.add(name);
64
+ const candidates = aliases[name];
65
+ if (!candidates || candidates.length === 0)
66
+ break;
67
+ const candidate = candidates.find((candidate) => candidate.context === current.ctxt);
68
+ if (!candidate)
69
+ break;
70
+ current = candidate.expression;
71
+ }
72
+ return current;
73
+ };
54
74
  const isStaticArgValue = (node) => node.type === 'StringLiteral' ||
55
75
  node.type === 'NumericLiteral' ||
56
76
  node.type === 'BooleanLiteral' ||
@@ -237,11 +257,32 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
237
257
  }
238
258
  const components = [];
239
259
  const propAliases = new Map();
260
+ const propDefaults = new Map();
261
+ const declaredProps = new Map();
240
262
  const addComponent = (name, fn) => {
241
263
  components.push({ name, node: fn });
264
+ const first = fn.params[0];
265
+ const pattern = unwrapPatternDefault(first?.pat ?? first);
266
+ const defaults = new Map();
267
+ for (const item of pattern?.properties ?? []) {
268
+ if (item.type === 'AssignmentPatternProperty' && item.value)
269
+ defaults.set(item.key.value, item.value);
270
+ if (item.type === 'KeyValuePatternProperty' &&
271
+ item.value?.type === 'AssignmentPattern')
272
+ defaults.set(item.key.value, item.value.right);
273
+ }
274
+ propDefaults.set(name, defaults);
242
275
  const aliases = destructuredPropAliases(fn);
243
276
  if (aliases.size > 0)
244
277
  propAliases.set(name, aliases);
278
+ const declared = new Set();
279
+ for (const item of pattern?.properties ?? []) {
280
+ const key = item.key;
281
+ if (t.isIdentifier(key) || t.isStringLiteral(key))
282
+ declared.add(String(key.value));
283
+ }
284
+ if (declared.size > 0)
285
+ declaredProps.set(name, declared);
245
286
  };
246
287
  for (const node of ast.body) {
247
288
  const statement = unwrapExport(node);
@@ -474,12 +515,15 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
474
515
  const checkStyleAliasAssignment = (decl) => {
475
516
  if (!t.isIdentifier(decl.id) || !decl.init)
476
517
  return;
477
- const init = decl.init;
518
+ const init = unwrapExpression(decl.init);
478
519
  if (t.isMemberExpression(init) && t.isIdentifier(init.object)) {
479
520
  const objName = init.object.value;
480
521
  if (localCreateStyles[objName] !== undefined ||
481
522
  mergedCreateTable[objName] !== undefined) {
482
- localStyleAliases[decl.id.value] = init;
523
+ (localStyleAliases[decl.id.value] ??= []).push({
524
+ expression: init,
525
+ context: decl.id.ctxt,
526
+ });
483
527
  }
484
528
  }
485
529
  };
@@ -489,9 +533,43 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
489
533
  const idSpans = new Set();
490
534
  const excludedSpans = new Set();
491
535
  const referenceIdents = collectReferenceIdentifiers(ast);
536
+ const topLevelDeclarators = new Set();
537
+ for (const statement of ast.body) {
538
+ const declaration = unwrapExport(statement);
539
+ if (t.isVariableDeclaration(declaration)) {
540
+ declaration.declarations.forEach((decl) => topLevelDeclarators.add(decl));
541
+ }
542
+ }
543
+ const unwrapStyleExpression = (node) => {
544
+ while (node &&
545
+ [
546
+ 'ParenthesisExpression',
547
+ 'TsAsExpression',
548
+ 'TsSatisfiesExpression',
549
+ 'TsNonNullExpression',
550
+ 'TsConstAssertion',
551
+ 'TsTypeAssertion',
552
+ ].includes(node.type))
553
+ node = node.expression;
554
+ return node;
555
+ };
556
+ const literalObjectArgument = (node, seen = new Set()) => {
557
+ node = unwrapStyleExpression(node);
558
+ if (t.isObjectExpression(node))
559
+ return node;
560
+ if (!t.isIdentifier(node) || seen.has(node.value))
561
+ return null;
562
+ seen.add(node.value);
563
+ const declaration = [...topLevelDeclarators].find((decl) => t.isIdentifier(decl.id) &&
564
+ decl.id.value === node.value);
565
+ return declaration?.init
566
+ ? literalObjectArgument(declaration.init, seen)
567
+ : null;
568
+ };
569
+ const registeredStyleCalls = new Set();
492
570
  const registerStyle = (node, declSpan, isExported) => {
493
571
  let propName;
494
- const init = node.init;
572
+ const init = unwrapStyleExpression(node.init);
495
573
  if (t.isIdentifier(node.id) &&
496
574
  init &&
497
575
  t.isCallExpression(init) &&
@@ -516,6 +594,19 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
516
594
  }
517
595
  }
518
596
  if (propName && init && t.isCallExpression(init)) {
597
+ if (['create', 'createTheme', 'createStatic'].includes(propName) &&
598
+ !topLevelDeclarators.has(node)) {
599
+ throwCompilationError(`Plumeria: css.${propName} must be assigned to a top-level variable so its styles can be resolved across files. Move this declaration outside the function or block.`, node);
600
+ }
601
+ if (['create', 'createTheme', 'createStatic'].includes(propName)) {
602
+ const argumentIndex = propName === 'createTheme' ? 1 : 0;
603
+ const argument = init.arguments[argumentIndex];
604
+ const object = argument && literalObjectArgument(argument.expression);
605
+ if (!object)
606
+ throwCompilationError(`Plumeria: css.${propName} needs a style object it can read at build time. Pass an object literal or a top-level constant containing one.`, init);
607
+ argument.expression = object;
608
+ registeredStyleCalls.add(init.span.start);
609
+ }
519
610
  if (propName === 'create' &&
520
611
  t.isObjectExpression(init.arguments[0].expression)) {
521
612
  const obj = objectExpressionToObject(init.arguments[0].expression, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
@@ -713,6 +804,10 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
713
804
  }
714
805
  }
715
806
  if (propName) {
807
+ if (['create', 'createTheme', 'createStatic'].includes(propName) &&
808
+ !registeredStyleCalls.has(node.span.start)) {
809
+ throwCompilationError(`Plumeria: css.${propName} must be assigned to a named top-level variable. Destructuring, assignment statements, and default-exported calls cannot be compiled.`, node);
810
+ }
716
811
  const args = node.arguments;
717
812
  if (propName === 'keyframes' && args.length > 0) {
718
813
  const expr = args[0].expression;
@@ -778,12 +873,17 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
778
873
  const jsxOpeningElementMap = new Map();
779
874
  const componentParamNames = new Set();
780
875
  const addFirstParamName = (fn) => {
781
- const p = fn.params[0];
876
+ const first = fn.params[0];
877
+ const p = unwrapPatternDefault(first?.pat ?? first);
782
878
  if (t.isIdentifier(p)) {
783
879
  componentParamNames.add(p.value);
784
880
  }
785
- else if (p?.pat && t.isIdentifier(p.pat)) {
786
- componentParamNames.add(p.pat.value);
881
+ else if (p?.type === 'ObjectPattern') {
882
+ for (const item of p.properties) {
883
+ if (item.type === 'RestElement' && t.isIdentifier(item.argument)) {
884
+ componentParamNames.add(item.argument.value);
885
+ }
886
+ }
787
887
  }
788
888
  };
789
889
  for (const node of ast.body) {
@@ -820,6 +920,22 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
820
920
  return sourceBuffer.subarray(start, end).toString('utf-8');
821
921
  };
822
922
  const resolveStyleObject = (expr) => {
923
+ expr = unwrapExpression(expr);
924
+ expr = resolveLocalStyleAlias(localStyleAliases, expr);
925
+ if (expr.type === 'ArrayExpression') {
926
+ let merged = {};
927
+ for (const element of expr.elements ?? []) {
928
+ if (!element)
929
+ continue;
930
+ if (element.spread)
931
+ throwCompilationError(spreadStyleMessage(getSource(element.expression)), element.expression);
932
+ const style = resolveStyleObject(element.expression);
933
+ if (!style)
934
+ return null;
935
+ merged = deepMerge(merged, style);
936
+ }
937
+ return merged;
938
+ }
823
939
  if (t.isObjectExpression(expr)) {
824
940
  return objectExpressionToObject(expr, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
825
941
  }
@@ -879,6 +995,26 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
879
995
  return Boolean(localCreateStyles[callee.object.value]?.functions?.[callee.property.value] ??
880
996
  createFunctionImportMap[callee.object.value]?.[callee.property.value]);
881
997
  };
998
+ const carriesStyleReference = (expression) => {
999
+ const node = unwrapExpression(expression);
1000
+ if (node.type === 'ArrayExpression')
1001
+ return node.elements.some((element) => !!element && carriesStyleReference(element.expression));
1002
+ if (isStyleFunctionCall(node))
1003
+ return true;
1004
+ if (t.isObjectExpression(node))
1005
+ return false;
1006
+ return resolveStyleObject(node) !== null;
1007
+ };
1008
+ const assertNoStyleArraySpread = (expression) => {
1009
+ const node = unwrapExpression(expression);
1010
+ if (node.type !== 'ArrayExpression')
1011
+ return;
1012
+ const spread = node.elements.find((element) => element?.spread);
1013
+ if (!spread)
1014
+ return;
1015
+ if (carriesStyleReference(node))
1016
+ throwCompilationError(spreadStyleMessage(getSource(spread.expression)), spread.expression);
1017
+ };
882
1018
  const resolveDynamicCall = (expr, forceRuntime = false) => {
883
1019
  if (!t.isCallExpression(expr) || !t.isMemberExpression(expr.callee))
884
1020
  return null;
@@ -894,8 +1030,14 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
894
1030
  if (callArgs.some((a) => a.spread))
895
1031
  return null;
896
1032
  const tempStaticTable = { ...mergedStaticTable };
1033
+ const providedParams = new Set();
897
1034
  const runtime = [];
898
- const resolveObjectArg = (argExpr) => objectExpressionToObject(argExpr, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1035
+ const resolveObjectArg = (argExpr) => objectExpressionToObject({
1036
+ ...argExpr,
1037
+ properties: argExpr.properties.filter((prop) => prop.type === 'Identifier' ||
1038
+ (prop.type === 'KeyValueProperty' &&
1039
+ isStaticArgValue(prop.value))),
1040
+ }, mergedStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
899
1041
  if (func.named) {
900
1042
  const argExpr = callArgs[0]?.expression;
901
1043
  if (callArgs.length > 1 ||
@@ -925,8 +1067,10 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
925
1067
  }
926
1068
  if (!forceRuntime &&
927
1069
  isStaticArgValue(source) &&
928
- argObj[key] !== undefined)
1070
+ argObj[key] !== undefined) {
929
1071
  tempStaticTable[local] = argObj[key];
1072
+ providedParams.add(local);
1073
+ }
930
1074
  else
931
1075
  runtime.push({ param: local, source });
932
1076
  });
@@ -937,8 +1081,10 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
937
1081
  return null;
938
1082
  const argObj = resolveObjectArg(callArgs[0].expression) ?? {};
939
1083
  func.params.forEach((p) => {
940
- if (argObj[p] !== undefined)
1084
+ if (argObj[p] !== undefined) {
941
1085
  tempStaticTable[p] = argObj[p];
1086
+ providedParams.add(p);
1087
+ }
942
1088
  });
943
1089
  }
944
1090
  else {
@@ -949,7 +1095,7 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
949
1095
  runtime.push({ param: p, source: callArg.expression });
950
1096
  });
951
1097
  }
952
- const resolved = resolveDynamicStyle(func, runtime.map(({ param }) => param), tempStaticTable, dynamicStyleTables);
1098
+ const resolved = resolveDynamicStyle(func, runtime.map(({ param }) => param), tempStaticTable, dynamicStyleTables, providedParams);
953
1099
  if (!resolved)
954
1100
  return null;
955
1101
  const { style, varGroups } = resolved;
@@ -987,10 +1133,7 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
987
1133
  };
988
1134
  const buildClassParts = (args, dynamicClassParts = [], existingClass = '', isStyleProp = false) => {
989
1135
  args.forEach((arg) => {
990
- const expr = arg.expression;
991
- if (t.isIdentifier(expr) && localStyleAliases[expr.value]) {
992
- arg.expression = localStyleAliases[expr.value];
993
- }
1136
+ arg.expression = resolveLocalStyleAlias(localStyleAliases, arg.expression);
994
1137
  });
995
1138
  const conditionals = [];
996
1139
  const dynamicVars = [];
@@ -1114,6 +1257,8 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1114
1257
  return buildDecision(node, prefix, off, { n: 0 }, scoped);
1115
1258
  };
1116
1259
  const collectConditions = (node, currentTestStrings = [], argOrder) => {
1260
+ node = unwrapExpression(node);
1261
+ node = resolveLocalStyleAlias(localStyleAliases, node);
1117
1262
  if (isNoOpStyle(node))
1118
1263
  return true;
1119
1264
  let branchStyle = resolveStyleObject(node);
@@ -1192,6 +1337,12 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1192
1337
  t.isIdentifier(expr.property);
1193
1338
  if (!t.isIdentifier(expr) && !isParamMember)
1194
1339
  return false;
1340
+ const paramObject = isParamMember
1341
+ ? expr.object.value
1342
+ : undefined;
1343
+ const isPropsMember = Boolean(paramObject &&
1344
+ localCreateStyles[paramObject] === undefined &&
1345
+ mergedCreateTable[paramObject] === undefined);
1195
1346
  const varName = t.isIdentifier(expr)
1196
1347
  ? expr.value
1197
1348
  : expr.property.value;
@@ -1216,8 +1367,43 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1216
1367
  if (candidates.length > 0)
1217
1368
  possibilities = candidates;
1218
1369
  }
1219
- if (!possibilities || possibilities.length === 0)
1220
- return false;
1370
+ const fallback = owner && propDefaults.get(owner)?.get(propName);
1371
+ if (fallback) {
1372
+ const style = resolveStyleObject(fallback);
1373
+ if (style) {
1374
+ const atoms = {};
1375
+ getStyleRecords(style).forEach((record) => {
1376
+ atoms[record.key] = record.hash;
1377
+ });
1378
+ const key = genBase36Hash(Object.values(atoms).join(' '), 1, 8);
1379
+ possibilities = [
1380
+ ...(possibilities ?? []),
1381
+ { key, styleObj: style },
1382
+ ];
1383
+ const span = fallback.span;
1384
+ {
1385
+ for (let i = replacements.length - 1; i >= 0; i--) {
1386
+ const replacement = replacements[i];
1387
+ if (replacement.start >= span.start - baseByteOffset &&
1388
+ replacement.end <= span.end - baseByteOffset)
1389
+ replacements.splice(i, 1);
1390
+ }
1391
+ excludeSubtreeSpans(fallback);
1392
+ replacements.push({
1393
+ start: span.start - baseByteOffset,
1394
+ end: span.end - baseByteOffset,
1395
+ content: JSON.stringify(key),
1396
+ });
1397
+ }
1398
+ }
1399
+ else {
1400
+ assertResolvable(fallback);
1401
+ }
1402
+ }
1403
+ if (!possibilities || possibilities.length === 0) {
1404
+ return Boolean(isPropsMember ||
1405
+ (owner && declaredProps.get(owner)?.has(propName)));
1406
+ }
1221
1407
  const carriesVars = possibilities.some((entry) => entry.hasVars);
1222
1408
  if (carriesVars && !isStyleProp) {
1223
1409
  throwCompilationError(`Plumeria: "${varName}" carries a dynamic function key, and css.use() returns only a class name. ` +
@@ -1933,50 +2119,61 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1933
2119
  }
1934
2120
  }
1935
2121
  if (compKey) {
2122
+ if (node.value?.type === 'JSXExpressionContainer')
2123
+ assertNoStyleArraySpread(node.value.expression);
1936
2124
  const list = scannedTables.componentPropsTable?.[compKey]?.[attrName];
1937
2125
  if (list &&
1938
2126
  node.value &&
1939
2127
  node.value.type === 'JSXExpressionContainer') {
1940
2128
  const expr = node.value.expression;
1941
- const carrierFor = (subNode) => {
2129
+ const carrierFor = (subNode, calls) => {
1942
2130
  const vars = [];
1943
2131
  let unresolved = false;
1944
- traverse(subNode, {
1945
- CallExpression({ node: call }) {
1946
- if (!isStyleFunctionCall(call))
1947
- return;
1948
- const resolved = resolveDynamicCall(call, true);
1949
- if (!resolved) {
1950
- unresolved = true;
1951
- return;
1952
- }
1953
- vars.push(...resolved.vars);
1954
- },
1955
- });
2132
+ for (const expression of calls ?? [subNode])
2133
+ traverse(expression, {
2134
+ CallExpression({ node: call }) {
2135
+ if (!isStyleFunctionCall(call))
2136
+ return;
2137
+ const resolved = resolveDynamicCall(call, true);
2138
+ if (!resolved) {
2139
+ unresolved = true;
2140
+ return;
2141
+ }
2142
+ vars.push(...resolved.vars);
2143
+ },
2144
+ });
1956
2145
  if (unresolved)
1957
2146
  return null;
1958
2147
  return `{ ${foldDynamicVars(vars).join(', ')} }`;
1959
2148
  };
1960
2149
  const replaceWithKey = (subNode) => {
1961
- const entry = list.find((x) => x.spanStart === subNode.span.start &&
1962
- x.filePath === resourcePath);
1963
- if (entry) {
1964
- let content = JSON.stringify(entry.key);
2150
+ const entries = list.filter((entry) => entry.spanStart === subNode.span.start &&
2151
+ entry.filePath === resourcePath);
2152
+ if (!entries.length)
2153
+ return false;
2154
+ let content = '""';
2155
+ for (const entry of [...entries].reverse()) {
2156
+ let value = JSON.stringify(entry.key);
1965
2157
  if (entry.hasVars) {
1966
- const carrier = carrierFor(subNode);
2158
+ const carrier = carrierFor(subNode, entry.dynamicCalls);
1967
2159
  if (!carrier)
1968
2160
  return false;
1969
- content = `{ key: ${content}, vars: ${carrier} }`;
2161
+ value = `{ key: ${value}, vars: ${carrier} }`;
1970
2162
  }
1971
- replacements.push({
1972
- start: subNode.span.start - baseByteOffset,
1973
- end: subNode.span.end - baseByteOffset,
1974
- content,
1975
- });
2163
+ const condition = entry.conditions
2164
+ ?.map(({ test, truthy }) => `${truthy ? '' : '!'}(${getSource(test)})`)
2165
+ .join(' && ');
2166
+ content = condition
2167
+ ? `((${condition}) ? ${value} : ${content})`
2168
+ : value;
1976
2169
  processStyleRecords(entry.styleObj);
1977
- return true;
1978
2170
  }
1979
- return false;
2171
+ replacements.push({
2172
+ start: subNode.span.start - baseByteOffset,
2173
+ end: subNode.span.end - baseByteOffset,
2174
+ content,
2175
+ });
2176
+ return true;
1980
2177
  };
1981
2178
  traverse(expr, {
1982
2179
  MemberExpression({ node: subNode }) {
@@ -2005,7 +2202,11 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
2005
2202
  const args = expr.type === 'ArrayExpression'
2006
2203
  ? expr.elements
2007
2204
  .filter((el) => el !== undefined)
2008
- .map((el) => ({ expression: el.expression }))
2205
+ .map((el) => {
2206
+ if (el.spread)
2207
+ throwCompilationError(spreadStyleMessage(getSource(el.expression)), el.expression);
2208
+ return { expression: el.expression };
2209
+ })
2009
2210
  : [{ expression: expr }];
2010
2211
  const dynamicClassParts = [];
2011
2212
  const existingStyleParts = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/unplugin",
3
- "version": "18.4.2",
3
+ "version": "18.5.1",
4
4
  "description": "Universal Plumeria plugin for various build tools",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",
@@ -94,7 +94,7 @@
94
94
  "dependencies": {
95
95
  "@rollup/pluginutils": "^5.4.0",
96
96
  "unplugin": "^3.0.0",
97
- "@plumeria/utils": "^18.4.2"
97
+ "@plumeria/utils": "^18.5.1"
98
98
  },
99
99
  "devDependencies": {
100
100
  "@swc/core": "1.15.47",