├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── CNAME ├── LICENSE ├── README.md ├── banner.png ├── examples ├── react │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.tsx │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── solid-start │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── entry-client.tsx │ │ ├── entry-server.tsx │ │ ├── root.tsx │ │ └── routes │ │ │ ├── [...404].tsx │ │ │ └── index.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── solid │ ├── .babelrc │ ├── build.js │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.tsx │ │ └── index.tsx │ └── tsconfig.json ├── vanilla │ ├── build.js │ ├── package.json │ └── src │ │ └── index.ts └── vite │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── src │ ├── main.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── packages ├── babel │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __snapshots__ │ │ │ └── index.test.ts.snap │ │ ├── global.d.ts │ │ ├── index.test.ts │ │ ├── index.ts │ │ ├── styled.ts │ │ ├── transforms │ │ │ ├── callExpression.ts │ │ │ ├── postprocess.ts │ │ │ └── preprocess.ts │ │ ├── types.ts │ │ └── utils.ts │ └── tsconfig.json ├── core │ ├── README.md │ ├── package.json │ ├── src │ │ ├── create-runtime-fn.ts │ │ ├── dynamic.ts │ │ ├── index.ts │ │ └── types.ts │ └── tsconfig.json ├── esbuild │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── integration │ ├── README.md │ ├── package.json │ └── src │ │ ├── babel.ts │ │ ├── compile.ts │ │ └── index.ts ├── qwik │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── runtime.ts │ └── tsconfig.json ├── react │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __snapshots__ │ │ │ └── runtime.test.ts.snap │ │ ├── index.ts │ │ ├── runtime.test.ts │ │ └── runtime.ts │ └── tsconfig.json ├── solid │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── runtime.test.ts │ │ └── runtime.ts │ └── tsconfig.json └── vite │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ └── tsconfig.json ├── scripts ├── build.ts ├── config.ts ├── publish.ts └── types.ts ├── site ├── .gitignore ├── CNAME ├── package-lock.json ├── package.json ├── public │ ├── macaron-inline.svg │ ├── macaron-name.svg │ ├── macaron-stacked.svg │ ├── macaron-symbol.svg │ └── share.jpg ├── renderer │ ├── Link.tsx │ ├── PageShell.tsx │ ├── _default.page.client.tsx │ ├── _default.page.server.tsx │ ├── _error.page.tsx │ ├── types.ts │ └── usePageContext.tsx ├── server │ └── index.ts ├── src │ ├── code-examples │ │ └── home.jsx │ ├── components │ │ ├── button.tsx │ │ ├── code-block.tsx │ │ └── pre.tsx │ ├── pages │ │ ├── docs │ │ │ ├── docs-layout.tsx │ │ │ ├── dynamic-styling.page.mdx │ │ │ ├── examples │ │ │ │ ├── esbuild.page.mdx │ │ │ │ ├── meta.json │ │ │ │ ├── react.page.mdx │ │ │ │ ├── solid-start.page.mdx │ │ │ │ ├── solid.page.mdx │ │ │ │ ├── stackblitz.tsx │ │ │ │ ├── vite.page.mdx │ │ │ │ └── webpack.page.mdx │ │ │ ├── installation.page.mdx │ │ │ ├── macro.page.mdx │ │ │ ├── styling.page.mdx │ │ │ ├── theming.page.mdx │ │ │ └── working.page.mdx │ │ ├── index │ │ │ └── index.page.tsx │ │ └── playground │ │ │ ├── editor.tsx │ │ │ ├── index.page.tsx │ │ │ ├── setup.ts │ │ │ └── theme.json │ └── theme.ts ├── tsconfig.json └── vite.config.ts └── tsconfig.json /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Next.js site to GitHub Pages 2 | # 3 | # To get started with Next.js see: https://nextjs.org/docs/getting-started 4 | # 5 | name: Deploy Next.js site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["main"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow one concurrent deployment 22 | concurrency: 23 | group: "pages" 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Detect package manager 34 | id: detect-package-manager 35 | run: | 36 | if [ -f "${{ github.workspace }}/yarn.lock" ]; then 37 | echo "manager=yarn" >> $GITHUB_OUTPUT 38 | echo "command=install" >> $GITHUB_OUTPUT 39 | echo "runner=yarn" >> $GITHUB_OUTPUT 40 | exit 0 41 | elif [ -f "${{ github.workspace }}/package.json" ]; then 42 | echo "manager=npm" >> $GITHUB_OUTPUT 43 | echo "command=ci" >> $GITHUB_OUTPUT 44 | echo "runner=npx --no-install" >> $GITHUB_OUTPUT 45 | exit 0 46 | else 47 | echo "Unable to determine packager manager" 48 | exit 1 49 | fi 50 | - name: Setup Node 51 | uses: actions/setup-node@v3 52 | with: 53 | node-version: "16" 54 | cache: ${{ steps.detect-package-manager.outputs.manager }} 55 | - name: Setup Pages 56 | uses: actions/configure-pages@v2 57 | - name: Install dependencies 58 | working-directory: ./site 59 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} 60 | - name: Build with Next.js 61 | working-directory: ./site 62 | run: ${{ steps.detect-package-manager.outputs.runner }} vite build 63 | - name: Upload artifact 64 | uses: actions/upload-pages-artifact@v1 65 | with: 66 | path: ./site/dist/client 67 | 68 | # Deployment job 69 | deploy: 70 | environment: 71 | name: github-pages 72 | url: ${{ steps.deployment.outputs.page_url }} 73 | runs-on: ubuntu-latest 74 | needs: build 75 | steps: 76 | - name: Deploy to GitHub Pages 77 | id: deployment 78 | uses: actions/deploy-pages@v1 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | build 4 | .DS_Store -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | macaron.js.org -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mokshit Jain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | CSS-in-JS with zero runtime, type safety and colocation 7 |

8 | 9 | ## Features 10 | 11 | 12 | 13 | - Zero runtime bundles with build time style extraction. 14 | - Colocation of styles and components. 15 | - First class Typescript support. 16 | - Supports both styled-components API and vanilla styling API. 17 | - [Stitches](https://stitches.dev/)-like variants API. 18 | - Out of box support for React, Solid and Qwik. 19 | - Integrations for [esbuild](https://esbuild.github.io/) and [Vite](https://vitejs.dev/). 20 | 21 | ## Documentation 22 | 23 | For full documentation, visit [https://macaron.js.org](https://macaron.js.org) 24 | 25 | ## Example 26 | 27 | ### Styled API 28 | 29 | ```jsx 30 | // or import it from @macaron-css/solid 31 | import { styled } from '@macaron-css/react'; 32 | 33 | const Button = styled('button', { 34 | base: { 35 | borderRadius: 6, 36 | }, 37 | variants: { 38 | color: { 39 | neutral: { background: 'whitesmoke' }, 40 | brand: { background: 'blueviolet' }, 41 | accent: { background: 'slateblue' }, 42 | }, 43 | size: { 44 | small: { padding: 12 }, 45 | medium: { padding: 16 }, 46 | large: { padding: 24 }, 47 | }, 48 | rounded: { 49 | true: { borderRadius: 999 }, 50 | }, 51 | }, 52 | compoundVariants: [ 53 | { 54 | variants: { 55 | color: 'neutral', 56 | size: 'large', 57 | }, 58 | style: { 59 | background: 'ghostwhite', 60 | }, 61 | }, 62 | ], 63 | 64 | defaultVariants: { 65 | color: 'accent', 66 | size: 'medium', 67 | }, 68 | }); 69 | 70 | // Use it like a regular solidjs/react component 71 | function App() { 72 | return ( 73 | 76 | ); 77 | } 78 | ``` 79 | 80 | ### Styling API 81 | 82 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, improving the DX. 83 | 84 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/api/style/) for the API reference. 85 | 86 | These functions can also be called directly inside expressions to provide a `css` prop-like API with zero-runtime cost:- 87 | 88 | ```js 89 | import { style } from '@macaron-css/core'; 90 | 91 | function App() { 92 | return ( 93 |
99 | 100 |
101 | ); 102 | } 103 | ``` 104 | 105 | ## Playground 106 | 107 | You can try about these above examples at https://macaron.js.org/playground and see how macaron figures out which expressions have to be extracted and what your code would look like after being transpiled 108 | 109 | ## How it works 110 | 111 | [https://macaron.js.org/docs/working/](https://macaron.js.org/docs/working/) 112 | 113 | ## Acknowledgements 114 | 115 | - [Vanilla Extract](https://vanilla-extract.style) 116 | Thanks to the vanilla-extract team for their work on VE, which macaron uses for evaluating files. 117 | 118 | - [Dax Raad](https://twitter.com/thdxr) 119 | Thanks to Dax for helping me with this and thoroughly testing it outß, helping me find numerous bugs and improving macaron. 120 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macaron-css/macaron/bc8c481269b1be644e2588f163a5fe68b19ddfd7/banner.png -------------------------------------------------------------------------------- /examples/react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macaron-react-example", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "@macaron-css/core": "1.5.2", 14 | "@macaron-css/vite": "1.5.1", 15 | "@macaron-css/react": "1.5.3" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.0.17", 19 | "@types/react-dom": "^18.0.6", 20 | "@vitejs/plugin-react": "^2.1.0", 21 | "typescript": "^4.6.4", 22 | "vite": "^3.1.8" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/react/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@macaron-css/react'; 2 | 3 | const Button = styled('button', { 4 | base: { 5 | borderRadius: 4, 6 | border: 0, 7 | margin: 12, 8 | cursor: 'pointer', 9 | color: 'white', 10 | textTransform: 'uppercase', 11 | fontSize: 12, 12 | }, 13 | variants: { 14 | color: { 15 | neutral: { background: 'whitesmoke', color: '#333' }, 16 | brand: { background: 'blueviolet' }, 17 | accent: { background: 'slateblue' }, 18 | }, 19 | size: { 20 | small: { padding: 12 }, 21 | medium: { padding: 16 }, 22 | large: { padding: 24 }, 23 | }, 24 | rounded: { 25 | true: { borderRadius: 999 }, 26 | }, 27 | }, 28 | 29 | // Applied when multiple variants are set at once 30 | compoundVariants: [ 31 | { 32 | variants: { 33 | color: 'neutral', 34 | size: 'large', 35 | }, 36 | style: { 37 | background: 'ghostwhite', 38 | }, 39 | }, 40 | ], 41 | 42 | defaultVariants: { 43 | color: 'accent', 44 | size: 'medium', 45 | }, 46 | }); 47 | 48 | function App() { 49 | return ( 50 |
51 | 58 |
59 | ); 60 | } 61 | 62 | export default App; 63 | -------------------------------------------------------------------------------- /examples/react/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import App from './App'; 3 | import { globalStyle } from '@macaron-css/core'; 4 | import { StrictMode } from 'react'; 5 | 6 | globalStyle('*', { 7 | padding: 0, 8 | margin: 0, 9 | boxSizing: 'border-box', 10 | }); 11 | 12 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /examples/react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { macaronVitePlugin } from "@macaron-css/vite"; 3 | import react from "@vitejs/plugin-react"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(), macaronVitePlugin()], 8 | esbuild: { 9 | jsxInject: `import React from 'react'`, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /examples/solid-start/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | .solid 4 | .output 5 | .vercel 6 | .netlify 7 | netlify 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | *.launch 17 | .settings/ 18 | 19 | # Temp 20 | gitignore 21 | 22 | # System Files 23 | .DS_Store 24 | Thumbs.db 25 | -------------------------------------------------------------------------------- /examples/solid-start/README.md: -------------------------------------------------------------------------------- 1 | # SolidStart 2 | 3 | Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); 4 | 5 | ## Creating a project 6 | 7 | ```bash 8 | # create a new project in the current directory 9 | npm init solid 10 | 11 | # create a new project in my-app 12 | npm init solid my-app 13 | ``` 14 | 15 | ## Developing 16 | 17 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 18 | 19 | ```bash 20 | npm run dev 21 | 22 | # or start the server and open the app in a new browser tab 23 | npm run dev -- --open 24 | ``` 25 | 26 | ## Building 27 | 28 | Solid apps are built with _adapters_, which optimise your project for deployment to different environments. 29 | 30 | By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different adapter, add it to the `devDependencies` in `package.json` and specify in your `vite.config.js`. -------------------------------------------------------------------------------- /examples/solid-start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macaron-solid-start-example", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "dev": "solid-start dev", 6 | "build": "solid-start build", 7 | "start": "solid-start start" 8 | }, 9 | "type": "module", 10 | "devDependencies": { 11 | "solid-start-node": "^0.2.0", 12 | "typescript": "^4.8.4", 13 | "vite": "^3.1.8", 14 | "@macaron-css/vite": "1.0.0" 15 | }, 16 | "dependencies": { 17 | "@solidjs/meta": "^0.28.0", 18 | "@solidjs/router": "^0.5.0", 19 | "solid-js": "^1.6.0", 20 | "solid-start": "^0.2.0", 21 | "undici": "^5.11.0", 22 | "@macaron-css/core": "1.5.2", 23 | "@macaron-css/solid": "1.5.3" 24 | }, 25 | "engines": { 26 | "node": ">=16" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/solid-start/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macaron-css/macaron/bc8c481269b1be644e2588f163a5fe68b19ddfd7/examples/solid-start/public/favicon.ico -------------------------------------------------------------------------------- /examples/solid-start/src/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import { mount, StartClient } from "solid-start/entry-client"; 2 | 3 | mount(() => , document); 4 | -------------------------------------------------------------------------------- /examples/solid-start/src/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createHandler, 3 | renderAsync, 4 | StartServer, 5 | } from "solid-start/entry-server"; 6 | 7 | export default createHandler( 8 | renderAsync((event) => ) 9 | ); 10 | -------------------------------------------------------------------------------- /examples/solid-start/src/root.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { Suspense } from 'solid-js'; 3 | import { 4 | A, 5 | Body, 6 | ErrorBoundary, 7 | FileRoutes, 8 | Head, 9 | Html, 10 | Meta, 11 | Routes, 12 | Scripts, 13 | Title, 14 | } from 'solid-start'; 15 | import { globalStyle } from '@macaron-css/core'; 16 | 17 | globalStyle('*', { 18 | padding: 0, 19 | margin: 0, 20 | boxSizing: 'border-box', 21 | }); 22 | 23 | export default function Root() { 24 | return ( 25 | 26 | 27 | SolidStart - Bare 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /examples/solid-start/src/routes/[...404].tsx: -------------------------------------------------------------------------------- 1 | import { Title } from "solid-start"; 2 | import { HttpStatusCode } from "solid-start/server"; 3 | 4 | export default function NotFound() { 5 | return ( 6 |
7 | Not Found 8 | 9 |

Page Not Found

10 |

11 | Visit{" "} 12 | 13 | start.solidjs.com 14 | {" "} 15 | to learn how to build SolidStart apps. 16 |

