@mochi-css/config 3.0.0 → 3.1.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/dist/index.js CHANGED
@@ -27,8 +27,8 @@ let fs = require("fs");
27
27
  fs = __toESM(fs);
28
28
  let jiti = require("jiti");
29
29
  jiti = __toESM(jiti);
30
- let clsx = require("clsx");
31
- clsx = __toESM(clsx);
30
+ let __mochi_css_core = require("@mochi-css/core");
31
+ __mochi_css_core = __toESM(__mochi_css_core);
32
32
  let __swc_core = require("@swc/core");
33
33
  __swc_core = __toESM(__swc_core);
34
34
 
@@ -154,20 +154,51 @@ const fileFilter = ({ filter }, { filePath }) => {
154
154
  function makeFilePipeline() {
155
155
  return new FilteredTransformationPipeline(fileFilter);
156
156
  }
157
- var AnalysisHookCollector = class {
157
+ var SourceTransformCollector = class {
158
158
  hooks = [];
159
159
  register(hook) {
160
160
  this.hooks.push(hook);
161
161
  }
162
- getHooks() {
162
+ getAll() {
163
163
  return [...this.hooks];
164
164
  }
165
165
  };
166
+ var StageCollector = class {
167
+ stageList = [];
168
+ register(stage) {
169
+ this.stageList.push(stage);
170
+ }
171
+ getAll() {
172
+ return [...this.stageList];
173
+ }
174
+ };
175
+ var EmitHookCollector = class {
176
+ hooks = [];
177
+ register(hook) {
178
+ this.hooks.push(hook);
179
+ }
180
+ getAll() {
181
+ return [...this.hooks];
182
+ }
183
+ };
184
+ var CleanupCollector = class {
185
+ fns = [];
186
+ register(fn) {
187
+ this.fns.push(fn);
188
+ }
189
+ runAll() {
190
+ for (const fn of this.fns) fn();
191
+ }
192
+ };
166
193
  var FullContext = class {
167
- sourceTransform = makeFilePipeline();
168
- analysisTransform = new AnalysisHookCollector();
169
- getAnalysisHooks() {
170
- return this.analysisTransform.getHooks();
194
+ filePreProcess = makeFilePipeline();
195
+ sourceTransforms = new SourceTransformCollector();
196
+ stages = new StageCollector();
197
+ emitHooks = new EmitHookCollector();
198
+ cleanup = new CleanupCollector();
199
+ onDiagnostic;
200
+ constructor(onDiagnostic) {
201
+ this.onDiagnostic = onDiagnostic;
171
202
  }
172
203
  };
173
204
 
@@ -180,7 +211,6 @@ async function resolveConfig(fileConfig, inlineConfig, defaults) {
180
211
  const plugins = mergeArrays(fileConfig.plugins, inlineConfig?.plugins) ?? defaults?.plugins ?? [];
181
212
  const resolved = {
182
213
  roots: mergeArrays(fileConfig.roots, inlineConfig?.roots) ?? defaults?.roots ?? [],
183
- extractors: mergeArrays(fileConfig.extractors, inlineConfig?.extractors) ?? defaults?.extractors ?? [],
184
214
  splitCss: inlineConfig?.splitCss ?? fileConfig.splitCss ?? defaults?.splitCss ?? false,
185
215
  onDiagnostic: mergeCallbacks(fileConfig.onDiagnostic, inlineConfig?.onDiagnostic),
186
216
  plugins,
@@ -208,213 +238,15 @@ async function loadConfig(cwd) {
208
238
  return config;
209
239
  }
210
240
 
211
- //#endregion
212
- //#region ../vanilla/dist/index.mjs
213
- /**
214
- * Hashing utilities for generating short, deterministic class names.
215
- * Uses djb2 algorithm for fast string hashing.
216
- * @module hash
217
- */
218
- /** Characters used for base-62 encoding (css-name safe variant of base-64) */
219
- const hashBase = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
220
- const base = 64;
221
- /**
222
- * Converts a number to a base-62 string representation.
223
- * @param num - The number to convert
224
- * @param maxLength - Optional maximum length of the output string
225
- * @returns Base-62 encoded string representation of the number
226
- */
227
- function numberToBase62(num, maxLength) {
228
- let out = "";
229
- while (num > 0 && out.length < (maxLength ?? Infinity)) {
230
- out = hashBase[num % base] + out;
231
- num = Math.floor(num / base);
232
- }
233
- return out.length > 0 ? out : "0";
234
- }
235
- /**
236
- * Generates a short hash string from input using the djb2 algorithm.
237
- * Used to create unique, deterministic CSS class names from style content.
238
- * @param input - The string to hash
239
- * @param length - Maximum length of the hash output (default: 8)
240
- * @returns A short, css-safe hash string
241
- * @example
242
- * shortHash("color: red;") // Returns something like "A1b2C3d4"
243
- */
244
- function shortHash(input, length = 8) {
245
- let h = 5381;
246
- for (let i = 0; i < input.length; i++) h = h * 33 ^ input.charCodeAt(i);
247
- h >>>= 0;
248
- return numberToBase62(h, length);
249
- }
250
- const MOCHI_CSS_TYPEOF = Symbol.for("mochi-css.MochiCSS");
251
- /**
252
- * Runtime representation of a CSS style definition with variant support.
253
- * Holds generated class names and provides methods to compute the final
254
- * className string based on selected variants.
255
- *
256
- * @template V - The variant definitions type mapping variant names to their options
257
- *
258
- * @example
259
- * const styles = MochiCSS.from(new CSSObject({
260
- * color: 'blue',
261
- * variants: { size: { small: { fontSize: 12 }, large: { fontSize: 18 } } }
262
- * }))
263
- * styles.variant({ size: 'large' }) // Returns combined class names
264
- */
265
- var MochiCSS = class MochiCSS$1 {
266
- $$typeof = MOCHI_CSS_TYPEOF;
267
- /**
268
- * Creates a new MochiCSS instance.
269
- * @param classNames - Base class names to always include
270
- * @param variantClassNames - Mapping of variant names to option class names
271
- * @param defaultVariants - Default variant selections when not specified
272
- */
273
- constructor(classNames, variantClassNames, defaultVariants) {
274
- this.classNames = classNames;
275
- this.variantClassNames = variantClassNames;
276
- this.defaultVariants = defaultVariants;
277
- }
278
- /**
279
- * Computes the final className string based on variant selections.
280
- * Compound variants are handled purely via CSS combined selectors,
281
- * so no runtime matching is needed here.
282
- * @param props - Variant selections
283
- * @returns Combined className string for use in components
284
- */
285
- variant(props) {
286
- const keys = new Set([...Object.keys(props), ...Object.keys(this.defaultVariants)].filter((k) => k in this.variantClassNames));
287
- return (0, clsx.default)(this.classNames, ...keys.values().map((k) => {
288
- const variantGroup = this.variantClassNames[k];
289
- if (!variantGroup) return false;
290
- const variantKey = ((k in props ? props[k] : void 0) ?? this.defaultVariants[k])?.toString();
291
- if (variantKey == null) return false;
292
- const selectedClassname = variantGroup[variantKey];
293
- if (selectedClassname !== void 0) return selectedClassname;
294
- const defaultKey = this.defaultVariants[k];
295
- if (defaultKey == null) return false;
296
- return variantGroup[defaultKey.toString()];
297
- }));
298
- }
299
- /**
300
- * Returns the CSS selector for this style (e.g. `.abc123`).
301
- * Useful for targeting this component from another style.
302
- */
303
- get selector() {
304
- return this.classNames.map((n) => `.${n}`).join();
305
- }
306
- toString() {
307
- return this.selector;
308
- }
309
- /**
310
- * Creates a MochiCSS instance from a CSSObject.
311
- * Extracts class names from the compiled CSS blocks.
312
- * @template V - The variant definitions type
313
- * @param object - The compiled CSSObject to extract from
314
- * @returns A new MochiCSS instance with the extracted class names
315
- */
316
- static from(object) {
317
- return new MochiCSS$1([object.mainBlock.className], Object.fromEntries(Object.entries(object.variantBlocks).map(([key, variantOptions]) => {
318
- return [key, Object.fromEntries(Object.entries(variantOptions).map(([optionKey, block]) => {
319
- return [optionKey, block.className];
320
- }))];
321
- })), object.variantDefaults);
322
- }
323
- };
324
- /**
325
- * Creates a CSS style definition.
326
- * The primary API for defining styles in Mochi-CSS.
327
- *
328
- * @template V - Tuple of variant definition types
329
- * @param props - One or more style objects or existing MochiCSS instances to merge
330
- * @returns A MochiCSS instance with all styles and variants combined
331
- *
332
- * @example
333
- * // Simple usage
334
- * const button = css({ padding: 8, borderRadius: 4 })
335
- *
336
- * @example
337
- * // With variants
338
- * const button = css({
339
- * padding: 8,
340
- * variants: {
341
- * size: {
342
- * small: { padding: 4 },
343
- * large: { padding: 16 }
344
- * }
345
- * },
346
- * defaultVariants: { size: 'small' }
347
- * })
348
- * button.variant({ size: 'large' }) // Get class names for large size
349
- *
350
- * @example
351
- * // Merging multiple styles
352
- * const combined = css(baseStyles, additionalStyles)
353
- */
354
- const emptyMochiCSS = new MochiCSS([], {}, {});
355
- /**
356
- * Wraps a condition in parentheses if not already wrapped.
357
- */
358
- function wrapParens(condition) {
359
- const trimmed = condition.trim();
360
- if (trimmed.startsWith("(") && trimmed.endsWith(")")) return trimmed;
361
- return `(${trimmed})`;
362
- }
363
- function mediaFn(condition) {
364
- return `@media ${wrapParens(condition)}`;
365
- }
366
- mediaFn.and = function(...conditions) {
367
- return `@media ${conditions.map(wrapParens).join(" and ")}`;
368
- };
369
- mediaFn.or = function(...conditions) {
370
- return `@media ${conditions.map(wrapParens).join(", ")}`;
371
- };
372
- Object.defineProperties(mediaFn, {
373
- dark: {
374
- get: () => "@media (prefers-color-scheme: dark)",
375
- enumerable: true
376
- },
377
- light: {
378
- get: () => "@media (prefers-color-scheme: light)",
379
- enumerable: true
380
- },
381
- motion: {
382
- get: () => "@media (prefers-reduced-motion: no-preference)",
383
- enumerable: true
384
- },
385
- print: {
386
- get: () => "@media print",
387
- enumerable: true
388
- }
389
- });
390
- function containerFn(condition) {
391
- return `@container ${wrapParens(condition)}`;
392
- }
393
- containerFn.named = function(name, condition) {
394
- return `@container ${name} ${wrapParens(condition)}`;
395
- };
396
- function supportsFn(condition) {
397
- return `@supports ${wrapParens(condition)}`;
398
- }
399
- supportsFn.not = function(condition) {
400
- return `@supports not ${wrapParens(condition)}`;
401
- };
402
- supportsFn.and = function(...conditions) {
403
- return `@supports ${conditions.map(wrapParens).join(" and ")}`;
404
- };
405
- supportsFn.or = function(...conditions) {
406
- return `@supports ${conditions.map(wrapParens).join(" or ")}`;
407
- };
408
-
409
241
  //#endregion
410
242
  //#region src/styledIdTransform.ts
411
243
  const STABLE_ID_RE = /^s-[0-9A-Za-z_-]+$/;
412
- function collectStyledCalls(ast) {
244
+ function collectCallsBySymbol(ast, symbolNames) {
413
245
  const results = [];
414
246
  function visitExpr(expr, varName) {
415
247
  if (expr.type === "CallExpression") {
416
248
  const callee = expr.callee;
417
- if (callee.type === "Identifier" && callee.value === "styled" || callee.type === "MemberExpression" && callee.property.type === "Identifier" && callee.property.value === "styled") results.push({
249
+ if (callee.type === "Identifier" && symbolNames.has(callee.value) || callee.type === "MemberExpression" && callee.property.type === "Identifier" && symbolNames.has(callee.property.value)) results.push({
418
250
  call: expr,
419
251
  varName
420
252
  });
@@ -434,22 +266,23 @@ function collectStyledCalls(ast) {
434
266
  return results;
435
267
  }
436
268
  /**
437
- * Transforms source code to inject stable `s-` class IDs into every `styled()` call.
438
- * Idempotent: skips calls that already have an `s-` string as the last argument.
269
+ * Transforms source code to inject stable `s-` class IDs into every call matching
270
+ * one of the given symbol names. Idempotent: skips calls that already have an `s-`
271
+ * string as the last argument.
439
272
  */
440
- function transformStyledIds(source, filePath) {
441
- if (!source.includes("styled")) return source;
273
+ function transformCallIds(source, filePath, symbolNames) {
274
+ if (![...symbolNames].some((name) => source.includes(name))) return source;
442
275
  let ast;
443
276
  try {
444
277
  ast = __swc_core.parseSync(source, {
445
278
  syntax: "typescript",
446
- tsx: filePath.endsWith(".tsx"),
279
+ tsx: filePath.endsWith(".tsx") || filePath.endsWith(".jsx"),
447
280
  target: "es2022"
448
281
  });
449
282
  } catch {
450
283
  return source;
451
284
  }
452
- const calls = collectStyledCalls(ast);
285
+ const calls = collectCallsBySymbol(ast, symbolNames);
453
286
  if (calls.length === 0) return source;
454
287
  const toInject = calls.filter((entry) => {
455
288
  const args = entry.call.arguments;
@@ -463,7 +296,9 @@ function transformStyledIds(source, filePath) {
463
296
  let searchFrom = 0;
464
297
  const callsWithOffset = [];
465
298
  for (const entry of sortedAsc) {
466
- const idx = source.indexOf("styled(", searchFrom);
299
+ const symbolName = entry.call.callee.type === "Identifier" ? entry.call.callee.value : entry.call.callee.type === "MemberExpression" && entry.call.callee.property.type === "Identifier" ? entry.call.callee.property.value : null;
300
+ if (!symbolName) continue;
301
+ const idx = source.indexOf(symbolName + "(", searchFrom);
467
302
  if (idx < 0) continue;
468
303
  if (callsWithOffset.length === 0) {
469
304
  const callee = entry.call.callee;
@@ -480,7 +315,7 @@ function transformStyledIds(source, filePath) {
480
315
  callsWithOffset.forEach(({ entry, offset }, i) => {
481
316
  const sortIdx = callsWithOffset.length - 1 - i;
482
317
  const varName = entry.varName ?? String(sortIdx);
483
- const id = "s-" + shortHash(filePath + ":" + varName);
318
+ const id = "s-" + (0, __mochi_css_core.shortHash)(filePath + ":" + varName);
484
319
  if (offset < 0 || offset >= result.length || result[offset] !== ")") return;
485
320
  result = result.slice(0, offset) + `, '${id}'` + result.slice(offset);
486
321
  });
@@ -501,28 +336,45 @@ function hasStableId(call) {
501
336
  return last?.expression.type === "StringLiteral" && STABLE_ID_RE.test(last.expression.value);
502
337
  }
503
338
  /**
504
- * Returns a MochiPlugin that injects stable `s-` class IDs into every `styled()` call.
505
- * - Registers a `sourceTransform` for runtime source injection (Vite/Next `transform` hook).
506
- * - Registers an `analysisTransform` for CSS extraction via direct AST mutation.
339
+ * Returns a MochiPlugin that injects stable `s-` class IDs into every call expression
340
+ * matched by the given extractors.
341
+ * - Registers a `filePreProcess` transformation for runtime source injection (Vite/Next `transform` hook).
342
+ * - Registers a `sourceTransforms` hook for CSS extraction via direct AST mutation,
343
+ * using data from the extractor pipeline stages (`extractedCallExpressions`).
507
344
  */
508
- function styledIdPlugin() {
345
+ function styledIdPlugin(extractors) {
346
+ const symbolNames = new Set(extractors.map((e) => e.symbolName));
509
347
  return {
510
348
  name: "mochi-styled-ids",
511
349
  onLoad(context) {
512
- context.sourceTransform.registerTransformation((source, { filePath }) => transformStyledIds(source, filePath), { filter: "*.{ts,tsx,js,jsx}" });
513
- context.analysisTransform.register((index) => {
514
- for (const [filePath, fileInfo] of index.files) collectStyledCalls(fileInfo.ast).filter(({ call }) => !hasStableId(call)).forEach(({ call, varName }, i) => {
515
- const id = "s-" + shortHash(filePath + ":" + (varName ?? String(i)));
516
- call.arguments.push({
517
- spread: void 0,
518
- expression: {
519
- type: "StringLiteral",
520
- span: DUMMY_SPAN,
521
- value: id,
522
- raw: `'${id}'`
350
+ context.filePreProcess.registerTransformation((source, { filePath }) => transformCallIds(source, filePath, symbolNames), { filter: "*.{ts,tsx,js,jsx}" });
351
+ context.sourceTransforms.register((index) => {
352
+ for (const [filePath, fileInfo] of index.files) {
353
+ const callToVarName = /* @__PURE__ */ new Map();
354
+ for (const binding of fileInfo.moduleBindings.values()) {
355
+ if (binding.declarator.type !== "variable") continue;
356
+ const init = binding.declarator.declarator.init;
357
+ if (init?.type === "CallExpression") callToVarName.set(init, binding.identifier.value);
358
+ }
359
+ let fallbackIdx = 0;
360
+ for (const extractor of extractors) {
361
+ const calls = fileInfo.extractedCallExpressions.get(extractor) ?? [];
362
+ for (const call of calls) {
363
+ if (hasStableId(call)) continue;
364
+ const varName = callToVarName.get(call) ?? String(fallbackIdx++);
365
+ const id = "s-" + (0, __mochi_css_core.shortHash)(filePath + ":" + varName);
366
+ call.arguments.push({
367
+ spread: void 0,
368
+ expression: {
369
+ type: "StringLiteral",
370
+ span: DUMMY_SPAN,
371
+ value: id,
372
+ raw: `'${id}'`
373
+ }
374
+ });
523
375
  }
524
- });
525
- });
376
+ }
377
+ }
526
378
  });
527
379
  }
528
380
  };