@bizrk/leancss 0.2.1 → 0.3.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/chunk-XCXQQEHP.mjs +5833 -0
- package/dist/cli/index.js +17 -5584
- package/dist/cli/index.mjs +4 -5573
- package/dist/index.js +123 -0
- package/dist/index.mjs +113 -2
- package/package.json +6 -3
- package/readme.md +41 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -24,10 +34,123 @@ __export(index_exports, {
|
|
|
24
34
|
});
|
|
25
35
|
module.exports = __toCommonJS(index_exports);
|
|
26
36
|
var import_postcss = require("postcss");
|
|
37
|
+
var import_path2 = __toESM(require("path"));
|
|
38
|
+
|
|
39
|
+
// src/core/designParser.ts
|
|
40
|
+
var import_fs = __toESM(require("fs"));
|
|
41
|
+
var import_path = __toESM(require("path"));
|
|
42
|
+
var import_fast_glob = __toESM(require("fast-glob"));
|
|
43
|
+
var import_yaml = __toESM(require("yaml"));
|
|
44
|
+
function processDesignMd(rootPath) {
|
|
45
|
+
const designMdPath = import_path.default.join(rootPath, "design.md");
|
|
46
|
+
if (!import_fs.default.existsSync(designMdPath)) {
|
|
47
|
+
return { generatedFile: null, tokens: null };
|
|
48
|
+
}
|
|
49
|
+
let content = import_fs.default.readFileSync(designMdPath, "utf8");
|
|
50
|
+
content = content.replace(/^\uFEFF/, "");
|
|
51
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
52
|
+
if (!match) {
|
|
53
|
+
console.log("LeanCSS: design.md found, but could not extract YAML frontmatter. Ensure it starts with ---");
|
|
54
|
+
return { generatedFile: null, tokens: null };
|
|
55
|
+
}
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = import_yaml.default.parse(match[1]);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error("LeanCSS: Failed to parse design.md YAML frontmatter", err);
|
|
61
|
+
return { generatedFile: null, tokens: null };
|
|
62
|
+
}
|
|
63
|
+
let css = "/* Auto-generated by LeanCSS from design.md */\n";
|
|
64
|
+
css += ":root {\n";
|
|
65
|
+
const toKebabCase = (str) => str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
|
|
66
|
+
const resolveValue = (val) => {
|
|
67
|
+
if (typeof val === "string" && val.startsWith("{") && val.endsWith("}")) {
|
|
68
|
+
const refPath = val.slice(1, -1);
|
|
69
|
+
return `var(--${refPath.replace(/\./g, "-")})`;
|
|
70
|
+
}
|
|
71
|
+
return val;
|
|
72
|
+
};
|
|
73
|
+
const cssVars = [];
|
|
74
|
+
if (parsed.colors) {
|
|
75
|
+
for (const [key, val] of Object.entries(parsed.colors)) {
|
|
76
|
+
cssVars.push(` --colors-${toKebabCase(key)}: ${val};`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (parsed.spacing) {
|
|
80
|
+
for (const [key, val] of Object.entries(parsed.spacing)) {
|
|
81
|
+
cssVars.push(` --spacing-${toKebabCase(key)}: ${val};`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (parsed.rounded) {
|
|
85
|
+
for (const [key, val] of Object.entries(parsed.rounded)) {
|
|
86
|
+
cssVars.push(` --rounded-${toKebabCase(key)}: ${val};`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
css += cssVars.join("\n") + "\n}\n\n";
|
|
90
|
+
if (parsed.typography) {
|
|
91
|
+
for (const [name, props] of Object.entries(parsed.typography)) {
|
|
92
|
+
css += `@set ${name} {
|
|
93
|
+
`;
|
|
94
|
+
for (const [propName, propVal] of Object.entries(props)) {
|
|
95
|
+
const cssProp = toKebabCase(propName);
|
|
96
|
+
css += ` ${cssProp}: ${resolveValue(propVal)};
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
css += `}
|
|
100
|
+
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (parsed.components) {
|
|
105
|
+
for (const [name, props] of Object.entries(parsed.components)) {
|
|
106
|
+
css += `@set ${name} {
|
|
107
|
+
`;
|
|
108
|
+
for (const [propName, propVal] of Object.entries(props)) {
|
|
109
|
+
let cssProp = toKebabCase(propName);
|
|
110
|
+
if (cssProp === "text-color") cssProp = "color";
|
|
111
|
+
if (cssProp === "rounded") cssProp = "border-radius";
|
|
112
|
+
if (cssProp === "size") {
|
|
113
|
+
css += ` width: ${resolveValue(propVal)};
|
|
114
|
+
height: ${resolveValue(propVal)};
|
|
115
|
+
`;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
css += ` ${cssProp}: ${resolveValue(propVal)};
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
css += `}
|
|
122
|
+
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const matches = import_fast_glob.default.sync("**/design.lean.css", { cwd: rootPath, ignore: ["node_modules/**"] });
|
|
127
|
+
let outputPath = matches.length > 0 ? import_path.default.join(rootPath, matches[0]) : import_path.default.join(rootPath, "design.lean.css");
|
|
128
|
+
let existingCss = "";
|
|
129
|
+
if (import_fs.default.existsSync(outputPath)) {
|
|
130
|
+
existingCss = import_fs.default.readFileSync(outputPath, "utf8");
|
|
131
|
+
}
|
|
132
|
+
if (existingCss !== css) {
|
|
133
|
+
import_fs.default.writeFileSync(outputPath, css, "utf8");
|
|
134
|
+
console.log(`LeanCSS: Regenerated ${outputPath} from design.md`);
|
|
135
|
+
}
|
|
136
|
+
return { generatedFile: outputPath, tokens: parsed };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/index.ts
|
|
27
140
|
var leancss = (opts = {}) => {
|
|
28
141
|
return {
|
|
29
142
|
postcssPlugin: "leancss",
|
|
30
143
|
Once(root, { result }) {
|
|
144
|
+
const designResult = processDesignMd(process.cwd());
|
|
145
|
+
if (designResult.generatedFile) {
|
|
146
|
+
const designMdPath = import_path2.default.join(process.cwd(), "design.md");
|
|
147
|
+
result.messages.push({
|
|
148
|
+
type: "dependency",
|
|
149
|
+
plugin: "leancss",
|
|
150
|
+
file: designMdPath,
|
|
151
|
+
parent: root.source?.input.file
|
|
152
|
+
});
|
|
153
|
+
}
|
|
31
154
|
const sets = /* @__PURE__ */ new Map();
|
|
32
155
|
const mediaThresholds = /* @__PURE__ */ new Map([
|
|
33
156
|
["sm", "640px"],
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,122 @@
|
|
|
1
|
-
import "./chunk-6DZX6EAA.mjs";
|
|
2
|
-
|
|
3
1
|
// src/index.ts
|
|
4
2
|
import { Rule } from "postcss";
|
|
3
|
+
import path2 from "path";
|
|
4
|
+
|
|
5
|
+
// src/core/designParser.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fg from "fast-glob";
|
|
9
|
+
import yaml from "yaml";
|
|
10
|
+
function processDesignMd(rootPath) {
|
|
11
|
+
const designMdPath = path.join(rootPath, "design.md");
|
|
12
|
+
if (!fs.existsSync(designMdPath)) {
|
|
13
|
+
return { generatedFile: null, tokens: null };
|
|
14
|
+
}
|
|
15
|
+
let content = fs.readFileSync(designMdPath, "utf8");
|
|
16
|
+
content = content.replace(/^\uFEFF/, "");
|
|
17
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
18
|
+
if (!match) {
|
|
19
|
+
console.log("LeanCSS: design.md found, but could not extract YAML frontmatter. Ensure it starts with ---");
|
|
20
|
+
return { generatedFile: null, tokens: null };
|
|
21
|
+
}
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = yaml.parse(match[1]);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("LeanCSS: Failed to parse design.md YAML frontmatter", err);
|
|
27
|
+
return { generatedFile: null, tokens: null };
|
|
28
|
+
}
|
|
29
|
+
let css = "/* Auto-generated by LeanCSS from design.md */\n";
|
|
30
|
+
css += ":root {\n";
|
|
31
|
+
const toKebabCase = (str) => str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
|
|
32
|
+
const resolveValue = (val) => {
|
|
33
|
+
if (typeof val === "string" && val.startsWith("{") && val.endsWith("}")) {
|
|
34
|
+
const refPath = val.slice(1, -1);
|
|
35
|
+
return `var(--${refPath.replace(/\./g, "-")})`;
|
|
36
|
+
}
|
|
37
|
+
return val;
|
|
38
|
+
};
|
|
39
|
+
const cssVars = [];
|
|
40
|
+
if (parsed.colors) {
|
|
41
|
+
for (const [key, val] of Object.entries(parsed.colors)) {
|
|
42
|
+
cssVars.push(` --colors-${toKebabCase(key)}: ${val};`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (parsed.spacing) {
|
|
46
|
+
for (const [key, val] of Object.entries(parsed.spacing)) {
|
|
47
|
+
cssVars.push(` --spacing-${toKebabCase(key)}: ${val};`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (parsed.rounded) {
|
|
51
|
+
for (const [key, val] of Object.entries(parsed.rounded)) {
|
|
52
|
+
cssVars.push(` --rounded-${toKebabCase(key)}: ${val};`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
css += cssVars.join("\n") + "\n}\n\n";
|
|
56
|
+
if (parsed.typography) {
|
|
57
|
+
for (const [name, props] of Object.entries(parsed.typography)) {
|
|
58
|
+
css += `@set ${name} {
|
|
59
|
+
`;
|
|
60
|
+
for (const [propName, propVal] of Object.entries(props)) {
|
|
61
|
+
const cssProp = toKebabCase(propName);
|
|
62
|
+
css += ` ${cssProp}: ${resolveValue(propVal)};
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
css += `}
|
|
66
|
+
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (parsed.components) {
|
|
71
|
+
for (const [name, props] of Object.entries(parsed.components)) {
|
|
72
|
+
css += `@set ${name} {
|
|
73
|
+
`;
|
|
74
|
+
for (const [propName, propVal] of Object.entries(props)) {
|
|
75
|
+
let cssProp = toKebabCase(propName);
|
|
76
|
+
if (cssProp === "text-color") cssProp = "color";
|
|
77
|
+
if (cssProp === "rounded") cssProp = "border-radius";
|
|
78
|
+
if (cssProp === "size") {
|
|
79
|
+
css += ` width: ${resolveValue(propVal)};
|
|
80
|
+
height: ${resolveValue(propVal)};
|
|
81
|
+
`;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
css += ` ${cssProp}: ${resolveValue(propVal)};
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
css += `}
|
|
88
|
+
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const matches = fg.sync("**/design.lean.css", { cwd: rootPath, ignore: ["node_modules/**"] });
|
|
93
|
+
let outputPath = matches.length > 0 ? path.join(rootPath, matches[0]) : path.join(rootPath, "design.lean.css");
|
|
94
|
+
let existingCss = "";
|
|
95
|
+
if (fs.existsSync(outputPath)) {
|
|
96
|
+
existingCss = fs.readFileSync(outputPath, "utf8");
|
|
97
|
+
}
|
|
98
|
+
if (existingCss !== css) {
|
|
99
|
+
fs.writeFileSync(outputPath, css, "utf8");
|
|
100
|
+
console.log(`LeanCSS: Regenerated ${outputPath} from design.md`);
|
|
101
|
+
}
|
|
102
|
+
return { generatedFile: outputPath, tokens: parsed };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/index.ts
|
|
5
106
|
var leancss = (opts = {}) => {
|
|
6
107
|
return {
|
|
7
108
|
postcssPlugin: "leancss",
|
|
8
109
|
Once(root, { result }) {
|
|
110
|
+
const designResult = processDesignMd(process.cwd());
|
|
111
|
+
if (designResult.generatedFile) {
|
|
112
|
+
const designMdPath = path2.join(process.cwd(), "design.md");
|
|
113
|
+
result.messages.push({
|
|
114
|
+
type: "dependency",
|
|
115
|
+
plugin: "leancss",
|
|
116
|
+
file: designMdPath,
|
|
117
|
+
parent: root.source?.input.file
|
|
118
|
+
});
|
|
119
|
+
}
|
|
9
120
|
const sets = /* @__PURE__ */ new Map();
|
|
10
121
|
const mediaThresholds = /* @__PURE__ */ new Map([
|
|
11
122
|
["sm", "640px"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bizrk/leancss",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CSS-first utility composition with @set and @lift that provides a clean, layer-aware alternative to Sass mixins and Tailwind @apply for atomic compositions.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -46,10 +46,13 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^20.12.7",
|
|
49
|
-
"fast-glob": "^3.3.3",
|
|
50
49
|
"postcss": "^8.4.38",
|
|
51
50
|
"tsup": "^8.5.1",
|
|
52
51
|
"typescript": "^5.4.5",
|
|
53
52
|
"vitest": "^1.5.0"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"fast-glob": "^3.3.3",
|
|
56
|
+
"yaml": "^2.8.3"
|
|
54
57
|
}
|
|
55
|
-
}
|
|
58
|
+
}
|
package/readme.md
CHANGED
|
@@ -102,6 +102,7 @@ LeanCSS is intentionally simple.
|
|
|
102
102
|
• reusable style bundles via `@set` and `@drop`
|
|
103
103
|
• composition via `@lift`
|
|
104
104
|
• responsive shorthands (`@md`, `@c-lg`)
|
|
105
|
+
• **Zero-config `design.md` parsing & generation**
|
|
105
106
|
• alias sets
|
|
106
107
|
• cascade layer friendly
|
|
107
108
|
• works with `.css` and `.scss`
|
|
@@ -282,6 +283,46 @@ LeanCSS treats these properties as compile-time values and substitutes them grac
|
|
|
282
283
|
|
|
283
284
|
* * *
|
|
284
285
|
|
|
286
|
+
# Design System Generation (`design.md`)
|
|
287
|
+
|
|
288
|
+
LeanCSS has built-in, zero-config support for parsing Google's **`design.md`** specification to automatically build your foundational design tokens and utility sets.
|
|
289
|
+
|
|
290
|
+
If LeanCSS detects a `design.md` file in your project root, it will automatically parse the YAML frontmatter and generate a `design.lean.css` file containing:
|
|
291
|
+
1. All your defined `colors`, `spacing`, and `rounded` tokens as native CSS `--variables` attached to `:root`.
|
|
292
|
+
2. All your `typography` and `components` blocks as fully expanded LeanCSS `@set` bundles.
|
|
293
|
+
|
|
294
|
+
Example `design.md`:
|
|
295
|
+
```yaml
|
|
296
|
+
---
|
|
297
|
+
name: My Design System
|
|
298
|
+
colors:
|
|
299
|
+
primary: "#1A1C1E"
|
|
300
|
+
spacing:
|
|
301
|
+
sm: "8px"
|
|
302
|
+
components:
|
|
303
|
+
card-sm:
|
|
304
|
+
backgroundColor: "{colors.primary}"
|
|
305
|
+
padding: "{spacing.sm}"
|
|
306
|
+
---
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
When LeanCSS compiles, it automatically generates a `design.lean.css` file:
|
|
310
|
+
```css
|
|
311
|
+
:root {
|
|
312
|
+
--colors-primary: #1A1C1E;
|
|
313
|
+
--spacing-sm: 8px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@set card-sm {
|
|
317
|
+
background-color: var(--colors-primary);
|
|
318
|
+
padding: var(--spacing-sm);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
You can then freely `@lift: card-sm;` anywhere in your project! No configuration required, and it automatically hot-reloads when running inside tools like Vite or Webpack.
|
|
323
|
+
|
|
324
|
+
* * *
|
|
325
|
+
|
|
285
326
|
# CLI Tools
|
|
286
327
|
|
|
287
328
|
LeanCSS is reversible and comes with maintenance tools to keep your project clean.
|