Zero runtime type-safe CSS in the same file as components

Overview

macaron

comptime-css is now called macaron!

macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind

  • Powered by vanilla-extract
  • Allows defining styles in the same file as components
  • Zero runtime builds
  • Supports both styled-components API and plain styling api that returns classes.
  • Stitches-like variants
  • First class typescript support
  • Out of box support for solidjs
  • Supports esbuild and vite (with hmr)

Example

Styled API

import { styled } from '@macaron-css/solid';

const StyledButton = styled('button', {
  base: {
    borderRadius: 6,
  },
  variants: {
    color: {
      neutral: { background: 'whitesmoke' },
      brand: { background: 'blueviolet' },
      accent: { background: 'slateblue' },
    },
    size: {
      small: { padding: 12 },
      medium: { padding: 16 },
      large: { padding: 24 },
    },
    rounded: {
      true: { borderRadius: 999 },
    },
  },
  compoundVariants: [
    {
      variants: {
        color: 'neutral',
        size: 'large',
      },
      style: {
        background: 'ghostwhite',
      },
    },
  ],

  defaultVariants: {
    color: 'accent',
    size: 'medium',
  },
});

// Use it like a regular solidjs component
function App() {
  return (
    <StyledButton color="accent" size="small" rounded={true}>
      Click me!
    </StyledButton>
  );
}

Styling API

The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience.

Check out vanilla-extract docs

How it works

The esbuild/vite plugin loads every ts and js file and runs @macaron-css/babel plugin on it.

The babel plugin looks for variable declarations in your code and checks if they are of macarons's API like styled or recipe etc.

It finds the styles defined in the currently processed file, looks for all their referenced identifiers, gets all their declarations and repeats this cycle until no more are found and constructs a new babel program of these statements.

It then converts that AST to string and generates a virtual file from it and returns the contents of this virtual file along with the metadata.

It also generates unique filename for these css files based on murmurhash.

It also adds imports for those extracted styles in the current file.

It tags all these extracted files in a namespace and pre evaluates them at build time using vanilla-extract

For example, the plugin will transpile:

// main.ts
import { styled } from '@macaron-css/solid';

const StyledButton = styled('button', {
  base: {
    borderRadius: 6,
  },
  variants: {
    color: {
      neutral: { background: 'whitesmoke' },
      brand: { background: 'blueviolet' },
      accent: { background: 'slateblue' },
    },
  },
  defaultVariants: {
    color: 'accent',
  },
});

To This:

// main.ts
import { _StyledButton } from 'extracted_1747103777.css.ts';
import { $$styled as _$$styled } from '@macaron-css/solid/runtime';

const StyledButton = _$$styled('button', _StyledButton);

and

// extracted_1747103777.css.ts
import { recipe as _recipe } from '@macaron-css/core';

const _StyledButton = _recipe({
  base: {
    borderRadius: 6,
  },
  variants: {
    color: {
      neutral: { background: 'whitesmoke' },
      brand: { background: 'blueviolet' },
      accent: { background: 'slateblue' },
    },
  },
  defaultVariants: {
    color: 'accent',
  },
});

module.exports = { _StyledButton };

Which gets further compiled to:

// extracted_1747103777.css.ts
import 'extracted_1747103777.vanilla.css';
import { createRuntimeFn } from '@macaron-css/core/create-runtime-fn';

const _StyledButton = createRuntimeFn({
  defaultClassName: 'extracted_1747103777__1g7h5za0',
  variantClassNames: {
    color: {
      neutral: 'extracted_1747103777_color_neutral__1g7h5za1',
      brand: 'extracted_1747103777_color_brand__1g7h5za2',
      accent: 'extracted_1747103777_color_accent__1g7h5za3',
    },
  },
  defaultVariants: {
    color: 'accent',
  },
});

module.exports = { _StyledButton };

The extracted css will look something like this:

