@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/README.md +62 -30
- package/dist/index.d.mts +48 -15
- package/dist/index.d.ts +48 -15
- package/dist/index.js +86 -234
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -232
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -6
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
|
|
31
|
-
|
|
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
|
|
157
|
+
var SourceTransformCollector = class {
|
|
158
158
|
hooks = [];
|
|
159
159
|
register(hook) {
|
|
160
160
|
this.hooks.push(hook);
|
|
161
161
|
}
|
|
162
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
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
|
|
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
|
|
438
|
-
* Idempotent: skips calls that already have an `s-`
|
|
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
|
|
441
|
-
if (!source.includes(
|
|
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 =
|
|
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
|
|
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
|
|
505
|
-
*
|
|
506
|
-
* - Registers
|
|
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.
|
|
513
|
-
context.
|
|
514
|
-
for (const [filePath, fileInfo] of index.files)
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
};
|