@plumeria/unplugin 18.3.18 → 18.4.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.
package/README.md CHANGED
@@ -156,6 +156,10 @@ plumeria.vite({
156
156
  | `exclude` | — | Files to skip. |
157
157
  | `devEmitToDisk` | `false` | Write CSS to disk in development so the bundler's watcher drives HMR. |
158
158
  | `styleProp` | `'classStyle'` | The JSX prop that carries styles. |
159
+ | `withoutLogicalProperties` | `false` | Reject the logical name of a property that also has a physical name. Takes `true` or `{ sizes?: boolean }`. |
160
+ | `withoutPhysicalProperties` | `false` | Reject the physical name of a property that also has a logical name. Takes `true` or `{ sizes?: boolean }`. |
161
+
162
+ The two spelling options contradict each other, so enabling both is a configuration error. See [the docs](https://plumeria.dev/docs/api-reference/plugins/unplugin) for why the pair has to be settled and how it lines up with the lint rules.
159
163
 
160
164
  ### styleProp
161
165
 
package/dist/core.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { UnpluginFactory } from 'unplugin';
2
- export interface PluginOptions {
2
+ import type { PropertyPolicyOptions } from '@plumeria/utils';
3
+ export interface PluginOptions extends PropertyPolicyOptions {
3
4
  include?: string | RegExp | Array<string | RegExp>;
4
5
  exclude?: string | RegExp | Array<string | RegExp>;
5
6
  devEmitToDisk?: boolean;
package/dist/core.js CHANGED
@@ -71,81 +71,6 @@ const isStaticArgValue = (node) => node.type === 'StringLiteral' ||
71
71
  (node.type === 'TemplateLiteral' && node.expressions.length === 0) ||
72
72
  utils_1.t.isIdentifier(node) ||
73
73
  utils_1.t.isMemberExpression(node);
74
- const namedParamsOf = (params, defaults) => {
75
- if (params.length !== 1)
76
- return undefined;
77
- const first = params[0];
78
- const pattern = (first?.pat ?? first);
79
- if (pattern?.type !== 'ObjectPattern')
80
- return undefined;
81
- const named = [];
82
- for (const prop of pattern.properties ?? []) {
83
- if (prop.type === 'AssignmentPatternProperty' && utils_1.t.isIdentifier(prop.key)) {
84
- named.push({ key: prop.key.value, local: prop.key.value });
85
- if (prop.value)
86
- defaults[prop.key.value] = prop.value;
87
- }
88
- else if (prop.type === 'KeyValuePatternProperty' &&
89
- utils_1.t.isIdentifier(prop.key) &&
90
- utils_1.t.isIdentifier(prop.value)) {
91
- named.push({ key: prop.key.value, local: prop.value.value });
92
- }
93
- else if (prop.type === 'KeyValuePatternProperty' &&
94
- utils_1.t.isIdentifier(prop.key) &&
95
- prop.value?.type === 'AssignmentPattern' &&
96
- utils_1.t.isIdentifier(prop.value.left)) {
97
- const pattern = prop.value;
98
- named.push({ key: String(prop.key.value), local: pattern.left.value });
99
- defaults[pattern.left.value] = pattern.right;
100
- }
101
- else {
102
- return undefined;
103
- }
104
- }
105
- return named.length > 0 ? named : undefined;
106
- };
107
- const styleFunctionsOf = (objExpr) => {
108
- const styleFunctions = {};
109
- objExpr.properties.forEach((prop) => {
110
- if (prop.type !== 'KeyValueProperty' || prop.key.type !== 'Identifier')
111
- return;
112
- const func = prop.value;
113
- if (func.type !== 'ArrowFunctionExpression' &&
114
- func.type !== 'FunctionExpression')
115
- return;
116
- const defaults = {};
117
- const params = func.params.map((p) => {
118
- const pattern = typeof p === 'object' && p !== null && 'pat' in p ? p.pat : p;
119
- if (utils_1.t.isIdentifier(pattern))
120
- return pattern.value;
121
- if (pattern?.type === 'AssignmentPattern' &&
122
- utils_1.t.isIdentifier(pattern.left)) {
123
- defaults[pattern.left.value] = pattern.right;
124
- return pattern.left.value;
125
- }
126
- return 'arg';
127
- });
128
- let actualBody = func.body;
129
- if (actualBody?.type === 'ParenthesisExpression')
130
- actualBody = actualBody.expression;
131
- if (actualBody?.type === 'BlockStatement') {
132
- const first = actualBody.stmts?.[0];
133
- if (first?.type === 'ReturnStatement')
134
- actualBody = first.argument;
135
- if (actualBody?.type === 'ParenthesisExpression')
136
- actualBody = actualBody.expression;
137
- }
138
- if (actualBody && actualBody.type === 'ObjectExpression') {
139
- styleFunctions[prop.key.value] = {
140
- params,
141
- named: namedParamsOf(func.params, defaults),
142
- defaults,
143
- body: actualBody,
144
- };
145
- }
146
- });
147
- return styleFunctions;
148
- };
149
74
  const foldDynamicVars = (vars) => {
150
75
  const grouped = new Map();
151
76
  [...vars]
@@ -167,61 +92,9 @@ const foldDynamicVars = (vars) => {
167
92
  return `"${cssVar}": ${value}`;
168
93
  });
169
94
  };
170
- const isUnitlessProp = (prop) => zss_engine_1.exceptionCamelCase.includes(prop) || prop.startsWith('--');
171
- const collectVarProps = (style, cssVar, props) => {
172
- const reference = `var(${cssVar})`;
173
- for (const [prop, value] of Object.entries(style)) {
174
- if (typeof value === 'string' && value.includes(reference))
175
- props.push(prop);
176
- else if (value !== null && typeof value === 'object')
177
- collectVarProps(value, cssVar, props);
178
- }
179
- };
180
- const retargetVar = (style, cssVar, next, unitless) => {
181
- const reference = `var(${cssVar})`;
182
- for (const [prop, value] of Object.entries(style)) {
183
- if (typeof value === 'string' && value.includes(reference)) {
184
- if (isUnitlessProp(prop) === unitless)
185
- style[prop] = value
186
- .split(reference)
187
- .join(`var(${next})`);
188
- }
189
- else if (value !== null && typeof value === 'object') {
190
- retargetVar(value, cssVar, next, unitless);
191
- }
192
- }
193
- };
194
- const splitVarByUnit = (style, cssVar) => {
195
- const props = [];
196
- collectVarProps(style, cssVar, props);
197
- if (props.length === 0)
198
- return [];
199
- const lead = props[0];
200
- const split = props.find((prop) => isUnitlessProp(prop) !== isUnitlessProp(lead));
201
- if (!split)
202
- return [{ cssVar, prop: lead }];
203
- const splitVar = `${cssVar}-${(0, zss_engine_1.camelToKebabCase)(split).replace(/^-+/, '')}`;
204
- retargetVar(style, cssVar, splitVar, isUnitlessProp(split));
205
- return [
206
- { cssVar, prop: lead },
207
- { cssVar: splitVar, prop: split },
208
- ];
209
- };
210
- const applyVarFallback = (style, cssVar, literal) => {
211
- const reference = `var(${cssVar})`;
212
- for (const [prop, value] of Object.entries(style)) {
213
- if (typeof value === 'string' && value.includes(reference)) {
214
- style[prop] = value
215
- .split(reference)
216
- .join(`var(${cssVar}, ${(0, zss_engine_1.applyCssValue)(literal, (0, zss_engine_1.camelToKebabCase)(prop))})`);
217
- }
218
- else if (value !== null && typeof value === 'object') {
219
- applyVarFallback(value, cssVar, literal);
220
- }
221
- }
222
- };
223
95
  const unpluginFactory = (options = {}, unpluginMeta) => {
224
96
  const filter = (0, pluginutils_1.createFilter)(options.include, options.exclude);
97
+ const propertyPolicy = (0, utils_1.resolvePropertyPolicy)(options);
225
98
  const styleProp = options.styleProp ?? utils_1.DEFAULT_STYLE_PROP;
226
99
  const cssLookup = new Map();
227
100
  const cssFileLookup = new Map();
@@ -291,6 +164,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
291
164
  tsx: true,
292
165
  target: 'es2022',
293
166
  });
167
+ (0, utils_1.assertPropertyPolicy)(ast, propertyPolicy, id);
294
168
  const leadingLen = (0, utils_1.getLeadingCommentLength)(source);
295
169
  const sourceBuffer = Buffer.from(source, 'utf-8');
296
170
  const leadingBytes = Buffer.byteLength(source.slice(0, leadingLen), 'utf-8');
@@ -491,7 +365,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
491
365
  scannedTables.createHashTable[uniqueKey];
492
366
  }
493
367
  if (scannedTables.createFunctionTable[uniqueKey]) {
494
- createFunctionImportMap[localName] = styleFunctionsOf(scannedTables.createFunctionTable[uniqueKey]);
368
+ createFunctionImportMap[localName] = (0, utils_1.styleFunctionsOf)(scannedTables.createFunctionTable[uniqueKey]);
495
369
  }
496
370
  if (scannedTables.variantsHashTable[uniqueKey]) {
497
371
  variantsImportMap[localName] =
@@ -592,6 +466,16 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
592
466
  for (const key of Object.keys(createStaticImportMap)) {
593
467
  mergedCreateStaticHashTable[key] = createStaticImportMap[key];
594
468
  }
469
+ const dynamicStyleTables = {
470
+ keyframesHashTable: mergedKeyframesTable,
471
+ viewTransitionHashTable: mergedViewTransitionTable,
472
+ createThemeHashTable: mergedCreateThemeHashTable,
473
+ createThemeObjectTable: scannedTables.createThemeObjectTable,
474
+ createHashTable: mergedCreateTable,
475
+ createStaticHashTable: mergedCreateStaticHashTable,
476
+ createStaticObjectTable: scannedTables.createStaticObjectTable,
477
+ variantsHashTable: mergedVariantsTable,
478
+ };
595
479
  const localCreateStyles = {};
596
480
  const localStyleAliases = {};
597
481
  const checkStyleAliasAssignment = (decl) => {
@@ -652,7 +536,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
652
536
  records.forEach((r) => (atomMap[r.key] = r.hash));
653
537
  hashMap[key] = atomMap;
654
538
  });
655
- const styleFunctions = styleFunctionsOf(init.arguments[0].expression);
539
+ const styleFunctions = (0, utils_1.styleFunctionsOf)(init.arguments[0].expression);
656
540
  if (utils_1.t.isIdentifier(node.id)) {
657
541
  idSpans.add(node.id.span.start);
658
542
  }
@@ -993,7 +877,16 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
993
877
  }
994
878
  return null;
995
879
  };
