@hiscovega/grisso 1.0.2 → 1.0.4

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/lib/build.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ export interface BuildOptions {
2
+ /** Ruta a grisso.config.mjs del consumidor */
3
+ config?: string;
4
+ /** Rutas glob de archivos a escanear para tree-shaking */
5
+ content?: string[];
6
+ /** Minificar el CSS de salida (default: true) */
7
+ minify?: boolean;
8
+ }
9
+ /**
10
+ * API principal de Grisso — genera, purga y optimiza CSS
11
+ * sin depender de PostCSS.
12
+ *
13
+ * @example
14
+ * // Build completo (todo el CSS, minificado)
15
+ * const css = await buildCSS();
16
+ *
17
+ * // Build con tree-shaking
18
+ * const css = await buildCSS({
19
+ * content: ["./src/**\/*.{js,ts,jsx,tsx,css}"],
20
+ * config: "./grisso.config.mjs",
21
+ * });
22
+ */
23
+ export declare function buildCSS(options?: BuildOptions): Promise<string>;
package/lib/build.js ADDED
@@ -0,0 +1,29 @@
1
+ import { generateCSS } from "./index.js";
2
+ import { optimizeCSS } from "./optimize.js";
3
+ import { purgeCSS } from "./purge.js";
4
+ /**
5
+ * API principal de Grisso — genera, purga y optimiza CSS
6
+ * sin depender de PostCSS.
7
+ *
8
+ * @example
9
+ * // Build completo (todo el CSS, minificado)
10
+ * const css = await buildCSS();
11
+ *
12
+ * // Build con tree-shaking
13
+ * const css = await buildCSS({
14
+ * content: ["./src/**\/*.{js,ts,jsx,tsx,css}"],
15
+ * config: "./grisso.config.mjs",
16
+ * });
17
+ */
18
+ export async function buildCSS(options = {}) {
19
+ const { config, content, minify = true } = options;
20
+ // 1. Generar CSS raw desde los generators
21
+ let css = await generateCSS(config);
22
+ // 2. Tree-shaking si hay rutas de contenido
23
+ if (content && content.length > 0) {
24
+ css = await purgeCSS(css, { content });
25
+ }
26
+ // 3. Optimizar (nesting, autoprefixer, minificación)
27
+ css = await optimizeCSS(css, { minify });
28
+ return css;
29
+ }
@@ -1,4 +1,4 @@
1
- import type { Breakpoints, TokenMap, Declarations } from "./types.js";
1
+ import type { Breakpoints, Declarations, TokenMap } from "./types.js";
2
2
  /**
3
3
  * Generadores de clases CSS utility.
4
4
  * Replican los mixins grisso_simple_class, grisso_complex_class de SCSS.
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { resolveConfig } from "./resolve-config.js";
2
1
  import partials from "./partials/index.js";
2
+ import { resolveConfig } from "./resolve-config.js";
3
3
  /**
4
4
  * Genera el CSS completo de Grisso.
5
5
  *
@@ -0,0 +1,14 @@
1
+ export interface OptimizeOptions {
2
+ /** Minificar el CSS de salida (default: true) */
3
+ minify?: boolean;
4
+ }
5
+ /**
6
+ * Optimiza CSS usando Lightning CSS:
7
+ * - Merge de media queries idénticas
8
+ * - Nesting
9
+ * - Autoprefixing (via browserslist "defaults")
10
+ * - Minificación
11
+ *
12
+ * Imports dinámicos para que solo se carguen cuando se usen.
13
+ */
14
+ export declare function optimizeCSS(css: string, options?: OptimizeOptions): Promise<string>;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Agrupa bloques @media con la misma query en un solo bloque.
3
+ * Esto reduce significativamente el tamaño del CSS generado
4
+ * por los generators (cada clase genera su propio @media block).
5
+ */
6
+ function mergeMediaQueries(css) {
7
+ const mediaBlocks = new Map();
8
+ const topLevel = [];
9
+ let i = 0;
10
+ while (i < css.length) {
11
+ // Saltar whitespace
12
+ while (i < css.length && /\s/.test(css[i]))
13
+ i++;
14
+ if (i >= css.length)
15
+ break;
16
+ if (css.startsWith("@media", i)) {
17
+ // Encontrar la llave de apertura
18
+ const braceStart = css.indexOf("{", i);
19
+ if (braceStart === -1)
20
+ break;
21
+ const query = css.slice(i, braceStart).trim();
22
+ // Encontrar la llave de cierre balanceada
23
+ let depth = 1;
24
+ let j = braceStart + 1;
25
+ while (j < css.length && depth > 0) {
26
+ if (css[j] === "{")
27
+ depth++;
28
+ else if (css[j] === "}")
29
+ depth--;
30
+ j++;
31
+ }
32
+ const content = css.slice(braceStart + 1, j - 1).trim();
33
+ if (!mediaBlocks.has(query)) {
34
+ mediaBlocks.set(query, []);
35
+ }
36
+ mediaBlocks.get(query)?.push(content);
37
+ i = j;
38
+ }
39
+ else {
40
+ // Regla top-level (no @media)
41
+ const braceStart = css.indexOf("{", i);
42
+ if (braceStart === -1)
43
+ break;
44
+ let depth = 1;
45
+ let j = braceStart + 1;
46
+ while (j < css.length && depth > 0) {
47
+ if (css[j] === "{")
48
+ depth++;
49
+ else if (css[j] === "}")
50
+ depth--;
51
+ j++;
52
+ }
53
+ topLevel.push(css.slice(i, j));
54
+ i = j;
55
+ }
56
+ }
57
+ // Reconstruir: reglas top-level primero, luego media queries agrupadas
58
+ let result = topLevel.join("\n");
59
+ for (const [query, contents] of mediaBlocks) {
60
+ result += `\n${query}{\n${contents.join("\n")}\n}`;
61
+ }
62
+ return result;
63
+ }
64
+ /**
65
+ * Optimiza CSS usando Lightning CSS:
66
+ * - Merge de media queries idénticas
67
+ * - Nesting
68
+ * - Autoprefixing (via browserslist "defaults")
69
+ * - Minificación
70
+ *
71
+ * Imports dinámicos para que solo se carguen cuando se usen.
72
+ */
73
+ export async function optimizeCSS(css, options = {}) {
74
+ const { minify = true } = options;
75
+ // Agrupar media queries idénticas antes de transformar
76
+ const merged = mergeMediaQueries(css);
77
+ const { transform, browserslistToTargets, Features } = await import("lightningcss");
78
+ const browserslist = (await import("browserslist")).default;
79
+ const targets = browserslistToTargets(browserslist("defaults"));
80
+ const result = transform({
81
+ filename: "grisso.css",
82
+ code: new TextEncoder().encode(merged),
83
+ minify,
84
+ targets,
85
+ include: Features.Nesting,
86
+ });
87
+ return new TextDecoder().decode(result.code);
88
+ }
@@ -1,4 +1,4 @@
1
- import { simpleClass, complexClass } from "../generators.js";
1
+ import { complexClass, simpleClass } from "../generators.js";
2
2
  import { omit } from "../utils.js";
