Skip to content

Commit

Permalink
refactor: major
Browse files Browse the repository at this point in the history
  • Loading branch information
maxholman committed Jun 22, 2024
1 parent 6f3cbe3 commit 62d733b
Show file tree
Hide file tree
Showing 28 changed files with 412 additions and 227 deletions.
15 changes: 11 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ SRCS = $(wildcard lib/**)
.DEFAULT_GOAL := all

.PHONY: all
all: build types
all: build dist build/global.scss build/global.css

node_modules: package.json pnpm-lock.yaml
pnpm install
touch $@

bin/token.js: dist build
dist/bin/token.js: build bin/token.ts ${SRCS}
pnpm exec tsc -b

build/global.css: dist/bin/token.js
node dist/bin/token.js -t css > $@

build/global.scss: dist/bin/token.js
node dist/bin/token.js -t scss > $@

tsconfig.json: tsconfig-vite.src.json
pnpm exec tsc -p tsconfig-vite.src.json --showConfig 1> $@
Expand All @@ -34,7 +41,7 @@ dev-server: node_modules vite.config.ts

PHONY: typecheck
typecheck: node_modules tsconfig.json
pnpm exec tsc --noEmit false --emitDeclarationOnly -w --preserveWatchOutput
pnpm exec tsc -w --preserveWatchOutput

.PHONY: dev
dev:
Expand All @@ -45,7 +52,7 @@ debug:
DEBUG_BUILD=1 $(MAKE)

dist: node_modules tsconfig.json
pnpm exec tsc --noEmit false --emitDeclarationOnly
pnpm exec tsc

.PHONY: clean
clean: node_modules tsconfig.json
Expand Down
260 changes: 168 additions & 92 deletions bin/token.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,182 @@
// eslint-disable-next-line import/no-extraneous-dependencies
// import { camelCase } from 'change-case';
import { createGlobalThemeMapFn } from '../lib/css-helpers.js';
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-restricted-syntax */
import { createWriteStream } from 'node:fs';
import { PassThrough } from 'node:stream';
import { parseArgs } from 'node:util';
import { camelCase } from 'change-case';
import { leafNodeMapper, type PathStr, type VarFn, vargEx } from './utils.js';
import {
badgeVars,
badgeVarsMapFnPrefix,
buttonVars,
calloutVars,
formControlVars,
generalVars,
buttonVarsMapFnPrefix,
formControlTokens,
formControlVarsMapFnPrefix,
globalTokens,
globalVars,
} from '../lib/ve.css.js';
globalVarsMapFnPrefix,
panelTokens,
panelVars,
panelVarsMapFnPrefix,
propsTokens,
propsVars,
propsVarsMapFnPrefix,
} from '#vars';

type JsonObject = { [Key in string]: string | JsonObject };
const {
values: { type, outputPath },
} = parseArgs({
options: {
type: {
type: 'string',
short: 't',
default: 'css',
},
outputPath: {
type: 'string',
short: 'o',
},
},
});
/**
* vars
*/
const knownVars = new Map<VarFn, { path: PathStr }>();
for (const [vars, prefix] of [
[globalVars, globalVarsMapFnPrefix] as const,
[propsVars, propsVarsMapFnPrefix] as const,
[panelVars, panelVarsMapFnPrefix] as const,
[badgeVars, badgeVarsMapFnPrefix] as const,
[buttonVars, buttonVarsMapFnPrefix] as const,
]) {
leafNodeMapper(
vars,
(path, value) => {
knownVars.set(value, { path });
},
[prefix],
);
}

/**
* tokens
*/
const knownTokens = new Map<VarFn, { value: string }>();

