gulp-mu-css 2.0.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 +135 -0
- package/docs/manual/imgs/bc_alu.png +0 -0
- package/docs/manual/imgs/bc_aqua.png +0 -0
- package/docs/manual/imgs/logo.png +0 -0
- package/docs/manual/imgs/strip_result_smoke.png +0 -0
- package/docs/manual/microCSS-Handbuch.md +774 -0
- package/docs/microCSS.pdf +0 -0
- package/package.json +31 -0
- package/src/api/Colors.mjs +260 -0
- package/src/api/Cursors.mjs +90 -0
- package/src/api/Preload.mjs +35 -0
- package/src/api/Sprites.mjs +171 -0
- package/src/build/BuildCache.mjs +81 -0
- package/src/build/MediaSteps.mjs +171 -0
- package/src/build/SkinBuilder.mjs +197 -0
- package/src/compile/Compiler.mjs +168 -0
- package/src/css/CssDocument.mjs +244 -0
- package/src/eval/MuContext.mjs +62 -0
- package/src/index.mjs +13 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// CssDocument wraps a PostCSS AST and provides the manipulation API used by
|
|
2
|
+
// Gulp pipelines and µ-directives: finding rules by selector path, adding and
|
|
3
|
+
// changing properties, and a plain JSON representation (docs/CONCEPT.md,
|
|
4
|
+
// sections 3 and 4).
|
|
5
|
+
|
|
6
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
import postcss from "postcss";
|
|
9
|
+
|
|
10
|
+
// Collapses whitespace so that "div.a > span" matches "div.a > span".
|
|
11
|
+
function _NormalizeSelector(_selector) {
|
|
12
|
+
return _selector.replace(/\s+/g, " ").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Display selector of a node: rules use their selector, at-rules the
|
|
16
|
+
// "@name params" form (e.g. "@keyframes spin", "@media (max-width: 600px)").
|
|
17
|
+
function _NodeSelector(_node) {
|
|
18
|
+
if (_node.type === "rule") return _NormalizeSelector(_node.selector);
|
|
19
|
+
if (_node.type === "atrule") return _NormalizeSelector(`@${_node.name} ${_node.params}`.trim());
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function _Matches(_node, _matcher) {
|
|
24
|
+
const selector = _NodeSelector(_node);
|
|
25
|
+
if (_matcher instanceof RegExp) return _matcher.test(selector);
|
|
26
|
+
return selector === _NormalizeSelector(String(_matcher));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Wrapper around a PostCSS rule or at-rule node.
|
|
30
|
+
export class CssRule {
|
|
31
|
+
constructor(_node) {
|
|
32
|
+
this.node = _node;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get selector() {
|
|
36
|
+
return _NodeSelector(this.node);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set selector(_value) {
|
|
40
|
+
if (this.node.type === "rule") this.node.selector = _value;
|
|
41
|
+
else throw new Error("CssRule: cannot set the selector of an at-rule");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Returns the value of the first declaration with the given property
|
|
45
|
+
// name, or null if not present.
|
|
46
|
+
GetProperty(_prop) {
|
|
47
|
+
let value = null;
|
|
48
|
+
this.node.each((_child) => {
|
|
49
|
+
if (_child.type === "decl" && _child.prop === _prop && value === null) value = _child.value;
|
|
50
|
+
});
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Appends a declaration (duplicates allowed, like the legacy AddProperty).
|
|
55
|
+
AddProperty(_prop, _value, _important = false) {
|
|
56
|
+
this.node.append({ prop: _prop, value: String(_value), important: _important });
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Changes all declarations with the given property, or appends one if the
|
|
61
|
+
// property does not exist yet.
|
|
62
|
+
ChangeProperty(_prop, _value, _important = false) {
|
|
63
|
+
let found = false;
|
|
64
|
+
this.node.each((_child) => {
|
|
65
|
+
if (_child.type === "decl" && _child.prop === _prop) {
|
|
66
|
+
_child.value = String(_value);
|
|
67
|
+
_child.important = _important;
|
|
68
|
+
found = true;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
if (!found) this.AddProperty(_prop, _value, _important);
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Removes all declarations with the given property name.
|
|
76
|
+
RemoveProperty(_prop) {
|
|
77
|
+
const doomed = [];
|
|
78
|
+
this.node.each((_child) => {
|
|
79
|
+
if (_child.type === "decl" && _child.prop === _prop) doomed.push(_child);
|
|
80
|
+
});
|
|
81
|
+
doomed.forEach((_child) => _child.remove());
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// All declarations as { prop, value, important } objects.
|
|
86
|
+
GetProperties() {
|
|
87
|
+
const result = [];
|
|
88
|
+
this.node.each((_child) => {
|
|
89
|
+
if (_child.type === "decl") result.push({ prop: _child.prop, value: _child.value, important: !!_child.important });
|
|
90
|
+
});
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Finds the first direct or nested child rule matching the selector path
|
|
95
|
+
// (one argument per nesting level), e.g. FindRule("50%") inside keyframes.
|
|
96
|
+
FindRule(..._path) {
|
|
97
|
+
return _FindByPath(this.node, _path);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// All child rules (recursive) matching the selector or regex.
|
|
101
|
+
FindRules(_matcher) {
|
|
102
|
+
return _FindAll(this.node, _matcher);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Adds a nested rule and returns its wrapper.
|
|
106
|
+
AddRule(_selector) {
|
|
107
|
+
const rule = postcss.rule({ selector: _selector });
|
|
108
|
+
this.node.append(rule);
|
|
109
|
+
return new CssRule(rule);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Remove() {
|
|
113
|
+
this.node.remove();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function _FindByPath(_container, _path) {
|
|
118
|
+
let current = _container;
|
|
119
|
+
for (const segment of _path) {
|
|
120
|
+
let next = null;
|
|
121
|
+
current.each((_child) => {
|
|
122
|
+
if (next) return;
|
|
123
|
+
if ((_child.type === "rule" || _child.type === "atrule") && _Matches(_child, segment)) next = _child;
|
|
124
|
+
});
|
|
125
|
+
if (!next) return null;
|
|
126
|
+
current = next;
|
|
127
|
+
}
|
|
128
|
+
return current === _container ? null : new CssRule(current);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _FindAll(_container, _matcher) {
|
|
132
|
+
const result = [];
|
|
133
|
+
_container.walkRules((_node) => {
|
|
134
|
+
if (_Matches(_node, _matcher)) result.push(new CssRule(_node));
|
|
135
|
+
});
|
|
136
|
+
_container.walkAtRules((_node) => {
|
|
137
|
+
if (_Matches(_node, _matcher)) result.push(new CssRule(_node));
|
|
138
|
+
});
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ------------------------------------------------------ JSON representation
|
|
143
|
+
|
|
144
|
+
function _NodeToJson(_node) {
|
|
145
|
+
switch (_node.type) {
|
|
146
|
+
case "rule":
|
|
147
|
+
return { type: "rule", selector: _node.selector, nodes: _node.nodes.map(_NodeToJson) };
|
|
148
|
+
case "atrule":
|
|
149
|
+
return {
|
|
150
|
+
type: "atrule",
|
|
151
|
+
name: _node.name,
|
|
152
|
+
params: _node.params,
|
|
153
|
+
...(_node.nodes ? { nodes: _node.nodes.map(_NodeToJson) } : {})
|
|
154
|
+
};
|
|
155
|
+
case "decl":
|
|
156
|
+
return { type: "decl", prop: _node.prop, value: _node.value, ...(_node.important ? { important: true } : {}) };
|
|
157
|
+
case "comment":
|
|
158
|
+
return { type: "comment", text: _node.text };
|
|
159
|
+
default:
|
|
160
|
+
throw new Error(`CssDocument.ToJson: unsupported node type "${_node.type}"`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function _NodeFromJson(_json) {
|
|
165
|
+
switch (_json.type) {
|
|
166
|
+
case "rule": {
|
|
167
|
+
const rule = postcss.rule({ selector: _json.selector });
|
|
168
|
+
(_json.nodes || []).forEach((_child) => rule.append(_NodeFromJson(_child)));
|
|
169
|
+
return rule;
|
|
170
|
+
}
|
|
171
|
+
case "atrule": {
|
|
172
|
+
const atrule = postcss.atRule({ name: _json.name, params: _json.params || "" });
|
|
173
|
+
if (_json.nodes) {
|
|
174
|
+
atrule.nodes = [];
|
|
175
|
+
_json.nodes.forEach((_child) => atrule.append(_NodeFromJson(_child)));
|
|
176
|
+
}
|
|
177
|
+
return atrule;
|
|
178
|
+
}
|
|
179
|
+
case "decl":
|
|
180
|
+
return postcss.decl({ prop: _json.prop, value: _json.value, important: !!_json.important });
|
|
181
|
+
case "comment":
|
|
182
|
+
return postcss.comment({ text: _json.text });
|
|
183
|
+
default:
|
|
184
|
+
throw new Error(`CssDocument.FromJson: unsupported node type "${_json.type}"`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ------------------------------------------------------------- document
|
|
189
|
+
|
|
190
|
+
export class CssDocument {
|
|
191
|
+
constructor(_root) {
|
|
192
|
+
this.root = _root;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static FromString(_css, _options = {}) {
|
|
196
|
+
return new CssDocument(postcss.parse(_css, { from: _options.from }));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static async FromFile(_path) {
|
|
200
|
+
const css = await readFile(_path, "utf8");
|
|
201
|
+
return CssDocument.FromString(css, { from: _path });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static FromJson(_json) {
|
|
205
|
+
const root = postcss.root();
|
|
206
|
+
(_json.nodes || []).forEach((_child) => root.append(_NodeFromJson(_child)));
|
|
207
|
+
return new CssDocument(root);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// First top-level (or nested, via path segments) rule matching the path.
|
|
211
|
+
FindRule(..._path) {
|
|
212
|
+
return _FindByPath(this.root, _path);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// All rules and at-rules (recursive) matching the selector or regex.
|
|
216
|
+
FindRules(_matcher) {
|
|
217
|
+
return _FindAll(this.root, _matcher);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Adds a top-level rule and returns its wrapper. By default the rule is
|
|
221
|
+
// appended; _options.after (a CssRule) inserts it behind an existing rule
|
|
222
|
+
// instead, which keeps generated rules near their source position.
|
|
223
|
+
AddRule(_selector, _options = {}) {
|
|
224
|
+
const rule = postcss.rule({ selector: _selector });
|
|
225
|
+
// Insert into the anchor's container so the position also holds for
|
|
226
|
+
// rules nested in at-rules (e.g. inside @media).
|
|
227
|
+
if (_options.after) _options.after.node.parent.insertAfter(_options.after.node, rule);
|
|
228
|
+
else this.root.append(rule);
|
|
229
|
+
return new CssRule(rule);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
ToJson() {
|
|
233
|
+
return { type: "root", nodes: this.root.nodes.map(_NodeToJson) };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
ToCss() {
|
|
237
|
+
return this.root.toString();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async ToFile(_path) {
|
|
241
|
+
await mkdir(dirname(_path), { recursive: true });
|
|
242
|
+
await writeFile(_path, this.ToCss(), "utf8");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// MuContext is the evaluation environment for µ() interpolations and -µ:
|
|
2
|
+
// directives. Expressions are plain JavaScript, compiled once per source via
|
|
3
|
+
// new Function and evaluated against a scope that contains the skin
|
|
4
|
+
// variables ($), the color API and any user-defined helpers.
|
|
5
|
+
|
|
6
|
+
import { Lighten, Alpha, MixColors, AlphaValue, ParseColor, FormatColor } from "../api/Colors.mjs";
|
|
7
|
+
|
|
8
|
+
// Legacy PhPxUnit: numbers become "<n>px", everything else (e.g. calc()
|
|
9
|
+
// strings) passes through unchanged.
|
|
10
|
+
export function PxUnit(_value) {
|
|
11
|
+
return typeof _value === "number" ? `${_value}px` : String(_value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const BUILTINS = { Lighten, Alpha, MixColors, AlphaValue, ParseColor, FormatColor, PxUnit };
|
|
15
|
+
|
|
16
|
+
const compileCache = new Map();
|
|
17
|
+
|
|
18
|
+
function _Compile(_source, _scopeKeys) {
|
|
19
|
+
const cacheKey = `${_scopeKeys.join(",")}\u0000${_source}`;
|
|
20
|
+
let fn = compileCache.get(cacheKey);
|
|
21
|
+
if (!fn) {
|
|
22
|
+
try {
|
|
23
|
+
// eslint-disable-next-line no-new-func
|
|
24
|
+
fn = new Function(..._scopeKeys, `"use strict";\nreturn (${_source}\n);`);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
// new Function reports the error without any source reference.
|
|
27
|
+
throw new Error(`invalid JavaScript expression "${_source}" (${error.message})`, { cause: error });
|
|
28
|
+
}
|
|
29
|
+
compileCache.set(cacheKey, fn);
|
|
30
|
+
}
|
|
31
|
+
return fn;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class MuContext {
|
|
35
|
+
// _options.vars: skin variables, accessible as $.name
|
|
36
|
+
// _options.helpers: user functions, accessible by their name
|
|
37
|
+
constructor(_options = {}) {
|
|
38
|
+
this.vars = _options.vars ?? {};
|
|
39
|
+
this.helpers = _options.helpers ?? {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Evaluates a JavaScript expression. _extraScope adds (or overrides)
|
|
43
|
+
// bindings for this single evaluation, e.g. rule-bound directive helpers.
|
|
44
|
+
//
|
|
45
|
+
// Helper functions (declared with "function", not arrows) are invoked
|
|
46
|
+
// with "this" bound to the evaluation scope, so macros can use
|
|
47
|
+
// this.AddProperty(...), this.InsertRule(...), this.rule, this.$ etc.
|
|
48
|
+
// like the legacy µ.$.* functions used the µ globals. The binding also
|
|
49
|
+
// survives indirect calls, e.g. afterWork hooks passed to Sprite().
|
|
50
|
+
Evaluate(_source, _extraScope = {}) {
|
|
51
|
+
const scope = { $: this.vars, ...BUILTINS, ...this.helpers, ..._extraScope };
|
|
52
|
+
for (const key of Object.keys(this.helpers)) {
|
|
53
|
+
const helper = this.helpers[key];
|
|
54
|
+
if (typeof helper === "function" && scope[key] === helper) {
|
|
55
|
+
scope[key] = function (..._args) { return helper.apply(scope, _args); };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const keys = Object.keys(scope).filter((_key) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(_key));
|
|
59
|
+
const fn = _Compile(_source, keys);
|
|
60
|
+
return fn(...keys.map((_key) => scope[_key]));
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// microCSS - public API (milestones M1 core pipeline, M2 sprites/cursors and
|
|
2
|
+
// M3 manifest/build, see docs/CONCEPT.md).
|
|
3
|
+
|
|
4
|
+
export { CssDocument, CssRule } from "./css/CssDocument.mjs";
|
|
5
|
+
export { MuContext, PxUnit } from "./eval/MuContext.mjs";
|
|
6
|
+
export { CompileMcss, ReplaceInterpolations } from "./compile/Compiler.mjs";
|
|
7
|
+
export { Lighten, Alpha, AlphaValue, MixColors, ParseColor, FormatColor } from "./api/Colors.mjs";
|
|
8
|
+
export { SpriteManager } from "./api/Sprites.mjs";
|
|
9
|
+
export { CursorManager } from "./api/Cursors.mjs";
|
|
10
|
+
export { PreloadRegistry, PRELOAD_SELECTOR } from "./api/Preload.mjs";
|
|
11
|
+
export { DefineSkin, BuildSkin } from "./build/SkinBuilder.mjs";
|
|
12
|
+
export { BuildCache, FileFingerprint, FingerprintFiles, FingerprintsMatch } from "./build/BuildCache.mjs";
|
|
13
|
+
export { RunMediaStep } from "./build/MediaSteps.mjs";
|