996
- const resolveDynamicCall = (expr) => {
880
+ const isStyleFunctionCall = (expr) => {
881
+ if (!utils_1.t.isCallExpression(expr) || !utils_1.t.isMemberExpression(expr.callee))
882
+ return false;
883
+ const callee = expr.callee;
884
+ if (!utils_1.t.isIdentifier(callee.object) || !utils_1.t.isIdentifier(callee.property))
885
+ return false;
886
+ return Boolean(localCreateStyles[callee.object.value]?.functions?.[callee.property.value] ??
887
+ createFunctionImportMap[callee.object.value]?.[callee.property.value]);
888
+ };
889
+ const resolveDynamicCall = (expr, forceRuntime = false) => {
997
890
  if (!utils_1.t.isCallExpression(expr) || !utils_1.t.isMemberExpression(expr.callee))
998
891
  return null;
999
892
  const callee = expr.callee;
@@ -1037,7 +930,9 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1037
930
  throwCompilationError(`Plumeria: ${getSource(expr)} leaves "${key}" unset, and a dynamic style function has no value to fall back on.`, expr);
1038
931
  return;
1039
932
  }
1040
- if (isStaticArgValue(source) && argObj[key] !== undefined)
933
+ if (!forceRuntime &&
934
+ isStaticArgValue(source) &&
935
+ argObj[key] !== undefined)
1041
936
  tempStaticTable[local] = argObj[key];
1042
937
  else
1043
938
  runtime.push({ param: local, source });
@@ -1045,6 +940,8 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1045
940
  }
