kempo-css 2.2.0 → 2.2.1

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/bin/shake.js CHANGED
@@ -11,16 +11,16 @@ const flagIndex = (flag) => {
11
11
 
12
12
  const htmlPath = flagIndex('--html') || args.find(a => !a.startsWith('--'));
13
13
  const themePath = flagIndex('--theme');
14
+ const colorMode = flagIndex('--color-mode');
14
15
 
15
16
  if(!htmlPath){
16
- console.error('Usage: kempo-css-shake --html <file.html> [--theme <theme.css>]');
17
+ console.error('Usage: kempo-css-shake --html <file.html> [--theme <theme.css>] [--color-mode light|dark]');
17
18
  process.exit(1);
18
19
  }
19
20
 
20
21
  const html = readFileSync(resolve(htmlPath), 'utf-8');
21
22
  const options = {};
22
- if(themePath){
23
- options.theme = resolve(themePath);
24
- }
23
+ if(themePath) options.theme = resolve(themePath);
24
+ if(colorMode) options.colorMode = colorMode;
25
25
 
26
26
  process.stdout.write(shake(html, options));
package/dist/shake.js ADDED
@@ -0,0 +1 @@
1
+ import{readFileSync as e}from"fs";import{resolve as t,dirname as r}from"path";import{fileURLToPath as o}from"url";import*as s from"css-tree";import{Parser as a}from"htmlparser2";const l=r(o(import.meta.url)),n=t(l,"kempo.css"),i=e=>{const t=new Map;return s.parse(e).children.forEach(e=>{if("Rule"!==e.type)return;":root"===s.generate(e.prelude)&&e.block.children.forEach(e=>{"Declaration"===e.type&&e.property.startsWith("--")&&t.set(e.property,s.generate(e.value))})}),t};export default(r,{theme:o,colorMode:l}={})=>{const c=e(n,"utf-8"),p=i(c);if(o){const r="string"==typeof o&&o.includes("{")?o:e(t(o),"utf-8");i(r).forEach((e,t)=>p.set(t,e))}const m=(e=>{const t=new Map(e);let r=!0,o=0;for(;r&&o<30;){r=!1,o++;for(const[e,o]of t){const s=o.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^)]+))?\)/g,(o,s,a)=>{if(s===e)return o;const l=t.get(s);return void 0===l||l.includes(`var(${e})`)?void 0!==a?(r=!0,a.trim()):o:(r=!0,l)});s!==o&&t.set(e,s)}}return t})(p),d=(e=>{const t=new Set(["html","body"]),r=new Set,o=new Set,s=new Set,l=new a({onopentag(e,a){t.add(e.toLowerCase()),a.class&&a.class.split(/\s+/).forEach(e=>{e&&r.add(e)}),a.id&&o.add(a.id),Object.keys(a).forEach(e=>s.add(e.toLowerCase()))}});return l.write(e),l.end(),{tags:t,classes:r,ids:o,attrs:s}})(r),h=s.parse(c);s.walk(h,{visit:"Rule",enter(e,t,r){if(":root"===s.generate(e.prelude)){let o=!0;if(e.block.children.forEach(e=>{"Declaration"===e.type&&e.property.startsWith("--")||(o=!1)}),o)return void r.remove(t);const s=[];return e.block.children.forEach((e,t)=>{"Declaration"===e.type&&e.property.startsWith("--")&&s.push(t)}),void s.forEach(t=>e.block.children.remove(t))}}}),s.walk(h,{visit:"Rule",enter(e,t,r){const o=s.generate(e.prelude);/\[theme=/.test(o)&&r.remove(t)}}),s.walk(h,{visit:"Atrule",enter(e,t,r){if("media"!==e.name)return;s.generate(e.prelude).includes("prefers-color-scheme")&&r.remove(t)}}),s.walk(h,{visit:"Rule",enter(e,t,r){((e,t)=>e.split(",").some(e=>{const r=e.trim();return!!r&&(":root"===r||"*"===r||r.replace(/::[\w-]+/g,"").replace(/:[\w-]+(\([^)]*\))?/g,"").split(/[\s>+~]+/).filter(Boolean).every(e=>{const r=e.match(/^([a-zA-Z][\w-]*)/),o=[...e.matchAll(/\.([\w-]+)/g)].map(e=>e[1]),s=[...e.matchAll(/#([\w-]+)/g)].map(e=>e[1]),a=[...e.matchAll(/\[([\w-]+)/g)].map(e=>e[1].toLowerCase());return!(r&&!t.tags.has(r[1].toLowerCase())||o.length&&o.some(e=>!t.classes.has(e))||s.length&&s.some(e=>!t.ids.has(e))||a.length&&a.some(e=>!t.attrs.has(e)))}))}))(s.generate(e.prelude),d)||r.remove(t)}}),s.walk(h,{visit:"Atrule",enter(e,t,r){"media"===e.name&&e.block&&0===e.block.children.size&&r.remove(t)}});let u=((e,t)=>{let r,o=e;do{r=o,o=o.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^)]+))?\)/g,(e,r,o)=>{const s=t.get(r);return void 0!==s?s:void 0!==o?o.trim():e})}while(o!==r);return o})(s.generate(h),m);return"light"!==l&&"dark"!==l||(u=((e,t)=>{let r=e,o=0;for(;;){const e=r.indexOf("light-dark(",o);if(-1===e)break;const s=e;let a=e+11,l=1,n=-1;for(;a<r.length&&l>0&&("("===r[a]?l++:")"===r[a]?l--:","===r[a]&&1===l&&-1===n&&(n=a),0!==l);)a++;if(-1===n||0!==l){o=e+1;continue}const i=r.slice(s+11,n).trim(),c=r.slice(n+1,a).trim(),p="dark"===t?c:i;r=r.slice(0,s)+p+r.slice(a+1),o=s}return r})(u,l)),u};
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "kempo-css",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "scripts/build.js",
7
7
  "exports": {
8
8
  ".": "./scripts/build.js",
9
- "./shake": "./src/shake.js"
9
+ "./shake": "./dist/shake.js"
10
10
  },
11
11
  "bin": {
12
12
  "kempo-css-shake": "bin/shake.js"
package/scripts/build.js CHANGED
@@ -132,6 +132,23 @@ minifiedFiles.forEach(file => {
132
132
  console.log(`Copied ${srcPath} → ${destPath}`);
133
133
  });
134
134
 
135
+ // Minify shake.js
136
+ console.log('\nMinifying src/shake.js...');
137
+ try{
138
+ const shakeCode = fs.readFileSync('src/shake.js', 'utf-8');
139
+ const shakeResult = await minify(shakeCode, { module: true });
140
+ fs.writeFileSync(path.join(outputDir, 'shake.js'), shakeResult.code);
141
+ const origSize = Buffer.byteLength(shakeCode, 'utf-8');
142
+ const minSize = Buffer.byteLength(shakeResult.code, 'utf-8');
143
+ const savings = ((origSize - minSize) / origSize * 100).toFixed(1);
144
+ console.log(`src/shake.js → dist/shake.js`);
145
+ console.log(` Original: ${(origSize / 1024).toFixed(1)}KB`);
146
+ console.log(` Minified: ${(minSize / 1024).toFixed(1)}KB`);
147
+ console.log(` Savings: ${savings}%`);
148
+ }catch(error){
149
+ console.error('Error minifying shake.js:', error.message);
150
+ }
151
+
135
152
  console.log('\nBuild complete!');
136
153
  console.log(`Minified files are in the ${outputDir}/ directory`);
137
154
 
package/src/shake.js CHANGED
@@ -133,10 +133,44 @@ const inlineVars = (css, vars) => {
133
133
  return result;
134
134
  };
135
135
 
136
+ /*
137
+ Resolve light-dark(a, b) → a (light) or b (dark)
138
+ Handles nested parens inside arguments
139
+ */
140
+ const resolveLightDark = (css, mode) => {
141
+ let result = css;
142
+ let searchFrom = 0;
143
+ while(true){
144
+ const idx = result.indexOf('light-dark(', searchFrom);
145
+ if(idx === -1) break;
146
+ const start = idx;
147
+ let i = idx + 'light-dark('.length;
148
+ let depth = 1;
149
+ let commaPos = -1;
150
+ while(i < result.length && depth > 0){
151
+ if(result[i] === '(') depth++;
152
+ else if(result[i] === ')') depth--;
153
+ else if(result[i] === ',' && depth === 1 && commaPos === -1) commaPos = i;
154
+ if(depth === 0) break;
155
+ i++;
156
+ }
157
+ if(commaPos === -1 || depth !== 0){
158
+ searchFrom = idx + 1;
159
+ continue;
160
+ }
161
+ const lightVal = result.slice(start + 'light-dark('.length, commaPos).trim();
162
+ const darkVal = result.slice(commaPos + 1, i).trim();
163
+ const replacement = mode === 'dark' ? darkVal : lightVal;
164
+ result = result.slice(0, start) + replacement + result.slice(i + 1);
165
+ searchFrom = start;
166
+ }
167
+ return result;
168
+ };
169
+
136
170
  /*
137
171
  Main shake function
138
172
  */
139
- export default (html, { theme } = {}) => {
173
+ export default (html, { theme, colorMode } = {}) => {
140
174
  const kempoCss = readFileSync(kempoPath, 'utf-8');
141
175
  const vars = extractRootVars(kempoCss);
142
176
  if(theme){
@@ -222,5 +256,9 @@ export default (html, { theme } = {}) => {
222
256
  }
223
257
  });
224
258
 
225
- return inlineVars(csstree.generate(ast), resolved);
259
+ let output = inlineVars(csstree.generate(ast), resolved);
260
+ if(colorMode === 'light' || colorMode === 'dark'){
261
+ output = resolveLightDark(output, colorMode);
262
+ }
263
+ return output;
226
264
  };