@plumeria/unplugin 18.3.17 → 18.4.0

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
@@ -167,6 +167,46 @@ const foldDynamicVars = (vars) => {
167
167
  return `"${cssVar}": ${value}`;
168
168
  });
169
169
  };
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
+ };
170
210
  const applyVarFallback = (style, cssVar, literal) => {
171
211
  const reference = `var(${cssVar})`;
172
212
  for (const [prop, value] of Object.entries(style)) {
@@ -180,20 +220,9 @@ const applyVarFallback = (style, cssVar, literal) => {
180
220
  }
181
221
  }
182
222
  };
183
- const findVarProp = (style, cssVar) => {
184
- for (const [prop, value] of Object.entries(style)) {
185
- if (typeof value === 'string' && value.includes(cssVar))
186
- return prop;
187
- if (value !== null && typeof value === 'object') {
188
- const nested = findVarProp(value, cssVar);
189
- if (nested)
190
- return nested;
191
- }
192
- }
193
- return undefined;
194
- };
195
223
  const unpluginFactory = (options = {}, unpluginMeta) => {
196
224
  const filter = (0, pluginutils_1.createFilter)(options.include, options.exclude);
225
+ const propertyPolicy = (0, utils_1.resolvePropertyPolicy)(options);
197
226
  const styleProp = options.styleProp ?? utils_1.DEFAULT_STYLE_PROP;
198
227
  const cssLookup = new Map();
199
228
  const cssFileLookup = new Map();
@@ -263,6 +292,7 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
263
292
  tsx: true,
264
293
  target: 'es2022',
265
294
  });
295
+ (0, utils_1.assertPropertyPolicy)(ast, propertyPolicy, id);
266
296
  const leadingLen = (0, utils_1.getLeadingCommentLength)(source);
267
297
  const sourceBuffer = Buffer.from(source, 'utf-8');
268
298
  const leadingBytes = Buffer.byteLength(source.slice(0, leadingLen), 'utf-8');