1046
941
  else if (callArgs.length === 1 &&
1047
942
  callArgs[0].expression.type === 'ObjectExpression') {
943
+ if (forceRuntime)
944
+ return null;
1048
945
  const argObj = resolveObjectArg(callArgs[0].expression) ?? {};
1049
946
  func.params.forEach((p) => {
1050
947
  if (argObj[p] !== undefined)
@@ -1059,39 +956,10 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1059
956
  runtime.push({ param: p, source: callArg.expression });
1060
957
  });
1061
958
  }
1062
- const withDefault = Object.entries(func.defaults ?? {}).filter(([param]) => tempStaticTable[param] === undefined);
1063
- const cssVars = {};
1064
- const varParams = Array.from(new Set([
1065
- ...runtime.map(({ param }) => param),
1066
- ...withDefault.map(([param]) => param),
1067
- ]));
1068
- if (varParams.length > 0) {
1069
- varParams.forEach((param) => (tempStaticTable[param] = param));
1070
- const probe = (0, utils_1.objectExpressionToObject)(func.body, tempStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1071
- const hash = (0, zss_engine_1.genBase36Hash)(probe ?? {}, 1, 8);
1072
- varParams.forEach((param) => {
1073
- const cssVar = `--${hash}-${param}`;
1074
- tempStaticTable[param] = `var(${cssVar})`;
1075
- cssVars[param] = cssVar;
1076
- });
1077
- }
1078
- const style = (0, utils_1.objectExpressionToObject)(func.body, tempStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1079
- if (!style)
959
+ const resolved = (0, utils_1.resolveDynamicStyle)(func, runtime.map(({ param }) => param), tempStaticTable, dynamicStyleTables);
960
+ if (!resolved)
1080
961
  return null;
1081
- const varGroups = new Map();
1082
- varParams.forEach((param) => {
1083
- const cssVar = cssVars[param];
1084
- if (cssVar)
1085
- varGroups.set(param, splitVarByUnit(style, cssVar));
1086
- });
1087
- withDefault.forEach(([param, expr]) => {
1088
- const literal = expr.type === 'StringLiteral' || expr.type === 'NumericLiteral'
1089
- ? expr.value
1090
- : undefined;
1091
- if (literal === undefined)
1092
- return;
1093
- (varGroups.get(param) ?? []).forEach(({ cssVar }) => applyVarFallback(style, cssVar, literal));
1094
- });
962
+ const { style, varGroups } = resolved;
1095
963
  const vars = [];
1096
964
  runtime.forEach(({ param, source }) => {
1097
965
  const groups = varGroups.get(param);
@@ -1115,7 +983,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1115
983
  valueExpr = JSON.stringify((0, zss_engine_1.applyCssValue)(argSource.slice(1, -1), kebabProp));
1116
984
  }
1117
985
  else {
1118
- valueExpr = isUnitlessProp(prop)
986
+ valueExpr = (0, utils_1.isUnitlessProp)(prop)
1119
987
  ? argSource
1120
988
  : `(typeof (${argSource}) === 'number' ? (${argSource}) + 'px' : (${argSource}))`;
1121
989
  }
@@ -1133,6 +1001,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1133
1001
  });
1134
1002
  const conditionals = [];
1135
1003
  const dynamicVars = [];
1004
+ const propVarSpreads = [];
1136
1005
  let groupIdCounter = 0;
1137
1006
  let sourceOrder = 0;
1138
1007
  const baseChunks = [];
@@ -1351,9 +1220,23 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1351
1220
  }
