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