3
3
  export default function flexAndGrid(config) {
4
4
  const { columns, breakpoints, spacing } = config;
@@ -1,12 +1,12 @@
1
- import layout from "./layout.js";
2
- import flexAndGrid from "./flex-and-grid.js";
3
- import spacingPartial from "./spacing.js";
4
- import sizing from "./sizing.js";
5
1
  import backgrounds from "./backgrounds.js";
6
2
  import borders from "./borders.js";
7
- import typography from "./typography.js";
8
3
  import effects from "./effects.js";
4
+ import flexAndGrid from "./flex-and-grid.js";
9
5
  import icons from "./icons.js";
6
+ import layout from "./layout.js";
7
+ import sizing from "./sizing.js";
8
+ import spacingPartial from "./spacing.js";
9
+ import typography from "./typography.js";
10
10
  /**
11
11
  * Registry de partials en el orden de importación de grisso.scss.
12
12
  * El orden determina la cascada CSS.
@@ -1,4 +1,4 @@
1
- import { simpleClass, complexClass } from "../generators.js";
1
+ import { complexClass, simpleClass } from "../generators.js";
2
2
  export default function layout(config) {
3
3
  const { columns, breakpoints, spacing } = config;
4
4
  let css = "";
@@ -1,4 +1,4 @@
1
- import { simpleClass, complexClass } from "../generators.js";
1
+ import { complexClass, simpleClass } from "../generators.js";
2
2
  import { fractionPercent } from "../utils.js";
3
3
  export default function sizing(config) {
4
4
  const { columns, breakpoints } = config;
@@ -1,4 +1,4 @@
1
- import { simpleClass, complexClass, customClass } from "../generators.js";
1
+ import { complexClass, customClass, simpleClass } from "../generators.js";
2
2
  import { omit } from "../utils.js";
3
3
  export default function typography(config) {
4
4
  const { breakpoints, foregroundColors, spacing } = config;
package/lib/purge.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export interface PurgeOptions {
2
+ /** Rutas glob de archivos a escanear */
3
+ content: string[];
4
+ /** Patrones greedy adicionales a preservar */
5
+ safelist?: RegExp[];
6
+ }
7
+ /**
8
+ * Elimina clases CSS no utilizadas mediante PurgeCSS.
9
+ * Import dinámico para que los consumidores que no necesiten tree-shaking
10
+ * no paguen el coste de cargar purgecss.
11
+ */
12
+ export declare function purgeCSS(css: string, options: PurgeOptions): Promise<string>;
package/lib/purge.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Elimina clases CSS no utilizadas mediante PurgeCSS.
3
+ * Import dinámico para que los consumidores que no necesiten tree-shaking
4
+ * no paguen el coste de cargar purgecss.
5
+ */
6
+ export async function purgeCSS(css, options) {
7
+ const { PurgeCSS } = await import("purgecss");
8
+ const purgecss = new PurgeCSS();
9
+ const [result] = await purgecss.purge({
10
+ content: options.content,
11
+ css: [{ raw: css }],
12
+ extractors: [
13
+ {
14
+ // Extractor para uso directo en HTML/JSX: class="flex gap-md"
15
+ extractor: (content) => content.match(/[A-Za-z0-9_-]+/g) || [],
16
+ extensions: ["html", "jsx", "tsx", "js", "ts"],
17
+ },
18
+ {
19
+ // Extractor para CSS Modules: composes: flex gap-md from global
20
+ extractor: (content) => {
21
+ const matches = content.match(/composes:\s*([^;]+)\s*from\s+global/g) || [];
22
+ const classes = [];
23
+ for (const match of matches) {
24
+ const classStr = match
25
+ .replace(/composes:\s*/, "")
26
+ .replace(/\s*from\s+global/, "");
27
+ classes.push(...classStr.trim().split(/\s+/));
28
+ }
29
+ return classes;
30
+ },
31
+ extensions: ["css"],
32
+ },
33
+ ],
34
+ safelist: {
35
+ greedy: [/^bg-/, ...(options.safelist ?? [])],
36
+ },
37
+ });
38
+ return result ? result.css : css;
39
+ }
package/lib/utils.js CHANGED
@@ -16,5 +16,5 @@ export function omit(obj, ...keys) {
16
16
  */
17
17
  export function fractionPercent(numerator, denominator) {
18
18
  const value = ((numerator / denominator) * 100).toFixed(10);
19
- return value.replace(/\.?0+$/, "") + "%";
19
+ return `${value.replace(/\.?0+$/, "")}%`;
20
20
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hiscovega/grisso",
3
3
  "description": "Griddo CSS utility class library",
4
- "version": "1.0.2",
4
+ "version": "1.0.4",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
7
7
  "type": "module",
@@ -13,6 +13,7 @@
13
13
  "import": "./plugin.js",
14
14
  "require": "./plugin.cjs"
15
15
  },
16
+ "./build": "./lib/build.js",
16
17
  "./config": "./lib/defaults.js",
17
18
  "./tokens-example.css": "./tokens-example.css"
18
19
  },
