Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/runtime-p1-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'typestyles': minor
---

Move `color` helpers to the `typestyles/color` subpath entry to shrink the main runtime bundle (~14.9 KB gzip). CI enforces a gzip budget on `dist/index.js`.

**Breaking:** `import { color } from 'typestyles'` is removed — use `import { color } from 'typestyles/color'` (or named imports from that subpath).
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,8 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build typestyles and check bundle budget
run: pnpm --filter typestyles build

- name: Run tests
run: pnpm run test
2 changes: 1 addition & 1 deletion IMPROVEMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Bugs and credibility issues that lose evaluations on contact. Do these first.

## P1 — Competitive table stakes

- [ ] **P1.5 — Cut the runtime for the common path** (PR: )
- [x] **P1.5 — Cut the runtime for the common path** (PR: #90)
- Main entry is ~15 KB gzip; `color` namespace and helpers are eagerly bundled.
Move `color` to a `typestyles/color` subpath entry.
- Add a size budget check to CI and publish the measured number in the README
Expand Down
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,17 +424,20 @@ color.primary; // "var(--color-primary)"

High-level tradeoffs (details and nuance: [docs — framework comparison](./docs/content/docs/framework-comparison.md)):

| Feature | TypeStyles | StyleX | Emotion / styled-components | Panda CSS | vanilla-extract | CSS Modules | Tailwind |
| -------------------------- | ---------------------------- | ---------------- | --------------------------- | ------------ | ---------------- | ----------- | --------------- |
| Readable class names | Yes | No | No | Utilities | Hashed / modules | Scoped | Utility classes |
| Type-safe | Yes | Yes | Partial | Yes | Yes | Partial | No |
| No build step required | Yes | No | Yes | No | No | No | No |
| Works with plain CSS | Yes | Difficult | Limited | Moderate | Good | Excellent | Yes |
| Incremental adoption | Yes | Difficult | Yes | Moderate | Moderate | Yes | Yes |
| CSS custom property tokens | First-class | Limited | Manual | First-class | Manual | Manual | Via config |
| Zero-runtime path | Yes (opt-in build plugins) | Compiler default | Varies | Build output | Always | Always | Build output |
| SSR support | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Runtime overhead | Minimal (off when extracted) | None | Moderate | Low–none | None | None | None |
| Feature | TypeStyles | StyleX | Emotion / styled-components | Panda CSS | vanilla-extract | CSS Modules | Tailwind |
| -------------------------- | ---------------------------- | ------------------ | --------------------------- | ------------ | ---------------- | ----------- | --------------- |
| Readable class names | Yes | No | No | Utilities | Hashed / modules | Scoped | Utility classes |
| Type-safe | Yes | Yes | Partial | Yes | Yes | Partial | No |
| No build step required | Yes | No | Yes | No | No | No | No |
| Works with plain CSS | Yes | Difficult | Limited | Moderate | Good | Excellent | Yes |
| Incremental adoption | Yes | Difficult | Yes | Moderate | Moderate | Yes | Yes |
| CSS custom property tokens | First-class | Limited | Manual | First-class | Manual | Manual | Via config |
| Zero-runtime path | Yes (opt-in build plugins) | Compiler default | Varies | Build output | Always | Always | Build output |
| SSR support | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Runtime overhead | Minimal (off when extracted) | None | Moderate | Low–none | None | None | None |
| Main entry size (gzip) | ~14.9 KB | N/A (compile-time) | ~12 KB+ (varies) | Build output | N/A | N/A | N/A |

Color helpers (`rgb`, `oklch`, `mix`, …) live on `typestyles/color` so the common import path stays smaller. CI enforces a gzip budget on `dist/index.js`.

## Installation

Expand Down
5 changes: 3 additions & 2 deletions docs/content/docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ Keyframe animation API.

- `keyframes.create(name, stops)`: Creates @keyframes animation

### `color`
### `color` (`typestyles/color`)

Type-safe CSS color function helpers, on a **separate subpath** so the main `typestyles` entry stays lean. Import `color` (namespace) or named functions from `typestyles/color`.

Type-safe CSS color function helpers.
Each function returns a plain CSS color string — no runtime color math.
Composes naturally with token references.

Expand Down
5 changes: 3 additions & 2 deletions docs/content/docs/color.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The `color` API provides type-safe helpers for modern CSS color functions. These
Create `rgb()` colors with space-separated syntax:

```ts
import { color } from 'typestyles';
import { color } from 'typestyles/color';

color.rgb(0, 102, 255); // "rgb(0 102 255)"
color.rgb(0, 102, 255, 0.5); // "rgb(0 102 255 / 0.5)"
Expand Down Expand Up @@ -133,7 +133,8 @@ Note: This requires the browser to support `light-dark()` and the element to hav
All color functions accept token references since tokens are just CSS `var()` strings:

```ts
import { styles, tokens, color as colorFn } from 'typestyles';
import { styles, tokens } from 'typestyles';
import { color as colorFn } from 'typestyles/color';

const themeColor = tokens.create('color', {
primary: '#0066ff',
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/ai-glow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/amber.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/classic-system.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/default.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/forest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/neo-brutalist-shadows.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { type DesignShadowValues } from '../tokens/primitive';
import { colorTokens } from '../tokens';

Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/new-wave.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/rose.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/themes/windows-95.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';
import { createDesignTheme } from '../create-theme';
import { designPrimitiveTokens as p } from '../tokens';
import {
Expand Down
2 changes: 1 addition & 1 deletion examples/design-system/src/tokens/palette.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { color } from 'typestyles';
import { color } from 'typestyles/color';

export const PALETTE_STEPS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] as const;
export type PaletteStep = (typeof PALETTE_STEPS)[number];
Expand Down
2 changes: 1 addition & 1 deletion packages/typestyles/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import tseslint from 'typescript-eslint';
import { typestylesConfig } from '../../eslint.base.js';

export default tseslint.config(...typestylesConfig, {
ignores: ['**/dist/**'],
ignores: ['**/dist/**', 'scripts/**'],
});
13 changes: 12 additions & 1 deletion packages/typestyles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,24 @@
"types": "./dist/globals.d.cts",
"default": "./dist/globals.cjs"
}
},
"./color": {
"import": {
"types": "./dist/color.d.ts",
"default": "./dist/color.js"
},
"require": {
"types": "./dist/color.d.cts",
"default": "./dist/color.cjs"
}
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"build": "tsup && node scripts/check-bundle-size.mjs",
"check:bundle-size": "node scripts/check-bundle-size.mjs",
"dev": "tsup --watch",
"test": "vitest run",
"test:watch": "vitest",
Expand Down
39 changes: 39 additions & 0 deletions packages/typestyles/scripts/check-bundle-size.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from 'node:fs';
import zlib from 'node:zlib';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const distDir = path.join(__dirname, '../dist');
const indexPath = path.join(distDir, 'index.js');
const colorPath = path.join(distDir, 'color.js');

/** Gzip budget for the main runtime entry (`dist/index.js`). */
const INDEX_GZIP_BUDGET = 15_500;

function gzipSize(filePath) {
return zlib.gzipSync(fs.readFileSync(filePath)).length;
}

function fail(message) {
console.error(`[typestyles:bundle-size] ${message}`);
process.exit(1);
}

if (!fs.existsSync(indexPath)) {
fail(`Missing ${indexPath} — run pnpm build first.`);
}

const indexGzip = gzipSize(indexPath);
console.log(
`[typestyles:bundle-size] index.js gzip: ${indexGzip} bytes (budget ${INDEX_GZIP_BUDGET})`,
);

if (indexGzip > INDEX_GZIP_BUDGET) {
fail(`Main entry exceeds gzip budget by ${indexGzip - INDEX_GZIP_BUDGET} bytes.`);
}

if (fs.existsSync(colorPath)) {
const colorGzip = gzipSize(colorPath);
console.log(`[typestyles:bundle-size] color.js gzip: ${colorGzip} bytes (separate subpath)`);
}
8 changes: 8 additions & 0 deletions packages/typestyles/src/color-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export * from './color';
import * as colorFns from './color';

/**
* Type-safe CSS color function helpers (`rgb`, `oklch`, `mix`, …).
* Import from `typestyles/color` so the main entry stays lean.
*/
export const color = colorFns;
20 changes: 0 additions & 20 deletions packages/typestyles/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { createTypeStyles } from './create-type-styles';
import { createGlobal } from './create-global';
import { createTheme, createDarkMode, when, colorMode } from './theme';
import { createKeyframes } from './keyframes';
import * as colorFns from './color';
import {
getRegisteredCss,
subscribeRegisteredCss,
Expand Down Expand Up @@ -121,8 +120,6 @@ export { flattenTokenEntries } from './types';

export { createVar, assignVars };

export type { ColorMixSpace } from './color';

export { createTheme, createDarkMode, when, colorMode };

export type { ThemeEmitLayerContext } from './theme';
Expand Down Expand Up @@ -203,23 +200,6 @@ export const keyframes = {
create: createKeyframes,
} as const;

/**
* Type-safe CSS color function helpers.
*
* Each function returns a plain CSS color string — no runtime color math.
* Composes naturally with token references.
*
* @example
* ```ts
* color.rgb(0, 102, 255) // "rgb(0 102 255)"
* color.oklch(0.7, 0.15, 250) // "oklch(0.7 0.15 250)"
* color.mix(theme.primary, 'white', 20) // "color-mix(in srgb, var(--theme-primary) 20%, white)"
* color.alpha(theme.primary, 0.5) // "color-mix(in srgb, var(--theme-primary) 50%, transparent)"
* color.lightDark('#111', '#eee') // "light-dark(#111, #eee)"
* ```
*/
export const color = colorFns;

/**
* Return all registered CSS as a string (for SSR).
*/
Expand Down
1 change: 1 addition & 0 deletions packages/typestyles/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defineConfig } from 'tsup';
export default defineConfig({
entry: {
index: 'src/index.ts',
color: 'src/color-entry.ts',
globals: 'src/globals.ts',
server: 'src/server.ts',
hmr: 'src/hmr.ts',
Expand Down
Loading