1352
1221
  if (!possibilities || possibilities.length === 0)
1353
1222
  return false;
1354
- const testLHS = gates.length
1355
- ? `((${gates.join(' && ')}) ? ${source} : "")`
1223
+ const carriesVars = possibilities.some((entry) => entry.hasVars);
1224
+ if (carriesVars && !isStyleProp) {
1225
+ throwCompilationError(`Plumeria: "${varName}" carries a dynamic function key, and css.use() returns only a class name. ` +
1226
+ `Apply it to ${styleProp} on the element instead.`, expr);
1227
+ }
1228
+ const gate = gates.length ? `(${gates.join(' && ')}) && ` : '';
1229
+ if (carriesVars) {
1230
+ const spread = `...(${gate}${source} && ${source}.vars)`;
1231
+ if (!propVarSpreads.includes(spread))
1232
+ propVarSpreads.push(spread);
1233
+ }
1234
+ const token = carriesVars
1235
+ ? `((${source} && ${source}.key) || ${source})`
1356
1236
  : source;
1237
+ const testLHS = gates.length
1238
+ ? `((${gates.join(' && ')}) ? ${token} : "")`
1239
+ : token;
1357
1240
  const currentGroupId = ++groupIdCounter;
1358
1241
  const currentOrder = sourceOrder++;
1359
1242
  const seen = new Set();
@@ -1592,6 +1475,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1592
1475
  isOptimizable,
1593
1476
  baseStyle,
1594
1477
  dynamicVars,
1478
+ propVarSpreads,
1595
1479
  };
1596
1480
  }
1597
1481
  const stateSources = baseChunks.map(({ order, style }) => ({ order, style: style }));
@@ -1847,7 +1731,13 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1847
1731
  classParts.push(`(${JSON.stringify(results)}[${masterKeyExpr || '""'}] || ${baseConflictClass ? JSON.stringify(baseConflictClass) : '""'})`);
1848
1732
  }
1849
1733
  classParts.push(...dynamicClassParts);
1850
- return { classParts, isOptimizable, baseStyle, dynamicVars };
1734
+ return {
1735
+ classParts,
1736
+ isOptimizable,
1737
+ baseStyle,
1738
+ dynamicVars,
1739
+ propVarSpreads,
1740
+ };
1851
1741
  };
1852
1742
  (0, utils_1.traverse)(ast, {
1853
1743
  JSXOpeningElement({ node }) {
@@ -2048,14 +1938,40 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
2048
1938
  node.value &&
2049
1939
  node.value.type === 'JSXExpressionContainer') {
2050
1940
  const expr = node.value.expression;
1941
+ const carrierFor = (subNode) => {
1942
+ const vars = [];
1943
+ let unresolved = false;
1944
+ (0, utils_1.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
+ });
1956
+ if (unresolved)
1957
+ return null;
1958
+ return `{ ${foldDynamicVars(vars).join(', ')} }`;
1959
+ };
2051
1960
  const replaceWithKey = (subNode) => {
2052
1961
  const entry = list.find((x) => x.spanStart === subNode.span.start &&
2053
1962
  x.filePath === resourcePath);
2054
1963
  if (entry) {
1964
+ let content = JSON.stringify(entry.key);
1965
+ if (entry.hasVars) {
1966
+ const carrier = carrierFor(subNode);
1967
+ if (!carrier)
1968
+ return false;
1969
+ content = `{ key: ${content}, vars: ${carrier} }`;
1970
+ }
2055
1971
  replacements.push({
2056
1972
  start: subNode.span.start - baseByteOffset,
2057
1973
  end: subNode.span.end - baseByteOffset,
2058
- content: JSON.stringify(entry.key),
1974
+ content,
2059
1975
  });
2060
1976
  processStyleRecords(entry.styleObj);
2061
1977
  return true;
@@ -2073,6 +1989,11 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
2073
1989
  excludeSubtreeSpans(subNode);
2074
1990
  }
2075
1991
  },
1992
+ CallExpression({ node: subNode }) {
1993
+ if (replaceWithKey(subNode)) {
1994
+ excludeSubtreeSpans(subNode);
1995
+ }
1996
+ },
2076
1997
  });
2077
1998
  }
2078
1999
  }
@@ -2148,9 +2069,12 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
2148
2069
  }
2149
2070
  }
2150
2071
  }