/* extracted_1747103777.vanilla.css */
.extracted_1747103777__1g7h5za0 {
  border-radius: 6px;
}
.extracted_1747103777_color_neutral__1g7h5za1 {
  background: whitesmoke;
}
.extracted_1747103777_color_brand__1g7h5za2 {
  background: blueviolet;
}
.extracted_1747103777_color_accent__1g7h5za3 {
  background: slateblue;
}
Comments
  • Slow Hot Reload Times In Vite

    Slow Hot Reload Times In Vite

    Hey, thanks for making the library.

    I've been using Macaron plugin with Vite in a medium size TS Monorepo project with a React Project. After introducing the plugin library to Vite project I've noticed my hot reload times have increased substantially to 30-60 secs.

    These hot reload times are bigger than when I used library such as Chakra UI with Vite in the past. To be truthful, I'm no expert in debugging hot reload speed problem but from what I can see in my network tab, the lag in web-page rendering is when the macaron plugin transpiles code.

    Screenshot 2022-11-15 at 22 54 45

    Packages

    "vite": "^3.0.9"
    "@vitejs/plugin-react": "1.3.2"
    "@macaron-css/core": "^0.1.10"
    "@macaron-css/react": "^0.1.10"
    "@macaron-css/vite": "^0.1.12"
    

    My current Vite config

    import { defineConfig } from 'vite';
    import { macaronVitePlugin } from '@macaron-css/vite';
    import react from '@vitejs/plugin-react';
    
    export default defineConfig({
      plugins: [macaronVitePlugin(), react()],
      server: {
        port: 3000
      },
      build: {
        target: 'esnext'
      },
      optimizeDeps: {
        esbuildOptions: { preserveSymlinks: true }
      }
    });
    

    Can you offer any tips or suggestions to get the hot reload times down?

    opened by sam-frampton 3
  • Support css native kebab-case?

    Support css native kebab-case?

    I know this library is using VE under the hood. But solid gives opportunity to define style properties as they are named in CSS, so, and as I found out it does work, but Typescript shows error: image

    opened by MrFoxPro 2
  • Esbuild example with .html

    Esbuild example with .html

    Is there a full esbuild example? The one linked in the documentation here emits an index.js and an index.css files. What's the suggested way to use these files in an html page? Should I just add a <link src="index.css" rel="stylesheet" /> tag in the html? Would this always be single a compiled stylesheet if I have several styled components in different files?

    opened by posobin 1
  • Shipping native Macaron functions along with design system

    Shipping native Macaron functions along with design system

    import * as macaronCore from '@macaron-css/core'
    export const css = macaronCore.style;
    

    Hi, I am trying to include/bundle some useful Macaron functions inside of a design system package. Specifically things like style functions in order to give users a "css" prop to use without having to install macaron/core or macaron/react itself. Also things like createTheme to make custom themes. This pattern works with other libraries, including VE. Currently when importing a function from my package in the consuming app the below error is thrown. This doesn't happen when importing any functions directly form Macaron or when importing {style as css} from @macaron-css/core in the app. This also doesn't work when importing directly to another file within in the same directory, before compiling.

    Uncaught Error: Styles were unable to be assigned to a file. This is generally caused by one of the following:

    • You may have created styles outside of a '.css.ts' context
    • You may have incorrect configuration

    Any suggestions on how to resolve this? thanks!

    opened by zwagnr 1
  • vite lib mode/vite-dts build errors

    vite lib mode/vite-dts build errors

    I am currently in the processes of porting over a design system from Vanilla Extract to Macaron.

    I was compiling with Vite in Lib mode using vite-dts in Vanilla Extract and everything was working fine. When using Macaron, the builds are failing.

    viteissue

    Here's the vite.config

    import { defineConfig } from 'vite';
    import dts from 'vite-plugin-dts';
    import { peerDependencies } from './package.json';
    import { macaronVitePlugin } from '@macaron-css/vite';
    
    const externals = [...Object.keys(peerDependencies)];
    
    export default defineConfig({
      build: {
        lib: {
          entry: 'src/index.ts',
          fileName: 'index',
          formats: ['cjs', 'es'],
        },
        rollupOptions: {
          external: externals,
        },
      },
      plugins: [
        macaronVitePlugin(),
        dts({
          beforeWriteFile: (filePath, content) => ({
            content,
            filePath: filePath.replace('src', ''),
          }),
          compilerOptions: {
            baseUrl: './src/',
            emitDeclarationOnly: true,
            noEmit: false,
          },
          exclude: ['src/**/*.stories.tsx'],
          outputDir: 'dist/types',
        }),
      ],
    });
    

    FWIW, I was able to get builds working by removing vite-dts from the vite.config, building, and then running a separate build step with tsc and the following flags "tsc --emitDeclarationOnly --declaration --declarationDir dist/types".

    opened by zwagnr 1
  • order of bindings and styled nodes

    order of bindings and styled nodes

    The current published version of macaron pushes all the bindings first and the styled nodes later, this means that all the nodes that reference a style declaration in the beginning of the new virtual file and the styled nodes are later. This works fine if only the variables are being used inside declaration but becomes an issue when the declaration is used inside a another variable since babel considers both to be a "binding"

    Consider this:

    const color = 'red';
    const red = style({ color });
    const entireClass = `pt-2 ${red}`;
    

    the expected compiled output would be

    const color = 'red';
    const red = 'red_HASH';
    const entireClass = `pt-2 ${red}`;
    

    but it is actually

    const color = 'red';
    const entireClass = `pt-2 ${red}`;
    
    const red = 'red_HASH';
    

    This would produce an error and shouldn't be that hard to fix. I think the fix would require checking the source location of binding and declaration and based on that decide which one should be pushed first

    opened by Mokshit06 1
  • Variable naming scope conflict

    Variable naming scope conflict

    macaron extracts every style declaration out of the current file into a separate virtual file and adds imports for those declarations in the source file. It works like this:

    // main.ts
    const red = style({ color: 'red' })
    

    and this gets compiled into

    // main.ts
    import { red } from 'extracted_HASH.css.ts'
    
    // extracted_HASH.css.ts
    const red = style({ color: 'red' });
    module.exports = { red }
    

    The issue that this currently has is that it doesn't check the scope of these variables and just moves them. This means that if there is a variable red in the top scope and then another variable red in a function scope, it wouldn't rename them in the virtual file to prevent conflict and consider them to be the same style declaration

    opened by Mokshit06 1
  • Styled Component type definitions

    Styled Component type definitions

    First of all, thank you so much for making this library, great stuff!

    Is there currently a way to get Styled Component variant type definitions? Not sure if I am doing something wrong. Take a simple button using the traditional Vanilla Extract Recipe.

    const ButtonWithRecipe = recipe({
      base: {
        backgroundColor: 'gainsboro',
        borderRadius: '9999px',
      },
      variants: {
        sm: { padding: '12px' },
        md: { padding: '16px' },
      },
    });
    
    //When using the RecipeVariants import the correct type is returned
    export type ButtonRecipeVariants = RecipeVariants<typeof ButtonWithRecipe>;
    

    When recreating that same thing as a Styled Component I am having trouble getting the correct type definitions.

    const StyledButton = styled('button', {
        base: {
          backgroundColor: 'gainsboro',
          borderRadius: '9999px',
          fontSize: '13px',
          border: '0',
        },
        variants: {
          sm: { padding: '12px' },
          md: { padding: '16px' },
        },
      });
    
    //I am able to get type definitions for the _entire_ component including DOM props with the following, 
    export type WithDOMProps = React.ComponentProps<typeof StyledButton>;
    
    //With the provided StyleVariants import the style always returns "type StyledButtonVariants = unknown"
    export type StyledButtonVariants = StyleVariants<typeof ButtonWithRecipe>;
    
    opened by zwagnr 0
  • Composing Components Type Error

    Composing Components Type Error "... is not assignable to parameter of type 'string'"

    In Stiches you can compose components from other elements, i.e. where MyCoolElement is another component.

    Currently in Macaron it will throw a type error "... is not assignable to parameter of type 'string'"

    The functionality still works and I have just been using //@ts-expect-error but thats not ideal.

    const Button = styled(MyCoolElement, {
      base: {
        backgroundColor: 'gainsboro',
      },
    });
    
    opened by zwagnr 5
  • Astro support?

    Astro support?

    I'm trying to run macaron inside Astro project within Solid component. So I placed it inside Astro's vite config, plugins array. but getting an error:

    [vite] Internal server error: ENOENT: no such file or directory, open 'astro:scripts/before-hydration.js'
      Plugin: macaron-css-vite
      File: astro:scripts/before-hydration.js
    
    opened by MrFoxPro 2