@@ -1050,47 +1080,49 @@ const unpluginFactory = (options = {}, unpluginMeta) => {
1050
1080
  const style = (0, utils_1.objectExpressionToObject)(func.body, tempStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1051
1081
  if (!style)
1052
1082
  return null;
1053
- withDefault.forEach(([param, expr]) => {
1083
+ const varGroups = new Map();
1084
+ varParams.forEach((param) => {
1054
1085
  const cssVar = cssVars[param];
1055
- const targetProp = cssVar ? findVarProp(style, cssVar) : undefined;
1056
- if (!targetProp)
1057
- return;
1086
+ if (cssVar)
1087
+ varGroups.set(param, splitVarByUnit(style, cssVar));
1088
+ });
1089
+ withDefault.forEach(([param, expr]) => {
1058
1090
  const literal = expr.type === 'StringLiteral' || expr.type === 'NumericLiteral'
1059
1091
  ? expr.value
1060
1092
  : undefined;
1061
1093
  if (literal === undefined)
1062
1094
  return;
1063
- applyVarFallback(style, cssVar, literal);
1095
+ (varGroups.get(param) ?? []).forEach(({ cssVar }) => applyVarFallback(style, cssVar, literal));
1064
1096
  });
1065
1097
  const vars = [];
1066
1098
  runtime.forEach(({ param, source }) => {
1067
- const cssVar = cssVars[param];
1068
- const targetProp = cssVar ? findVarProp(style, cssVar) : undefined;
1069
- if (!targetProp)
1099
+ const groups = varGroups.get(param);
1100
+ if (!groups?.length)
1070
1101
  return;
1071
1102
  const argStart = source.span.start - baseByteOffset;
1072
1103
  const argEnd = source.span.end - baseByteOffset;
1073
1104
  const argSource = sourceBuffer
1074
1105
  .subarray(argStart, argEnd)
1075
1106
  .toString('utf-8');
1076
- let valueExpr;
1077
- const kebabProp = (0, zss_engine_1.camelToKebabCase)(targetProp);
1078
- const isUnitless = zss_engine_1.exceptionCamelCase.includes(targetProp) ||
1079
- targetProp.startsWith('--');
1080
1107
  const maybeNumber = Number(argSource);
1081
- if (!isNaN(maybeNumber) && argSource.trim() === String(maybeNumber)) {
1082
- valueExpr = JSON.stringify((0, zss_engine_1.applyCssValue)(maybeNumber, kebabProp));
1083
- }
1084
- else if ((argSource.startsWith('"') && argSource.endsWith('"')) ||
1085
- (argSource.startsWith("'") && argSource.endsWith("'"))) {
1086
- valueExpr = JSON.stringify((0, zss_engine_1.applyCssValue)(argSource.slice(1, -1), kebabProp));
1087
- }
1088
- else {
1089
- valueExpr = isUnitless
1090
- ? argSource
1091
- : `(typeof (${argSource}) === 'number' ? (${argSource}) + 'px' : (${argSource}))`;
1092
- }
1093
- vars.push({ cssVar, valueExpr });
1108
+ groups.forEach(({ cssVar, prop }) => {
1109
+ let valueExpr;
1110
+ const kebabProp = (0, zss_engine_1.camelToKebabCase)(prop);
1111
+ if (!isNaN(maybeNumber) &&
1112
+ argSource.trim() === String(maybeNumber)) {
1113
+ valueExpr = JSON.stringify((0, zss_engine_1.applyCssValue)(maybeNumber, kebabProp));
1114
+ }
1115
+ else if ((argSource.startsWith('"') && argSource.endsWith('"')) ||
1116
+ (argSource.startsWith("'") && argSource.endsWith("'"))) {
1117
+ valueExpr = JSON.stringify((0, zss_engine_1.applyCssValue)(argSource.slice(1, -1), kebabProp));
1118
+ }
1119
+ else {
1120
+ valueExpr = isUnitlessProp(prop)
1121
+ ? argSource
1122
+ : `(typeof (${argSource}) === 'number' ? (${argSource}) + 'px' : (${argSource}))`;
1123
+ }
1124
+ vars.push({ cssVar, valueExpr });
1125
+ });
1094
1126
  });
1095
1127
  return { style, vars };
1096
1128
  };
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, 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';
5
+ import { traverse, resolvePropertyPolicy, assertPropertyPolicy, 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';
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) => {
@@ -131,6 +131,46 @@ const foldDynamicVars = (vars) => {
131
131
  return `"${cssVar}": ${value}`;
132
132
  });
133
133
  };
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
+ };
134
174
  const applyVarFallback = (style, cssVar, literal) => {
135
175
  const reference = `var(${cssVar})`;
136
176
  for (const [prop, value] of Object.entries(style)) {
@@ -144,20 +184,9 @@ const applyVarFallback = (style, cssVar, literal) => {
144
184
  }
145
185
  }
146
186
  };
147
- const findVarProp = (style, cssVar) => {
148
- for (const [prop, value] of Object.entries(style)) {
149
- if (typeof value === 'string' && value.includes(cssVar))
150
- return prop;
151
- if (value !== null && typeof value === 'object') {
152
- const nested = findVarProp(value, cssVar);
153
- if (nested)
154
- return nested;
155
- }
156
- }
157
- return undefined;
158
- };
159
187
  export const unpluginFactory = (options = {}, unpluginMeta) => {
160
188
  const filter = createFilter(options.include, options.exclude);
189
+ const propertyPolicy = resolvePropertyPolicy(options);
161
190
  const styleProp = options.styleProp ?? DEFAULT_STYLE_PROP;
162
191
  const cssLookup = new Map();
163
192
  const cssFileLookup = new Map();
@@ -227,6 +256,7 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
227
256
  tsx: true,
228
257
  target: 'es2022',
229
258
  });
259
+ assertPropertyPolicy(ast, propertyPolicy, id);
230
260
  const leadingLen = getLeadingCommentLength(source);
231
261
  const sourceBuffer = Buffer.from(source, 'utf-8');
232
262
  const leadingBytes = Buffer.byteLength(source.slice(0, leadingLen), 'utf-8');