2151
- const { classParts, isOptimizable, baseStyle, dynamicVars } = buildClassParts(args, dynamicClassParts, existingClassExpr, true);
2152
- const dynamicStyleParts = foldDynamicVars(dynamicVars);
2153
- const styleParts = [...existingStyleParts, ...dynamicStyleParts];
2072
+ const { classParts, isOptimizable, baseStyle, dynamicVars, propVarSpreads, } = buildClassParts(args, dynamicClassParts, existingClassExpr, true);
2073
+ const styleParts = [
2074
+ ...existingStyleParts,
2075
+ ...foldDynamicVars(dynamicVars),
2076
+ ...propVarSpreads,
2077
+ ];
2154
2078
  const styleAttr = styleParts.length > 0 || existingStyleExpr
2155
2079
  ? ` style={{ ${[existingStyleExpr, ...styleParts].filter(Boolean).join(', ')} }}`
2156
2080
  : '';
package/dist/core.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createFilter } from '@rollup/pluginutils';
2
2
  import { parseSync } from '@swc/core';
3
3
  import * as path from 'path';
4
- import { applyCssValue, genBase36Hash, exceptionCamelCase, camelToKebabCase, isAtRule, } from 'zss-engine';
5
- import { traverse, collectReferenceIdentifiers, getStyleRecords, getStateWeights, collectLocalConsts, objectExpressionToObject, t, getRootIdentifier, extractOndemandStyles, deepMerge, scanAll, resolveFileError, resolveImportPath, getLeadingCommentLength, optimizer, themeHashOf, getFileDependencies, resolveExport, resolveComponentKey, resolveThemeSelector, DEFAULT_STYLE_PROP, } from '@plumeria/utils';
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';
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) => {
@@ -35,81 +35,6 @@ const isStaticArgValue = (node) => node.type === 'StringLiteral' ||
35
35
  (node.type === 'TemplateLiteral' && node.expressions.length === 0) ||
36
36
  t.isIdentifier(node) ||
37
37
  t.isMemberExpression(node);
38
- const namedParamsOf = (params, defaults) => {
39
- if (params.length !== 1)
40
- return undefined;
41
- const first = params[0];
42
- const pattern = (first?.pat ?? first);
43
- if (pattern?.type !== 'ObjectPattern')
44
- return undefined;
45
- const named = [];
46
- for (const prop of pattern.properties ?? []) {
47
- if (prop.type === 'AssignmentPatternProperty' && t.isIdentifier(prop.key)) {
48
- named.push({ key: prop.key.value, local: prop.key.value });
49
- if (prop.value)
50
- defaults[prop.key.value] = prop.value;
51
- }
52
- else if (prop.type === 'KeyValuePatternProperty' &&
53
- t.isIdentifier(prop.key) &&
54
- t.isIdentifier(prop.value)) {
55
- named.push({ key: prop.key.value, local: prop.value.value });
56
- }
57
- else if (prop.type === 'KeyValuePatternProperty' &&
58
- t.isIdentifier(prop.key) &&
59
- prop.value?.type === 'AssignmentPattern' &&
60
- t.isIdentifier(prop.value.left)) {
61
- const pattern = prop.value;
62
- named.push({ key: String(prop.key.value), local: pattern.left.value });
63
- defaults[pattern.left.value] = pattern.right;
64
- }
65
- else {
66
- return undefined;
67
- }
68
- }
69
- return named.length > 0 ? named : undefined;
70
- };
71
- const styleFunctionsOf = (objExpr) => {
72
- const styleFunctions = {};
73
- objExpr.properties.forEach((prop) => {
74
- if (prop.type !== 'KeyValueProperty' || prop.key.type !== 'Identifier')
75
- return;
76
- const func = prop.value;
77
- if (func.type !== 'ArrowFunctionExpression' &&
78
- func.type !== 'FunctionExpression')
79
- return;
80
- const defaults = {};
81
- const params = func.params.map((p) => {
82
- const pattern = typeof p === 'object' && p !== null && 'pat' in p ? p.pat : p;
83
- if (t.isIdentifier(pattern))
84
- return pattern.value;
85
- if (pattern?.type === 'AssignmentPattern' &&
86
- t.isIdentifier(pattern.left)) {
87
- defaults[pattern.left.value] = pattern.right;
88
- return pattern.left.value;
89
- }
90
- return 'arg';
91
- });
92
- let actualBody = func.body;
93
- if (actualBody?.type === 'ParenthesisExpression')
94
- actualBody = actualBody.expression;
95
- if (actualBody?.type === 'BlockStatement') {
96
- const first = actualBody.stmts?.[0];
97
- if (first?.type === 'ReturnStatement')
98
- actualBody = first.argument;
99
- if (actualBody?.type === 'ParenthesisExpression')
100
- actualBody = actualBody.expression;
101
- }
102
- if (actualBody && actualBody.type === 'ObjectExpression') {
103
- styleFunctions[prop.key.value] = {
104
- params,
105
- named: namedParamsOf(func.params, defaults),
106
- defaults,
107
- body: actualBody,
108
- };
109
- }
110
- });
111
- return styleFunctions;
112
- };
113
38
  const foldDynamicVars = (vars) => {
114
39
  const grouped = new Map();
115
40
  [...vars]
@@ -131,61 +56,9 @@ const foldDynamicVars = (vars) => {
131
56
  return `"${cssVar}": ${value}`;
132
57
  });
133
58
  };