Releases(v1.0.1)
  • v1.0.1(Dec 17, 2022)

    Version 1.0.1 - 17/12/2022, 18:12

    Changes

    Fix

    • adapter: styled component type def (fix #13) (e678248) by Mokshit06

    Chore

    • remove .npmrc (3055097) by Mokshit06
    • fix publish script (55ab99a) by Mokshit06
    • add .npmrc (a1f4e0e) by Mokshit06
    • remove release-it and setup publish script (1186faa) by Mokshit06
    • add acknowledgements (64ca10a) by Mokshit06
    • update readme banner (8db51d4) by Mokshit06
    • update readme banner (4395c3c) by Mokshit06
    • fix typo in installation docs (6acd3b0) by Mokshit06
    • docs: bundler setup more readable - installation page (#11) (c96556c) by Kavin Valli

    Docs

    • webpack plugin (coming soon) (318ec3e) by Mokshit06
    • add macro api docs (456c9a3) by Mokshit06
    • add stackblitz examples (bc9a3cb) by Mokshit06

    Examples

    • remove document.write from vanilla example (069a110) by Mokshit06

    Packages

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Dec 5, 2022)

Owner
Mokshit Jain
Tech Enthusiast | Google Code-in '19 Finalist.
Mokshit Jain
A zero-dependency, buildless, terse, and type-safe way to write HTML in JavaScript.

hdot A sensible way to write HTML in JavaScript. Type-safe. Helps you follow the HTML spec. Terse. Almost character for character with plain HTML. Bui

Will Martin 31 Oct 24, 2022
100% type-safe query builder for node-postgres :: Generated types, call any function, tree-shakable, implicit type casts, and more

⚠️ This library is currently in alpha. Contributors wanted! tusken Postgres client from a galaxy far, far away. your database is the source-of-truth f

alloc 54 Dec 29, 2022
Generate deterministic fake values: The same input will always generate the same fake-output.

import { copycat } from '@snaplet/copycat' copycat.email('foo') // => '[email protected]' copycat.email('bar') // => 'Thurman.Schowalter668@

Snaplet 201 Dec 30, 2022
A script that combines a folder of SVG files into a single sprites file and generates type definitions for safe usage.

remix-sprites-example A script that combines a folder of .svg files into a single sprites.svg file and type definitions for safe usage. Technical Over

Nicolas Kleiderer 19 Nov 9, 2022
👩‍🎤 Headless, type-safe, UI components for the next generation Web3.Storage APIs.

Headless, type-safe, UI components for the next generation Web3.Storage APIs. Documentation beta.ui.web3.storage Examples React Sign up / Sign in Sing

Web3 Storage 47 Dec 22, 2022
Simple, lightweight at-runtime type checking functions, with full TypeScript support

pheno Simple, lightweight at-runtime type checking functions, with full TypeScript support Features Full TypeScript integration: TypeScript understand

Lily Scott 127 Sep 5, 2022
Runtime type checking in pure javascript.

Install npm install function-schema Usage import { signature } from 'function-schema'; const myFunction = signature(...ParamTypeChecks)(ReturnValueCh

Jeysson Guevara 3 May 30, 2022
TypeScript type definitions for Bun's JavaScript runtime APIs

Bun TypeScript type definitions These are the type definitions for Bun's JavaScript runtime APIs. Installation Install the bun-types npm package: # ya

Oven 73 Dec 16, 2022
✨ A cli can automatically export files of the same type

auto-export A cli can automatically export files Why When you want to export many files of the same type in one folder, you may cost a lot of time to

Frozen FIsh 22 Aug 11, 2022
Bun-Bakery is a web framework for Bun. It uses a file based router in style like svelte-kit. No need to define routes during runtime.

Bun Bakery Bun-Bakery is a web framework for Bun. It uses a file based router in style like svelte-kit. No need to define routes during runtime. Quick

Dennis Dudek 44 Dec 6, 2022
Build type-safe web apps with PureScript.

PUX Build type-safe web applications with PureScript. Documentation | Examples | Chat Pux is a PureScript library for building web applications. Inter

Alex Mingoia 567 Jun 18, 2022
Type Safe Object Notation & Validation

tson Type Safe Object Notation & Validation ?? Work in Progress, not ready for production... Features ?? Functional ?? Immutable ✅ Well tested Why? Af

null 9 Aug 10, 2022
Cloudy is a set of constructs for the AWS Cloud Development Kit that aim to improve the DX by providing a faster and type-safe code environment.

cloudy-ts These packages aren't yet published on npm. This is still highly experimental. Need to figure out a few things before releasing the first ve

Cristian Pallarés 5 Nov 3, 2022
A next-gen framework for type-safe command-line applications

Zors ?? Next-gen framework for building modern, type-safe command-line applications. ?? Tiny (zero dependencies) ?? Runtime agonistic (supports both D

Sidharth Rathi 13 Dec 1, 2022
A compiled-away, type-safe, readable RegExp alternative

?? magic-regexp A compiled-away, type-safe, readable RegExp alternative ✨ Changelog ?? Documentation ▶️ Online playground Features Runtime is zero-dep

Daniel Roe 1.5k Jan 8, 2023
A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects.

?? typesafe-i18n A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects. Advantages ?? lightwe

Hofer Ivan 1.3k Jan 4, 2023
A functional, immutable, type safe and simple dependency injection library inspired by angular.

func-di English | 简体中文 A functional, immutable, type safe and simple dependency injection library inspired by Angular. Why func-di Installation Usage

null 24 Dec 11, 2022
Type safe library for interacting with Mindbody's Public API (v6) and Webhooks

Mindbody API Type safe library for interacting with Mindbody's Public API (v6) and Webhooks ⚠️ Read before installing This library is typed according

SplitPass 4 Dec 9, 2022
A fully type-safe and lightweight way of using exceptions instead of throwing errors

??️ exceptionally A fully type-safe and lightweight way of using exceptions instead of throwing errors ?? fully typesafe ?? lightweight (209 bytes) ??

Hofer Ivan 16 Jan 4, 2023