@@ -1014,47 +1044,49 @@ export const unpluginFactory = (options = {}, unpluginMeta) => {
1014
1044
  const style = objectExpressionToObject(func.body, tempStaticTable, mergedKeyframesTable, mergedViewTransitionTable, mergedCreateThemeHashTable, scannedTables.createThemeObjectTable, mergedCreateTable, mergedCreateStaticHashTable, scannedTables.createStaticObjectTable, mergedVariantsTable);
1015
1045
  if (!style)
1016
1046
  return null;
1017
- withDefault.forEach(([param, expr]) => {
1047
+ const varGroups = new Map();
1048
+ varParams.forEach((param) => {
1018
1049
  const cssVar = cssVars[param];
1019
- const targetProp = cssVar ? findVarProp(style, cssVar) : undefined;
1020
- if (!targetProp)
1021
- return;
1050
+ if (cssVar)
1051
+ varGroups.set(param, splitVarByUnit(style, cssVar));
1052
+ });
1053
+ withDefault.forEach(([param, expr]) => {
1022
1054
  const literal = expr.type === 'StringLiteral' || expr.type === 'NumericLiteral'
1023
1055
  ? expr.value
1024
1056
  : undefined;
1025
1057
  if (literal === undefined)
1026
1058
  return;
1027
- applyVarFallback(style, cssVar, literal);
1059
+ (varGroups.get(param) ?? []).forEach(({ cssVar }) => applyVarFallback(style, cssVar, literal));
1028
1060
  });
1029
1061
  const vars = [];
1030
1062
  runtime.forEach(({ param, source }) => {
1031
- const cssVar = cssVars[param];
1032
- const targetProp = cssVar ? findVarProp(style, cssVar) : undefined;
1033
- if (!targetProp)
1063
+ const groups = varGroups.get(param);
1064
+ if (!groups?.length)
1034
1065
  return;
1035
1066
  const argStart = source.span.start - baseByteOffset;
1036
1067
  const argEnd = source.span.end - baseByteOffset;
1037
1068
  const argSource = sourceBuffer
1038
1069
  .subarray(argStart, argEnd)
1039
1070
  .toString('utf-8');
1040
- let valueExpr;
1041
- const kebabProp = camelToKebabCase(targetProp);
1042
- const isUnitless = exceptionCamelCase.includes(targetProp) ||
1043
- targetProp.startsWith('--');
1044
1071
  const maybeNumber = Number(argSource);
1045
- if (!isNaN(maybeNumber) && argSource.trim() === String(maybeNumber)) {
1046
- valueExpr = JSON.stringify(applyCssValue(maybeNumber, kebabProp));
1047
- }
1048
- else if ((argSource.startsWith('"') && argSource.endsWith('"')) ||
1049
- (argSource.startsWith("'") && argSource.endsWith("'"))) {
1050
- valueExpr = JSON.stringify(applyCssValue(argSource.slice(1, -1), kebabProp));
1051
- }
1052
- else {
1053
- valueExpr = isUnitless
1054
- ? argSource
1055
- : `(typeof (${argSource}) === 'number' ? (${argSource}) + 'px' : (${argSource}))`;
1056
- }
1057
- vars.push({ cssVar, valueExpr });
1072
+ groups.forEach(({ cssVar, prop }) => {
1073
+ let valueExpr;
1074
+ const kebabProp = camelToKebabCase(prop);
1075
+ if (!isNaN(maybeNumber) &&
1076
+ argSource.trim() === String(maybeNumber)) {
1077
+ valueExpr = JSON.stringify(applyCssValue(maybeNumber, kebabProp));
1078
+ }
1079
+ else if ((argSource.startsWith('"') && argSource.endsWith('"')) ||
1080
+ (argSource.startsWith("'") && argSource.endsWith("'"))) {
1081
+ valueExpr = JSON.stringify(applyCssValue(argSource.slice(1, -1), kebabProp));
1082
+ }
1083
+ else {
1084
+ valueExpr = isUnitlessProp(prop)
1085
+ ? argSource
1086
+ : `(typeof (${argSource}) === 'number' ? (${argSource}) + 'px' : (${argSource}))`;
1087
+ }
1088
+ vars.push({ cssVar, valueExpr });
1089
+ });
1058
1090
  });
1059
1091
  return { style, vars };
1060
1092
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/unplugin",
3
- "version": "18.3.17",
3
+ "version": "18.4.0",
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.17"
97
+ "@plumeria/utils": "^18.4.0"
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",