17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /examples/solid-start/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@macaron-css/solid'; 2 | 3 | const Button = styled('button', { 4 | base: { 5 | borderRadius: 4, 6 | border: 0, 7 | margin: 12, 8 | cursor: 'pointer', 9 | color: 'white', 10 | textTransform: 'uppercase', 11 | fontSize: 12, 12 | }, 13 | variants: { 14 | color: { 15 | neutral: { background: 'whitesmoke', color: '#333' }, 16 | brand: { background: 'blueviolet' }, 17 | accent: { background: 'slateblue' }, 18 | }, 19 | size: { 20 | small: { padding: 12 }, 21 | medium: { padding: 16 }, 22 | large: { padding: 24 }, 23 | }, 24 | rounded: { 25 | true: { borderRadius: 999 }, 26 | }, 27 | }, 28 | 29 | // Applied when multiple variants are set at once 30 | compoundVariants: [ 31 | { 32 | variants: { 33 | color: 'neutral', 34 | size: 'large', 35 | }, 36 | style: { 37 | background: 'ghostwhite', 38 | }, 39 | }, 40 | ], 41 | 42 | defaultVariants: { 43 | color: 'accent', 44 | size: 'medium', 45 | }, 46 | }); 47 | 48 | export default function Home() { 49 | return ( 50 |
51 | 58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /examples/solid-start/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "jsxImportSource": "solid-js", 9 | "jsx": "preserve", 10 | "types": ["vite/client"], 11 | "baseUrl": "./", 12 | "paths": { 13 | "~/*": ["./src/*"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/solid-start/vite.config.ts: -------------------------------------------------------------------------------- 1 | import solid from 'solid-start/vite'; 2 | import { defineConfig } from 'vite'; 3 | import { macaronVitePlugin } from '@macaron-css/vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [macaronVitePlugin(), solid()], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/solid/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "solid" 4 | ] 5 | } -------------------------------------------------------------------------------- /examples/solid/build.js: -------------------------------------------------------------------------------- 1 | const { macaronEsbuildPlugins } = require('@macaron-css/esbuild'); 2 | const esbuild = require('esbuild'); 3 | 4 | esbuild.build({ 5 | entryPoints: ['src/index.tsx'], 6 | plugins: [...macaronEsbuildPlugins()], 7 | outdir: 'dist', 8 | format: 'esm', 9 | bundle: true, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/solid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | macaron-css solidjs example 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/solid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macaron-solid-example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "node build.js", 8 | "start": "npx serve ." 9 | }, 10 | "dependencies": { 11 | "@macaron-css/esbuild": "1.6.1", 12 | "@macaron-css/solid": "1.5.3", 13 | "babel-preset-solid": "^1.4.2", 14 | "@macaron-css/core": "1.5.2", 15 | "esbuild": "^0.14.42", 16 | "solid-js": "^1.4.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/solid/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@macaron-css/solid'; 2 | 3 | const Button = styled('button', { 4 | base: { 5 | borderRadius: 4, 6 | border: 0, 7 | margin: 12, 8 | cursor: 'pointer', 9 | color: 'white', 10 | textTransform: 'uppercase', 11 | fontSize: 12, 12 | }, 13 | variants: { 14 | color: { 15 | neutral: { background: 'whitesmoke', color: '#333' }, 16 | brand: { background: 'blueviolet' }, 17 | accent: { background: 'slateblue' }, 18 | }, 19 | size: { 20 | small: { padding: 12 }, 21 | medium: { padding: 16 }, 22 | large: { padding: 24 }, 23 | }, 24 | rounded: { 25 | true: { borderRadius: 999 }, 26 | }, 27 | }, 28 | 29 | // Applied when multiple variants are set at once 30 | compoundVariants: [ 31 | { 32 | variants: { 33 | color: 'neutral', 34 | size: 'large', 35 | }, 36 | style: { 37 | background: 'ghostwhite', 38 | }, 39 | }, 40 | ], 41 | 42 | defaultVariants: { 43 | color: 'accent', 44 | size: 'medium', 45 | }, 46 | }); 47 | 48 | function App() { 49 | return ( 50 |
51 | 58 |
59 | ); 60 | } 61 | 62 | export default App; 63 | -------------------------------------------------------------------------------- /examples/solid/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'solid-js/web'; 2 | import App from './App'; 3 | import { globalStyle } from '@macaron-css/core'; 4 | 5 | globalStyle('*', { 6 | padding: 0, 7 | margin: 0, 8 | boxSizing: 'border-box', 9 | }); 10 | 11 | render(() => , document.getElementById('app')!); 12 | -------------------------------------------------------------------------------- /examples/solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "preserve", 4 | "moduleResolution": "node", 5 | "jsxImportSource": "solid-js", 6 | "target": "ESNext", 7 | "esModuleInterop": true, 8 | "declaration": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "noEmit": true 12 | } 13 | } -------------------------------------------------------------------------------- /examples/vanilla/build.js: -------------------------------------------------------------------------------- 1 | const { macaronEsbuildPlugins } = require('@macaron-css/esbuild'); 2 | const esbuild = require('esbuild'); 3 | 4 | esbuild.build({ 5 | entryPoints: ['src/index.ts'], 6 | plugins: [...macaronEsbuildPlugins()], 7 | outdir: 'dist', 8 | format: 'esm', 9 | bundle: true, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/vanilla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macaron-vanilla-example", 3 | "version": "0.1.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@macaron-css/esbuild": "1.6.1", 8 | "@macaron-css/core": "1.5.2", 9 | "esbuild": "^0.14.42" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/vanilla/src/index.ts: -------------------------------------------------------------------------------- 1 | import { recipe, style } from '@macaron-css/core'; 2 | 3 | const button = recipe({ 4 | base: { 5 | borderRadius: 4, 6 | border: 0, 7 | margin: 12, 8 | cursor: 'pointer', 9 | color: 'white', 10 | textTransform: 'uppercase', 11 | fontSize: 12, 12 | }, 13 | variants: { 14 | color: { 15 | neutral: { background: 'whitesmoke', color: '#333' }, 16 | brand: { background: 'blueviolet' }, 17 | accent: { background: 'slateblue' }, 18 | }, 19 | size: { 20 | small: { padding: 12 }, 21 | medium: { padding: 16 }, 22 | large: { padding: 24 }, 23 | }, 24 | rounded: { 25 | true: { borderRadius: 999 }, 26 | }, 27 | }, 28 | 29 | // Applied when multiple variants are set at once 30 | compoundVariants: [ 31 | { 32 | variants: { 33 | color: 'neutral', 34 | size: 'large', 35 | }, 36 | style: { 37 | background: 'ghostwhite', 38 | }, 39 | }, 40 | ], 41 | 42 | defaultVariants: { 43 | color: 'accent', 44 | size: 'medium', 45 | }, 46 | }); 47 | 48 | const el = document.createElement('button'); 49 | el.className = button({ color: 'brand', size: 'medium' }); 50 | el.innerText = 'Click me!'; 51 | 52 | document.body.appendChild(el); 53 | -------------------------------------------------------------------------------- /examples/vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite App 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macaron-vite-example", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@macaron-css/core": "1.5.2", 12 | "@macaron-css/vite": "1.5.1" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^4.5.4", 16 | "vite": "^3.1.8" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/vite/src/main.ts: -------------------------------------------------------------------------------- 1 | import { recipe } from '@macaron-css/core'; 2 | 3 | const button = recipe({ 4 | base: { 5 | borderRadius: 4, 6 | border: 0, 7 | margin: 12, 8 | cursor: 'pointer', 9 | color: 'white', 10 | textTransform: 'uppercase', 11 | fontSize: 12, 12 | }, 13 | variants: { 14 | color: { 15 | neutral: { background: 'whitesmoke', color: '#333' }, 16 | brand: { background: 'blueviolet' }, 17 | accent: { background: 'slateblue' }, 18 | }, 19 | size: { 20 | small: { padding: 12 }, 21 | medium: { padding: 16 }, 22 | large: { padding: 24 }, 23 | }, 24 | rounded: { 25 | true: { borderRadius: 999 }, 26 | }, 27 | }, 28 | 29 | // Applied when multiple variants are set at once 30 | compoundVariants: [ 31 | { 32 | variants: { 33 | color: 'neutral', 34 | size: 'large', 35 | }, 36 | style: { 37 | background: 'ghostwhite', 38 | }, 39 | }, 40 | ], 41 | 42 | defaultVariants: { 43 | color: 'accent', 44 | size: 'medium', 45 | }, 46 | }); 47 | 48 | document.write(` 49 | 52 | `); 53 | -------------------------------------------------------------------------------- /examples/vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/vite/vite.config.js: -------------------------------------------------------------------------------- 1 | import { macaronVitePlugin } from '@macaron-css/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [macaronVitePlugin()], 6 | }); 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.tsx?$': 'esbuild-jest', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macaron-css", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "private": true, 6 | "author": { 7 | "name": "Mokshit Jain", 8 | "url": "https://mokshitjain.co", 9 | "email": "mokshitjain2006@gmail.com" 10 | }, 11 | "workspaces": [ 12 | "./packages/vite", 13 | "./packages/solid", 14 | "./packages/react", 15 | "./packages/qwik", 16 | "./packages/integration", 17 | "./packages/esbuild", 18 | "./packages/core", 19 | "./packages/babel", 20 | "./examples/vite", 21 | "./examples/react", 22 | "./examples/solid", 23 | "./examples/solid-start", 24 | "./examples/vanilla", 25 | "./examples/react" 26 | ], 27 | "scripts": { 28 | "build": "node -r esbuild-register scripts/build.ts", 29 | "bump": "node -r esbuild-register scripts/bump.ts", 30 | "test": "jest", 31 | "release": "node -r esbuild-register scripts/publish.ts" 32 | }, 33 | "dependencies": { 34 | "esbuild": "^0.14.42", 35 | "esbuild-register": "^3.3.2" 36 | }, 37 | "devDependencies": { 38 | "@commitlint/parse": "^17.2.0", 39 | "@release-it-plugins/workspaces": "^3.2.0", 40 | "@types/babel-plugin-tester": "^9.0.5", 41 | "@types/jest": "^28.1.1", 42 | "@types/jsonfile": "^6.1.0", 43 | "@types/luxon": "^3.1.0", 44 | "@types/node": "^17.0.36", 45 | "@types/semver": "^7.3.9", 46 | "@types/stream-to-array": "^2.3.0", 47 | "axios": "^1.2.1", 48 | "babel-plugin-tester": "^10.1.0", 49 | "current-git-branch": "^1.1.0", 50 | "esbuild-jest": "^0.5.0", 51 | "git-log-parser": "^1.2.0", 52 | "jest": "^28.1.0", 53 | "jsonfile": "^6.1.0", 54 | "luxon": "^3.1.1", 55 | "release-it": "^15.5.1", 56 | "semver": "^7.3.7", 57 | "stream-to-array": "^2.3.0", 58 | "tsup": "^8.0.2", 59 | "typescript": "^4.7.2" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/babel/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react and solidjs 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/babel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/babel", 3 | "version": "1.5.1", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.mjs", 6 | "types": "./dist/index.d.ts", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/mokshit06/macaron.git", 11 | "directory": "packages/babel" 12 | }, 13 | "dependencies": { 14 | "@babel/core": "^7.18.2", 15 | "@babel/generator": "^7.18.2", 16 | "@babel/helper-module-imports": "^7.16.7", 17 | "@babel/preset-typescript": "^7.22.5", 18 | "@emotion/hash": "^0.8.0", 19 | "@types/babel__core": "^7.1.19" 20 | }, 21 | "files": [ 22 | "dist", 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /packages/babel/src/global.d.ts: -------------------------------------------------------------------------------- 1 | // import { NodePath, Node } from '@babel/core'; 2 | 3 | declare module '@babel/helper-module-imports' { 4 | export function addNamed( 5 | path: import('@babel/core').NodePath, 6 | named: string, 7 | source: string, 8 | opts?: { nameHint: string } 9 | ): import('@babel/types').Identifier; 10 | } 11 | -------------------------------------------------------------------------------- /packages/babel/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { PluginObj } from '@babel/core'; 2 | import { transformCallExpression } from './transforms/callExpression'; 3 | import postprocess from './transforms/postprocess'; 4 | import preprocess from './transforms/preprocess'; 5 | import type { PluginOptions, PluginState } from './types'; 6 | 7 | export { styledComponentsPlugin as macaronStyledComponentsPlugin } from './styled'; 8 | export type { PluginOptions }; 9 | 10 | export function macaronBabelPlugin(): PluginObj { 11 | return { 12 | name: 'macaron-css-babel', 13 | visitor: { 14 | Program: { 15 | enter: preprocess, 16 | exit: postprocess, 17 | }, 18 | CallExpression: transformCallExpression, 19 | }, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/babel/src/styled.ts: -------------------------------------------------------------------------------- 1 | import { types as t, type PluginObj } from '@babel/core'; 2 | import type { PluginState, ProgramScope } from './types'; 3 | import { registerImportMethod } from './utils'; 4 | import * as generator from '@babel/generator'; 5 | export function styledComponentsPlugin(): PluginObj { 6 | return { 7 | name: 'macaron-css-babel:styled', 8 | visitor: { 9 | Program: { 10 | enter(path) { 11 | (path.scope as any).macaronData ??= {}; 12 | path.traverse({ 13 | CallExpression(callPath) { 14 | const callee = callPath.get('callee'); 15 | 16 | const isSolidAdapter = callee.referencesImport( 17 | '@macaron-css/solid', 18 | 'styled' 19 | ); 20 | const isReactAdapter = callee.referencesImport( 21 | '@macaron-css/react', 22 | 'styled' 23 | ); 24 | if (!isSolidAdapter && !isReactAdapter) return; 25 | 26 | const runtimeImport = isSolidAdapter 27 | ? '@macaron-css/solid/runtime' 28 | : '@macaron-css/react/runtime'; 29 | 30 | const [tag, styles, ...args] = callPath.node.arguments; 31 | const styledIdent = registerImportMethod( 32 | callPath, 33 | '$$styled', 34 | runtimeImport 35 | ); 36 | const recipeIdent = registerImportMethod( 37 | callPath, 38 | 'recipe', 39 | '@macaron-css/core' 40 | ); 41 | 42 | const recipeCallExpression = t.callExpression(recipeIdent, [ 43 | t.cloneNode(styles), 44 | ]); 45 | t.addComments( 46 | recipeCallExpression, 47 | 'leading', 48 | callPath.node.leadingComments ?? [] 49 | ); 50 | const callExpression = t.callExpression(styledIdent, [ 51 | t.cloneNode(tag), 52 | recipeCallExpression, 53 | ...args, 54 | ]); 55 | t.addComment(callExpression, 'leading', ' @__PURE__ '); 56 | 57 | callPath.replaceWith(callExpression); 58 | 59 | // recompute the references 60 | // later used in `referencesImports` to check if imported from macaron 61 | path.scope.crawl(); 62 | }, 63 | }); 64 | }, 65 | }, 66 | }, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /packages/babel/src/transforms/callExpression.ts: -------------------------------------------------------------------------------- 1 | import { type NodePath, types as t } from '@babel/core'; 2 | import type { PluginState, ProgramScope } from '../types'; 3 | import { 4 | extractionAPIs, 5 | getNearestIdentifier, 6 | registerImportMethod, 7 | } from '../utils'; 8 | import * as generator from '@babel/generator'; 9 | 10 | export function transformCallExpression( 11 | callPath: NodePath, 12 | _callState: PluginState 13 | ) { 14 | const callee = callPath.get('callee'); 15 | if ( 16 | !extractionAPIs.some(api => 17 | callee.referencesImport('@macaron-css/core', api) 18 | ) 19 | ) { 20 | return; 21 | } 22 | 23 | const programParent = callPath.scope.getProgramParent() as ProgramScope; 24 | 25 | if ( 26 | callPath.node.leadingComments?.some( 27 | comment => comment.value.trim() === 'macaron-ignore' 28 | ) || 29 | callPath.parent?.leadingComments?.some( 30 | comment => comment.value.trim() === 'macaron-ignore' 31 | ) 32 | ) { 33 | const bindings = getBindings(callPath.get('callee')); 34 | for (const binding of bindings) { 35 | programParent.macaronData.nodes.push(findRootBinding(binding).node); 36 | } 37 | 38 | return; 39 | } 40 | 41 | const nearestIdentifier = getNearestIdentifier(callPath); 42 | const ident = nearestIdentifier 43 | ? programParent.generateUidIdentifier( 44 | `$macaron$$${nearestIdentifier.node.name}` 45 | ) 46 | : programParent.generateUidIdentifier('$macaron$$unknown'); 47 | const importedIdent = registerImportMethod( 48 | callPath, 49 | ident.name, 50 | programParent.macaronData.cssFile 51 | ); 52 | 53 | const bindings = getBindings(callPath); 54 | for (const binding of bindings) { 55 | programParent.macaronData.nodes.push(findRootBinding(binding).node); 56 | } 57 | 58 | programParent.macaronData.nodes.push( 59 | t.exportNamedDeclaration( 60 | t.variableDeclaration('var', [t.variableDeclarator(ident, callPath.node)]) 61 | ) 62 | ); 63 | // add a variable alias 64 | // because other transforms use the imported ident as reference 65 | programParent.macaronData.nodes.push( 66 | t.variableDeclaration('var', [t.variableDeclarator(importedIdent, ident)]) 67 | ); 68 | 69 | callPath.replaceWith(importedIdent); 70 | } 71 | 72 | function findRootBinding(path: NodePath) { 73 | let rootPath: NodePath; 74 | if (!('parent' in path) || path.parentPath?.isProgram()) { 75 | rootPath = path; 76 | } else { 77 | rootPath = path.parentPath!; 78 | } 79 | 80 | return rootPath; 81 | } 82 | 83 | function getBindings(path: NodePath) { 84 | const programParent = path.scope.getProgramParent() as ProgramScope; 85 | const bindings: Array> = []; 86 | 87 | path.traverse({ 88 | Expression(expressionPath, state) { 89 | if (!expressionPath.isIdentifier()) return; 90 | 91 | const binding = path.scope.getBinding(expressionPath as any); 92 | 93 | if ( 94 | !binding || 95 | programParent.macaronData.bindings.includes(binding.path) || 96 | bindings.includes(binding.path) 97 | ) 98 | return; 99 | 100 | const rootBinding = findRootBinding(binding.path); 101 | 102 | // prevents infinite loop in a few cases like having arguments in a function declaration 103 | // if the path being checked is the same as the latest path, then the bindings will be same 104 | if (path === rootBinding) { 105 | bindings.push(binding.path); 106 | return; 107 | } 108 | 109 | const bindingOfBindings = getBindings(rootBinding); 110 | 111 | bindings.push(...bindingOfBindings, binding.path); 112 | }, 113 | }); 114 | 115 | programParent.macaronData.bindings.push(...bindings); 116 | 117 | return bindings; 118 | } 119 | -------------------------------------------------------------------------------- /packages/babel/src/transforms/postprocess.ts: -------------------------------------------------------------------------------- 1 | import { type NodePath, types as t } from '@babel/core'; 2 | import * as generator from '@babel/generator'; 3 | import type { PluginState, ProgramScope } from '../types'; 4 | 5 | export default function postprocess( 6 | path: NodePath, 7 | state: PluginState 8 | ) { 9 | const programParent = path.scope as ProgramScope; 10 | const cssExtract = generator.default( 11 | t.program(programParent.macaronData.nodes as t.Statement[]) 12 | ).code; 13 | 14 | state.opts.result = [programParent.macaronData.cssFile, cssExtract]; 15 | } 16 | -------------------------------------------------------------------------------- /packages/babel/src/transforms/preprocess.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath, types as t } from '@babel/core'; 2 | import type { PluginState, ProgramScope } from '../types'; 3 | import hash from '@emotion/hash'; 4 | 5 | export default function preprocess( 6 | path: NodePath, 7 | state: PluginState 8 | ) { 9 | (path.scope as ProgramScope).macaronData = { 10 | imports: new Map(), 11 | cssFile: `extracted_${hash(path.toString())}.css.ts`, 12 | nodes: [], 13 | bindings: [], 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/babel/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { NodePath, PluginPass, types as t } from '@babel/core'; 2 | import type { Scope } from '@babel/traverse'; 3 | 4 | export type PluginOptions = { 5 | result: [string, string]; 6 | /** 7 | * @deprecated no longer used 8 | */ 9 | path?: string; 10 | }; 11 | 12 | export type PluginState = PluginPass & { opts: PluginOptions }; 13 | 14 | export type ProgramScope = Scope & { 15 | macaronData: { 16 | imports: Map; 17 | bindings: Array>; 18 | nodes: Array; 19 | cssFile: string; 20 | }; 21 | }; 22 | 23 | export type MacaronNode = ProgramScope['macaronData']['nodes'][number]; 24 | -------------------------------------------------------------------------------- /packages/babel/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { type NodePath, types as t } from '@babel/core'; 2 | import { addNamed } from '@babel/helper-module-imports'; 3 | import type { ProgramScope } from './types'; 4 | 5 | export function invariant(cond: boolean, message: string): asserts cond { 6 | if (!cond) { 7 | throw new Error(message); 8 | } 9 | } 10 | 11 | export function registerImportMethod( 12 | path: NodePath, 13 | name: string, 14 | moduleName = '@macaron-css/core' 15 | ) { 16 | const imports = 17 | (path.scope.getProgramParent() as ProgramScope).macaronData.imports || 18 | ((path.scope.getProgramParent() as ProgramScope).macaronData.imports = 19 | new Map()); 20 | 21 | if (!imports.has(`${moduleName}:${name}`)) { 22 | let id = addNamed(path, name, moduleName); 23 | imports.set(`${moduleName}:${name}`, id); 24 | return id; 25 | } else { 26 | let iden = imports.get(`${moduleName}:${name}`)!; 27 | // the cloning is required to play well with babel-preset-env which is 28 | // transpiling import as we add them and using the same identifier causes 29 | // problems with the multiple identifiers of the same thing 30 | return t.cloneNode(iden); 31 | } 32 | } 33 | 34 | export function getNearestIdentifier(path: NodePath) { 35 | let currentPath: NodePath | null = path; 36 | 37 | while (currentPath.parentPath !== null) { 38 | if (currentPath.isIdentifier()) { 39 | return currentPath; 40 | } 41 | 42 | let id = currentPath.get('id'); 43 | if (!Array.isArray(id)) { 44 | if (id.isIdentifier()) return id; 45 | if (id.isArrayPattern()) { 46 | for (const el of id.get('elements')) { 47 | if (el.isIdentifier()) return el; 48 | } 49 | } 50 | } 51 | 52 | let key = currentPath.get('key'); 53 | if (!Array.isArray(key) && key.isIdentifier()) { 54 | return key; 55 | } 56 | 57 | currentPath = currentPath.parentPath; 58 | } 59 | 60 | return null; 61 | } 62 | 63 | export const extractionAPIs = [ 64 | // @macaron-css/core 65 | 'macaron$', 66 | // @vanilla-extract/css 67 | 'style', 68 | 'styleVariants', 69 | 'globalStyle', 70 | 'createTheme', 71 | 'createGlobalTheme', 72 | 'createThemeContract', 73 | 'createGlobalThemeContract', 74 | 'assignVars', 75 | 'createVar', 76 | 'fallbackVar', 77 | 'fontFace', 78 | 'globalFontFace', 79 | 'keyframes', 80 | 'globalKeyframes', 81 | 'style', 82 | 'styleVariants', 83 | 'globalStyle', 84 | 'createTheme', 85 | 'createGlobalTheme', 86 | 'createThemeContract', 87 | 'createGlobalThemeContract', 88 | 'assignVars', 89 | 'createVar', 90 | 'fallbackVar', 91 | 'fontFace', 92 | 'globalFontFace', 93 | 'keyframes', 94 | 'globalKeyframes', 95 | // @vanilla-extract/recipes 96 | 'recipe', 97 | ]; 98 | -------------------------------------------------------------------------------- /packages/babel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node16", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "isolatedModules": true, 11 | "noEmit": true 12 | }, 13 | "exclude": [ 14 | "dist", 15 | "node_modules" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react and solidjs 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/core", 3 | "version": "1.5.2", 4 | "main": "dist/index.js", 5 | "module": "dist/index.mjs", 6 | "types": "dist/index.d.ts", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/mokshit06/macaron.git", 11 | "directory": "packages/core" 12 | }, 13 | "sideEffects": false, 14 | "exports": { 15 | ".": { 16 | "import": "./dist/index.mjs", 17 | "require": "./dist/index.js" 18 | }, 19 | "./dist/*": "./dist/*", 20 | "./create-runtime-fn": { 21 | "types": "./dist/create-runtime-fn.d.ts", 22 | "import": "./dist/create-runtime-fn.mjs", 23 | "require": "./dist/create-runtime-fn.js" 24 | }, 25 | "./dynamic": { 26 | "types": "./dist/dynamic.d.ts", 27 | "import": "./dist/dynamic.mjs", 28 | "require": "./dist/dynamic.js" 29 | }, 30 | "./types": { 31 | "types": "./dist/types.d.ts", 32 | "import": "./dist/types.mjs", 33 | "require": "./dist/types.js" 34 | } 35 | }, 36 | "dependencies": { 37 | "@vanilla-extract/css": "^1.7.1", 38 | "@vanilla-extract/dynamic": "^2.0.3", 39 | "@vanilla-extract/recipes": "^0.2.5" 40 | }, 41 | "files": [ 42 | "dist", 43 | "src" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/create-runtime-fn.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | PatternResult, 3 | RuntimeFn, 4 | VariantGroups, 5 | VariantSelection, 6 | } from './types'; 7 | 8 | const shouldApplyCompound = ( 9 | compoundCheck: VariantSelection, 10 | selections: VariantSelection, 11 | defaultVariants: VariantSelection 12 | ) => { 13 | for (const key of Object.keys(compoundCheck)) { 14 | if (compoundCheck[key] !== (selections[key] ?? defaultVariants[key])) { 15 | return false; 16 | } 17 | } 18 | 19 | return true; 20 | }; 21 | 22 | export const createRuntimeFn = ( 23 | config: PatternResult 24 | ) => { 25 | const runtimeFn: RuntimeFn & { 26 | macaronMeta: { 27 | variants: Array; 28 | defaultClassName: string; 29 | variantConcat: (variants: VariantSelection) => string; 30 | }; 31 | } = options => { 32 | let className = config.defaultClassName; 33 | 34 | const selections: VariantSelection = { 35 | ...config.defaultVariants, 36 | ...options, 37 | }; 38 | for (const variantName in selections) { 39 | const variantSelection = 40 | selections[variantName] ?? config.defaultVariants[variantName]; 41 | 42 | if (variantSelection != null) { 43 | let selection = variantSelection; 44 | 45 | if (typeof selection === 'boolean') { 46 | // @ts-expect-error 47 | selection = selection === true ? 'true' : 'false'; 48 | } 49 | 50 | const selectionClassName = 51 | // @ts-expect-error 52 | config.variantClassNames[variantName]?.[selection]; 53 | 54 | if (selectionClassName) { 55 | className += ' ' + selectionClassName; 56 | } 57 | } 58 | } 59 | 60 | for (const [compoundCheck, compoundClassName] of config.compoundVariants) { 61 | if ( 62 | shouldApplyCompound(compoundCheck, selections, config.defaultVariants) 63 | ) { 64 | className += ' ' + compoundClassName; 65 | } 66 | } 67 | 68 | return className; 69 | }; 70 | 71 | runtimeFn.macaronMeta = { 72 | variants: Object.keys(config.variantClassNames), 73 | defaultClassName: config.defaultClassName, 74 | variantConcat(options) { 75 | let className = config.defaultClassName; 76 | 77 | for (const variantName in options) { 78 | const variantSelection = options[variantName]; 79 | 80 | if (variantSelection != null) { 81 | let selection = variantSelection; 82 | 83 | if (typeof selection === 'boolean') { 84 | // @ts-expect-error 85 | selection = selection === true ? 'true' : 'false'; 86 | } 87 | 88 | const selectionClassName = 89 | // @ts-expect-error 90 | config.variantClassNames[variantName]?.[selection]; 91 | 92 | if (selectionClassName) { 93 | className += ' ' + selectionClassName; 94 | } 95 | } 96 | } 97 | 98 | return className; 99 | }, 100 | }; 101 | 102 | return runtimeFn; 103 | }; 104 | -------------------------------------------------------------------------------- /packages/core/src/dynamic.ts: -------------------------------------------------------------------------------- 1 | export * from '@vanilla-extract/dynamic'; 2 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@vanilla-extract/css'; 2 | 3 | // moved over from 4 | // https://github.com/seek-oss/vanilla-extract/blob/master/packages/recipes/src/index.ts 5 | // with the import path changed to macaron 6 | import { addRecipe } from '@vanilla-extract/css/recipe'; 7 | import { style, styleVariants } from '@vanilla-extract/css'; 8 | 9 | import { createRuntimeFn } from './create-runtime-fn'; 10 | import type { 11 | PatternOptions, 12 | PatternResult, 13 | RecipeVariants, 14 | RuntimeFn, 15 | VariantGroups, 16 | VariantSelection, 17 | } from './types'; 18 | 19 | export type { RecipeVariants }; 20 | 21 | function mapValues, OutputValue>( 22 | input: Input, 23 | fn: (value: Input[keyof Input], key: keyof Input) => OutputValue 24 | ): Record { 25 | const result: any = {}; 26 | 27 | for (const key in input) { 28 | result[key] = fn(input[key], key); 29 | } 30 | 31 | return result; 32 | } 33 | 34 | export function recipe( 35 | options: PatternOptions, 36 | debugId?: string 37 | ): RuntimeFn { 38 | const { 39 | variants = {}, 40 | defaultVariants = {}, 41 | compoundVariants = [], 42 | base = '', 43 | } = options; 44 | 45 | const defaultClassName = 46 | typeof base === 'string' ? base : style(base, debugId); 47 | 48 | // @ts-expect-error 49 | const variantClassNames: PatternResult['variantClassNames'] = 50 | mapValues(variants, (variantGroup, variantGroupName) => 51 | styleVariants( 52 | variantGroup, 53 | styleRule => (typeof styleRule === 'string' ? [styleRule] : styleRule), 54 | debugId ? `${debugId}_${variantGroupName}` : variantGroupName 55 | ) 56 | ); 57 | 58 | const compounds: Array<[VariantSelection, string]> = []; 59 | 60 | for (const { style: theStyle, variants } of compoundVariants) { 61 | compounds.push([ 62 | variants, 63 | typeof theStyle === 'string' 64 | ? theStyle 65 | : style(theStyle, `${debugId}_compound_${compounds.length}`), 66 | ]); 67 | } 68 | 69 | const config: PatternResult = { 70 | defaultClassName, 71 | variantClassNames, 72 | defaultVariants, 73 | compoundVariants: compounds, 74 | }; 75 | 76 | return addRecipe(createRuntimeFn(config), { 77 | importPath: '@macaron-css/core/create-runtime-fn', 78 | importName: 'createRuntimeFn', 79 | // @ts-expect-error 80 | args: [config], 81 | }); 82 | } 83 | 84 | export const macaron$ = (block: () => T) => { 85 | return block(); 86 | }; 87 | -------------------------------------------------------------------------------- /packages/core/src/types.ts: -------------------------------------------------------------------------------- 1 | // Sourced from @vanilla-extract/recipes 2 | 3 | import type { ComplexStyleRule } from '@vanilla-extract/css'; 4 | type RecipeStyleRule = ComplexStyleRule | string; 5 | export type VariantDefinitions = Record; 6 | type BooleanMap = T extends 'true' | 'false' ? boolean : T; 7 | export type VariantGroups = Record; 8 | export type VariantSelection = { 9 | [VariantGroup in keyof Variants]?: BooleanMap; 10 | }; 11 | export type PatternResult = { 12 | defaultClassName: string; 13 | variantClassNames: { 14 | [P in keyof Variants]: { 15 | [P in keyof Variants[keyof Variants]]: string; 16 | }; 17 | }; 18 | defaultVariants: VariantSelection; 19 | compoundVariants: Array<[VariantSelection, string]>; 20 | }; 21 | export interface CompoundVariant { 22 | variants: VariantSelection; 23 | style: RecipeStyleRule; 24 | } 25 | export type PatternOptions = { 26 | base?: RecipeStyleRule; 27 | variants?: Variants; 28 | defaultVariants?: VariantSelection; 29 | compoundVariants?: Array>; 30 | }; 31 | export type RuntimeFn = ( 32 | options?: VariantSelection 33 | ) => string; 34 | export type RecipeVariants> = 35 | Parameters[0]; 36 | 37 | export {}; 38 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node16", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "emitDeclarationOnly": true, 11 | }, 12 | "include": [ 13 | "src/**/*.ts", 14 | ], 15 | "exclude": [ 16 | "dist", 17 | "node_modules" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/esbuild/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react and solidjs 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/esbuild/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/esbuild", 3 | "version": "1.6.1", 4 | "main": "dist/index.js", 5 | "module": "dist/index.mjs", 6 | "types": "dist/index.d.ts", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/mokshit06/macaron.git", 11 | "directory": "packages/esbuild" 12 | }, 13 | "dependencies": { 14 | "@vanilla-extract/esbuild-plugin": "^2.0.5", 15 | "@vanilla-extract/integration": "^6.0.0", 16 | "esbuild": "^0.14.42", 17 | "@macaron-css/integration": "1.5.1" 18 | }, 19 | "files": [ 20 | "dist", 21 | "src" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/esbuild/src/index.ts: -------------------------------------------------------------------------------- 1 | import { processVanillaFile } from '@vanilla-extract/integration'; 2 | import { Plugin as EsbuildPlugin } from 'esbuild'; 3 | import { dirname, join } from 'path'; 4 | import { babelTransform, compile } from '@macaron-css/integration'; 5 | 6 | /* 7 | -> load /(t|j)sx?/ 8 | -> extract css and inject imports (extracted_HASH.css.ts) 9 | -> resolve all imports 10 | -> load imports, bundle code again 11 | -> add file scope to all files 12 | -> evaluate and generate .vanilla.css.ts files 13 | -> gets resolved by @vanilla-extract/esbuild-plugin 14 | -> process the file with vanilla-extract 15 | -> resolve with js loader 16 | */ 17 | 18 | interface MacaronEsbuildPluginOptions { 19 | includeNodeModulesPattern?: RegExp; 20 | } 21 | 22 | export function macaronEsbuildPlugin({ 23 | includeNodeModulesPattern, 24 | }: MacaronEsbuildPluginOptions = {}): EsbuildPlugin { 25 | return { 26 | name: 'macaron-css-esbuild', 27 | setup(build) { 28 | let resolvers = new Map(); 29 | let resolverCache = new Map(); 30 | 31 | build.onEnd(() => { 32 | resolvers.clear(); 33 | resolverCache.clear(); 34 | }); 35 | 36 | build.onResolve({ filter: /^extracted_(.*)\.css\.ts$/ }, async args => { 37 | if (!resolvers.has(args.path)) { 38 | return; 39 | } 40 | 41 | let resolvedPath = join(args.importer, '..', args.path); 42 | 43 | return { 44 | namespace: 'extracted-css', 45 | path: resolvedPath, 46 | pluginData: { 47 | path: args.path, 48 | mainFilePath: args.pluginData.mainFilePath, 49 | }, 50 | }; 51 | }); 52 | 53 | build.onLoad( 54 | { filter: /.*/, namespace: 'extracted-css' }, 55 | async ({ path, pluginData }) => { 56 | const resolverContents = resolvers.get(pluginData.path)!; 57 | const { source, watchFiles } = await compile({ 58 | esbuild: build.esbuild, 59 | filePath: path, 60 | originalPath: pluginData.mainFilePath!, 61 | contents: resolverContents, 62 | externals: [], 63 | cwd: build.initialOptions.absWorkingDir, 64 | resolverCache, 65 | }); 66 | 67 | try { 68 | const contents = await processVanillaFile({ 69 | source, 70 | filePath: path, 71 | outputCss: undefined, 72 | identOption: 73 | undefined ?? (build.initialOptions.minify ? 'short' : 'debug'), 74 | }); 75 | 76 | return { 77 | contents, 78 | loader: 'js', 79 | resolveDir: dirname(path), 80 | }; 81 | } catch (error) { 82 | if (error instanceof ReferenceError) { 83 | return { 84 | errors: [ 85 | { 86 | text: error.toString(), 87 | detail: 88 | 'This usually happens if you use a browser api at the top level of a file being imported.', 89 | }, 90 | ], 91 | }; 92 | } 93 | 94 | throw error; 95 | } 96 | } 97 | ); 98 | 99 | build.onLoad({ filter: /\.(j|t)sx?$/ }, async args => { 100 | if (args.path.includes('node_modules')) { 101 | if(!includeNodeModulesPattern) return; 102 | if(!includeNodeModulesPattern.test(args.path)) return; 103 | }; 104 | 105 | // gets handled by @vanilla-extract/esbuild-plugin 106 | if (args.path.endsWith('.css.ts')) return; 107 | 108 | const { 109 | code, 110 | result: [file, cssExtract], 111 | } = await babelTransform(args.path); 112 | 113 | // the extracted code and original are the same -> no css extracted 114 | if (file && cssExtract && cssExtract !== code) { 115 | resolvers.set(file, cssExtract); 116 | resolverCache.delete(args.path); 117 | } 118 | 119 | return { 120 | contents: code!, 121 | loader: args.path.match(/\.(ts|tsx)$/i) ? 'ts' : 'js', 122 | pluginData: { 123 | mainFilePath: args.path, 124 | }, 125 | }; 126 | }); 127 | }, 128 | }; 129 | } 130 | 131 | export const macaronEsbuildPlugins = (options: MacaronEsbuildPluginOptions = {}) => [ 132 | macaronEsbuildPlugin(options), 133 | require('@vanilla-extract/esbuild-plugin').vanillaExtractPlugin(), 134 | ]; 135 | -------------------------------------------------------------------------------- /packages/esbuild/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node16", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "emitDeclarationOnly": true, 11 | }, 12 | "include": [ 13 | "src/**/*.ts", 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | "build" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/integration/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react and solidjs 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/integration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/integration", 3 | "version": "1.5.1", 4 | "main": "dist/index.js", 5 | "module": "dist/index.mjs", 6 | "types": "dist/index.d.ts", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/mokshit06/macaron.git", 11 | "directory": "packages/integration" 12 | }, 13 | "dependencies": { 14 | "@babel/core": "^7.18.2", 15 | "@babel/plugin-syntax-jsx": "^7.17.12", 16 | "@macaron-css/babel": "1.5.1", 17 | "@vanilla-extract/integration": "^6.0.0", 18 | "esbuild": "^0.14.42" 19 | }, 20 | "files": [ 21 | "dist", 22 | "src" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/integration/src/babel.ts: -------------------------------------------------------------------------------- 1 | import { TransformOptions, transformFileAsync } from '@babel/core'; 2 | import { 3 | macaronBabelPlugin, 4 | PluginOptions, 5 | macaronStyledComponentsPlugin, 6 | } from '@macaron-css/babel'; 7 | 8 | export type BabelOptions = Omit; 9 | 10 | export async function babelTransform(path: string, babel: BabelOptions = {}) { 11 | const options: PluginOptions = { result: ['', ''], path }; 12 | const result = await transformFileAsync(path, { 13 | ...babel, 14 | plugins: [ 15 | ...(Array.isArray(babel.plugins) ? babel.plugins : []), 16 | macaronStyledComponentsPlugin(), 17 | [macaronBabelPlugin(), options] 18 | ], 19 | presets: [ 20 | ...(Array.isArray(babel.presets) ? babel.presets : []), 21 | '@babel/preset-typescript' 22 | ], 23 | sourceMaps: false, 24 | }); 25 | 26 | if (result === null || result.code === null) 27 | throw new Error(`Could not transform ${path}`); 28 | 29 | return { result: options.result, code: result.code }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/integration/src/compile.ts: -------------------------------------------------------------------------------- 1 | import { transformSync } from '@babel/core'; 2 | import { macaronStyledComponentsPlugin } from '@macaron-css/babel'; 3 | import { addFileScope, getPackageInfo } from '@vanilla-extract/integration'; 4 | import defaultEsbuild, { PluginBuild } from 'esbuild'; 5 | import fs from 'fs'; 6 | import { basename, dirname, join } from 'path'; 7 | 8 | interface CompileOptions { 9 | esbuild?: PluginBuild['esbuild']; 10 | filePath: string; 11 | contents: string; 12 | cwd?: string; 13 | externals?: Array; 14 | resolverCache: Map; 15 | originalPath: string; 16 | } 17 | 18 | export async function compile({ 19 | esbuild = defaultEsbuild, 20 | filePath, 21 | cwd = process.cwd(), 22 | externals = [], 23 | contents, 24 | resolverCache, 25 | originalPath, 26 | }: CompileOptions) { 27 | const packageInfo = getPackageInfo(cwd); 28 | let source: string; 29 | 30 | if (resolverCache.has(originalPath)) { 31 | source = resolverCache.get(originalPath)!; 32 | } else { 33 | source = addFileScope({ 34 | source: contents, 35 | filePath: originalPath, 36 | rootPath: cwd, 37 | packageName: packageInfo.name, 38 | }); 39 | 40 | resolverCache.set(originalPath, source); 41 | } 42 | 43 | const result = await esbuild.build({ 44 | stdin: { 45 | contents: source, 46 | loader: 'tsx', 47 | resolveDir: dirname(filePath), 48 | sourcefile: basename(filePath), 49 | }, 50 | metafile: true, 51 | bundle: true, 52 | external: [ 53 | '@vanilla-extract', 54 | // 'solid-js', 55 | '@macaron-css', 56 | // '@comptime-css', 57 | ...externals, 58 | ], 59 | platform: 'node', 60 | write: false, 61 | absWorkingDir: cwd, 62 | plugins: [ 63 | { 64 | name: 'macaron:stub-solid-template-export', 65 | setup(build) { 66 | build.onResolve({ filter: /^solid-js\/web$/ }, args => { 67 | return { 68 | namespace: 'solid-web', 69 | path: args.path, 70 | }; 71 | }); 72 | 73 | // TODO: change this to use the server transform from solid 74 | build.onLoad({ filter: /.*/, namespace: 'solid-web' }, async args => { 75 | return { 76 | contents: ` 77 | const noop = () => { 78 | return new Proxy({}, { 79 | get() { 80 | throw new Error("macaron: This file tried to call template() directly and use its result. Please check your compiled solid-js output and if it is correct, please file an issue at https://github.com/mokshit06/macaron/issues"); 81 | } 82 | }); 83 | } 84 | 85 | export const template = noop; 86 | export const delegateEvents = noop; 87 | 88 | export * from ${JSON.stringify(require.resolve('solid-js/web'))}; 89 | `, 90 | resolveDir: dirname(args.path), 91 | }; 92 | }); 93 | }, 94 | }, 95 | { 96 | name: 'macaron:custom-extract-scope', 97 | setup(build) { 98 | build.onLoad({ filter: /\.(t|j)sx?$/ }, async args => { 99 | const contents = await fs.promises.readFile(args.path, 'utf8'); 100 | let source = addFileScope({ 101 | source: contents, 102 | filePath: args.path, 103 | rootPath: build.initialOptions.absWorkingDir!, 104 | packageName: packageInfo.name, 105 | }); 106 | 107 | source = transformSync(source, { 108 | filename: args.path, 109 | plugins: [macaronStyledComponentsPlugin()], 110 | presets: ['@babel/preset-typescript'], 111 | sourceMaps: false, 112 | })!.code!; 113 | 114 | return { 115 | contents: source, 116 | loader: 'tsx', 117 | resolveDir: dirname(args.path), 118 | }; 119 | }); 120 | }, 121 | }, 122 | ], 123 | }); 124 | 125 | const { outputFiles, metafile } = result; 126 | 127 | if (!outputFiles || outputFiles.length !== 1) { 128 | throw new Error('Invalid child compilation'); 129 | } 130 | 131 | return { 132 | source: outputFiles[0].text, 133 | watchFiles: Object.keys(metafile?.inputs || {}).map(filePath => 134 | join(cwd, filePath) 135 | ), 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /packages/integration/src/index.ts: -------------------------------------------------------------------------------- 1 | export { babelTransform, BabelOptions } from './babel'; 2 | export { compile } from './compile'; 3 | -------------------------------------------------------------------------------- /packages/qwik/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react, solidjs and qwik 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/qwik/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/qwik", 3 | "version": "1.5.3", 4 | "license": "MIT", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/mokshit06/macaron.git", 9 | "directory": "packages/qwik" 10 | }, 11 | "exports": { 12 | ".": { 13 | "types": "./dist/index.d.mts", 14 | "import": "./dist/index.mjs" 15 | }, 16 | "./runtime": { 17 | "types": "./dist/index.d.mts", 18 | "import": "./dist/runtime.mjs" 19 | } 20 | }, 21 | "dependencies": { 22 | "@macaron-css/core": "1.5.2" 23 | }, 24 | "devDependencies": { 25 | "@builder.io/qwik": "^1.1.5", 26 | "@vanilla-extract/recipes": "^0.2.5" 27 | }, 28 | "peerDependencies": { 29 | "@builder.io/qwik": "^1.1.5", 30 | "@vanilla-extract/recipes": "^0.2.5" 31 | }, 32 | "files": [ 33 | "dist", 34 | "src" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/qwik/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | PatternOptions, 3 | VariantGroups, 4 | VariantSelection, 5 | } from '@macaron-css/core/types'; 6 | import { 7 | Component, 8 | JSXChildren, 9 | QwikIntrinsicElements, 10 | } from '@builder.io/qwik'; 11 | 12 | type IntrinsicProps = TComponent extends keyof QwikIntrinsicElements 13 | ? QwikIntrinsicElements[TComponent] 14 | : any; 15 | 16 | type StyledComponent< 17 | TProps = {}, 18 | Variants extends VariantGroups = {} 19 | > = Component & { 20 | variants: Array; 21 | selector: (variants: VariantSelection) => string; 22 | }; 23 | 24 | export function styled< 25 | TProps, 26 | TComponent extends string | keyof QwikIntrinsicElements, 27 | Variants extends VariantGroups = {} 28 | >( 29 | component: TComponent, 30 | options: PatternOptions 31 | ): StyledComponent< 32 | IntrinsicProps & VariantSelection, 33 | Variants 34 | >; 35 | 36 | export function styled( 37 | component: Component, 38 | options: PatternOptions 39 | ): StyledComponent, Variants>; 40 | 41 | export function styled(component: any, options: any): any { 42 | // the following doesn't work because vanilla-extract's function serializer 43 | // cannot serialize complex functions like `$$styled` 44 | 45 | // const runtimeFn = recipe(options); 46 | 47 | // return addFunctionSerializer($$styled(component, runtimeFn as any), { 48 | // importPath: '@macaron-css/qwik/runtime', 49 | // args: [component, runtimeFn], 50 | // importName: '$$styled', 51 | // }); 52 | 53 | throw new Error( 54 | "This function shouldn't be there in your final code. If you're seeing this, there is probably some issue with your build config. If you think everything looks fine, then file an issue at https://github.com/mokshit06/macaron/issues" 55 | ); 56 | } 57 | 58 | export type StyleVariants> = 59 | T extends StyledComponent 60 | ? VariantSelection 61 | : unknown; 62 | -------------------------------------------------------------------------------- /packages/qwik/src/runtime.ts: -------------------------------------------------------------------------------- 1 | import { component$, h, useComputed$ } from '@builder.io/qwik'; 2 | 3 | export function $$styled( 4 | Comp: any, 5 | styles: ((options?: any) => string) & { 6 | macaronMeta: { 7 | variants: string[]; 8 | defaultClassName: string; 9 | variantConcat(options: any): string; 10 | }; 11 | } 12 | ) { 13 | const StyledComponent: any = component$(({ as, ...props }: any) => { 14 | let CompToRender = as ?? Comp; 15 | const propsSignal = useComputed$(() => { 16 | const [classes, others]: any[] = [{}, {}]; 17 | 18 | for (const [key, value] of Object.entries(props)) { 19 | if (StyledComponent.variants.includes(key)) { 20 | classes[key] = value; 21 | } else { 22 | others[key] = value; 23 | } 24 | } 25 | 26 | return { variants: classes, others }; 27 | }); 28 | const className = useComputed$(() => { 29 | const classes = StyledComponent.classes( 30 | propsSignal.value.variants, 31 | props.className 32 | ); 33 | return classes.join(' '); 34 | }); 35 | 36 | if (typeof CompToRender === 'string') { 37 | return h(CompToRender, { ...propsSignal.value.others, className }); 38 | } 39 | 40 | return h(CompToRender, { ...props, className }); 41 | }); 42 | 43 | StyledComponent.toString = () => StyledComponent.selector(null); 44 | StyledComponent.variants = [ 45 | ...(styles.macaronMeta.variants ?? []), 46 | ...(Comp.variants ?? []), 47 | ]; 48 | StyledComponent.variantConcat = styles.macaronMeta.variantConcat; 49 | StyledComponent.classes = ( 50 | variants: any, 51 | merge?: string, 52 | fn: any = styles 53 | ) => { 54 | const classes = new Set( 55 | classNames(fn(variants) + (merge ? ` ${merge}` : '')) 56 | ); 57 | 58 | if (Comp.classes) { 59 | for (const c of Comp.classes( 60 | variants, 61 | merge, 62 | Comp.variantConcat 63 | ) as string[]) { 64 | classes.add(c); 65 | } 66 | } 67 | 68 | return Array.from(classes); 69 | }; 70 | StyledComponent.selector = (variants: any) => { 71 | const classes = StyledComponent.classes( 72 | variants, 73 | undefined, 74 | styles.macaronMeta.variantConcat 75 | ); 76 | // first element isn't empty 77 | if (classes.length > 0 && classes[0].length > 0) { 78 | return '.' + classes.join('.'); 79 | } 80 | return classes.join('.'); 81 | }; 82 | 83 | return StyledComponent; 84 | } 85 | 86 | function classNames(className: string) { 87 | return className.split(' '); 88 | } 89 | -------------------------------------------------------------------------------- /packages/qwik/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node16", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "emitDeclarationOnly": true, 11 | }, 12 | "include": [ 13 | "src/**/*.ts", 14 | ], 15 | "exclude": [ 16 | "dist", 17 | "node_modules" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react and solidjs 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/react", 3 | "version": "1.5.3", 4 | "license": "MIT", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/mokshit06/macaron.git", 11 | "directory": "packages/react" 12 | }, 13 | "exports": { 14 | ".": { 15 | "import": "./dist/index.mjs", 16 | "require": "./dist/index.js" 17 | }, 18 | "./dist/*": "./dist/*", 19 | "./runtime": { 20 | "import": "./dist/runtime.mjs", 21 | "require": "./dist/runtime.js" 22 | } 23 | }, 24 | "dependencies": { 25 | "@macaron-css/core": "1.5.2" 26 | }, 27 | "files": [ 28 | "dist", 29 | "src" 30 | ], 31 | "peerDependencies": { 32 | "@vanilla-extract/recipes": "^0.2.5", 33 | "react": ">=17", 34 | "react-dom": ">=17" 35 | }, 36 | "devDependencies": { 37 | "@types/react": "^18.0.12", 38 | "@types/react-dom": "^18.0.6", 39 | "@vanilla-extract/recipes": "^0.2.5", 40 | "react": "^18.1.0", 41 | "react-dom": "^18.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/react/src/__snapshots__/runtime.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`base component renders correctly 1`] = `"
"`; 4 | 5 | exports[`inherit styled component 1`] = `"
"`; 6 | 7 | exports[`inherit styled component 2`] = `"
"`; 8 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, ComponentType } from 'react'; 2 | import { 3 | PatternOptions, 4 | RuntimeFn, 5 | VariantGroups, 6 | VariantSelection, 7 | } from '@macaron-css/core/types'; 8 | 9 | type StyledComponent< 10 | TProps = {}, 11 | Variants extends VariantGroups = {} 12 | > = ComponentType> & { 13 | variants: Array; 14 | selector: RuntimeFn; 15 | }; 16 | 17 | type IntrinsicProps = TComponent extends keyof JSX.IntrinsicElements 18 | ? JSX.IntrinsicElements[TComponent] 19 | : any; 20 | 21 | export function styled( 22 | component: ComponentType, 23 | options: PatternOptions 24 | ): StyledComponent, Variants>; 25 | 26 | export function styled< 27 | TProps, 28 | TComponent extends string | keyof JSX.IntrinsicElements, 29 | Variants extends VariantGroups = {} 30 | >( 31 | component: TComponent, 32 | options: PatternOptions 33 | ): StyledComponent< 34 | IntrinsicProps & VariantSelection, 35 | Variants 36 | >; 37 | 38 | export function styled(component: any, options: any): any { 39 | // const runtimeFn = recipe(options); 40 | 41 | // return addFunctionSerializer($$styled(component, runtimeFn as any), { 42 | // importPath: '@macaron-css/react/runtime', 43 | // args: [component, runtimeFn], 44 | // importName: '$$styled', 45 | // }); 46 | throw new Error( 47 | "This function shouldn't be there in your final code. If you're seeing this, there is probably some issue with your build config. If you think everything looks fine, then file an issue at https://github.com/mokshit06/macaron/issues" 48 | ); 49 | } 50 | 51 | export type StyleVariants> = 52 | T extends StyledComponent 53 | ? VariantSelection 54 | : unknown; 55 | -------------------------------------------------------------------------------- /packages/react/src/runtime.test.ts: -------------------------------------------------------------------------------- 1 | import { $$styled } from './runtime'; 2 | import { createElement } from 'react'; 3 | import { renderToString } from 'react-dom/server'; 4 | import { createRuntimeFn } from '../../core/src/create-runtime-fn'; 5 | 6 | function makeComponent() { 7 | return $$styled( 8 | 'div', 9 | createRuntimeFn({ 10 | defaultClassName: 'default', 11 | variantClassNames: { 12 | size: { 13 | sm: 'size_sm', 14 | md: 'size_md', 15 | lg: 'size_lg', 16 | }, 17 | color: { 18 | light: 'color_light', 19 | dark: 'color_dark', 20 | }, 21 | }, 22 | compoundVariants: [], 23 | defaultVariants: { 24 | size: 'sm', 25 | color: 'light', 26 | }, 27 | }) as any 28 | ); 29 | } 30 | 31 | test('component has variants', () => { 32 | const Component = makeComponent(); 33 | 34 | expect(Component.variants).toEqual(['size', 'color']); 35 | }); 36 | 37 | test('component as selector', () => { 38 | const Component = makeComponent(); 39 | 40 | expect(Component.toString()).toBe('.default'); 41 | expect(`${Component}`).toBe('.default'); 42 | 43 | expect(Component.selector({ size: 'md' })).toBe('.default.size_md'); 44 | expect(Component.selector({ size: 'md', color: 'dark' })).toBe( 45 | '.default.size_md.color_dark' 46 | ); 47 | }); 48 | 49 | test('base component renders correctly', () => { 50 | const Component = makeComponent(); 51 | 52 | expectRendersSnapshot( 53 | createElement(Component, { size: 'md', className: 'custom_extra_class' }) 54 | ); 55 | }); 56 | 57 | test('inherit styled component', () => { 58 | const Component = makeComponent(); 59 | const InheritedComponent = $$styled( 60 | Component, 61 | createRuntimeFn({ 62 | defaultClassName: 'inherited', 63 | variantClassNames: { 64 | border: { 65 | true: 'border_true', 66 | }, 67 | }, 68 | compoundVariants: [], 69 | defaultVariants: {}, 70 | }) as any 71 | ); 72 | 73 | // expect(InheritedComponent.toString()).toBe('.inherited.default'); 74 | // expect(`${InheritedComponent}`).toBe('.inherited.default'); 75 | 76 | expect(InheritedComponent.selector({ size: 'md' })).toBe( 77 | '.inherited.default.size_md' 78 | ); 79 | expect(InheritedComponent.selector({ size: 'md', color: 'dark' })).toBe( 80 | '.inherited.default.size_md.color_dark' 81 | ); 82 | expect(InheritedComponent.selector({ border: true })).toBe( 83 | '.inherited.border_true.default' 84 | ); 85 | 86 | expect(InheritedComponent.classes({})).toEqual(['inherited', 'default']); 87 | 88 | expectRendersSnapshot(createElement(InheritedComponent, {})); 89 | expectRendersSnapshot( 90 | createElement(InheritedComponent, { 91 | size: 'lg', 92 | className: 'custom_extra_cls', 93 | }) 94 | ); 95 | }); 96 | 97 | function expectRendersSnapshot(component: any) { 98 | expect(renderToString(component)).toMatchSnapshot(); 99 | } 100 | -------------------------------------------------------------------------------- /packages/react/src/runtime.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, createElement, forwardRef, FC } from 'react'; 2 | 3 | export function $$styled( 4 | Comp: any, 5 | styles: ((options?: any) => string) & { 6 | macaronMeta: { 7 | variants: string[]; 8 | defaultClassName: string; 9 | variantConcat(options: any): string; 10 | }; 11 | } 12 | ) { 13 | const StyledComponent: any = forwardRef(({ as, ...props }: any, ref) => { 14 | let CompToRender = as ?? Comp; 15 | const [variants, others] = useMemo(() => { 16 | const [classes, others]: any[] = [{}, {}]; 17 | 18 | for (const [key, value] of Object.entries(props)) { 19 | if (StyledComponent.variants.includes(key)) { 20 | classes[key] = value; 21 | } else { 22 | others[key] = value; 23 | } 24 | } 25 | 26 | return [classes, others]; 27 | }, [props]); 28 | const className = useMemo(() => { 29 | const classes = StyledComponent.classes(variants, props.className); 30 | return classes.join(' '); 31 | }, [variants, props.className]); 32 | 33 | if (typeof CompToRender === 'string') { 34 | return createElement(CompToRender, { ...others, className, ref }); 35 | } 36 | 37 | return createElement(CompToRender, { ...props, className, ref }); 38 | }); 39 | 40 | StyledComponent.displayName = `Macaron(${Comp})`; 41 | StyledComponent.toString = () => StyledComponent.selector(null); 42 | StyledComponent.variants = [ 43 | ...(styles.macaronMeta.variants ?? []), 44 | ...(Comp.variants ?? []), 45 | ]; 46 | StyledComponent.variantConcat = styles.macaronMeta.variantConcat; 47 | StyledComponent.classes = ( 48 | variants: any, 49 | merge?: string, 50 | fn: any = styles 51 | ) => { 52 | const classes = new Set( 53 | classNames(fn(variants) + (merge ? ` ${merge}` : '')) 54 | ); 55 | 56 | if (Comp.classes) { 57 | for (const c of Comp.classes( 58 | variants, 59 | merge, 60 | Comp.variantConcat 61 | ) as string[]) { 62 | classes.add(c); 63 | } 64 | } 65 | 66 | return Array.from(classes); 67 | }; 68 | StyledComponent.selector = (variants: any) => { 69 | const classes = StyledComponent.classes( 70 | variants, 71 | undefined, 72 | styles.macaronMeta.variantConcat 73 | ); 74 | // first element isn't empty 75 | if (classes.length > 0 && classes[0].length > 0) { 76 | return '.' + classes.join('.'); 77 | } 78 | return classes.join('.'); 79 | }; 80 | 81 | return StyledComponent; 82 | } 83 | 84 | function classNames(className: string) { 85 | return className.split(' '); 86 | } 87 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node16", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "emitDeclarationOnly": true, 11 | }, 12 | "include": [ 13 | "src/**/*.ts", 14 | ], 15 | "exclude": [ 16 | "dist", 17 | "node_modules" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/solid/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react and solidjs 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/solid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/solid", 3 | "version": "1.5.3", 4 | "license": "MIT", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/mokshit06/macaron.git", 9 | "directory": "packages/solid" 10 | }, 11 | "exports": { 12 | ".": { 13 | "types": "./dist/index.d.mts", 14 | "import": "./dist/index.mjs" 15 | }, 16 | "./dist/*": "./dist/*", 17 | "./runtime": { 18 | "types": "./dist/runtime.d.mts", 19 | "import": "./dist/runtime.mjs" 20 | } 21 | }, 22 | "dependencies": { 23 | "@macaron-css/core": "1.5.2" 24 | }, 25 | "devDependencies": { 26 | "@vanilla-extract/recipes": "^0.2.5", 27 | "solid-js": "^1.4.3" 28 | }, 29 | "peerDependencies": { 30 | "@vanilla-extract/recipes": "^0.2.5", 31 | "solid-js": "^1.4.3" 32 | }, 33 | "files": [ 34 | "dist", 35 | "src" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/solid/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Component, JSX, ParentComponent } from 'solid-js'; 2 | import { 3 | PatternOptions, 4 | VariantGroups, 5 | VariantSelection, 6 | RuntimeFn, 7 | } from '@macaron-css/core/types'; 8 | 9 | type IntrinsicProps = TComponent extends keyof JSX.IntrinsicElements 10 | ? JSX.IntrinsicElements[TComponent] 11 | : any; 12 | 13 | type StyledComponent< 14 | TProps = {}, 15 | Variants extends VariantGroups = {} 16 | > = ParentComponent & { 17 | variants: Array; 18 | selector: RuntimeFn; 19 | }; 20 | 21 | export function styled< 22 | TProps, 23 | TComponent extends keyof JSX.IntrinsicElements | string, 24 | Variants extends VariantGroups = {} 25 | >( 26 | component: TComponent, 27 | options: PatternOptions 28 | ): StyledComponent< 29 | IntrinsicProps & VariantSelection, 30 | Variants 31 | >; 32 | 33 | export function styled( 34 | component: Component, 35 | options: PatternOptions 36 | ): StyledComponent, Variants>; 37 | 38 | export function styled(component: any, options: any): (props: any) => any { 39 | // the following doesn't work because vanilla-extract's function serializer 40 | // cannot serialize complex functions like `$$styled` 41 | 42 | // const runtimeFn = recipe(options); 43 | 44 | // return addFunctionSerializer($$styled(component, runtimeFn as any), { 45 | // importPath: '@macaron-css/solid/runtime', 46 | // args: [component, runtimeFn], 47 | // importName: '$$styled', 48 | // }); 49 | 50 | throw new Error( 51 | "This function shouldn't be there in your final code. If you're seeing this, there is probably some issue with your build config. If you think everything looks fine, then file an issue at https://github.com/mokshit06/macaron/issues" 52 | ); 53 | } 54 | 55 | export type StyleVariants> = 56 | T extends StyledComponent 57 | ? VariantSelection 58 | : unknown; 59 | -------------------------------------------------------------------------------- /packages/solid/src/runtime.test.ts: -------------------------------------------------------------------------------- 1 | import { $$styled } from './runtime'; 2 | import { createRuntimeFn } from '../../core/src/create-runtime-fn'; 3 | import { createComponent } from 'solid-js'; 4 | 5 | function makeComponent() { 6 | return $$styled( 7 | 'div', 8 | createRuntimeFn({ 9 | defaultClassName: 'default', 10 | variantClassNames: { 11 | size: { 12 | sm: 'size_sm', 13 | md: 'size_md', 14 | lg: 'size_lg', 15 | }, 16 | color: { 17 | light: 'color_light', 18 | dark: 'color_dark', 19 | }, 20 | }, 21 | compoundVariants: [], 22 | defaultVariants: { 23 | size: 'sm', 24 | color: 'light', 25 | }, 26 | }) as any 27 | ); 28 | } 29 | 30 | test('component has variants', () => { 31 | const Component = makeComponent(); 32 | 33 | expect(Component.variants).toEqual(['size', 'color']); 34 | }); 35 | 36 | test('component as selector', () => { 37 | const Component = makeComponent(); 38 | 39 | expect(Component.toString()).toBe('.default'); 40 | expect(`${Component}`).toBe('.default'); 41 | 42 | expect(Component.selector({ size: 'md' })).toBe('.default.size_md'); 43 | expect(Component.selector({ size: 'md', color: 'dark' })).toBe( 44 | '.default.size_md.color_dark' 45 | ); 46 | }); 47 | 48 | test('base component renders correctly', () => { 49 | const Component = makeComponent(); 50 | 51 | hasClasses( 52 | createComponent(Component, { size: 'md', class: 'custom_extra_class' }), 53 | 'default size_md color_light custom_extra_class' 54 | ); 55 | }); 56 | 57 | test('inherit styled component', () => { 58 | const Component = makeComponent(); 59 | const InheritedComponent = $$styled( 60 | Component, 61 | createRuntimeFn({ 62 | defaultClassName: 'inherited', 63 | variantClassNames: { 64 | border: { 65 | true: 'border_true', 66 | }, 67 | }, 68 | compoundVariants: [], 69 | defaultVariants: {}, 70 | }) as any 71 | ); 72 | 73 | // expect(InheritedComponent.toString()).toBe('.inherited.default'); 74 | // expect(`${InheritedComponent}`).toBe('.inherited.default'); 75 | 76 | expect(InheritedComponent.selector({ size: 'md' })).toBe( 77 | '.inherited.default.size_md' 78 | ); 79 | expect(InheritedComponent.selector({ size: 'md', color: 'dark' })).toBe( 80 | '.inherited.default.size_md.color_dark' 81 | ); 82 | expect(InheritedComponent.selector({ border: true })).toBe( 83 | '.inherited.border_true.default' 84 | ); 85 | 86 | expect(InheritedComponent.classes({})).toEqual(['inherited', 'default']); 87 | 88 | hasClasses( 89 | createComponent(InheritedComponent, {}), 90 | 'default size_sm color_light inherited' 91 | ); 92 | hasClasses( 93 | createComponent(InheritedComponent, { 94 | size: 'lg', 95 | class: 'custom_extra_cls', 96 | }), 97 | 'default size_lg color_light inherited custom_extra_cls' 98 | ); 99 | }); 100 | 101 | test('inherit custom component', () => { 102 | const Comp = (props: { class: string }) => `${props.class} custom`; 103 | const InheritedComponent = $$styled( 104 | Comp, 105 | createRuntimeFn({ 106 | defaultClassName: 'double_inherited', 107 | variantClassNames: { 108 | border: { 109 | true: 'border_true', 110 | }, 111 | }, 112 | compoundVariants: [], 113 | defaultVariants: {}, 114 | }) as any 115 | ); 116 | 117 | expect(InheritedComponent.selector({})).toBe('.double_inherited'); 118 | expect( 119 | InheritedComponent.selector({ 120 | border: true, 121 | }) 122 | ).toBe('.double_inherited.border_true'); 123 | expectRenders( 124 | createComponent(InheritedComponent, {}), 125 | 'double_inherited custom' 126 | ); 127 | expectRenders( 128 | createComponent(InheritedComponent, { border: true }), 129 | 'double_inherited border_true custom' 130 | ); 131 | }); 132 | 133 | function hasClasses(component: any, classes: string) { 134 | expectRenders(component, `
`); 135 | } 136 | 137 | function expectRenders(component: any, output: string) { 138 | expect(component.t ?? component).toBe(output); 139 | } 140 | -------------------------------------------------------------------------------- /packages/solid/src/runtime.ts: -------------------------------------------------------------------------------- 1 | import { createComponent, createMemo, mergeProps, splitProps } from 'solid-js'; 2 | import { Dynamic } from 'solid-js/web'; 3 | 4 | export function $$styled( 5 | Comp: any, 6 | styles: ((options: any) => string) & { 7 | macaronMeta: { 8 | variants: string[]; 9 | defaultClassName: string; 10 | variantConcat(options: any): string; 11 | }; 12 | } 13 | ) { 14 | function StyledComponent(props: any) { 15 | const [variants, others] = splitProps(props, StyledComponent.variants); 16 | 17 | if (typeof Comp === 'string') { 18 | return createComponent( 19 | Dynamic as any, 20 | mergeProps(others, { 21 | component: Comp, 22 | get ['class']() { 23 | const classes = StyledComponent.classes(variants, props.class); 24 | return classes.join(' '); 25 | }, 26 | }) 27 | ); 28 | } 29 | 30 | return createComponent( 31 | Comp, 32 | mergeProps(props, { 33 | get ['class']() { 34 | const classes = StyledComponent.classes(variants, props.class); 35 | return classes.join(' '); 36 | }, 37 | }) 38 | ); 39 | } 40 | 41 | StyledComponent.toString = () => StyledComponent.selector(null); 42 | StyledComponent.variants = [ 43 | ...(styles.macaronMeta.variants ?? []), 44 | ...(Comp.variants ?? []), 45 | ]; 46 | StyledComponent.variantConcat = styles.macaronMeta.variantConcat; 47 | StyledComponent.classes = ( 48 | variants: any, 49 | merge?: string, 50 | fn: any = styles 51 | ) => { 52 | const classes = new Set( 53 | classNames(fn(variants) + (merge ? ` ${merge}` : '')) 54 | ); 55 | 56 | if (Comp.classes) { 57 | for (const c of Comp.classes( 58 | variants, 59 | merge, 60 | Comp.variantConcat 61 | ) as string[]) { 62 | classes.add(c); 63 | } 64 | } 65 | 66 | return Array.from(classes); 67 | }; 68 | StyledComponent.selector = (variants: any) => { 69 | const classes = StyledComponent.classes( 70 | variants, 71 | undefined, 72 | styles.macaronMeta.variantConcat 73 | ); 74 | // first element isn't empty 75 | if (classes.length > 0 && classes[0].length > 0) { 76 | return '.' + classes.join('.'); 77 | } 78 | return classes.join('.'); 79 | }; 80 | 81 | return StyledComponent; 82 | } 83 | 84 | function classNames(className: string) { 85 | return className.split(' '); 86 | } 87 | -------------------------------------------------------------------------------- /packages/solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node16", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "emitDeclarationOnly": true, 11 | }, 12 | "include": [ 13 | "src/**/*.ts", 14 | ], 15 | "exclude": [ 16 | "dist", 17 | "node_modules" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/vite/README.md: -------------------------------------------------------------------------------- 1 | # macaron 2 | 3 | macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind 4 | 5 | - Powered by **vanilla-extract** 6 | - Allows defining styles in the same file as components 7 | - Zero runtime builds 8 | - Supports both styled-components API and plain styling api that returns classes. 9 | - Stitches-like variants 10 | - First class typescript support 11 | - Out of box support for react and solidjs 12 | - Supports esbuild and vite (with hmr) 13 | 14 | ## Example 15 | 16 | ### Styled API 17 | 18 | ```jsx 19 | import { styled } from '@macaron-css/solid'; 20 | 21 | const StyledButton = styled('button', { 22 | base: { 23 | borderRadius: 6, 24 | }, 25 | variants: { 26 | color: { 27 | neutral: { background: 'whitesmoke' }, 28 | brand: { background: 'blueviolet' }, 29 | accent: { background: 'slateblue' }, 30 | }, 31 | size: { 32 | small: { padding: 12 }, 33 | medium: { padding: 16 }, 34 | large: { padding: 24 }, 35 | }, 36 | rounded: { 37 | true: { borderRadius: 999 }, 38 | }, 39 | }, 40 | compoundVariants: [ 41 | { 42 | variants: { 43 | color: 'neutral', 44 | size: 'large', 45 | }, 46 | style: { 47 | background: 'ghostwhite', 48 | }, 49 | }, 50 | ], 51 | 52 | defaultVariants: { 53 | color: 'accent', 54 | size: 'medium', 55 | }, 56 | }); 57 | 58 | // Use it like a regular solidjs component 59 | function App() { 60 | return ( 61 | 62 | Click me! 63 | 64 | ); 65 | } 66 | ``` 67 | 68 | ### Styling API 69 | 70 | The styling API is the same api is vanilla-extract, but allows styles to be defined in the same file, increasing the authoring experience. 71 | 72 | Check out [vanilla-extract docs](https://vanilla-extract.style/documentation/styling-api/) 73 | -------------------------------------------------------------------------------- /packages/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@macaron-css/vite", 3 | "version": "1.5.1", 4 | "type": "module", 5 | "license": "MIT", 6 | "exports": { 7 | ".": { 8 | "types": "./dist/index.d.mts", 9 | "import": "./dist/index.mjs" 10 | } 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mokshit06/macaron.git", 15 | "directory": "packages/vite" 16 | }, 17 | "dependencies": { 18 | "@macaron-css/integration": "1.5.1", 19 | "@vanilla-extract/integration": "^6.0.0", 20 | "@vanilla-extract/vite-plugin": "^3.1.6" 21 | }, 22 | "devDependencies": { 23 | "@vanilla-extract/recipes": "^0.2.5", 24 | "vite": "^3.0.0" 25 | }, 26 | "files": [ 27 | "dist", 28 | "src" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/vite/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | babelTransform, 3 | compile, 4 | BabelOptions, 5 | } from '@macaron-css/integration'; 6 | import { processVanillaFile } from '@vanilla-extract/integration'; 7 | import fs from 'fs'; 8 | import { join, resolve } from 'path'; 9 | import { 10 | normalizePath, 11 | PluginOption, 12 | ResolvedConfig, 13 | ViteDevServer, 14 | } from 'vite'; 15 | 16 | const extractedCssFileFilter = /extracted_(.*)\.css\.ts(\?used)?$/; 17 | 18 | export function macaronVitePlugin(options?: { 19 | babel?: BabelOptions; 20 | }): PluginOption { 21 | let config: ResolvedConfig; 22 | let server: ViteDevServer; 23 | const cssMap = new Map(); 24 | const resolverCache = new Map(); 25 | const resolvers = new Map(); 26 | const idToPluginData = new Map>(); 27 | 28 | const virtualExt = '.vanilla.css'; 29 | 30 | return { 31 | name: 'macaron-css-vite', 32 | enforce: 'pre', 33 | buildStart() { 34 | // resolvers.clear(); 35 | // idToPluginData.clear(); 36 | // resolverCache.clear(); 37 | // cssMap.clear(); 38 | }, 39 | configureServer(_server) { 40 | server = _server; 41 | }, 42 | async configResolved(resolvedConfig) { 43 | config = resolvedConfig; 44 | }, 45 | resolveId(id, importer, options) { 46 | if (id.startsWith('\0')) return; 47 | 48 | if (extractedCssFileFilter.test(id)) { 49 | const normalizedId = id.startsWith('/') ? id.slice(1) : id; 50 | let resolvedPath = normalizePath(join(importer!, '..', normalizedId)); 51 | 52 | if (!resolvers.has(resolvedPath)) { 53 | return; 54 | } 55 | 56 | return resolvedPath; 57 | } 58 | 59 | if (id.endsWith(virtualExt)) { 60 | const normalizedId = id.startsWith('/') ? id.slice(1) : id; 61 | 62 | const key = normalizePath(resolve(config.root, normalizedId)); 63 | if (cssMap.has(key)) { 64 | return key; 65 | } 66 | } 67 | }, 68 | async load(id, options) { 69 | if (id.startsWith('\0')) return; 70 | 71 | if (extractedCssFileFilter.test(id)) { 72 | let normalizedId = customNormalize(id); 73 | let pluginData = idToPluginData.get(normalizedId); 74 | 75 | if (!pluginData) { 76 | return null; 77 | } 78 | 79 | const resolverContents = resolvers.get(pluginData.path); 80 | 81 | if (!resolverContents) { 82 | return null; 83 | } 84 | 85 | idToPluginData.set(id, { 86 | ...idToPluginData.get(id), 87 | filePath: id, 88 | originalPath: pluginData.mainFilePath, 89 | }); 90 | 91 | return resolverContents; 92 | } 93 | 94 | if (id.endsWith(virtualExt)) { 95 | const cssFileId = normalizePath(resolve(config.root, id)); 96 | const css = cssMap.get(cssFileId); 97 | 98 | if (typeof css !== 'string') { 99 | return; 100 | } 101 | 102 | return css; 103 | } 104 | }, 105 | async transform(code, id, ssrParam) { 106 | if (id.startsWith('\0')) return; 107 | 108 | const moduleInfo = idToPluginData.get(id); 109 | 110 | let ssr: boolean | undefined; 111 | 112 | if (typeof ssrParam === 'boolean') { 113 | ssr = ssrParam; 114 | } else { 115 | ssr = ssrParam?.ssr; 116 | } 117 | 118 | // is returned from extracted_HASH.css.ts 119 | if ( 120 | moduleInfo && 121 | moduleInfo.originalPath && 122 | moduleInfo.filePath && 123 | extractedCssFileFilter.test(id) 124 | ) { 125 | const { source, watchFiles } = await compile({ 126 | filePath: moduleInfo.filePath, 127 | cwd: config.root, 128 | originalPath: moduleInfo.originalPath, 129 | contents: code, 130 | resolverCache, 131 | externals: [], 132 | }); 133 | 134 | for (const file of watchFiles) { 135 | if (extractedCssFileFilter.test(file)) { 136 | continue; 137 | } 138 | // In start mode, we need to prevent the file from rewatching itself. 139 | // If it's a `build --watch`, it needs to watch everything. 140 | if (config.command === 'build' || file !== id) { 141 | this.addWatchFile(file); 142 | } 143 | } 144 | 145 | try { 146 | const contents = await processVanillaFile({ 147 | source, 148 | filePath: moduleInfo.filePath, 149 | identOption: 150 | undefined ?? (config.mode === 'production' ? 'short' : 'debug'), 151 | serializeVirtualCssPath: async ({ fileScope, source }) => { 152 | const id: string = `${fileScope.filePath}${virtualExt}`; 153 | const cssFileId = normalizePath(resolve(config.root, id)); 154 | 155 | if (server) { 156 | const { moduleGraph } = server; 157 | const moduleId = normalizePath(join(config.root, id)); 158 | const module = moduleGraph.getModuleById(moduleId); 159 | 160 | if (module) { 161 | moduleGraph.invalidateModule(module); 162 | module.lastHMRTimestamp = 163 | module.lastInvalidationTimestamp || Date.now(); 164 | } 165 | } 166 | 167 | cssMap.set(cssFileId, source); 168 | 169 | return `import "${id}";`; 170 | }, 171 | }); 172 | 173 | return contents; 174 | } catch (error) { 175 | throw error; 176 | } 177 | } 178 | 179 | if (/(j|t)sx?(\?used)?$/.test(id) && !id.endsWith('.vanilla.js')) { 180 | if (id.includes('node_modules')) return; 181 | 182 | // gets handled by @vanilla-extract/vite-plugin 183 | if (id.endsWith('.css.ts')) return; 184 | 185 | try { 186 | await fs.promises.access(id, fs.constants.F_OK); 187 | } catch { 188 | // probably a virtual file, to be handled by other plugin 189 | return; 190 | } 191 | 192 | const { 193 | code, 194 | result: [file, cssExtract], 195 | } = await babelTransform(id, options?.babel); 196 | 197 | if (!cssExtract || !file) return null; 198 | 199 | if (config.command === 'build' && config.build.watch) { 200 | this.addWatchFile(id); 201 | } 202 | 203 | let resolvedCssPath = normalizePath(join(id, '..', file)); 204 | 205 | if (server && resolvers.has(resolvedCssPath)) { 206 | const { moduleGraph } = server; 207 | 208 | const module = moduleGraph.getModuleById(resolvedCssPath); 209 | if (module) { 210 | moduleGraph.invalidateModule(module); 211 | } 212 | } 213 | 214 | const normalizedCssPath = customNormalize(resolvedCssPath); 215 | 216 | resolvers.set(resolvedCssPath, cssExtract); 217 | resolverCache.delete(id); 218 | idToPluginData.delete(id); 219 | idToPluginData.delete(normalizedCssPath); 220 | 221 | idToPluginData.set(id, { 222 | ...idToPluginData.get(id), 223 | mainFilePath: id, 224 | }); 225 | idToPluginData.set(normalizedCssPath, { 226 | ...idToPluginData.get(normalizedCssPath), 227 | mainFilePath: id, 228 | path: resolvedCssPath, 229 | }); 230 | 231 | return { 232 | code, 233 | map: { mappings: '' }, 234 | }; 235 | } 236 | 237 | return null; 238 | }, 239 | }; 240 | } 241 | 242 | function customNormalize(path: string) { 243 | return path.startsWith('/') ? path.slice(1) : path; 244 | } 245 | -------------------------------------------------------------------------------- /packages/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node16", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "emitDeclarationOnly": true, 11 | }, 12 | "include": [ 13 | "src/**/*.ts", 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | "build" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { join, posix } from 'path'; 2 | import { build as tsup } from 'tsup'; 3 | 4 | const packages: Array<[string, { entryPoints: string[]; esmOnly?: boolean }]> = 5 | [ 6 | ['packages/babel', { entryPoints: ['src/index.ts'] }], 7 | ['packages/integration', { entryPoints: ['src/index.ts'] }], 8 | [ 9 | 'packages/vite', 10 | { 11 | entryPoints: ['src/index.ts'], 12 | esmOnly: true, 13 | }, 14 | ], 15 | ['packages/esbuild', { entryPoints: ['src/index.ts'] }], 16 | [ 17 | 'packages/core', 18 | { 19 | entryPoints: [ 20 | 'src/index.ts', 21 | 'src/create-runtime-fn.ts', 22 | 'src/dynamic.ts', 23 | 'src/types.ts', 24 | ], 25 | }, 26 | ], 27 | [ 28 | 'packages/qwik', 29 | { 30 | entryPoints: ['src/index.ts', 'src/runtime.ts'], 31 | esmOnly: true, 32 | }, 33 | ], 34 | ['packages/react', { entryPoints: ['src/index.ts', 'src/runtime.ts'] }], 35 | [ 36 | 'packages/solid', 37 | { 38 | entryPoints: ['src/index.ts', 'src/runtime.ts'], 39 | esmOnly: true, 40 | }, 41 | ], 42 | ]; 43 | 44 | async function build() { 45 | const withDts = !process.argv.includes('--no-dts'); 46 | const watch = process.argv.includes('--watch'); 47 | 48 | for (const [packageDir, { entryPoints, esmOnly }] of packages) { 49 | try { 50 | await tsup({ 51 | entry: entryPoints.map(entryPoint => 52 | // tsup has some weird bug where it can't resolve backslashes 53 | posix.join(packageDir, entryPoint) 54 | ), 55 | format: esmOnly ? 'esm' : ['cjs', 'esm'], 56 | bundle: true, 57 | dts: withDts, 58 | sourcemap: true, 59 | outDir: join(packageDir, 'dist'), 60 | skipNodeModulesBundle: true, 61 | watch, 62 | }); 63 | } catch (e) { 64 | console.error(e); 65 | } 66 | } 67 | } 68 | 69 | build(); 70 | -------------------------------------------------------------------------------- /scripts/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { BranchConfig, Package } from './types'; 3 | 4 | // TODO: List your npm packages here. The first package will be used as the versioner. 5 | export const packages: Package[] = [ 6 | { 7 | name: '@macaron-css/core', 8 | packageDir: 'core', 9 | srcDir: 'src', 10 | }, 11 | { 12 | name: '@macaron-css/react', 13 | packageDir: 'react', 14 | srcDir: 'src', 15 | }, 16 | { 17 | name: '@macaron-css/qwik', 18 | packageDir: 'qwik', 19 | srcDir: 'src', 20 | }, 21 | { 22 | name: '@macaron-css/solid', 23 | packageDir: 'solid', 24 | srcDir: 'src', 25 | }, 26 | { 27 | name: '@macaron-css/integration', 28 | packageDir: 'integration', 29 | srcDir: 'src', 30 | dependencies: ['@macaron-css/babel'], 31 | }, 32 | { 33 | name: '@macaron-css/babel', 34 | packageDir: 'babel', 35 | srcDir: 'src', 36 | }, 37 | { 38 | name: '@macaron-css/vite', 39 | packageDir: 'vite', 40 | srcDir: 'src', 41 | dependencies: ['@macaron-css/integration'], 42 | }, 43 | { 44 | name: '@macaron-css/esbuild', 45 | packageDir: 'esbuild', 46 | srcDir: 'src', 47 | peerDependencies: ['@macaron-css/integration'], 48 | }, 49 | ]; 50 | 51 | export const latestBranch = 'main'; 52 | 53 | export const branchConfigs: Record = { 54 | main: { 55 | prerelease: false, 56 | ghRelease: true, 57 | }, 58 | // next: { 59 | // prerelease: true, 60 | // ghRelease: true, 61 | // }, 62 | // beta: { 63 | // prerelease: true, 64 | // ghRelease: true, 65 | // }, 66 | // alpha: { 67 | // prerelease: true, 68 | // ghRelease: true, 69 | // }, 70 | }; 71 | 72 | export const rootDir = path.resolve(__dirname, '..'); 73 | export const examplesDirs = [ 74 | 'examples/react', 75 | 'examples/solid', 76 | 'examples/solid-start', 77 | 'examples/vanilla', 78 | 'examples/vite', 79 | ]; 80 | -------------------------------------------------------------------------------- /scripts/types.ts: -------------------------------------------------------------------------------- 1 | export type Commit = { 2 | commit: CommitOrTree; 3 | tree: CommitOrTree; 4 | author: AuthorOrCommitter; 5 | committer: AuthorOrCommitter; 6 | subject: string; 7 | body: string; 8 | parsed: Parsed; 9 | }; 10 | 11 | export type CommitOrTree = { 12 | long: string; 13 | short: string; 14 | }; 15 | 16 | export type AuthorOrCommitter = { 17 | name: string; 18 | email: string; 19 | date: string; 20 | }; 21 | 22 | export type Parsed = { 23 | type: string; 24 | scope?: string | null; 25 | subject: string; 26 | merge?: null; 27 | header: string; 28 | body?: null; 29 | footer?: null; 30 | notes?: null[] | null; 31 | references?: null[] | null; 32 | mentions?: null[] | null; 33 | revert?: null; 34 | raw: string; 35 | }; 36 | 37 | export type Package = { 38 | name: string; 39 | packageDir: string; 40 | srcDir: string; 41 | dependencies?: string[]; 42 | peerDependencies?: string[]; 43 | }; 44 | 45 | export type BranchConfig = { 46 | prerelease: boolean; 47 | ghRelease: boolean; 48 | }; 49 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /site/CNAME: -------------------------------------------------------------------------------- 1 | macaron.js.org -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "npm run server", 4 | "prod": "npm run build && npm run server:prod", 5 | "build": "vite build", 6 | "server": "ts-node ./server", 7 | "server:prod": "cross-env NODE_ENV=production ts-node ./server" 8 | }, 9 | "dependencies": { 10 | "@babel/standalone": "^7.20.6", 11 | "@code-hike/mdx": "^0.7.4", 12 | "@macaron-css/babel": "^1.1.0", 13 | "@macaron-css/core": "^1.0.0", 14 | "@macaron-css/react": "^1.0.1", 15 | "@mdx-js/rollup": "^2.1.5", 16 | "@types/compression": "^1.7.2", 17 | "@types/express": "^4.17.14", 18 | "@types/node": "^18.11.9", 19 | "@types/react": "^18.0.8", 20 | "@types/react-dom": "^18.0.3", 21 | "@vitejs/plugin-react": "^2.0.1", 22 | "assert": "^2.0.0", 23 | "buffer": "^6.0.3", 24 | "compression": "^1.7.4", 25 | "cross-env": "^7.0.3", 26 | "express": "^4.18.1", 27 | "hast-util-to-html": "^8.0.3", 28 | "monaco-editor": "^0.34.1", 29 | "monaco-themes": "^0.4.3", 30 | "react": "^18.1.0", 31 | "react-dom": "^18.1.0", 32 | "refractor": "^4.8.0", 33 | "rehype-autolink-headings": "^6.1.1", 34 | "rehype-slug": "^5.1.0", 35 | "shiki": "^0.11.1", 36 | "sirv": "^2.0.2", 37 | "ts-node": "^10.7.0", 38 | "typescript": "^4.6.4", 39 | "use-debounce": "^9.0.2", 40 | "vite": "^3.0.9", 41 | "vite-plugin-ssr": "^0.4.54" 42 | }, 43 | "devDependencies": { 44 | "@macaron-css/vite": "^1.1.0", 45 | "@types/babel__standalone": "^7.1.4" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /site/public/macaron-name.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /site/public/macaron-stacked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /site/public/macaron-symbol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /site/public/share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macaron-css/macaron/bc8c481269b1be644e2588f163a5fe68b19ddfd7/site/public/share.jpg -------------------------------------------------------------------------------- /site/renderer/Link.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { usePageContext } from './usePageContext'; 3 | 4 | export { Link }; 5 | 6 | function Link(props: { 7 | href?: string; 8 | className?: string; 9 | children: React.ReactNode; 10 | }) { 11 | const pageContext = usePageContext(); 12 | const className = [ 13 | props.className, 14 | pageContext.urlPathname === props.href && 'is-active', 15 | ] 16 | .filter(Boolean) 17 | .join(' '); 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /site/renderer/PageShell.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PageContextProvider } from './usePageContext'; 3 | import type { PageContext } from './types'; 4 | import { globalStyle } from '@macaron-css/core'; 5 | import '@code-hike/mdx/styles'; 6 | 7 | globalStyle('*', { 8 | margin: 0, 9 | padding: 0, 10 | boxSizing: 'border-box', 11 | }); 12 | 13 | globalStyle('body', { 14 | minHeight: '100vh', 15 | background: 'linear-gradient(to bottom, #0a0d1bf3 20%, #171728f2)', 16 | backgroundAttachment: 'fixed', 17 | }); 18 | 19 | globalStyle('a', { 20 | textDecoration: 'none', 21 | }); 22 | 23 | globalStyle('#page-view', { 24 | display: 'flex', 25 | height: '100%', 26 | width: '100%', 27 | flexDirection: 'column', 28 | minHeight: '100vh', 29 | // justifyContent: 'center', 30 | // alignContent: 'center', 31 | fontFamily: "'Public Sans', system-ui", 32 | }); 33 | 34 | export function PageShell({ 35 | children, 36 | pageContext, 37 | }: { 38 | children: React.ReactNode; 39 | pageContext: PageContext; 40 | }) { 41 | return ( 42 | 43 | 44 | {children} 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /site/renderer/_default.page.client.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { hydrateRoot } from 'react-dom/client'; 3 | import { PageShell } from './PageShell'; 4 | import type { PageContextClient } from './types'; 5 | 6 | export { render }; 7 | 8 | async function render(pageContext: PageContextClient) { 9 | const { Page, pageProps } = pageContext; 10 | hydrateRoot( 11 | document.getElementById('page-view')!, 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export const clientRouting = true; 19 | -------------------------------------------------------------------------------- /site/renderer/_default.page.server.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOMServer from 'react-dom/server'; 2 | import React from 'react'; 3 | import { PageShell } from './PageShell'; 4 | import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'; 5 | import type { PageContextServer } from './types'; 6 | 7 | export { render }; 8 | // See https://vite-plugin-ssr.com/data-fetching 9 | export const passToClient = ['pageProps', 'urlPathname']; 10 | 11 | async function render(pageContext: PageContextServer) { 12 | const { Page, pageProps } = pageContext; 13 | const pageHtml = ReactDOMServer.renderToString( 14 | 15 | 16 | 17 | ); 18 | 19 | // See https://vite-plugin-ssr.com/head 20 | const { documentProps } = pageContext.exports; 21 | const title = 22 | (documentProps && documentProps.title) || 23 | 'macaron — CSS-in-JS with zero-runtime'; 24 | const desc = 25 | (documentProps && documentProps.description) || 26 | 'Typesafe CSS-in-JS with zero runtime, colocation, maximum safety and productivity. Macaron is a new compile time CSS-in-JS library with type safety.'; 27 | 28 | const documentHtml = escapeInject` 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 45 | 49 | ${title} 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
${dangerouslySkipEscape(pageHtml)}
59 | 60 | `; 61 | 62 | return { 63 | documentHtml, 64 | pageContext: { 65 | // We can add some `pageContext` here, which is useful if we want to do page redirection https://vite-plugin-ssr.com/page-redirection 66 | }, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /site/renderer/_error.page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export { Page } 4 | 5 | function Page({ is404 }: { is404: boolean }) { 6 | if (is404) { 7 | return ( 8 | <> 9 |

404 Page Not Found

10 |

This page could not be found.

11 | 12 | ) 13 | } else { 14 | return ( 15 | <> 16 |

500 Internal Server Error

17 |

Something went wrong.

18 | 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /site/renderer/types.ts: -------------------------------------------------------------------------------- 1 | export type { PageContextServer } 2 | export type { PageContextClient } 3 | export type { PageContext } 4 | export type { PageProps } 5 | 6 | import type { PageContextBuiltIn } from 'vite-plugin-ssr' 7 | // import type { PageContextBuiltInClient } from 'vite-plugin-ssr/client/router' // When using Client Routing 8 | import type { PageContextBuiltInClient } from 'vite-plugin-ssr/client' // When using Server Routing 9 | 10 | type Page = (pageProps: PageProps) => React.ReactElement 11 | type PageProps = {} 12 | 13 | export type PageContextCustom = { 14 | Page: Page 15 | pageProps?: PageProps 16 | urlPathname: string 17 | exports: { 18 | documentProps?: { 19 | title?: string 20 | description?: string 21 | } 22 | } 23 | } 24 | 25 | type PageContextServer = PageContextBuiltIn & PageContextCustom 26 | type PageContextClient = PageContextBuiltInClient & PageContextCustom 27 | 28 | type PageContext = PageContextClient | PageContextServer 29 | -------------------------------------------------------------------------------- /site/renderer/usePageContext.tsx: -------------------------------------------------------------------------------- 1 | // `usePageContext` allows us to access `pageContext` in any React component. 2 | // See https://vite-plugin-ssr.com/pageContext-anywhere 3 | 4 | import React, { useContext } from 'react' 5 | import type { PageContext } from './types' 6 | 7 | export { PageContextProvider } 8 | export { usePageContext } 9 | 10 | const Context = React.createContext(undefined as any) 11 | 12 | function PageContextProvider({ pageContext, children }: { pageContext: PageContext; children: React.ReactNode }) { 13 | return {children} 14 | } 15 | 16 | function usePageContext() { 17 | const pageContext = useContext(Context) 18 | return pageContext 19 | } 20 | -------------------------------------------------------------------------------- /site/server/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import compression from 'compression' 3 | import { renderPage } from 'vite-plugin-ssr' 4 | 5 | const isProduction = process.env.NODE_ENV === 'production' 6 | const root = `${__dirname}/..` 7 | 8 | startServer() 9 | 10 | async function startServer() { 11 | const app = express() 12 | 13 | app.use(compression()) 14 | 15 | if (isProduction) { 16 | const sirv = require('sirv') 17 | app.use(sirv(`${root}/dist/client`)) 18 | } else { 19 | const vite = require('vite') 20 | const viteDevMiddleware = ( 21 | await vite.createServer({ 22 | root, 23 | server: { middlewareMode: true } 24 | }) 25 | ).middlewares 26 | app.use(viteDevMiddleware) 27 | } 28 | 29 | app.get('*', async (req, res, next) => { 30 | const pageContextInit = { 31 | urlOriginal: req.originalUrl 32 | } 33 | const pageContext = await renderPage(pageContextInit) 34 | const { httpResponse } = pageContext 35 | if (!httpResponse) return next() 36 | const { body, statusCode, contentType, earlyHints } = httpResponse 37 | if (res.writeEarlyHints) res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) }) 38 | res.status(statusCode).type(contentType).send(body) 39 | }) 40 | 41 | const port = process.env.PORT || 3000 42 | app.listen(port) 43 | console.log(`Server running at http://localhost:${port}`) 44 | } 45 | -------------------------------------------------------------------------------- /site/src/code-examples/home.jsx: -------------------------------------------------------------------------------- 1 | const Button = styled('button', { 2 | base: { 3 | borderRadius: 6, 4 | }, 5 | variants: { 6 | color: { 7 | neutral: { background: 'whitesmoke' }, 8 | accent: { background: 'slateblue' }, 9 | }, 10 | rounded: { 11 | true: { borderRadius: 999 }, 12 | }, 13 | }, 14 | compoundVariants: [ 15 | { 16 | variants: { 17 | color: 'neutral', 18 | rounded: true, 19 | }, 20 | style: { background: 'ghostwhite' }, 21 | }, 22 | ], 23 | defaultVariants: { 24 | color: 'accent', 25 | }, 26 | }); 27 | 28 | ; 31 | -------------------------------------------------------------------------------- /site/src/components/button.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@macaron-css/react'; 2 | import { screens } from '../theme'; 3 | 4 | export const Button = styled('button', { 5 | base: { 6 | borderRadius: '50px', 7 | padding: '13px 25px', 8 | // marginTop: '10px', 9 | fontSize: '1.2rem', 10 | cursor: 'pointer', 11 | border: 'none', 12 | '@media': { [screens.sm]: { fontSize: '1rem' } }, 13 | }, 14 | variants: { 15 | color: { 16 | primary: { 17 | color: 'white', 18 | background: '#ff4089', 19 | }, 20 | secondary: { 21 | color: '#ff4089', 22 | background: 'white', 23 | }, 24 | }, 25 | }, 26 | defaultVariants: { 27 | color: 'primary', 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /site/src/components/code-block.tsx: -------------------------------------------------------------------------------- 1 | import { refractor } from 'refractor/lib/core'; 2 | import js from 'refractor/lang/javascript'; 3 | import jsx from 'refractor/lang/jsx'; 4 | import bash from 'refractor/lang/bash'; 5 | import css from 'refractor/lang/css'; 6 | import diff from 'refractor/lang/diff'; 7 | import { toHtml } from 'hast-util-to-html'; 8 | import { macaron$ } from '@macaron-css/core'; 9 | 10 | export function highlight(contents: string) { 11 | refractor.register(js); 12 | refractor.register(jsx); 13 | refractor.register(bash); 14 | refractor.register(css); 15 | refractor.register(diff); 16 | 17 | return toHtml(refractor.highlight(contents, 'jsx')); 18 | } 19 | 20 | // export const res = 21 | -------------------------------------------------------------------------------- /site/src/components/pre.tsx: -------------------------------------------------------------------------------- 1 | import { globalStyle, macaron$ } from '@macaron-css/core'; 2 | import { styled } from '@macaron-css/react'; 3 | import { screens } from '../theme'; 4 | 5 | export const Pre = styled('pre', { 6 | base: { 7 | boxSizing: 'border-box', 8 | borderRadius: '8px', 9 | padding: '20px ', 10 | // overflow: 'auto', 11 | fontSize: '15px', 12 | lineHeight: '19px', 13 | whiteSpace: 'pre', 14 | position: 'relative', 15 | color: 'hsl(210 6.0% 93.0%)', 16 | backdropFilter: 'brightness(70%) saturate(120%)', 17 | maxHeight: '80vh', 18 | '@media': { 19 | [screens.lg]: { fontSize: '13px' }, 20 | }, 21 | }, 22 | }); 23 | 24 | globalStyle(`${Pre} code`, { 25 | fontFamily: "'JetBrains Mono', monospace !important", 26 | }); 27 | 28 | macaron$(() => { 29 | const styles = { 30 | '.token.parameter': { 31 | color: '#ecedee', 32 | }, 33 | '.token.tag, .token.class-name, .token.selector, .token.selector .class, .token.function': 34 | { 35 | color: '#bcbcd2', 36 | }, 37 | '.token.attr-value, .token.class, .token.string, .token.number, .token.unit, .token.color, .token.boolean': 38 | { 39 | color: '#ff7cae', 40 | }, 41 | 42 | '.token.attr-name, .token.keyword, .token.rule, .token.operator, .token.pseudo-class, .token.important': 43 | { 44 | color: '#EC2B6C', 45 | }, 46 | 47 | '.token.punctuation, .token.module, .token.property': { 48 | color: '#bcbcd2', 49 | }, 50 | 51 | '.token.comment': { 52 | color: '#798086', 53 | }, 54 | 55 | '.token.atapply .token:not(.rule):not(.important)': { 56 | color: 'inherit', 57 | }, 58 | 59 | '.language-shell .token:not(.comment)': { 60 | color: 'inherit', 61 | }, 62 | 63 | '.language-css .token.function': { 64 | color: 'inherit', 65 | }, 66 | 67 | '.token.deleted:not(.prefix), .token.inserted:not(.prefix)': { 68 | display: 'block', 69 | padding: '0 $4', 70 | margin: '0 -20px', 71 | }, 72 | 73 | '.token.deleted:not(.prefix)': { 74 | color: 'hsl(358deg 100% 70%)', 75 | }, 76 | 77 | '.token.inserted:not(.prefix)': { 78 | color: '$$added', 79 | }, 80 | 81 | '.token.deleted.prefix, .token.inserted.prefix': { 82 | userSelect: 'none', 83 | }, 84 | 85 | // Line numbers 86 | // '&[data-line-numbers=true]': { 87 | // '.highlight-line': { 88 | // position: 'relative', 89 | // paddingLeft: '$4', 90 | 91 | // '&::before': { 92 | // content: 'attr(data-line)', 93 | // position: 'absolute', 94 | // left: -5, 95 | // top: 0, 96 | // color: '$$lineNumbers', 97 | // }, 98 | // }, 99 | // }, 100 | 101 | // Styles for highlighted lines 102 | '.highlight-line': { 103 | // '&, *': { 104 | // transition: 'color 150ms ease', 105 | // }, 106 | // '&[data-highlighted=false]': { 107 | // '&, *': { 108 | // color: '$$fadedLines', 109 | // }, 110 | // }, 111 | }, 112 | '.typewriter': { 113 | opacity: 0, 114 | }, 115 | }; 116 | 117 | for (const [selector, style] of Object.entries(styles)) { 118 | globalStyle(`${Pre} ${selector}`, style as any); 119 | } 120 | }); 121 | -------------------------------------------------------------------------------- /site/src/pages/docs/docs-layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren, useState } from 'react'; 2 | import { globalStyle, style } from '@macaron-css/core'; 3 | import { styled } from '@macaron-css/react'; 4 | import { Link } from '../../../renderer/Link'; 5 | import examplesMeta from './examples/meta.json'; 6 | 7 | globalStyle('blockquote', { 8 | padding: '0.8rem', 9 | borderLeft: '2px solid #ff4089', 10 | background: '#ffffff10', 11 | }); 12 | 13 | globalStyle('blockquote p', { 14 | fontSize: '1rem !important', 15 | }); 16 | 17 | globalStyle('#docs p code', { 18 | background: 'rgba(255,255,255, 0.2)', 19 | padding: '2px 4px', 20 | borderRadius: '5px', 21 | color: 'white', 22 | }); 23 | 24 | globalStyle('#docs p a', { 25 | color: 'white', 26 | textDecoration: 'underline', 27 | textUnderlineOffset: 3, 28 | fontWeight: 500, 29 | }); 30 | 31 | globalStyle('.ch-scrollycoding-step-content', { 32 | padding: '1rem !important', 33 | margin: '0.5rem 0 !important', 34 | border: '1px solid #7977af2b !important', 35 | }); 36 | 37 | globalStyle('.ch-codeblock, .ch-codegroup', { 38 | border: '1px solid #3f3e63 !important', 39 | }); 40 | 41 | globalStyle('.ch-scrollycoding-step-content[data-selected]', { 42 | border: '2px solid #ff4089 !important', 43 | backdropFilter: 'brightness(80%) saturate(120%) !important', 44 | }); 45 | 46 | globalStyle('.ch-codegroup .ch-editor-button, .ch-codeblock .ch-code-button', { 47 | display: 'none', 48 | }); 49 | 50 | globalStyle( 51 | '.ch-codegroup:hover .ch-editor-button, .ch-codeblock:hover .ch-code-button', 52 | { 53 | display: 'block', 54 | } 55 | ); 56 | 57 | globalStyle('.bundler.ch-spotlight', { 58 | flexDirection: 'column', 59 | alignItems: 'stretch', 60 | }); 61 | 62 | globalStyle('.bundler.ch-spotlight .ch-spotlight-tabs', { 63 | flexFlow: 'row', 64 | }); 65 | 66 | globalStyle('.bundler.ch-spotlight .ch-spotlight-tabs .ch-spotlight-tab', { 67 | border: '0 !important', 68 | borderBottom: '1px solid #EFEFEF !important', 69 | borderRadius: '0 !important', 70 | marginRight: '1rem', 71 | cursor: 'pointer', 72 | }); 73 | 74 | globalStyle( 75 | '.bundler.ch-spotlight .ch-spotlight-tabs .ch-spotlight-tab:hover, .bundler.ch-spotlight .ch-spotlight-tabs .ch-spotlight-tab[data-selected]', 76 | { 77 | color: '#ff4089 !important', 78 | borderColor: '#ff4089 !important', 79 | } 80 | ); 81 | 82 | globalStyle( 83 | '.bundler.ch-spotlight .ch-spotlight-sticker, .bundler.ch-spotlight .ch-spotlight-sticker .ch-code-parent', 84 | { 85 | width: 'auto', 86 | } 87 | ); 88 | 89 | const MarkdownView = styled('div', { 90 | base: { 91 | width: '100%', 92 | position: 'relative', 93 | padding: '2rem', 94 | fontFamily: 'system-ui', 95 | color: 'white', 96 | right: '5px', 97 | lineHeight: 1, 98 | }, 99 | }); 100 | 101 | globalStyle(`${MarkdownView} h1`, { 102 | fontSize: '2.5rem', 103 | fontWeight: '600', 104 | backgroundClip: 'text', 105 | backgroundImage: 'linear-gradient(60deg, #ff4089, #ff81b1)', 106 | WebkitBackgroundClip: 'text', 107 | color: 'transparent', 108 | margin: '0.5rem 0 2rem', 109 | paddingBottom: '10px', 110 | lineHeight: '2rem', 111 | }); 112 | 113 | globalStyle(`${MarkdownView} h2`, { 114 | fontSize: '2rem', 115 | margin: '2rem 0 1rem', 116 | fontWeight: '500', 117 | backgroundClip: 'text', 118 | backgroundImage: 'linear-gradient(60deg, #ff4089, #ff81b1)', 119 | WebkitBackgroundClip: 'text', 120 | paddingBottom: '10px', 121 | color: 'transparent', 122 | }); 123 | 124 | globalStyle(`${MarkdownView} p`, { 125 | fontSize: '1.1rem', 126 | fontWeight: 300, 127 | lineHeight: 1.5, 128 | }); 129 | 130 | globalStyle(`${MarkdownView} h3`, { 131 | marginTop: '1rem', 132 | fontSize: '1.25rem', 133 | // color: '#ff81b1', 134 | fontWeight: '500', 135 | marginBottom: '0.5rem', 136 | }); 137 | 138 | const Sidebar = styled('aside', { 139 | base: { 140 | height: 'calc(100vh - 4rem)', 141 | padding: '1rem', 142 | position: 'fixed', 143 | boxShadow: 144 | 'rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(255, 255, 255, 0.1) -1px 0px 0px 0px inset', 145 | display: 'flex', 146 | flexDirection: 'column', 147 | color: 'white', 148 | gap: '0.2rem', 149 | fontSize: '1rem', 150 | transform: 'translateY(-100%)', 151 | top: '4rem', 152 | '@media': { 153 | '(min-width: 1024px)': { 154 | minWidth: '16rem', 155 | position: 'sticky', 156 | transform: 'translate(0)', 157 | }, 158 | '(max-width: 1024px)': { 159 | zIndex: 10, 160 | width: '100%', 161 | background: 'rgb(22,24,38)', 162 | transition: 'transform .8s cubic-bezier(.52,.16,.04,1)', 163 | }, 164 | }, 165 | }, 166 | variants: { 167 | isOpen: { 168 | true: { 169 | transform: 'translate(0)', 170 | }, 171 | }, 172 | }, 173 | }); 174 | 175 | globalStyle(`body:has(${Sidebar.selector({ isOpen: true })})`, { 176 | overflow: 'hidden', 177 | }); 178 | 179 | globalStyle(`${Sidebar} a.is-active`, { 180 | backgroundColor: '#ff307f57', 181 | }); 182 | 183 | const SidebarLink = styled(Link, { 184 | base: { 185 | marginLeft: '0.2rem', 186 | padding: '0.4rem', 187 | borderRadius: '5px', 188 | color: '#bcbcd2', 189 | background: 'transparent', 190 | transition: 'background 100ms ease-in-out ', 191 | }, 192 | }); 193 | 194 | const MenuIcon = styled('button', { 195 | base: { 196 | background: 'transparent', 197 | border: 'none', 198 | color: 'white', 199 | borderRadius: '5px', 200 | padding: '5px', 201 | '@media': { 202 | '(min-width: 1024px)': { 203 | display: 'none', 204 | }, 205 | }, 206 | }, 207 | variants: { 208 | isOpen: { 209 | true: { 210 | backgroundColor: 'rgba(255,255,255,0.2)', 211 | boxShadow: '#ff4089 0px 0px 0px 1px inset', 212 | }, 213 | }, 214 | }, 215 | }); 216 | 217 | const SidebarHeader = styled('p', { 218 | base: { padding: '8px 0', fontSize: '1.1rem' }, 219 | }); 220 | 221 | export function DocsLayout(props: PropsWithChildren) { 222 | const [isNavOpen, setNavOpen] = useState(false); 223 | 224 | return ( 225 |
226 |
248 | 249 | 253 | 254 | 261 | Docs 262 | 263 | 267 | 277 | 278 | setNavOpen(!isNavOpen)}> 279 | 287 | 288 | 294 | 295 | 301 | 302 | 308 | 309 | 310 | 311 |
312 |
323 | 324 | Docs 325 | Installation 326 | How it works 327 | Styling 328 | Theming 329 | 330 | Dynamic Styling 331 | 332 | Macro API 333 | 334 | Examples 335 | 336 | {Object.entries(examplesMeta).map(([path, name]) => ( 337 | 338 | {name} 339 | 340 | ))} 341 | 342 | {props.children} 343 |
344 |
345 | ); 346 | } 347 | -------------------------------------------------------------------------------- /site/src/pages/docs/dynamic-styling.page.mdx: -------------------------------------------------------------------------------- 1 | import { DocsLayout as Layout } from './docs-layout'; 2 | 3 | export const documentProps = { 4 | title: 'Dynamic Styling — macaron', 5 | }; 6 | 7 | 8 | # Dynamic Styling 9 | 10 | Since macaron compiles the styles to static stylesheets at build time, you can't generate new styles dynamically. 11 | 12 |
13 | But you can use **CSS variables** to dynamically update properties. Macaron also 14 | provides APIs to hash and scope css variables to your app. 15 | 16 | ## Example 17 | 18 | ### Styled API 19 | 20 | ```jsx 21 | import { createVar, fallbackVar } from '@macaron-css/core'; 22 | import { styled } from '@macaron-css/solid'; 23 | 24 | const colorVar = createVar(); 25 | 26 | const Button = styled('button', { 27 | base: { 28 | color: fallbackVar(colorVar, 'red'), 29 | border: 'none', 30 | }, 31 | }); 32 | 33 | function MyButton(props) { 34 | return ( 35 | 42 | ); 43 | } 44 | ``` 45 | 46 | ### Vanilla API 47 | 48 | ```jsx 49 | import { createVar } from '@macaron-css/core'; 50 | 51 | const colorVar = createVar(); 52 | 53 | function MyButton(props) { 54 | return ( 55 | 64 | ); 65 | } 66 | ``` 67 | 68 |
69 | -------------------------------------------------------------------------------- /site/src/pages/docs/examples/esbuild.page.mdx: -------------------------------------------------------------------------------- 1 | import { DocsLayout as Layout } from '../docs-layout'; 2 | import { Stackblitz } from './stackblitz'; 3 | 4 | export const documentProps = { 5 | title: 'Esbuild example — macaron', 6 | }; 7 | 8 | 9 | # Esbuild Example 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /site/src/pages/docs/examples/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "esbuild": "Esbuild", 3 | "solid-start": "Solid Start", 4 | "vite": "Vite", 5 | "react": "React", 6 | "solid": "Solid", 7 | "webpack": "Webpack (Coming soon)" 8 | } -------------------------------------------------------------------------------- /site/src/pages/docs/examples/react.page.mdx: -------------------------------------------------------------------------------- 1 | import { DocsLayout as Layout } from '../docs-layout'; 2 | import { Stackblitz } from './stackblitz'; 3 | 4 | export const documentProps = { 5 | title: 'React example — macaron', 6 | }; 7 | 8 | 9 | # React Example 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /site/src/pages/docs/examples/solid-start.page.mdx: -------------------------------------------------------------------------------- 1 | import { DocsLayout as Layout } from '../docs-layout'; 2 | import { Stackblitz } from './stackblitz'; 3 | 4 | export const documentProps = { 5 | title: 'Solid Start example — macaron', 6 | }; 7 | 8 | 9 | # Solid Start Example 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /site/src/pages/docs/examples/solid.page.mdx: -------------------------------------------------------------------------------- 1 | import { DocsLayout as Layout } from '../docs-layout'; 2 | import { Stackblitz } from './stackblitz'; 3 | 4 | export const documentProps = { 5 | title: 'Solid example — macaron', 6 | }; 7 | 8 | 9 | # Solid Example 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /site/src/pages/docs/examples/stackblitz.tsx: -------------------------------------------------------------------------------- 1 | import { style } from '@macaron-css/core'; 2 | import React from 'react'; 3 | 4 | const fileMap = { 5 | react: 'src/App.tsx', 6 | solid: 'src/App.tsx', 7 | 'solid-start': 'src/routes/index.tsx', 8 | vanilla: 'src/index.ts', 9 | vite: 'src/main.ts', 10 | }; 11 | 12 | export function Stackblitz({ example }: { example: keyof typeof fileMap }) { 13 | return ( 14 |
19 |