├── .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 |
74 | Click me!
75 |
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 | Click me
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 | console.log('Clicked')}
55 | >
56 | Click Me
57 |
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 | console.log('Clicked')}
55 | >
56 | Click Me
57 |
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 | console.log('Clicked')}
55 | >
56 | Click Me
57 |
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 |
50 | Click me!
51 |
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 |
29 | Click me
30 | ;
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 |
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 |
40 | Click me
41 |
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 |
62 | Click me
63 |
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 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/site/src/pages/docs/examples/vite.page.mdx:
--------------------------------------------------------------------------------
1 | import { DocsLayout as Layout } from '../docs-layout';
2 | import { Stackblitz } from './stackblitz';
3 |
4 | export const documentProps = {
5 | title: 'Vite example — macaron',
6 | };
7 |
8 |
9 | # Vite Example
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/site/src/pages/docs/examples/webpack.page.mdx:
--------------------------------------------------------------------------------
1 | import { DocsLayout as Layout } from '../docs-layout';
2 | import { Stackblitz } from './stackblitz';
3 |
4 | export const documentProps = {
5 | title: 'Webpack example — macaron',
6 | };
7 |
8 |
9 | # Webpack Example
10 |
11 | The webpack plugin for macaron is coming soon. If you would like to contribute, please reach out on [Twitter](https://twitter.com/mokshit06) or start a discussion on [Github](https://github.com/macaron-css/macaron)
12 |
13 |
14 |
--------------------------------------------------------------------------------
/site/src/pages/docs/installation.page.mdx:
--------------------------------------------------------------------------------
1 | import { DocsLayout as Layout } from './docs-layout';
2 |
3 | export const documentProps = {
4 | title: 'Installation — macaron',
5 | };
6 |
7 |
8 | # Installation
9 |
10 |
11 | ```fish Solid
12 | # npm
13 | npm install @macaron-css/core @macaron-css/solid
14 |
15 | # yarn
16 | yarn add @macaron-css/core @macaron-css/solid
17 |
18 | ```
19 |
20 | ```fish React
21 | # npm
22 | npm install @macaron-css/core @macaron-css/react
23 |
24 | # yarn
25 | yarn add @macaron-css/core @macaron-css/react
26 | ```
27 |
28 |
29 | ## Setting up bundler
30 |
31 | Macaron currently supports vite and esbuild. Install and setup the plugin for your bundler :-
32 |
33 |
34 |
35 | ```fish Installation
36 | ```
37 |
38 | ---
39 |
40 | vite
41 |
42 |
43 | ```fish Installation
44 | npm install @macaron-css/vite
45 | ```
46 |
47 | ---
48 |
49 | ```js vite.config.js
50 | import { macaronVitePlugin } from '@macaron-css/vite';
51 | import { defineConfig } from 'vite';
52 |
53 | export default defineConfig({
54 | plugins: [
55 | // mark
56 | macaronVitePlugin(),
57 | // other plugins
58 | ],
59 | });
60 | ```
61 |
62 |
63 | ---
64 |
65 | esbuild
66 |
67 |
68 | ```fish Installation
69 | npm install @macaron-css/esbuild
70 | ```
71 |
72 | ---
73 |
74 | ```js config
75 | const { macaronEsbuildPlugins } = require('@macaron-css/esbuild');
76 | const esbuild = require('esbuild');
77 |
78 | esbuild.build({
79 | entryPoints: ['src/index.tsx'],
80 | // mark
81 | plugins: [...macaronEsbuildPlugins()],
82 | outdir: 'dist',
83 | format: 'esm',
84 | bundle: true,
85 | });
86 | ```
87 |
88 |
89 | > In the `vite.config.js`, add the macaron plugin before other plugins
90 |
91 | ## Usage
92 |
93 |
94 | ### Create a styled component
95 |
96 | Import `styled` from either `@macaron-css/solid` or `@macaron-css/react` and create a styled component.
97 |
98 | ```js button.tsx focus=3
99 | import { styled } from '@macaron-css/solid';
100 |
101 | const Button = styled('button', {});
102 | ```
103 |
104 | ---
105 |
106 | ### Add styles
107 |
108 | Add base styles that are applied to the component by default. This can include hover states, media queries, nested selectors etc.
109 | All styling APIs in macaron take style objects as inputs. This makes the styles type-safe and provides autocomplete via `csstype`.
110 |
111 | ```js button.tsx focus=4:12
112 | import { styled } from '@macaron-css/solid';
113 |
114 | const Button = styled('button', {
115 | base: {
116 | backgroundColor: 'gainsboro',
117 | borderRadius: '9999px',
118 | fontSize: '13px',
119 | padding: '10px 15px',
120 | ':hover': {
121 | backgroundColor: 'lightgray',
122 | },
123 | },
124 | });
125 | ```
126 |
127 | ---
128 |
129 | ### Add variants
130 |
131 | You can add variants by using the variants key. There is no limit to how many variants you can add.
132 |
133 | A variant accepts the same style object as the base styles.
134 |
135 | ```js button.tsx focus=13:29
136 | import { styled } from '@macaron-css/solid';
137 |
138 | const Button = styled('button', {
139 | base: {
140 | backgroundColor: 'gainsboro',
141 | borderRadius: '9999px',
142 | fontSize: '13px',
143 | padding: '10px 15px',
144 | ':hover': {
145 | backgroundColor: 'lightgray',
146 | },
147 | },
148 | variants: {
149 | color: {
150 | violet: {
151 | backgroundColor: 'blueviolet',
152 | color: 'white',
153 | ':hover': {
154 | backgroundColor: 'darkviolet',
155 | },
156 | },
157 | gray: {
158 | backgroundColor: 'gainsboro',
159 | ':hover': {
160 | backgroundColor: 'lightgray',
161 | },
162 | },
163 | },
164 | },
165 | });
166 | ```
167 |
168 | ---
169 |
170 | ### Default variants
171 |
172 | You can use the defaultVariants feature to set a variant by default. This variants can be overriden at the time of usage.
173 |
174 | ```js button.tsx focus=30:32
175 | import { styled } from '@macaron-css/solid';
176 |
177 | const Button = styled('button', {
178 | base: {
179 | backgroundColor: 'gainsboro',
180 | borderRadius: '9999px',
181 | fontSize: '13px',
182 | padding: '10px 15px',
183 | ':hover': {
184 | backgroundColor: 'lightgray',
185 | },
186 | },
187 | variants: {
188 | color: {
189 | violet: {
190 | backgroundColor: 'blueviolet',
191 | color: 'white',
192 | ':hover': {
193 | backgroundColor: 'darkviolet',
194 | },
195 | },
196 | gray: {
197 | backgroundColor: 'gainsboro',
198 | ':hover': {
199 | backgroundColor: 'lightgray',
200 | },
201 | },
202 | },
203 | },
204 | defaultVariants: {
205 | color: 'violet',
206 | },
207 | });
208 | ```
209 |
210 | ---
211 |
212 | ### Rendering the component
213 |
214 | This component can now be used just like any regular Solid/React component with type-safe props that are derived from your variants.
215 | Unlike vanilla-extract that restricts style declarations to `.css.ts`, macaron allows you to declare styles in the same file providing _true_ colocation.
216 |
217 | ```js button.tsx focus=35:39
218 | import { styled } from '@macaron-css/solid';
219 |
220 | const Button = styled('button', {
221 | base: {
222 | backgroundColor: 'gainsboro',
223 | borderRadius: '9999px',
224 | fontSize: '13px',
225 | padding: '10px 15px',
226 | ':hover': {
227 | backgroundColor: 'lightgray',
228 | },
229 | },
230 | variants: {
231 | color: {
232 | violet: {
233 | backgroundColor: 'blueviolet',
234 | color: 'white',
235 | ':hover': {
236 | backgroundColor: 'darkviolet',
237 | },
238 | },
239 | gray: {
240 | backgroundColor: 'gainsboro',
241 | ':hover': {
242 | backgroundColor: 'lightgray',
243 | },
244 | },
245 | },
246 | },
247 | defaultVariants: {
248 | color: 'violet',
249 | },
250 | });
251 |
252 | () => Click me! ;
253 | ```
254 |
255 |
256 |
257 | ## Available functions
258 |
259 | Other than `styled`, macaron provides the following functions:-
260 |
261 | ```js focus=1[10:15],4:14
262 | import { styled } from '@macaron-css/solid';
263 |
264 | import {
265 | style,
266 | recipe,
267 | createTheme,
268 | createThemeContract,
269 | styleVariants,
270 | createVar,
271 | fallbackVar,
272 | assignVars,
273 | keyframs,
274 | fontFace,
275 | createContainer,
276 | } from '@macaron-css/core';
277 | ```
278 |
279 |
280 |
281 |
--------------------------------------------------------------------------------
/site/src/pages/docs/macro.page.mdx:
--------------------------------------------------------------------------------
1 | import { DocsLayout as Layout } from './docs-layout';
2 |
3 | export const documentProps = {
4 | title: 'Macros — macaron',
5 | };
6 |
7 |
8 | # Build time macros
9 |
10 | Even though macaron's main aim is to be a compile time CSS-in-JS library, it's sophisticated compiler can be used for purposes _other_ than styling.
11 |
12 |
13 | It can also be used as a smart **compile time macro** system where you can execute
14 | functions at build time, similar to `comptime` in zig, or `babel-plugin-macros` while
15 | authoring the macros in the same file, as if they were regular functions.
16 |
17 | This can be useful to read config files, doing complex calculations or processing files (for eg. optimizing images without needing a seperate plugin) right inside your application code.
18 |
19 |
20 |
21 | You can wrap your functions at usage site in the `macaron$` function and they will act as a _macro_, be executed at build time and their return value will be inlined in the final bundle.
22 |
23 |
24 |
25 | This can be used to do things like reading file system, using any Node API and even executing requests at build time (like `getStaticProps` in NextJS), all in a **client side** project.
26 |
27 | ## Example
28 |
29 | ```js
30 | import { macaron$ } from '@macaron-css/core';
31 | import { readFileSync } from 'fs';
32 |
33 | const files = macaron$(() => readFileSync('file.txt', 'utf8'));
34 | ```
35 |
36 | The code above gets compiled into :-
37 |
38 | ```js
39 | const files = '__CONTENTS_OF_FILE_HERE__';
40 | ```
41 |
42 |
43 |
--------------------------------------------------------------------------------
/site/src/pages/docs/styling.page.mdx:
--------------------------------------------------------------------------------
1 | import { DocsLayout as Layout } from './docs-layout';
2 |
3 | export const documentProps = {
4 | title: 'Styling — macaron',
5 | };
6 |
7 |
8 | # Styling
9 |
10 | All styling APIs in macaron take style objects as inputs. These makes the styles type-safe and provides autocomplete via `csstype`. Below are some examples.
11 |
12 | ### Component-less styles
13 |
14 | ```js
15 | import { style } from '@macaron-css/core';
16 |
17 | const button = style({
18 | backgroundColor: 'gainsboro',
19 | borderRadius: '9999px',
20 | fontSize: '13px',
21 | border: '0',
22 | });
23 |
24 | () => Button ;
25 | ```
26 |
27 | ### Global styles
28 |
29 | ```js
30 | import { globalStyle } from '@macaron-css/core';
31 |
32 | globalStyle('*', {
33 | margin: 0,
34 | padding: 0,
35 | });
36 | ```
37 |
38 | ### Base styles
39 |
40 | ```js
41 | import { styled } from '@macaron-css/solid';
42 |
43 | const Button = styled('button', {
44 | base: {
45 | backgroundColor: 'gainsboro',
46 | borderRadius: '9999px',
47 | fontSize: '13px',
48 | border: '0',
49 | },
50 | });
51 |
52 | () => Button ;
53 | ```
54 |
55 | ### Simple Psuedo selectors
56 |
57 | Selectors that don't take any parameters can be used at top level
58 |
59 | ```js focus=4:6
60 | const Button = styled('button', {
61 | base: {
62 | // base styles
63 | ':hover': {
64 | backgroundColor: 'lightgray',
65 | },
66 | },
67 | });
68 |
69 | () => Button ;
70 | ```
71 |
72 | ```js focus=4:15
73 | const Button = styled('button', {
74 | base: {
75 | // base styles
76 | '::before': {
77 | content: `''`,
78 | display: 'block',
79 | backgroundImage: 'linear-gradient(to right, #1fa2ff, #12d8fa, #a6ffcb)',
80 | position: 'absolute',
81 | top: '-3px',
82 | left: '-3px',
83 | width: 'calc(100% + 6px)',
84 | height: 'calc(100% + 6px)',
85 | borderRadius: 'inherit',
86 | zIndex: -1,
87 | },
88 | },
89 | });
90 |
91 | () => Button ;
92 | ```
93 |
94 | ### Complex selectors
95 |
96 | More complex selectors can be written inside `selectors`
97 |
98 | ```js focus=4:8
99 | const Button = styled('button', {
100 | base: {
101 | // base styles
102 | selectors: {
103 | '&[data-custom-attribute]': {
104 | boxShadow: '0 0 0 3px royalblue',
105 | },
106 | },
107 | },
108 | });
109 |
110 | // focus[15:35]
111 | // mark[15:35]
112 | () => Button ;
113 | ```
114 |
115 | ### Target a macaron component
116 |
117 | macaron components implement `.toString()` which returns their base styles' classname. This can be used to target a macaron component.
118 |
119 | ```js focus=14:16
120 | // mark[7:12]
121 | // focus[7:12]
122 | const Button = styled('button', {
123 | base: {
124 | backgroundColor: 'gainsboro',
125 | borderRadius: '9999px',
126 | fontSize: '13px',
127 | border: '0',
128 | },
129 | });
130 |
131 | const Icon = styled('svg', {
132 | base: {
133 | // base styles
134 | selectors: {
135 | [`${Button} &`]: {
136 | marginLeft: '5px',
137 | },
138 | },
139 | },
140 | });
141 | () => (
142 |
143 | Button
144 | ...
145 |
146 | );
147 | ```
148 |
149 | ### Target variants of macaron component
150 |
151 | macaron components also implement a `selector` method that can be used to get the css selector of the variants applied.
152 |
153 | ```js focus=25:27
154 | // mark[7:10]
155 | const Icon = styled('svg', {
156 | base: {
157 | display: 'inline-block',
158 | marginLeft: '5px',
159 | },
160 | variants: {
161 | size: {
162 | // mark[7:11]
163 | // focus[7:11]
164 | small: {
165 | width: '12px',
166 | },
167 | regular: {
168 | width: '16px',
169 | },
170 | },
171 | },
172 | defaultVariants: {
173 | size: 'regular',
174 | },
175 | });
176 |
177 | const Button = styled('button', {
178 | base: {
179 | // base styles
180 | selectors: {
181 | [`${Icon.selector({ size: 'small' })} &`]: {
182 | marginLeft: '5px',
183 | },
184 | },
185 | },
186 | });
187 |
188 | () => Button ;
189 | ```
190 |
191 | ### Generating styles
192 |
193 | There can be cases where you want to generate styles based on some theme config, or programmatically. Directly writing the code to transform the config into styles doesn't work since macaron's compiler only extracts the functions that are exported from macaron, not the closure surrounding it. To make it work, you can use the `macaron$` function. It evaluates the block passed to it at compile time and inlines the value returned in the bundle.
194 |
195 | ```js
196 | import { macaron$ } from '@macaron-css/core';
197 |
198 | const colors = [...];
199 |
200 | const styles = macaron$(() => {
201 | return colors.map(color => style({ backgroundColor: color }))
202 | });
203 | ```
204 |
205 |
206 |
--------------------------------------------------------------------------------
/site/src/pages/docs/theming.page.mdx:
--------------------------------------------------------------------------------
1 | import { DocsLayout as Layout } from './docs-layout';
2 |
3 | export const documentProps = {
4 | title: 'Theming — macaron',
5 | };
6 |
7 |
8 | # Theming
9 |
10 | Themes in macaron are fully type safe css-variables that are hashed and scoped to your app. These themes can either be set on a global element like `:root` or be scoped to a class.
11 |
12 | ### Creating scoped themes
13 |
14 | ```js
15 | import { createTheme } from '@macaron-css/core';
16 |
17 | export const [themeClass, vars] = createTheme({
18 | color: {
19 | brand: 'blue',
20 | },
21 | font: {
22 | body: 'arial',
23 | },
24 | });
25 | ```
26 |
27 | This theme can then be used like
28 |
29 | ```js
30 | const Button = styled('button', {
31 | base: {
32 | color: vars.color.brand,
33 | fontFamily: vars.font.body,
34 | },
35 | });
36 | ```
37 |
38 | ### Creating global theme
39 |
40 | ```js
41 | import { createGlobalTheme } from '@macaron-css/core';
42 |
43 | export const vars = createGlobalTheme(':root', {
44 | color: {
45 | brand: 'blue',
46 | },
47 | font: {
48 | body: 'arial',
49 | },
50 | });
51 | ```
52 |
53 | ### Creating scoped css variables
54 |
55 | You can also create individual scoped css variables that can be used for changing value of styles at runtime, without conflicting with other css variables of same name
56 |
57 | ```js
58 | import { createVar } from '@macaron-css/core';
59 |
60 | const colorVar = createVar();
61 | ```
62 |
63 | For more information view [https://vanilla-extract.style/documentation/theming](https://vanilla-extract.style/documentation/theming)
64 |
65 |
66 |
--------------------------------------------------------------------------------
/site/src/pages/docs/working.page.mdx:
--------------------------------------------------------------------------------
1 | import { DocsLayout as Layout } from './docs-layout';
2 |
3 | export const documentProps = {
4 | title: 'Working — macaron',
5 | };
6 |
7 |
8 |
9 | # How macaron works
10 |
11 |
12 | ### File resolution
13 |
14 | The esbuild/vite plugin loads every typescript and javascript file and runs `@macaron-css/babel` plugin on it.
15 |
16 | ```js main.tsx
17 | import { styled } from '@macaron-css/react';
18 |
19 | const borderRadius = 6;
20 |
21 | const Button = styled('button', {
22 | base: {
23 | borderRadius: borderRadius,
24 | },
25 | variants: {
26 | color: {
27 | neutral: { background: 'whitesmoke' },
28 | brand: { background: 'blueviolet' },
29 | accent: { background: 'slateblue' },
30 | },
31 | },
32 | defaultVariants: {
33 | color: 'accent',
34 | },
35 | });
36 | ```
37 |
38 | ---
39 |
40 | ### Babel transform
41 |
42 | The babel plugin looks for call expressions in your code and checks if the callee is a macarons API like `styled` or `recipe` etc.
43 |
44 | ```js main.tsx focus=5:19
45 | import { styled } from '@macaron-css/react';
46 |
47 | const borderRadius = 6;
48 |
49 | // mark[16:21]
50 | const Button = styled('button', {
51 | base: {
52 | borderRadius: borderRadius,
53 | },
54 | variants: {
55 | color: {
56 | neutral: { background: 'whitesmoke' },
57 | brand: { background: 'blueviolet' },
58 | accent: { background: 'slateblue' },
59 | },
60 | },
61 | defaultVariants: {
62 | color: 'accent',
63 | },
64 | });
65 | ```
66 |
67 | ---
68 |
69 | ### Finding references
70 |
71 | It traverses the call expressions and finds all the referenced identifiers, gets all their declarations and repeats this cycle until no more are found.
72 |
73 | ```js main.tsx focus=3,7
74 | import { styled } from '@macaron-css/react';
75 |
76 | // mark[7:18]
77 | const borderRadius = 6;
78 |
79 | const Button = styled('button', {
80 | base: {
81 | // mark[19:30]
82 | borderRadius: borderRadius,
83 | },
84 | variants: {
85 | color: {
86 | neutral: { background: 'whitesmoke' },
87 | brand: { background: 'blueviolet' },
88 | accent: { background: 'slateblue' },
89 | },
90 | },
91 | defaultVariants: {
92 | color: 'accent',
93 | },
94 | });
95 | ```
96 |
97 | ---
98 |
99 | ### Extract styles
100 |
101 | The babel plugin then extracts all these references and the styles into a seperate file and replaces the code with imports.
102 |
103 |
104 | ```js main.tsx
105 | // mark[10:25]
106 | import { $macaron$$Button } from 'extracted_rxrhwu.css.ts';
107 | import { $$styled } from '@macaron-css/react/runtime';
108 |
109 | // mark[35:50]
110 | const Button = $$styled('button', $macaron$$Button);
111 |
112 | ````
113 | ---
114 | ```js extracted_rxrhwu.css.ts
115 | import { recipe as _recipe } from "@macaron-css/core";
116 | const borderRadius = 6;
117 | // mark[12:28]
118 | export var _$macaron$$Button = _recipe({
119 | base: {
120 | borderRadius: borderRadius
121 | },
122 | variants: {
123 | color: {
124 | neutral: {
125 | background: 'whitesmoke'
126 | },
127 | brand: {
128 | background: 'blueviolet'
129 | },
130 | accent: {
131 | background: 'slateblue'
132 | }
133 | }
134 | },
135 | defaultVariants: {
136 | color: 'accent'
137 | }
138 | });
139 | var _$macaron$$Button2 = _$macaron$$Button;
140 | ````
141 |
142 |
143 |
144 | ---
145 |
146 | ### Evaluating to static css
147 |
148 | The plugin then evaluates this file in isolation and generates static css with hashed classes, removing all the runtime styles.
149 |
150 |
151 | ```js extracted_rxrhwu.css.ts
152 | // mark
153 | import 'extracted_rxrhwu.vanilla.css';
154 | import { createRuntimeFn } from '@macaron-css/core/create-runtime-fn';
155 |
156 | export var $macaron$$Button = createRuntimeFn({
157 | defaultClassName: 'extracted_rxrhwu__1g7h5za0',
158 | variantClassNames: {
159 | color: {
160 | neutral: 'extracted_rxrhwu_color_neutral__1g7h5za1',
161 | brand: 'extracted_rxrhwu_color_brand__1g7h5za2',
162 | accent: 'extracted_rxrhwu_color_accent__1g7h5za3',
163 | },
164 | },
165 | defaultVariants: {
166 | color: 'accent',
167 | },
168 | });
169 | ```
170 | ---
171 | ```css extracted_rxrhwu.vanilla.css
172 | .extracted_rxrhwu__1g7h5za0 {
173 | border-radius: 6px;
174 | }
175 | .extracted_rxrhwu_color_neutral__1g7h5za1 {
176 | background: whitesmoke;
177 | }
178 | .extracted_rxrhwu_color_brand__1g7h5za2 {
179 | background: blueviolet;
180 | }
181 | .extracted_rxrhwu_color_accent__1g7h5za3 {
182 | background: slateblue;
183 | }
184 | ```
185 |
186 |
187 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/site/src/pages/index/index.page.tsx:
--------------------------------------------------------------------------------
1 | import { globalStyle, macaron$, style } from '@macaron-css/core';
2 | import React from 'react';
3 | import { Button } from '../../components/button';
4 | import { Pre } from '../../components/pre';
5 | import { screens } from '../../theme';
6 | import fs from 'fs';
7 | import path from 'path';
8 | import { highlight } from '../../components/code-block';
9 | import { navigate } from 'vite-plugin-ssr/client/router';
10 |
11 | const code = macaron$(() => {
12 | const contents = fs.readFileSync(
13 | path.join(process.cwd(), 'src', 'code-examples', 'home.jsx'),
14 | 'utf8'
15 | );
16 |
17 | return highlight(contents);
18 | });
19 |
20 | export function Page() {
21 | return (
22 |
36 |
45 |
60 |
72 |
76 |
86 | Typesafe CSS-in-JS with zero runtime, colocation, maximum safety
87 | and productivity. Macaron is a new compile time CSS-in-JS library
88 | with type safety.
89 |
90 |
145 |
165 |
166 |
178 |
179 |
180 |
181 | );
182 | }
183 |
--------------------------------------------------------------------------------
/site/src/pages/playground/editor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import { Uri, languages, editor as mEditor } from 'monaco-editor';
3 | import { globalStyle, style } from '@macaron-css/core';
4 | import { useEffect } from 'react';
5 | import theme from './theme.json';
6 |
7 | globalStyle(`.monaco-editor .overflow-guard`, {
8 | borderRadius: '5px',
9 | border: '2px solid #3f3e63',
10 | });
11 |
12 | function Editor(props: {
13 | displayErrors?: boolean;
14 | value: string;
15 | class: string;
16 | disabled?: boolean;
17 | onDocChange?: (code: string) => void;
18 | }) {
19 | const parent = useRef(null);
20 | const editor = useRef();
21 |
22 | useEffect(() => {
23 | editor.current = mEditor.create(parent.current!, {
24 | value: props.value,
25 | automaticLayout: true,
26 | language: 'typescript',
27 | lineDecorationsWidth: 5,
28 | lineNumbersMinChars: 3,
29 | padding: { top: 15 },
30 | readOnly: props.disabled,
31 | minimap: { enabled: false },
32 | });
33 |
34 | mEditor.defineTheme('github-f', theme);
35 | mEditor.setTheme('github-f');
36 |
37 | editor.current.onDidChangeModelContent(() => {
38 | props.onDocChange?.(editor.current!.getValue());
39 | });
40 |
41 | return () => {
42 | editor.current?.dispose();
43 | };
44 | }, []);
45 |
46 | useEffect(() => {
47 | if (props.disabled) {
48 | editor.current!.setValue(props.value);
49 | }
50 | }, [props.disabled, props.value]);
51 |
52 | useEffect(() => {
53 | languages.typescript.typescriptDefaults.setDiagnosticsOptions({
54 | noSemanticValidation: !props.displayErrors,
55 | noSyntaxValidation: !props.displayErrors,
56 | });
57 | }, [props.displayErrors]);
58 |
59 | return
;
60 | }
61 |
62 | export default Editor;
63 |
--------------------------------------------------------------------------------
/site/src/pages/playground/index.page.tsx:
--------------------------------------------------------------------------------
1 | import './setup';
2 | import { registerPlugin, transform } from '@babel/standalone';
3 | import {
4 | macaronBabelPlugin,
5 | macaronStyledComponentsPlugin,
6 | } from '@macaron-css/babel/src';
7 | import { style } from '@macaron-css/core';
8 | import { styled } from '@macaron-css/react';
9 | import { useDebouncedCallback } from 'use-debounce';
10 | import { useEffect, useState } from 'react';
11 | import React from 'react';
12 |
13 | function Editor(props: any) {
14 | const [Comp, setComp] = useState(null as any);
15 |
16 | useEffect(() => {
17 | import('./editor').then(Comp => {
18 | setComp(Comp);
19 | });
20 | }, []);
21 |
22 | return Comp && ;
23 | }
24 |
25 | if (typeof document !== 'undefined') {
26 | registerPlugin('styled', macaronStyledComponentsPlugin);
27 | registerPlugin('macaron', macaronBabelPlugin);
28 | }
29 |
30 | function macaronTransform(source: string) {
31 | const options = { result: ['', ''], path: 'index.ts' };
32 |
33 | const result = transform(source, {
34 | presets: ['typescript', 'react'],
35 | plugins: ['styled', ['macaron', options]],
36 | filename: 'index.tsx',
37 | });
38 |
39 | return { extracted: options.result[1], transpiled: result.code! };
40 | }
41 |
42 | export function Page() {
43 | const [editor, setEditor] = useState({
44 | source: `import { styled } from '@macaron-css/react';
45 |
46 | const Button = styled('button', {
47 | base: {
48 | borderRadius: 6,
49 | },
50 | variants: {
51 | color: {
52 | neutral: { background: 'whitesmoke' },
53 | brand: { background: 'blueviolet' },
54 | accent: { background: 'slateblue' },
55 | },
56 | size: {
57 | small: { padding: 12 },
58 | medium: { padding: 16 },
59 | large: { padding: 24 },
60 | },
61 | rounded: {
62 | true: { borderRadius: 999 },
63 | },
64 | },
65 | compoundVariants: [
66 | {
67 | variants: {
68 | color: 'neutral',
69 | size: 'large',
70 | },
71 | style: {
72 | background: 'ghostwhite',
73 | },
74 | },
75 | ],
76 |
77 | defaultVariants: {
78 | color: 'accent',
79 | size: 'medium',
80 | },
81 | });
82 | `,
83 | transpiled: '',
84 | extracted: '',
85 | });
86 | const transform = useDebouncedCallback((source: string) => {
87 | const { extracted, transpiled } = macaronTransform(source);
88 | setEditor({ source, transpiled, extracted });
89 | }, 300);
90 |
91 | useEffect(() => {
92 | transform(editor.source);
93 | }, []);
94 |
95 | return (
96 |
106 |
107 | Source
108 |
113 |
114 |
123 |
124 | Transpiled
125 |
130 |
131 |
132 | Extracted
133 |
138 |
139 |
140 |
141 | );
142 | }
143 |
144 | const Heading = styled('h2', {
145 | base: { fontSize: '1.7rem', marginBottom: '10px' },
146 | });
147 |
--------------------------------------------------------------------------------
/site/src/pages/playground/setup.ts:
--------------------------------------------------------------------------------
1 | import Buffer from 'buffer';
2 |
3 | if (typeof document !== 'undefined') {
4 | globalThis.process = {
5 | env: {
6 | BABEL_TYPES_8_BREAKING: false,
7 | },
8 | };
9 |
10 | globalThis.Buffer = Buffer.Buffer;
11 | }
12 |
--------------------------------------------------------------------------------
/site/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalTheme, macaron$ } from '@macaron-css/core';
2 |
3 | export const theme = createGlobalTheme(':root', {});
4 |
5 | export const screens = {
6 | sm: '(max-width: 640px)',
7 | md: '(max-width: 768px)',
8 | lg: '(max-width: 1024px)',
9 | xl: '(max-width: 1280px)',
10 | '2xl': '(max-width: 1536px)',
11 | };
12 |
--------------------------------------------------------------------------------
/site/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "target": "ES2020",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "resolveJsonModule": true
13 | },
14 | "ts-node": {
15 | "transpileOnly": true,
16 | "compilerOptions": {
17 | "module": "CommonJS"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/site/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react';
2 | import ssr from 'vite-plugin-ssr/plugin';
3 | import { defineConfig, UserConfig } from 'vite';
4 | import { macaronVitePlugin } from '@macaron-css/vite';
5 | import { remarkCodeHike } from '@code-hike/mdx';
6 | import theme from 'shiki/themes/material-ocean.json';
7 |
8 | export default defineConfig(async () => {
9 | const mdx = await import('@mdx-js/rollup');
10 |
11 | return {
12 | define: {
13 | BABEL_TYPES_8_BREAKING: 'false',
14 | 'process.env.BABEL_TYPES_8_BREAKING': 'false',
15 | },
16 | optimizeDeps: {
17 | include: ['react/jsx-runtime'],
18 | },
19 | plugins: [
20 | react(),
21 | macaronVitePlugin(),
22 | mdx.default({
23 | remarkPlugins: [[remarkCodeHike, { theme, showCopyButton: true }]],
24 | rehypePlugins: [
25 | // esm packages, so can't import directly
26 | await import('rehype-slug').then(r => r.default),
27 | [
28 | await import('rehype-autolink-headings').then(r => r.default),
29 | {
30 | properties: { id: 'mdx-link', tabIndex: -1, ariaHidden: true },
31 | },
32 | ],
33 | ],
34 | }),
35 | ssr({ prerender: true }),
36 | ],
37 | };
38 | });
39 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "preserve",
4 | "target": "ESNext",
5 | "moduleResolution": "node16",
6 | "esModuleInterop": true,
7 | "declaration": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "noEmit": true
11 | },
12 | "exclude": [
13 | "node_modules"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------