@@ -33,21 +34,29 @@
33
34
  "lint": "biome check .",
34
35
  "lint:fix": "biome check --write .",
35
36
  "playground": "npm run build && node scripts/build.js --config playground/grisso.config.mjs --content playground/index.html --content playground/example.module.css --output playground/grisso.css && open playground/index.html",
36
- "prepublishOnly": "npm run build"
37
+ "test": "vitest run",
38
+ "test:watch": "vitest",
39
+ "prepublishOnly": "npm run build",
40
+ "release": "release-it"
37
41
  },
38
42
  "dependencies": {
39
- "@fullhuman/postcss-purgecss": "^6.0.0"
43
+ "browserslist": "^4.24.0",
44
+ "lightningcss": "^1.28.0",
45
+ "purgecss": "^6.0.0"
40
46
  },
41
47
  "devDependencies": {
42
48
  "@biomejs/biome": "2.4.5",
43
49
  "@types/node": "^25.3.3",
44
- "postcss": "^8.4.47",
45
- "postcss-cli": "^11.0.0",
46
- "postcss-combine-duplicated-selectors": "^10.0.3",
47
- "postcss-discard-empty": "^6.0.0",
48
- "postcss-minify": "^1.1.0",
49
- "postcss-preset-env": "^10.0.3",
50
- "postcss-sort-media-queries": "^5.2.0",
51
- "typescript": "^5.9.3"
50
+ "release-it": "^19.2.4",
51
+ "typescript": "^5.9.3",
52
+ "vitest": "^4.0.18"
53
+ },
54
+ "peerDependencies": {
55
+ "postcss": "^8.0.0"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "postcss": {
59
+ "optional": true
60
+ }
52
61
  }