134
- const isUnitlessProp = (prop) => exceptionCamelCase.includes(prop) || prop.startsWith('--');
135
- const collectVarProps = (style, cssVar, props) => {
136
- const reference = `var(${cssVar})`;
137
- for (const [prop, value] of Object.entries(style)) {
138
- if (typeof value === 'string' && value.includes(reference))
139
- props.push(prop);
140
- else if (value !== null && typeof value === 'object')
141
- collectVarProps(value, cssVar, props);
142
- }
143
- };
144
- const retargetVar = (style, cssVar, next, unitless) => {
145
- const reference = `var(${cssVar})`;
146
- for (const [prop, value] of Object.entries(style)) {
147
- if (typeof value === 'string' && value.includes(reference)) {
148
- if (isUnitlessProp(prop) === unitless)
149
- style[prop] = value
150
- .split(reference)
151
- .join(`var(${next})`);
152
- }
153
- else if (value !== null && typeof value === 'object') {
154
- retargetVar(value, cssVar, next, unitless);
155
- }
156
- }
157
- };
158
- const splitVarByUnit = (style, cssVar) => {
159
- const props = [];
160
- collectVarProps(style, cssVar, props);
161
- if (props.length === 0)
162
- return [];
163
- const lead = props[0];
164
- const split = props.find((prop) => isUnitlessProp(prop) !== isUnitlessProp(lead));
165
- if (!split)
166
- return [{ cssVar, prop: lead }];
167
- const splitVar = `${cssVar}-${camelToKebabCase(split).replace(/^-+/, '')}`;
168
- retargetVar(style, cssVar, splitVar, isUnitlessProp(split));
169
- return [
170
- { cssVar, prop: lead },
171
- { cssVar: splitVar, prop: split },
172
- ];
173
- };
174
- const applyVarFallback = (style, cssVar, literal) => {
175
- const reference = `var(${cssVar})`;
176
- for (const [prop, value] of Object.entries(style)) {
177
- if (typeof value === 'string' && value.includes(reference)) {
178
- style[prop] = value
179
- .split(reference)
180
- .join(`var(${cssVar}, ${applyCssValue(literal, camelToKebabCase(prop))})`);
181
- }
182
- else if (value !== null && typeof value === 'object') {
183
- applyVarFallback(value, cssVar, literal);
184
- }
185
- }
186
- };
187
59
  export const unpluginFactory = (options = {}, unpluginMeta) => {
188
60
  const filter = createFilter(options.include, options.exclude);
61
+ const propertyPolicy = resolvePropertyPolicy(options);
189
62
  const styleProp = options.styleProp ?? DEFAULT_STYLE_PROP;
190
63
  const cssLookup = new Map();
191
64
  const cssFileLookup = new Map();
@@ -255,6 +128,7 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
255
128
  tsx: true,
256
129
  target: 'es2022',
257
130
  });
131
+ assertPropertyPolicy(ast, propertyPolicy, id);
258
132
  const leadingLen = getLeadingCommentLength(source);
259
133
  const sourceBuffer = Buffer.from(source, 'utf-8');
260
134
  const leadingBytes = Buffer.byteLength(source.slice(0, leadingLen), 'utf-8');
@@ -556,6 +430,16 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
556
430
  for (const key of Object.keys(createStaticImportMap)) {
557
431
  mergedCreateStaticHashTable[key] = createStaticImportMap[key];
558
432
  }
433
+ const dynamicStyleTables = {
434
+ keyframesHashTable: mergedKeyframesTable,
435
+ viewTransitionHashTable: mergedViewTransitionTable,
436
+ createThemeHashTable: mergedCreateThemeHashTable,
437
+ createThemeObjectTable: scannedTables.createThemeObjectTable,
438
+ createHashTable: mergedCreateTable,
439
+ createStaticHashTable: mergedCreateStaticHashTable,
440
+ createStaticObjectTable: scannedTables.createStaticObjectTable,
441
+ variantsHashTable: mergedVariantsTable,
442
+ };
559
443
  const localCreateStyles = {};
560
444
  const localStyleAliases = {};
561
445
  const checkStyleAliasAssignment = (decl) => {
@@ -957,7 +841,16 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
957
841
  }
958
842
  return null;
959
843
  };
