Skip to content

Commit

Permalink
Merge pull request #16 from maxholman/refactor-variants
Browse files Browse the repository at this point in the history
feat: major refactor
  • Loading branch information
maxholman authored Jun 22, 2024
2 parents db21d6f + 62d733b commit 4a72f7f
Show file tree
Hide file tree
Showing 138 changed files with 4,510 additions and 3,574 deletions.
29 changes: 27 additions & 2 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ module.exports = {
extends: ['@block65/eslint-config', '@block65/eslint-config/react'],

parserOptions: {
ecmaVersion: 2022,

tsconfigRootDir: __dirname,
project: ['./tsconfig.json', './tsconfig-node.json'],
project: [
'./tsconfig.eslint.json',
// './tsconfig.node.json',
// './tsconfig.examples.json',
],
},

overrides: [
{
files: ['**/*.css.ts', 'src/**/*.tsx', 'src/**/*.ts'],
files: ['lib/**/*.css.ts'],
rules: {
'import/no-extraneous-dependencies': [
'error',
Expand All @@ -19,5 +25,24 @@ module.exports = {
'@typescript-eslint/no-import-type-side-effects': 'error',
},
},
{
files: ['bin/*.js', '*.config.*'],
rules: {
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
},
},
{
files: ['src/**/*.tsx', 'src/**/*.ts', 'lib/**/*.tsx', 'lib/**/*.ts'],
rules: {
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
'@typescript-eslint/no-import-type-side-effects': 'error',
},
},
],
};
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@ dist
build
coverage

bin/*.js
bin/*.js
vite.config.js
*.tsbuildinfo
13 changes: 13 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Capsize SCSS

MIT: https://github.com/seek-oss/capsize/blob/9d10414aa0597bb24101408b0b24c22d2ec58969/LICENSE

@AndrewLeedham via https://github.com/seek-oss/capsize

https://github.com/seek-oss/capsize/issues/24#issuecomment-679998850

## open-props-scss

MIT: https://github.com/mayank99/open-props-scss/blob/ddd1275c3ee55a31a7759ddd5b88703174987d56/LICENSE

https://github.com/mayank99/open-props-scss
84 changes: 46 additions & 38 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,73 +1,81 @@

SRCS = $(wildcard lib/**)

.DEFAULT_GOAL := all

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

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

bin/token.js: meta-bundle tsconfig-node.json
pnpm exec tsc -p tsconfig-node.json
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 > $@

build/tokens.scss: bin/token.js meta-bundle
tsconfig.json: tsconfig-vite.src.json
pnpm exec tsc -p tsconfig-vite.src.json --showConfig 1> $@

src/rds.module.scss: bin/token.js build
node $< > $@
pnpm exec tsc -b tsconfig-node.json --clean
prettier --write $@

build: $(SRCS) node_modules vite.config.ts vite-env.d.ts
NODE_ENV=production pnpm exec vite build
touch $@

.PHONY: build-watch
build-watch: $(SRCS) node_modules vite.config.ts vite-env.d.ts
pnpm exec vite build --mode=development --clearScreen false
touch -c build

.PHONY: meta-bundle
meta-bundle: $(SRCS) node_modules vite.config.ts types
NODE_ENV=production pnpm vite build
touch build
.PHONY: dev-server
dev-server: node_modules vite.config.ts
pnpm exec vite dev --mode=development --clearScreen false

build: meta-bundle build/tokens.scss
PHONY: typecheck
typecheck: node_modules tsconfig.json
pnpm exec tsc -w --preserveWatchOutput

.PHONY: dev
dev:
NODE_ENV=development $(MAKE) -j 3 build-watch dev-server typecheck

.PHONY: debug
debug:
DEBUG_BUILD=1 $(MAKE)

.PHONY: types
types: node_modules tsconfig.json
pnpm exec tsc --emitDeclarationOnly

.PHONY: types-watch
types-watch: node_modules tsconfig.json
pnpm exec tsc --emitDeclarationOnly -w
dist: node_modules tsconfig.json
pnpm exec tsc

.PHONY: clean
clean: node_modules tsconfig.json
pnpm exec tsc -b --clean
pnpm exec tsc -b tsconfig-node.json --clean
pnpm exec tsc -b tsconfig.node.json --clean
rm -rf build dist

.PHONY: clean
distclean: clean
rm -rf node_modules

.PHONY: test
test: node_modules tsconfig.json vite.config.ts
$(MAKE) lint
pnpm exec tsc --noEmit
test: lint dist build
DEBUG_BUILD=true pnpm vitest run
$(MAKE) build
pnpm bundlesize

.PHONY: dev
dev:
$(MAKE) -j 2 types-watch dev-server

.PHONY: dev-server
dev-server: node_modules vite.config.ts
pnpm vite dev
DEBUG_BUILD=true pnpm vitest run
pnpm exec bundlesize

.PHONY: lint
lint: node_modules
pnpm eslint .
pnpm prettier --check .
pnpm exec eslint .
pnpm exec prettier --check .

.PHONY: pretty
pretty: node_modules
pnpm eslint --fix .
pnpm prettier --write .

tsconfig.json: tsconfig-vite.src.json
pnpm exec tsc -p tsconfig-vite.src.json --showConfig > $@
pnpm exec eslint --fix .
pnpm exec prettier --write .
196 changes: 170 additions & 26 deletions bin/token.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,182 @@
// eslint-disable-next-line import/no-extraneous-dependencies
/* 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 { genericVars } from '#vars';
import { leafNodeMapper, type PathStr, type VarFn, vargEx } from './utils.js';
import {
badgeVars,
badgeVarsMapFnPrefix,
buttonVars,
buttonVarsMapFnPrefix,
formControlTokens,
formControlVarsMapFnPrefix,
globalTokens,
globalVars,
globalVarsMapFnPrefix,
panelTokens,
panelVars,
panelVarsMapFnPrefix,
propsTokens,
propsVars,
propsVarsMapFnPrefix,
} from '#vars';

function isPlainObject(obj: unknown): obj is Record<string, unknown> {
return (
typeof obj === 'object' &&
obj !== null &&
Object.getPrototypeOf(obj) === Object.prototype
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],
);
}

function extractCustomPropertyNames(
obj: Record<string, unknown>,
prefix = '',
): [string, string][] {
return Object.entries(obj).flatMap(([key, value]): [string, string][] => {
const k = prefix ? `${prefix}${key}` : key;
/**
* tokens
*/
const knownTokens = new Map<VarFn, { value: string }>();

if (isPlainObject(value)) {
return extractCustomPropertyNames(value, `${k}-`);
}
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],
);
}

// 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);

const v = String(value);
return [[k, v.match(/^var\((--\w+)\)$/)?.[1] || v]];
});
if (token) {
knownTokens.set(varName, token);
}
}
}
}

function writeScss([v, ident]: [string, string]) {
process.stdout.write(`$${camelCase(v)}: ${ident};`);
process.stdout.write('\n');
const output = new PassThrough();
if (outputPath) {
output.pipe(createWriteStream(outputPath));
} else {
output.pipe(process.stdout);
}

const propNames = extractCustomPropertyNames({
...genericVars,
});
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`);

/**
* 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;
}

propNames.forEach(writeScss);
output.end();
Loading

0 comments on commit 4a72f7f

Please sign in to comment.