53
62
  }
package/plugin.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { generateCSS } from "./lib/index.js";
2
+ import { purgeCSS } from "./lib/purge.js";
2
3
 
3
4
  /**
4
5
  * PostCSS plugin de Grisso.
5
6
  * Genera CSS desde JS y aplica tree-shaking usando PurgeCSS
6
7
  * si se pasan rutas de contenido.
7
8
  *
9
+ * No aplica optimización (el consumidor tiene su propio pipeline PostCSS).
10
+ *
8
11
  * @param {Object} options
9
12
  * @param {string[]} [options.content] - Rutas glob de archivos a escanear para tree-shaking.
10
13
  * Si se omite, se incluye todo el CSS de Grisso (útil en desarrollo).
@@ -12,7 +15,7 @@ import { generateCSS } from "./lib/index.js";
12
15
  *
13
16
  * @example
14
17
  * // postcss.config.js del proyecto consumidor
15
- * import grisso from "@griddo/grisso/plugin";
18
+ * import grisso from "@hiscovega/grisso/plugin";
16
19
  * export default {
17
20
  * plugins: [
18
21
  * grisso({ content: ["./src/**\/*.{js,ts,jsx,tsx,css}"] })
@@ -25,50 +28,13 @@ export default function grissoPlugin(options = {}) {
25
28
  return {
26
29
  postcssPlugin: "postcss-grisso",
27
30
  async Once(root, { parse }) {
28
- const grissoSource = await generateCSS(configPath);
31
+ let css = await generateCSS(configPath);
29
32
 
30
- if (!content || content.length === 0) {
31
- // Sin tree-shaking: inyectar todo el CSS
32
- const grissoRoot = parse(grissoSource);
33
- grissoRoot.each((node) => root.prepend(node.clone()));
34
- return;
33
+ if (content && content.length > 0) {
34
+ css = await purgeCSS(css, { content });
35
35
  }
36
36
 
37
- // Con tree-shaking: usar PurgeCSS para eliminar clases no usadas
38
- const { PurgeCSS } = await import("purgecss");
39
-
40
- const purgecss = new PurgeCSS();
41
- const [result] = await purgecss.purge({
42
- content,
43
- css: [{ raw: grissoSource }],
44
- extractors: [
45
- {
46
- // Extractor para uso directo en HTML/JSX: class="flex gap-md"
47
- extractor: (content) => content.match(/[A-Za-z0-9_-]+/g) || [],
48
- extensions: ["html", "jsx", "tsx", "js", "ts"],
49
- },
50
- {
51
- // Extractor para CSS Modules: composes: flex gap-md from global
52
- extractor: (content) => {
53
- const matches = content.match(/composes:\s*([^;]+)\s*from\s+global/g) || [];
54
- const classes = [];
55
- for (const match of matches) {
56
- const classStr = match.replace(/composes:\s*/, "").replace(/\s*from\s+global/, "");
57
- classes.push(...classStr.trim().split(/\s+/));
58
- }
59
- return classes;
60
- },
61
- extensions: ["css"],
62
- },
63
- ],
64
- safelist: {
65
- // Preservar clases de fondo (bg-) que pueden construirse dinámicamente
66
- greedy: [/^bg-/],
67
- },
68
- });
69
-
70
- const purgedCSS = result ? result.css : grissoSource;
71
- const grissoRoot = parse(purgedCSS);
37
+ const grissoRoot = parse(css);
72
38
  grissoRoot.each((node) => root.prepend(node.clone()));
73
39
  },
74
40
  };
@@ -1,4 +1,4 @@
1
- /* @griddo/grisso — tokens-example.css
1
+ /* @hiscovega/grisso — tokens-example.css
2
2
  Copia este archivo y adapta los valores a tu design system.
3
3
  Define estas variables en :root o en el scope que prefieras.
4
4