@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/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.2.1",
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.