960
- const resolveDynamicCall = (expr) => {
844
+ const isStyleFunctionCall = (expr) => {
845
+ if (!t.isCallExpression(expr) || !t.isMemberExpression(expr.callee))
846
+ return false;
847
+ const callee = expr.callee;
848
+ if (!t.isIdentifier(callee.object) || !t.isIdentifier(callee.property))
849
+ return false;
850
+ return Boolean(localCreateStyles[callee.object.value]?.functions?.[callee.property.value] ??
851
+ createFunctionImportMap[callee.object.value]?.[callee.property.value]);
852
+ };
853
+ const resolveDynamicCall = (expr, forceRuntime = false) => {
961
854
  if (!t.isCallExpression(expr) || !t.isMemberExpression(expr.callee))
962
855
  return null;
963
856
  const callee = expr.callee;
@@ -1001,7 +894,9 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1001
894
  throwCompilationError(`Plumeria: ${getSource(expr)} leaves "${key}" unset, and a dynamic style function has no value to fall back on.`, expr);
1002
895
  return;
1003
896
  }
1004
- if (isStaticArgValue(source) && argObj[key] !== undefined)
897
+ if (!forceRuntime &&
898
+ isStaticArgValue(source) &&
899
+ argObj[key] !== undefined)
1005
900
  tempStaticTable[local] = argObj[key];
1006
901
  else
1007
902
  runtime.push({ param: local, source });
@@ -1009,6 +904,8 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1009
904
  }
1010
905
  else if (callArgs.length === 1 &&
1011
906
  callArgs[0].expression.type === 'ObjectExpression') {
907
+ if (forceRuntime)
908
+ return null;
1012
909
  const argObj = resolveObjectArg(callArgs[0].expression) ?? {};
1013
910
  func.params.forEach((p) => {
1014
911
  if (argObj[p] !== undefined)
@@ -1023,39 +920,10 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1023
920
  runtime.push({ param: p, source: callArg.expression });
1024
921
  });
1025
922
  }
1026
- const withDefault = Object.entries(func.defaults ?? {}).filter(([param]) => tempStaticTable[param] === undefined);
1027
- const cssVars = {};
1028
- const varParams = Array.from(new Set([
1029
- ...runtime.map(({ param }) => param),
1030
- ...withDefault.map(([param]) => param),
1031
- ]));
1032
- if (varParams.length > 0) {
1033
- varParams.forEach((param) => (tempStaticTable[param] = param));
1034
- const probe = objectExpressionToObject(func.body, tempStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1035
- const hash = genBase36Hash(probe ?? {}, 1, 8);
1036
- varParams.forEach((param) => {
1037
- const cssVar = `--${hash}-${param}`;
1038
- tempStaticTable[param] = `var(${cssVar})`;
1039
- cssVars[param] = cssVar;
1040
- });
1041
- }
1042
- const style = objectExpressionToObject(func.body, tempStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1043
- if (!style)
923
+ const resolved = resolveDynamicStyle(func, runtime.map(({ param }) => param), tempStaticTable, dynamicStyleTables);
924
+ if (!resolved)
1044
925
  return null;
1045
- const varGroups = new Map();
1046
- varParams.forEach((param) => {
1047
- const cssVar = cssVars[param];
1048
- if (cssVar)
1049
- varGroups.set(param, splitVarByUnit(style, cssVar));
1050
- });
1051
- withDefault.forEach(([param, expr]) => {
1052
- const literal = expr.type === 'StringLiteral' || expr.type === 'NumericLiteral'
1053
- ? expr.value
1054
- : undefined;
1055
- if (literal === undefined)
1056
- return;
1057
- (varGroups.get(param) ?? []).forEach(({ cssVar }) => applyVarFallback(style, cssVar, literal));
1058
- });
926
+ const { style, varGroups } = resolved;
1059
927
  const vars = [];
1060
928
  runtime.forEach(({ param, source }) => {
1061
929
  const groups = varGroups.get(param);
@@ -1097,6 +965,7 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1097
965
  });
1098
966
  const conditionals = [];
1099
967
  const dynamicVars = [];
968
+ const propVarSpreads = [];
1100
969
  let groupIdCounter = 0;
1101
970
  let sourceOrder = 0;
1102
971
  const baseChunks = [];
@@ -1315,9 +1184,23 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1315
1184
  }
1316
1185
  if (!possibilities || possibilities.length === 0)
1317
1186
  return false;
1318
- const testLHS = gates.length
1319
- ? `((${gates.join(' && ')}) ? ${source} : "")`
1187
+ const carriesVars = possibilities.some((entry) => entry.hasVars);
1188
+ if (carriesVars && !isStyleProp) {
1189
+ throwCompilationError(`Plumeria: "${varName}" carries a dynamic function key, and css.use() returns only a class name. ` +
1190
+ `Apply it to ${styleProp} on the element instead.`, expr);
1191
+ }
1192
+ const gate = gates.length ? `(${gates.join(' && ')}) && ` : '';
1193
+ if (carriesVars) {
1194
+ const spread = `...(${gate}${source} && ${source}.vars)`;
1195
+ if (!propVarSpreads.includes(spread))
1196
+ propVarSpreads.push(spread);
1197
+ }
1198
+ const token = carriesVars
1199
+ ? `((${source} && ${source}.key) || ${source})`
1320
1200
  : source;
1201
+ const testLHS = gates.length
1202
+ ? `((${gates.join(' && ')}) ? ${token} : "")`
1203
+ : token;
1321
1204
  const currentGroupId = ++groupIdCounter;
1322
1205
  const currentOrder = sourceOrder++;
1323
1206
  const seen = new Set();
@@ -1556,6 +1439,7 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1556
1439
  isOptimizable,
1557
1440
  baseStyle,
1558
1441
  dynamicVars,
1442
+ propVarSpreads,
1559
1443
  };