function isPlainObject(obj: unknown): obj is Record<string, unknown> {
return (
typeof obj === 'object' &&
obj !== null &&
Object.getPrototypeOf(obj) === Object.prototype
for (const [vars, prefix] of [
[globalTokens, globalVarsMapFnPrefix] as const,
[propsTokens, propsVarsMapFnPrefix] as const,
[panelTokens, panelVarsMapFnPrefix] as const,
[formControlTokens, formControlVarsMapFnPrefix] as const,
]) {
leafNodeMapper(
vars,
(path, value) => {
knownTokens.set(path, { value });
},
[prefix],
);
}

function flattenObject(
obj: JsonObject,
prefixFn: (value: string | null, path: string[]) => string,
path: string[] = [],
): [string, string][] {
return Object.entries(obj).flatMap(([key, value]) => {
const thisPrefix = prefixFn(key, path);
if (isPlainObject(value)) {
return flattenObject(value, prefixFn, [...path, key]);
// resolved tokens, in case they have vars that reference each other
for (const [varName, { value }] of knownTokens) {
if (value.match(vargEx)) {
const cssVar = knownVars.get(value);

if (cssVar) {
// get the value of the css var
const token = knownTokens.get(cssVar.path);

if (token) {
knownTokens.set(varName, token);
}
}
return [[thisPrefix, value]];
});
}
}

// function writeScssCssVars([cssVarFunctionPrefix, bareCssVar]: [
// string,
// string,
// ]) {
// process.stdout.write(`$${camelCase(cssVarFunctionPrefix)}: ${bareCssVar};`);
// process.stdout.write('\n');
// }

const vars = flattenObject(
{
...globalVars,
...generalVars,
...badgeVars,
...buttonVars,
...calloutVars,
...formControlVars,
},
createGlobalThemeMapFn('geklol'),
);
const output = new PassThrough();
if (outputPath) {
output.pipe(createWriteStream(outputPath));
} else {
output.pipe(process.stdout);
}

const autoGeneratedBanner = [
'This file is auto-generated.',
'',
'Do not edit this file directly.',
'',
`Date: ${new Date().toISOString()} by ${process.env.USER}`,
];

output.write(`/*
* ${autoGeneratedBanner.join('\n * ')}
*/\n`);

/**
* {
'color.brand': 'var(--color-brand)',
'color.accent': 'var(--color-accent)',
'color.fgColor': 'var(--color-fgColor)',
'color.bgColor': 'var(--color-bgColor)',
'color.borderColor': 'var(--color-borderColor)',
'color.muted.fgColor': 'var(--color-muted-fgColor)',
'color.muted.bgColor': 'var(--color-muted-bgColor)',
'color.muted.borderColor': 'var(--color-muted-borderColor)',
'color.emphasis.fgColor': 'var(--colo
*/
console.log({ vars });

// console.log(flattenObject(globalVarTokens, 'globals'));
// console.log(flattenObject(generalTokens, 'general'));
// console.log(flattenObject(panelTokens, 'panel'));
// console.log(flattenObject(formControlTokens, 'formControl'));

// const tokens = flattenObject(
// {
// ...generalTokens,
// ...panelTokens,
// ...formControlTokens,
// },
// 'yay-tokens',
// );

// console.log({ tokens });

// vars.forEach(writeScssCssVars);

// function writeScssDefaults([cssVarFunctionPrefix, bareCssVar]: [
// string,
// string,
// ]) {
// process.stdout.write(`$${camelCase(cssVarFunctionPrefix)}: ${bareCssVar};`);
// process.stdout.write('\n');
// }

// const tokens = extractTokens({
// ...globalVarTokens,
// ...panelTokens,
// ...formControlTokens,
// });

// tokens.forEach(writeScssDefaults);

// ---
// ---
* Output
* */
switch (type) {
case 'scss': {
const defaults: string[] = [];
const definitions: string[] = [];

for (const [varFn, { path }] of knownVars) {
const tokenValue = knownTokens.get(path);

if (tokenValue) {
const [, varName] = varFn.match(vargEx) || [];

if (!varName) {
throw new Error(`Could not extract var name from ${varFn}`);
}
const scssVar = camelCase(varName, {
mergeAmbiguousCharacters: true,
});

// output.write(` $${scssVar}: ${tokenValue.value} !default;\n`);
// output.write(` --${varName}: #{$${scssVar}};\n\n`);

defaults.push(`$${scssVar}: ${tokenValue.value} !default;`);
definitions.push(`--${varName}: #{$${scssVar}};`);
} else {
output.write(
`\n/* WARN: skipped ${varFn} as token path "${path}" is not known */\n`,
);
}
}

output.write(defaults.join('\n'));
output.write('\n');
output.write(`@mixin tokens($args...) {\n`);
output.write(definitions.map((d) => ` ${d}`).join('\n'));
output.write('\n');
output.write(`}\n`);
break;
}
case 'css':
default:
output.write(`:root {\n`);

for (const [varFn, { path }] of knownVars) {
const tokenValue = knownTokens.get(path);

if (tokenValue) {
const [, varName] = varFn.match(vargEx) || [];

if (!varName) {
throw new Error(`Could not extract var name from ${varFn}`);
}

output.write(`--${varName}: ${tokenValue.value};\n`);
} else {
output.write(
`\n/* WARN: skipped ${varFn} as token path "${path}" is not known */\n`,
);
}
}
output.write(`}\n`);
break;
}

output.end();
34 changes: 34 additions & 0 deletions bin/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable no-restricted-syntax */
export type RecursiveStringStringObj = {
[Key in string]: string | RecursiveStringStringObj;
};

export type VarFn = string;
export type PathPart = string;
export type PathParts = PathPart[];
export type PathStr = PathPart | `${PathPart}.${PathPart}`;

function pathToString(parts: PathParts) {
return parts
.filter((part, idx) => !(part === '' && idx === 0))
.join('.') as PathStr;
}

// extract the var names from the leaf nodes of panelVars
// and add them to the knownVars map
export function leafNodeMapper(
obj: RecursiveStringStringObj,
callback: (path: PathStr, value: string) => void,
pathParts: string[] = [],
) {
for (const [key, value] of Object.entries(obj)) {
const thisPath = [...pathParts, key];
if (typeof value === 'string') {
callback(pathToString(thisPath), value);
} else {
leafNodeMapper(value, callback, thisPath);
}
}
}

export const vargEx = /var\(--(.*)\)/;
2 changes: 1 addition & 1 deletion lib/badge.css.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fallbackVar, style, styleVariants } from '@vanilla-extract/css';
import { purposeVariantVars } from './purpose.css.js';
import { globalVars } from './ve.css.js';
import { globalVars } from './vars.css.js';

const badgeClassName = style({
borderRadius: globalVars.border.radius,
Expand Down
Loading

0 comments on commit 62d733b

Please sign in to comment.