@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/README.md +125 -7
- package/dist/grisso.css +1 -1
- package/lib/build.d.ts +23 -0
- package/lib/build.js +29 -0
- package/lib/generators.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/optimize.d.ts +14 -0
- package/lib/optimize.js +88 -0
- package/lib/partials/flex-and-grid.js +1 -1
- package/lib/partials/index.js +5 -5
- package/lib/partials/layout.js +1 -1
- package/lib/partials/sizing.js +1 -1
- package/lib/partials/typography.js +1 -1
- package/lib/purge.d.ts +12 -0
- package/lib/purge.js +39 -0
- package/lib/utils.js +1 -1
- package/package.json +20 -11
- package/plugin.js +8 -42
- package/tokens-example.css +1 -1
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
|
+
}
|
package/lib/generators.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -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>;
|
package/lib/optimize.js
ADDED
|
@@ -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
|
+
}
|
package/lib/partials/index.js
CHANGED
|
@@ -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.
|
package/lib/partials/layout.js
CHANGED
package/lib/partials/sizing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
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.
|
|
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
|
-
"
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"prepublishOnly": "npm run build",
|
|
40
|
+
"release": "release-it"
|
|
37
41
|
},
|
|
38
42
|
"dependencies": {
|
|
39
|
-
"
|
|
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
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"postcss
|
|
50
|
-
|
|
51
|
-
|
|
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 "@
|
|
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
|
-
|
|
31
|
+
let css = await generateCSS(configPath);
|
|
29
32
|
|
|
30
|
-
if (
|
|
31
|
-
|
|
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
|
-
|
|
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
|
};
|
package/tokens-example.css
CHANGED