1560
1444
  }
1561
1445
  const stateSources = baseChunks.map(({ order, style }) => ({ order, style: style }));
@@ -1811,7 +1695,13 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1811
1695
  classParts.push(`(${JSON.stringify(results)}[${masterKeyExpr || '""'}] || ${baseConflictClass ? JSON.stringify(baseConflictClass) : '""'})`);
1812
1696
  }
1813
1697
  classParts.push(...dynamicClassParts);
1814
- return { classParts, isOptimizable, baseStyle, dynamicVars };
1698
+ return {
1699
+ classParts,
1700
+ isOptimizable,
1701
+ baseStyle,
1702
+ dynamicVars,
1703
+ propVarSpreads,
1704
+ };
1815
1705
  };
1816
1706
  traverse(ast, {
1817
1707
  JSXOpeningElement({ node }) {
@@ -2012,14 +1902,40 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
2012
1902
  node.value &&
2013
1903
  node.value.type === 'JSXExpressionContainer') {
2014
1904
  const expr = node.value.expression;
1905
+ const carrierFor = (subNode) => {
1906
+ const vars = [];
1907
+ let unresolved = false;
1908
+ traverse(subNode, {
1909
+ CallExpression({ node: call }) {
1910
+ if (!isStyleFunctionCall(call))
1911
+ return;
1912
+ const resolved = resolveDynamicCall(call, true);
1913
+ if (!resolved) {
1914
+ unresolved = true;
1915
+ return;
1916
+ }
1917
+ vars.push(...resolved.vars);
1918
+ },
1919
+ });
1920
+ if (unresolved)
1921
+ return null;
1922
+ return `{ ${foldDynamicVars(vars).join(', ')} }`;
1923
+ };
2015
1924
  const replaceWithKey = (subNode) => {
2016
1925
  const entry = list.find((x) => x.spanStart === subNode.span.start &&
2017
1926
  x.filePath === resourcePath);
2018
1927
  if (entry) {
1928
+ let content = JSON.stringify(entry.key);
1929
+ if (entry.hasVars) {
1930
+ const carrier = carrierFor(subNode);
1931
+ if (!carrier)
1932
+ return false;
1933
+ content = `{ key: ${content}, vars: ${carrier} }`;
1934
+ }
2019
1935
  replacements.push({
2020
1936
  start: subNode.span.start - baseByteOffset,
2021
1937
  end: subNode.span.end - baseByteOffset,
2022
- content: JSON.stringify(entry.key),
1938
+ content,
2023
1939
  });
2024
1940
  processStyleRecords(entry.styleObj);
2025
1941
  return true;
@@ -2037,6 +1953,11 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
2037
1953
  excludeSubtreeSpans(subNode);
2038
1954
  }
2039
1955
  },
1956
+ CallExpression({ node: subNode }) {
1957
+ if (replaceWithKey(subNode)) {
1958
+ excludeSubtreeSpans(subNode);
1959
+ }
1960
+ },
2040
1961
  });
2041
1962
  }
2042
1963
  }
@@ -2112,9 +2033,12 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
2112
2033
  }
2113
2034
  }
2114
2035
  }
2115
- const { classParts, isOptimizable, baseStyle, dynamicVars } = buildClassParts(args, dynamicClassParts, existingClassExpr, true);
2116
- const dynamicStyleParts = foldDynamicVars(dynamicVars);
2117
- const styleParts = [...existingStyleParts, ...dynamicStyleParts];
2036
+ const { classParts, isOptimizable, baseStyle, dynamicVars, propVarSpreads, } = buildClassParts(args, dynamicClassParts, existingClassExpr, true);
2037
+ const styleParts = [
2038
+ ...existingStyleParts,
2039
+ ...foldDynamicVars(dynamicVars),
2040
+ ...propVarSpreads,
2041
+ ];
2118
2042
  const styleAttr = styleParts.length > 0 || existingStyleExpr
2119
2043
  ? ` style={{ ${[existingStyleExpr, ...styleParts].filter(Boolean).join(', ')} }}`
2120
2044
  : '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/unplugin",
3
- "version": "18.3.18",
3
+ "version": "18.4.1",
4
4
  "description": "Universal Plumeria plugin for various build tools",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",
@@ -94,12 +94,12 @@
94
94
  "dependencies": {
95
95
  "@rollup/pluginutils": "^5.4.0",
96
96
  "unplugin": "^3.0.0",
97
- "@plumeria/utils": "^18.3.18"
97
+ "@plumeria/utils": "^18.4.1"
98
98
  },
99
99
  "devDependencies": {
100
100
  "@swc/core": "1.15.47",
101
101
  "vite": "^8.0.13",
102
- "zss-engine": "2.7.0"
102
+ "zss-engine": "2.8.0"
103
103
  },
104
104
  "publishConfig": {
105
105
  "access": "public",