├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── @docs ├── assets │ ├── logo │ │ ├── taddy1.png │ │ ├── taddy1.svg │ │ ├── taddy11.png │ │ ├── taddy11.svg │ │ ├── taddy2.png │ │ ├── taddy2.svg │ │ ├── taddy9.png │ │ └── taddy9.svg │ └── package.json └── website │ ├── .gitignore │ ├── README.md │ ├── compiler │ ├── index.ts │ └── stubs │ │ ├── babel-core.js │ │ ├── fs.js │ │ ├── module.js │ │ ├── path.js │ │ └── sync-rpc.js │ ├── components │ ├── BaseLink.tsx │ ├── Link.tsx │ ├── LinkButton.tsx │ ├── Sidebar │ │ ├── index.tsx │ │ └── styles.module.css │ ├── layout.tsx │ └── playground │ │ ├── CompiledCode.tsx │ │ ├── Editor.tsx │ │ ├── EditorLayer.tsx │ │ ├── LiveEditor.tsx │ │ ├── Options.tsx │ │ ├── ReactRender.tsx │ │ ├── atoms.ts │ │ └── index.tsx │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── _app.tsx │ ├── index.tsx │ ├── playground │ │ └── index.tsx │ └── roadmap.md │ ├── public │ ├── favicon.ico │ └── logo │ │ ├── taddy1.png │ │ ├── taddy1.svg │ │ ├── taddy11.png │ │ ├── taddy11.svg │ │ ├── taddy2.png │ │ ├── taddy2.svg │ │ ├── taddy9.png │ │ └── taddy9.svg │ ├── styles │ ├── globals.css │ ├── index.ts │ ├── mixins.ts │ ├── reset.css │ └── taddy.css │ ├── taddy-plugin.js │ ├── tsconfig.json │ ├── utils │ ├── code.tsx │ └── history.tsx │ └── vercel.json ├── @examples ├── astro │ ├── .gitignore │ ├── README.md │ ├── astro.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.svg │ ├── src │ │ ├── components │ │ │ └── Card.astro │ │ ├── env.d.ts │ │ ├── layouts │ │ │ └── Layout.astro │ │ └── pages │ │ │ └── index.astro │ └── tsconfig.json ├── nextjs │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── SuperComponent.tsx │ │ ├── api │ │ │ └── hello │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next.config.js │ ├── package.json │ ├── public │ │ ├── next.svg │ │ ├── thirteen.svg │ │ └── vercel.svg │ └── tsconfig.json ├── nuxt │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── app.vue │ ├── components │ │ ├── Test.tsx │ │ └── Test2.tsx │ ├── nuxt.config.ts │ ├── package.json │ ├── pages │ │ └── index.vue │ ├── public │ │ └── favicon.ico │ └── tsconfig.json ├── parcel │ ├── .babelrc │ ├── App.tsx │ ├── index.html │ ├── index.tsx │ ├── package.json │ └── tsconfig.json ├── react-native-expo │ ├── .gitignore │ ├── App.tsx │ ├── app.json │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── index.tsx │ ├── package.json │ └── tsconfig.json ├── remix │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── root.tsx │ │ └── routes │ │ │ ├── index.tsx │ │ │ └── test │ │ │ └── index.tsx │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── remix.config.js │ ├── remix.env.d.ts │ └── tsconfig.json ├── solid-start │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── components │ │ │ ├── Counter.tsx │ │ │ └── Hello.tsx │ │ ├── entry-client.tsx │ │ ├── entry-server.tsx │ │ ├── root.tsx │ │ └── routes │ │ │ ├── [...404].tsx │ │ │ └── index.tsx │ ├── tsconfig.json │ └── vite.config.ts └── svelte-kit │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── package.json │ ├── src │ ├── app.d.ts │ ├── app.html │ └── routes │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ └── test │ │ └── +page.svelte │ ├── static │ └── favicon.png │ ├── svelte.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── @taddy ├── babel-plugin │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── cache │ │ └── index.js │ ├── index.ts │ ├── macro.ts │ ├── package.json │ ├── src │ │ ├── Output.ts │ │ ├── Processor.ts │ │ ├── TSProcessor.ts │ │ ├── config.js │ │ ├── handlers.ts │ │ ├── helpers │ │ │ ├── BindingOptimizer.ts │ │ │ ├── buildCodeByPath.ts │ │ │ ├── evaluate.ts │ │ │ ├── evaluate.worker.cjs │ │ │ ├── findBindings.ts │ │ │ ├── getObjectPropertyKey.ts │ │ │ ├── mergeObjectProperties.ts │ │ │ ├── optimizeStaticStyles.ts │ │ │ ├── taggedTemplateToObject.ts │ │ │ └── utils.cjs │ │ ├── index.ts │ │ ├── macro-plugin.ts │ │ ├── plugin.ts │ │ ├── source-maps.ts │ │ ├── tests │ │ │ ├── common.ts │ │ │ ├── data │ │ │ │ └── mixins.ts │ │ │ ├── evaluate.test.ts │ │ │ ├── index.test.ts │ │ │ ├── static.test.ts │ │ │ └── typescript.test.ts │ │ └── types.ts │ └── tsconfig.json ├── core │ ├── .gitignore │ ├── CHANGELOG.md │ ├── NameGenerator │ │ ├── index.test.ts │ │ └── index.ts │ ├── README.md │ ├── common.ts │ ├── config.ts │ ├── h.ts │ ├── index.test.ts │ ├── index.ts │ ├── package.json │ ├── rollup.config.js │ ├── static │ │ └── index.ts │ └── tsconfig.json ├── esbuild-plugin │ ├── CHANGELOG.md │ ├── index.js │ ├── package.json │ └── tsconfig.json ├── next-plugin │ ├── CHANGELOG.md │ ├── index.js │ ├── loader.cjs │ ├── package.json │ ├── rollup.config.js │ └── tsconfig.json └── vite-plugin │ ├── index.js │ ├── package.json │ └── tsconfig.json ├── @types └── global.d.ts ├── LICENSE ├── README.md ├── ROADMAP.md ├── babel.config.js ├── jest.config.js ├── lerna.json ├── package.json ├── prettier.config.js ├── rollup.config.common.js ├── taddy.macro ├── .gitignore ├── CHANGELOG.md ├── README.md ├── index.d.ts ├── index.ts ├── package.json ├── rollup.config.js └── tsconfig.json ├── taddy ├── $css.ts ├── .gitignore ├── CHANGELOG.md ├── README.md ├── RuleInjector │ ├── Sheet.ts │ ├── StyleSheet.ts │ ├── VirtualStyleSheet.ts │ ├── common.ts │ └── index.ts ├── at.ts ├── css.js ├── index.native.ts ├── index.ts ├── mixin.ts ├── package.json ├── react-native │ └── processStyles.ts ├── rollup.config.js ├── tests │ ├── index.test.ts │ └── utils.ts ├── tsconfig.json ├── types.ts └── vue │ └── index.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | *.code-workspace 6 | .vscode 7 | .cache 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Linter.Config} */ 2 | module.exports = { 3 | root: true, 4 | parser: '@typescript-eslint/parser', 5 | parserOptions: { 6 | ecmaVersion: 12, 7 | sourceType: 'module', 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:import/recommended', 12 | 'plugin:import/typescript', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'plugin:react/recommended', 15 | 'plugin:react/jsx-runtime', 16 | 'plugin:react-hooks/recommended', 17 | 'prettier', 18 | ], 19 | settings: { 20 | 'import/ignore': ['react-native'], 21 | 22 | react: { 23 | version: '18', 24 | }, 25 | }, 26 | rules: { 27 | 'react/react-in-jsx-scope': 'off', 28 | }, 29 | overrides: [ 30 | { 31 | files: ['**/*.{js,cjs}', '@taddy/babel-plugin/**'], 32 | rules: { 33 | '@typescript-eslint/no-var-requires': 'off', 34 | }, 35 | }, 36 | ], 37 | env: { 38 | browser: true, 39 | es2022: true, 40 | node: true, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | 7 | steps: 8 | - uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 18 15 | cache: 'yarn' 16 | 17 | - run: yarn install --frozen-lockfile 18 | env: 19 | CI: true 20 | 21 | - name: Build 22 | run: yarn build --ci 23 | env: 24 | CI: true 25 | 26 | - name: Lint 27 | run: yarn lint 28 | env: 29 | CI: true 30 | 31 | - name: Typescript 32 | run: yarn check:ts --ci 33 | env: 34 | CI: true 35 | 36 | - name: Test 37 | run: yarn test --ci --coverage 38 | env: 39 | CI: true 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | 4 | node_modules 5 | 6 | dist 7 | lib 8 | *.code-workspace 9 | .vscode 10 | .cache 11 | 12 | !packages/taddy/.cache 13 | # taddy.css 14 | cache/*.css 15 | 16 | .vercel 17 | 18 | *.tsbuildinfo 19 | .parcel-cache 20 | .rollup.cache 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.tsbuildinfo 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | /** @type import('prettier').Options */ 2 | module.exports = { 3 | printWidth: 80, 4 | semi: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | bracketSpacing: false, 8 | }; 9 | -------------------------------------------------------------------------------- /@docs/assets/logo/taddy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/assets/logo/taddy1.png -------------------------------------------------------------------------------- /@docs/assets/logo/taddy11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/assets/logo/taddy11.png -------------------------------------------------------------------------------- /@docs/assets/logo/taddy2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/assets/logo/taddy2.png -------------------------------------------------------------------------------- /@docs/assets/logo/taddy9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/assets/logo/taddy9.png -------------------------------------------------------------------------------- /@docs/assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@docs/assets", 4 | "description": "taddy documentation assets", 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /@docs/website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /@docs/website/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | ## Learn More 18 | 19 | To learn more about Next.js, take a look at the following resources: 20 | 21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 23 | 24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 25 | 26 | ## Deploy on Vercel 27 | 28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 29 | 30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 31 | -------------------------------------------------------------------------------- /@docs/website/compiler/stubs/babel-core.js: -------------------------------------------------------------------------------- 1 | import {transformSync} from '@babel/core'; 2 | 3 | module.exports = {transformAsync: transformSync}; 4 | -------------------------------------------------------------------------------- /@docs/website/compiler/stubs/fs.js: -------------------------------------------------------------------------------- 1 | const {fs} = require('filer'); 2 | 3 | const noop = () => void 0; 4 | 5 | fs.writeFileSync = (...args) => fs.writeFile(...args, noop); 6 | fs.existsSync = (...args) => fs.exists(...args, noop); 7 | fs.readFileSync = (...args) => fs.readFile(...args, noop); 8 | fs.appendFileSync = (...args) => fs.appendFile(...args, noop); 9 | fs.mkdirSync = (...args) => fs.mkdir(...args, noop); 10 | 11 | module.exports = fs; 12 | -------------------------------------------------------------------------------- /@docs/website/compiler/stubs/module.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /@docs/website/compiler/stubs/path.js: -------------------------------------------------------------------------------- 1 | const {path} = require('filer'); 2 | 3 | module.exports = path; 4 | -------------------------------------------------------------------------------- /@docs/website/compiler/stubs/sync-rpc.js: -------------------------------------------------------------------------------- 1 | const transformWorker = require('@taddy/babel-plugin/src/helpers/evaluate.worker.cjs'); 2 | 3 | module.exports = () => transformWorker(); 4 | -------------------------------------------------------------------------------- /@docs/website/components/BaseLink.tsx: -------------------------------------------------------------------------------- 1 | import type {PropsWithChildren, CSSProperties} from 'react'; 2 | 3 | import NextLink from 'next/link'; 4 | import {useRouter} from 'next/router'; 5 | 6 | export const Link = ({ 7 | href, 8 | ...props 9 | }: PropsWithChildren<{ 10 | href: string; 11 | style?: CSSProperties; 12 | className?: string; 13 | }>) => { 14 | const router = useRouter(); 15 | const currentPage = router.pathname === href; 16 | return ( 17 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /@docs/website/components/Link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {css} from 'taddy'; 3 | 4 | import {Link as BaseLink} from './BaseLink'; 5 | 6 | export const Link = ({ 7 | href, 8 | className, 9 | style, 10 | children, 11 | }: { 12 | href: string; 13 | style?: object; 14 | className?: string; 15 | children: React.ReactNode; 16 | }) => { 17 | return ( 18 | 29 | {children} 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /@docs/website/components/LinkButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {css} from 'taddy'; 3 | 4 | import {Link} from './BaseLink'; 5 | 6 | const styles = { 7 | _variant: { 8 | normal: css({ 9 | color: 'violet', 10 | background: 'none', 11 | }), 12 | action: css({ 13 | color: 'white', 14 | background: 'violet', 15 | }), 16 | pseudo: css({ 17 | color: 'violet', 18 | borderColor: 'transparent', 19 | }), 20 | }, 21 | }; 22 | 23 | export const LinkButton = ({ 24 | href, 25 | variant = 'normal', 26 | className, 27 | style, 28 | children, 29 | }: { 30 | href: string; 31 | variant?: string; 32 | style?: object; 33 | className?: string; 34 | children: React.ReactNode; 35 | }) => { 36 | return ( 37 | 52 | {children} 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /@docs/website/components/Sidebar/styles.module.css: -------------------------------------------------------------------------------- 1 | .burger { 2 | position: absolute; 3 | display: none; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | height: 20px; 7 | z-index: 11; 8 | color: #333; 9 | } 10 | 11 | .input { 12 | position: absolute; 13 | display: none; 14 | margin: 0; 15 | width: 40px; 16 | height: 40px; 17 | opacity: 0; 18 | z-index: 12; 19 | cursor: pointer; 20 | } 21 | 22 | .burger, 23 | .input { 24 | top: 15px; 25 | left: 50%; 26 | transform: translateX(-50%); 27 | } 28 | 29 | .burger span { 30 | display: inline-block; 31 | width: 40px; 32 | height: 4px; 33 | background-color: currentColor; 34 | border-radius: 4px; 35 | } 36 | 37 | @media screen and (max-width: 900px) { 38 | .content { 39 | margin-top: 10px; 40 | } 41 | 42 | .burger { 43 | display: flex; 44 | } 45 | 46 | .input { 47 | display: block; 48 | } 49 | 50 | .input:checked ~ .menu { 51 | display: inline-flex; 52 | justify-content: center; 53 | } 54 | 55 | .input:checked ~ .burger { 56 | color: black; 57 | justify-content: flex-end; 58 | } 59 | 60 | .input:checked ~ .burger span { 61 | transform: rotate(45deg); 62 | } 63 | 64 | .input:checked ~ .burger span:first-child { 65 | display: none; 66 | } 67 | 68 | .input:checked ~ .burger span:last-child { 69 | transform: translate(-1px, -5px) rotate(-45deg); 70 | } 71 | 72 | .menu { 73 | display: none; 74 | position: absolute; 75 | width: 100%; 76 | height: 100%; 77 | z-index: 2; 78 | background: rgba(243 240 243 / 95%); 79 | font-size: 30px; 80 | z-index: 10; 81 | padding-top: 60px; 82 | } 83 | } 84 | 85 | .content { 86 | max-width: 100vw; 87 | } 88 | -------------------------------------------------------------------------------- /@docs/website/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import {css} from 'taddy'; 4 | 5 | const size = (v: number) => `${v * 4}px`; 6 | const margin = (gapY: number, gapX: number) => 7 | `${size(gapY / 2)} ${size(gapX / 2)}`; 8 | 9 | function flex({inline}) { 10 | return css({ 11 | display: inline ? 'inline-flex' : 'flex', 12 | 13 | ...(!inline && { 14 | flex: 1, 15 | maxWidth: '100%', 16 | 17 | '> *': { 18 | flex: 1, 19 | maxWidth: '100%', 20 | }, 21 | }), 22 | }); 23 | } 24 | 25 | type Size = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; 26 | 27 | export function row({ 28 | gap = 0, 29 | gapY = gap, 30 | gapX = gap, 31 | inline = false, 32 | }: { 33 | gap?: Size; 34 | gapY?: Size; 35 | gapX?: Size; 36 | inline?: boolean; 37 | wrap?: any; 38 | } = {}) { 39 | return css({ 40 | ...flex({inline}), 41 | 42 | flexDirection: 'row', 43 | 44 | margin: margin(-gapY, -gapX), 45 | 46 | ...(!inline && { 47 | width: `calc(100% + ${size(gapX)})`, 48 | }), 49 | 50 | '> *:not(:empty)': { 51 | display: 'flex', 52 | margin: margin(gapY, gapX), 53 | }, 54 | }); 55 | } 56 | 57 | export function column({ 58 | gap = 0, 59 | inline = false, 60 | }: {gap?: Size; gapY?: Size; gapX?: Size; inline?: boolean} = {}) { 61 | return css({ 62 | ...flex({inline}), 63 | 64 | flexDirection: 'column', 65 | 66 | '> *:not(:empty) + *:not(:empty)': { 67 | display: 'flex', 68 | marginTop: size(gap), 69 | }, 70 | }); 71 | } 72 | 73 | function shouldWrap(child) { 74 | return !(child == undefined || !!child.type.__unit__); 75 | } 76 | 77 | function wrapChildren(children) { 78 | return React.Children.map(children, (child) => 79 | shouldWrap(child) ?
{child}
: child, 80 | ); 81 | } 82 | 83 | export const Column = ({ 84 | as: Tag = 'div', 85 | gap, 86 | inline, 87 | style, 88 | className, 89 | children, 90 | ...props 91 | }: Partial<{ 92 | as: keyof JSX.IntrinsicElements; 93 | gap: Size; 94 | inline: boolean; 95 | className?: string; 96 | style?: object; 97 | children: React.ReactNode; 98 | }>) => ( 99 | 100 | {wrapChildren(children)} 101 | 102 | ); 103 | 104 | export const Row = ({ 105 | as: Tag = 'div', 106 | gap = 0, 107 | gapY = gap, 108 | gapX = gap, 109 | inline = false, 110 | style, 111 | className, 112 | children, 113 | ...props 114 | }: Partial<{ 115 | as: keyof JSX.IntrinsicElements; 116 | gap: Size; 117 | gapY: Size; 118 | gapX: Size; 119 | inline: boolean; 120 | className?: string; 121 | style?: object; 122 | children: React.ReactNode; 123 | wrap?: string; 124 | }>) => ( 125 |
136 | 145 | {wrapChildren(children)} 146 | 147 |
148 | ); 149 | -------------------------------------------------------------------------------- /@docs/website/components/playground/CompiledCode.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | import {useAtom} from '@reatom/react'; 4 | import type {PropsWithChildren} from 'react'; 5 | 6 | import {Column, Row} from '../layout'; 7 | import {transformAtom} from './atoms'; 8 | import {Editor} from './Editor'; 9 | import {EditorLayer} from './EditorLayer'; 10 | import {ReactRender} from './ReactRender'; 11 | 12 | const Title = ({children}: PropsWithChildren) =>

{children}

; 13 | 14 | const Wrapper = ({children}: PropsWithChildren) => ( 15 |
26 | {children} 27 |
28 | ); 29 | 30 | export const CompiledCode = ({ 31 | showCompiledCode, 32 | showCompiledCSS, 33 | showRender, 34 | }: { 35 | showCompiledCode?: boolean; 36 | showCompiledCSS?: boolean; 37 | showRender?: boolean; 38 | }) => { 39 | const data = useAtom(transformAtom); 40 | 41 | let layerProps: React.ComponentProps = {}; 42 | 43 | if (data.status === 'error') { 44 | layerProps = {variant: 'error', children: data.error.toString()}; 45 | } else if (data.status === 'pending') { 46 | layerProps = {variant: 'compiling', children: 'Compiling...'}; 47 | } 48 | 49 | const content = ( 50 | 60 | {showRender && ( 61 | 62 | Render 63 | 64 | 65 | 66 | )} 67 | 68 | {showCompiledCode && ( 69 | 70 | Compiled Module 71 | 72 | 79 | 80 | )} 81 | 82 | {showCompiledCSS && ( 83 | 84 | Compiled CSS 85 | 86 | 94 | 95 | )} 96 | 97 | ); 98 | 99 | return ( 100 |
106 | {content} 107 | 108 | 109 |
110 | ); 111 | }; 112 | -------------------------------------------------------------------------------- /@docs/website/components/playground/Editor.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | 3 | import * as React from 'react'; 4 | import type {ComponentProps} from 'react'; 5 | 6 | import {css} from 'taddy'; 7 | 8 | // @see https://github.com/securingsincity/react-ace/issues/725 9 | const AceEditor = dynamic( 10 | async () => { 11 | const reactAce = await import('react-ace'); 12 | 13 | await Promise.all([ 14 | // prevent warning in console about misspelled props name. 15 | import('ace-builds/src-min-noconflict/ext-language_tools'), 16 | import('ace-builds/src-min-noconflict/ext-beautify'), 17 | 18 | import('ace-builds/src-min-noconflict/mode-typescript'), 19 | import('ace-builds/src-min-noconflict/mode-tsx'), 20 | import('ace-builds/src-min-noconflict/mode-css'), 21 | 22 | import('ace-builds/src-min-noconflict/theme-textmate'), 23 | ]); 24 | 25 | // await import('ace-builds/src-min-noconflict/ext-language_tools'); 26 | // await import('ace-builds/src-min-noconflict/ext-beautify'); 27 | 28 | // // import your theme/mode here. 29 | // await import('ace-builds/src-min-noconflict/mode-typescript'); 30 | // await import('ace-builds/src-min-noconflict/mode-css'); 31 | // await import('ace-builds/src-min-noconflict/theme-textmate'); 32 | 33 | // as @Holgrabus commented you can paste these file into your /public folder. 34 | // You will have to set basePath and setModuleUrl accordingly. 35 | // eslint-disable-next-line @typescript-eslint/no-var-requires 36 | const ace = require('ace-builds/src-min-noconflict/ace'); 37 | ace.config.set( 38 | 'basePath', 39 | 'https://cdn.jsdelivr.net/npm/ace-builds@1.16.0/src-noconflict/', 40 | ); 41 | ace.config.setModuleUrl( 42 | 'ace/mode/javascript_worker', 43 | 'https://cdn.jsdelivr.net/npm/ace-builds@1.16.0/src-noconflict/worker-javascript.js', 44 | ); 45 | 46 | return reactAce; 47 | }, 48 | { 49 | ssr: false, // react-ace doesn't support server side rendering as it uses the window object. 50 | loading: () =>

Loading...

, 51 | }, 52 | ); 53 | 54 | export const Editor = ({ 55 | className, 56 | style, 57 | ...props 58 | }: ComponentProps) => ( 59 | 81 | ); 82 | -------------------------------------------------------------------------------- /@docs/website/components/playground/EditorLayer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import {css} from 'taddy'; 4 | 5 | const styles = { 6 | base: css({ 7 | position: 'absolute', 8 | left: 0, 9 | top: 0, 10 | width: '100%', 11 | height: '100%', 12 | whiteSpace: 'pre', 13 | zIndex: 5, 14 | padding: '20px', 15 | fontSize: '14px', 16 | overflowX: 'scroll', 17 | fontFamily: 'monospace', 18 | fontWeight: 'bold', 19 | textAlign: 'left', 20 | }), 21 | _variant: { 22 | compiling: css({ 23 | background: 'rgba(255 255 255 / 95%)', 24 | color: 'black', 25 | }), 26 | error: css({ 27 | background: 'rgba(2 10 10 / 80%)', 28 | color: 'white', 29 | }), 30 | }, 31 | _animated: css({ 32 | transitionProperty: 'opacity', 33 | transitionDuration: '150ms', 34 | }), 35 | _hidden: css({opacity: 0, zIndex: -1}), 36 | }; 37 | 38 | export const EditorLayer = ({ 39 | children, 40 | variant, 41 | className, 42 | }: { 43 | children?: React.ReactNode; 44 | variant?: keyof typeof styles._variant; 45 | className?: string; 46 | }) => { 47 | return ( 48 | 56 | {children} 57 | 58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /@docs/website/components/playground/LiveEditor.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useAction, useAtom} from '@reatom/react'; 3 | 4 | import {css, $} from 'taddy'; 5 | 6 | import {stripIndent} from 'common-tags'; 7 | 8 | import {useCode} from '../../utils/code'; 9 | import {playgroundAtom, updatePlayground} from './atoms'; 10 | import {Editor} from './Editor'; 11 | 12 | export const LiveEditor = ({ 13 | initialCode, 14 | persistent, 15 | }: { 16 | initialCode?: string; 17 | persistent?: boolean; 18 | }) => { 19 | const code = useAtom(playgroundAtom, (x) => x.code, ['code']); 20 | const storageCode = useCode(persistent ? code : null); 21 | 22 | const handleCode = useAction((code) => { 23 | return updatePlayground({code}); 24 | }); 25 | 26 | React.useEffect(() => { 27 | setTimeout(() => { 28 | handleCode(storageCode || stripIndent(initialCode) + '\n'); 29 | }, 0); 30 | }, []); 31 | 32 | return ( 33 |
34 |

Code

35 | 36 | 69 |
70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /@docs/website/components/playground/Options.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | import {useAtom, useAction} from '@reatom/react'; 3 | 4 | import {Column, Row} from '../layout'; 5 | 6 | import {playgroundAtom, updatePlayground} from './atoms'; 7 | 8 | export const Options = () => { 9 | const options = useAtom(playgroundAtom, (x) => x.options, ['options']); 10 | const handleOption = useAction((e) => { 11 | return updatePlayground({ 12 | // @ts-expect-error expected partial options 13 | options: {[e.target.name]: !!e.target.checked}, 14 | }); 15 | }); 16 | 17 | const compileOptionsDisabled = !options.taddy; 18 | 19 | return ( 20 | 21 | 30 | 31 | 32 |

33 | Compile Options: 34 |

35 | 36 | 46 | 47 | 57 | 58 | 68 |
69 |
70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /@docs/website/components/playground/atoms.ts: -------------------------------------------------------------------------------- 1 | import {declareAction, declareAtom} from '@reatom/core'; 2 | 3 | import {stripIndent} from 'common-tags'; 4 | 5 | type Playground = { 6 | options: { 7 | taddy: true; 8 | evaluate: boolean; 9 | unstable_typescript: boolean; 10 | unstable_CSSVariableFallback: boolean; 11 | unstable_useTaggedTemplateLiterals: boolean; 12 | }; 13 | code: string; 14 | }; 15 | 16 | export const updatePlayground = declareAction>( 17 | async (payload, store) => { 18 | const {code: source, options} = store.getState(playgroundAtom); 19 | 20 | store.dispatch(setTransformedCode({status: 'pending'})); 21 | 22 | if (!options.taddy) { 23 | store.dispatch( 24 | setTransformedCode({ 25 | status: 'done', 26 | result: {code: source, css: ''}, 27 | }), 28 | ); 29 | 30 | return; 31 | } 32 | 33 | const transformCode = await import('../../compiler').then( 34 | (x) => x.transformCode, 35 | ); 36 | 37 | transformCode(source, options) 38 | .then((result) => { 39 | store.dispatch(setTransformedCode({status: 'done', result})); 40 | }) 41 | .catch((error) => { 42 | console.error(error); 43 | 44 | store.dispatch( 45 | setTransformedCode({ 46 | status: 'error', 47 | error, 48 | }), 49 | ); 50 | }); 51 | }, 52 | ); 53 | export const playgroundAtom = declareAtom( 54 | { 55 | code: '', 56 | options: { 57 | taddy: true, 58 | unstable_typescript: true, 59 | evaluate: true, 60 | unstable_CSSVariableFallback: true, 61 | unstable_useTaggedTemplateLiterals: true, 62 | }, 63 | }, 64 | (on) => [ 65 | on(updatePlayground, (state, payload) => ({ 66 | ...state, 67 | ...payload, 68 | options: { 69 | ...state.options, 70 | ...payload.options, 71 | }, 72 | })), 73 | ], 74 | ); 75 | 76 | export type CompiledData = {result?: {code: string; css: string}} & ( 77 | | {status: 'pending'} 78 | | {status: 'done'} 79 | | {error: Error; status: 'error'} 80 | ); 81 | 82 | export const setTransformedCode = declareAction(); 83 | export const transformAtom = declareAtom( 84 | {status: 'done', result: {code: '', css: ''}}, 85 | (on) => [ 86 | on(setTransformedCode, (state, payload) => ({...state, ...payload})), 87 | ], 88 | ); 89 | -------------------------------------------------------------------------------- /@docs/website/components/playground/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | import {css} from 'taddy'; 4 | 5 | import {Column, Row} from '../layout'; 6 | 7 | import {LiveEditor} from './LiveEditor'; 8 | import {Options} from './Options'; 9 | import {CompiledCode} from './CompiledCode'; 10 | 11 | import {createStore} from '@reatom/core'; 12 | import {context} from '@reatom/react'; 13 | 14 | export default function Playground({ 15 | initialCode, 16 | showOptions, 17 | showCompiledCode, 18 | showCompiledCSS, 19 | showRender, 20 | persistent, 21 | }: { 22 | initialCode?: string; 23 | showOptions?: boolean; 24 | showCompiledCode?: boolean; 25 | showCompiledCSS?: boolean; 26 | showRender?: boolean; 27 | persistent?: boolean; 28 | }) { 29 | const store = createStore(); 30 | 31 | return ( 32 | 33 |
34 | 35 | 39 | 40 | 41 | 42 | {showOptions && } 43 | 44 | 45 | 49 | 50 | 53 | 54 | 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /@docs/website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /@docs/website/next.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const withMDX = require('@next/mdx'); 3 | const withTaddy = require('@taddy/next-plugin'); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const config = { 7 | transpilePackages: ['@taddy/babel-plugin', '@taddy/core', 'taddy'], 8 | 9 | pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'], 10 | 11 | webpack(config) { 12 | Object.assign(config.resolve.alias, { 13 | fs: require.resolve('./compiler/stubs/fs'), 14 | path: 'path-browserify', 15 | module: require.resolve('./compiler/stubs/module'), 16 | 'sync-rpc': require.resolve('./compiler/stubs/sync-rpc'), 17 | }); 18 | 19 | // config.plugins.push( 20 | // new webpack.ContextReplacementPlugin( 21 | // /\/filer\/|\/ts-morph\/|babel\/standalone/, 22 | // (data) => { 23 | // delete data.dependencies[0].critical; 24 | // return data; 25 | // }, 26 | // ), 27 | // ); 28 | 29 | // handle "node:assert" etc. 30 | config.plugins.push( 31 | new webpack.NormalModuleReplacementPlugin(/^node:/, (resource) => { 32 | resource.request = resource.request.replace(/^node:/, ''); 33 | }), 34 | ); 35 | 36 | config.externals.push({ 37 | typescript: 'window.ts', 38 | }); 39 | 40 | config.module = { 41 | ...config.module, 42 | // there are some packages like "@babel/standalone", "ts-morph" and "filer" that use require 43 | // @see https://github.com/babel/babel/issues/14301 44 | exprContextCritical: false, 45 | }; 46 | 47 | return config; 48 | }, 49 | 50 | typescript: { 51 | ignoreBuildErrors: true, 52 | }, 53 | 54 | eslint: { 55 | ignoreDuringBuilds: true, 56 | }, 57 | }; 58 | 59 | module.exports = withTaddy()( 60 | withMDX({ 61 | extension: /\.mdx?$/, 62 | })(config), 63 | ); 64 | -------------------------------------------------------------------------------- /@docs/website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@docs/website", 4 | "description": "taddy documentation website", 5 | "version": "0.0.0", 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@babel/preset-react": "^7.18.6", 13 | "@babel/preset-typescript": "^7.21.5", 14 | "@babel/standalone": "^7.21.2", 15 | "@mdx-js/react": "2.3.0", 16 | "@reatom/core": "1.1.5", 17 | "@reatom/react": "1.1.5", 18 | "@taddy/babel-plugin": "^0.1.0-alpha.0", 19 | "@taddy/next-plugin": "^0.1.0-alpha.0", 20 | "filer": "1.4.1", 21 | "history": "5.3.0", 22 | "lz-string": "1.5.0", 23 | "next": "13.2.4", 24 | "path-browserify": "^1.0.1", 25 | "postcss-js": "4.0.1", 26 | "prettier": "2.8.4", 27 | "qhistory": "1.1.0", 28 | "qs": "6.11.1", 29 | "react": "18.2.0", 30 | "react-ace": "10.1.0", 31 | "react-dom": "18.2.0", 32 | "sharp": "^0.31.3", 33 | "taddy": "^0.1.0-alpha.0" 34 | }, 35 | "devDependencies": { 36 | "@mdx-js/loader": "^2.3.0", 37 | "@next/mdx": "^13.2.4", 38 | "@types/babel__standalone": "^7.1.4", 39 | "@types/node": "^18.15.0", 40 | "@types/react": "^18.0.28", 41 | "imagemin-svgo": "^10.0.1", 42 | "next-compose-plugins": "2.2.1", 43 | "svg-sprite-loader": "^6.0.11", 44 | "webpack": "^5.76.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /@docs/website/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | import '@docs/website/styles/globals.css'; 4 | import '@docs/website/styles/reset.css'; 5 | 6 | // import '@taddy/babel-plugin/.cache'; 7 | // import '@taddy/babel-plugin/.cache/index.css'; 8 | 9 | import {css} from 'taddy'; 10 | 11 | import {MDXProvider} from '@mdx-js/react'; 12 | 13 | import {createStore} from '@reatom/core'; 14 | import {context} from '@reatom/react'; 15 | 16 | import {Sidebar} from '@docs/website/components/Sidebar/index'; 17 | import sidebarStyles from '@docs/website/components/Sidebar/styles.module.css'; 18 | 19 | import {Link} from '@docs/website/components/Link'; 20 | import {AppProps} from 'next/app'; 21 | 22 | import ico from '@docs/website/public/favicon.ico'; 23 | 24 | const store = createStore(); 25 | 26 | const components = { 27 | a: (props) => , 28 | pre: (props) =>
,
29 |     code: ({...props}) => ,
30 |     inlineCode: ({className, ...props}) => (
31 |         
43 |     ),
44 | };
45 | 
46 | function MyApp({Component, pageProps, router}: AppProps) {
47 |     const name = router.pathname.slice(1);
48 |     const title = 'taddy' + (name ? ' | ' + name : '');
49 | 
50 |     return (
51 |         
52 |             
53 |                 
54 | 55 | 59 | 60 | 61 | 62 | {title} 63 | 64 | 65 |
72 | 73 | 74 |
83 |
84 | 85 |
86 |
87 |
88 |
89 |
90 |
91 | ); 92 | } 93 | 94 | export default MyApp; 95 | -------------------------------------------------------------------------------- /@docs/website/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | import {Link} from '@docs/website/components/BaseLink'; 4 | import {Row, Column} from '@docs/website/components/layout'; 5 | import {LinkButton} from '@docs/website/components/LinkButton'; 6 | 7 | import logo1Image from '@docs/website/public/logo/taddy1.png'; 8 | import logo11Image from '@docs/website/public/logo/taddy11.png'; 9 | 10 | export default function Home() { 11 | return ( 12 | 24 | 25 | 26 |
27 | {/* */} 33 |

40 | Compile‑time Atomic CSS‑in‑JS 41 |

42 | {/*
*/} 43 |
44 | 45 | 52 | 57 | Get Started 58 | 59 | 60 | 61 | Playground 62 | 63 | 64 |
65 | ); 66 | } 67 | 68 | const Logos = () => { 69 | const transition = css.mixin({ 70 | transitionProperty: 'opacity', 71 | transitionDuration: '500ms', 72 | }); 73 | 74 | const second = css({ 75 | ...transition, 76 | 77 | position: 'absolute', 78 | width: '100%', 79 | height: '100%', 80 | top: 0, 81 | left: 0, 82 | opacity: 0, 83 | }); 84 | const first = css({ 85 | ...transition, 86 | }); 87 | 88 | return ( 89 | 103 |
109 | taddy logo 115 | 116 | taddy logo 122 |
123 | 124 |

130 | taddy 131 |

132 | 133 | ); 134 | }; 135 | -------------------------------------------------------------------------------- /@docs/website/pages/playground/index.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | 3 | import {css} from 'taddy'; 4 | 5 | import {Column} from '@docs/website/components/layout'; 6 | 7 | const Playground = dynamic( 8 | () => import('@docs/website/components/playground'), 9 | { 10 | ssr: false, 11 | loading: () =>

loading ...

, 12 | }, 13 | ); 14 | 15 | export default function PlaygroundPage() { 16 | return ( 17 | 18 |
25 |

Playground

26 | 27 | 41 | Hello, world! 42 | 43 | ) 44 | `} 45 | /> 46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /@docs/website/pages/roadmap.md: -------------------------------------------------------------------------------- 1 | ../../../ROADMAP.md -------------------------------------------------------------------------------- /@docs/website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/website/public/favicon.ico -------------------------------------------------------------------------------- /@docs/website/public/logo/taddy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/website/public/logo/taddy1.png -------------------------------------------------------------------------------- /@docs/website/public/logo/taddy11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/website/public/logo/taddy11.png -------------------------------------------------------------------------------- /@docs/website/public/logo/taddy2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/website/public/logo/taddy2.png -------------------------------------------------------------------------------- /@docs/website/public/logo/taddy9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@docs/website/public/logo/taddy9.png -------------------------------------------------------------------------------- /@docs/website/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | html, 19 | body { 20 | /* background: #fafafa; */ 21 | height: 100%; 22 | color: #333; 23 | width: 100vw; 24 | overflow-x: hidden; 25 | } 26 | 27 | use { 28 | stroke: violet !important; 29 | } 30 | 31 | svg * { 32 | all: inherit; 33 | } 34 | 35 | li + li, 36 | li > ul { 37 | margin-top: 10px; 38 | } 39 | -------------------------------------------------------------------------------- /@docs/website/styles/index.ts: -------------------------------------------------------------------------------- 1 | export const COLORS = { 2 | base0: 'violet', 3 | base1: 'darkslateblue', 4 | base2: 'royalblue', 5 | base3: 'cornflowerblue', 6 | base4: 'white', 7 | }; 8 | -------------------------------------------------------------------------------- /@docs/website/styles/mixins.ts: -------------------------------------------------------------------------------- 1 | import {$, css} from 'taddy'; 2 | 3 | const size = (v: number) => `${v * 4}px`; 4 | 5 | export const row = ({gap = 0, gapY = gap, gapX = gap, inline = false} = {}) => 6 | css.mixin({ 7 | display: 'flex', 8 | flexDirection: 'row', 9 | 10 | margin: `${size(-gapY / 2)} ${size(-gapX / 2)}`, 11 | 12 | [$`> *:not(:empty)`]: { 13 | margin: `${size(gapY / 2)} ${size(gapX / 2)}`, 14 | }, 15 | 16 | ...(!inline && { 17 | flex: '1', 18 | width: `calc(100% + ${size(gapX)})`, 19 | 20 | [$`> *`]: { 21 | flex: '1', 22 | }, 23 | }), 24 | }); 25 | 26 | export const column = ({gap = 0, inline = false} = {}) => 27 | css.mixin({ 28 | display: 'flex', 29 | flexDirection: 'column', 30 | 31 | [$`> *:not(:empty) + *:not(:empty)`]: { 32 | marginTop: size(gap), 33 | }, 34 | 35 | ...(!inline && { 36 | flex: '1', 37 | 38 | [$`> *`]: { 39 | flex: '1', 40 | }, 41 | }), 42 | }); 43 | -------------------------------------------------------------------------------- /@docs/website/styles/reset.css: -------------------------------------------------------------------------------- 1 | /*** 2 | The new CSS reset - version 1.8.4 (last updated 14.2.2023) 3 | GitHub page: https://github.com/elad2412/the-new-css-reset 4 | ***/ 5 | 6 | /* 7 | Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property 8 | - The "symbol *" part is to solve Firefox SVG sprite bug 9 | */ 10 | *:where( 11 | :not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *) 12 | ) { 13 | margin: 0; 14 | } 15 | 16 | /* Preferred box-sizing value */ 17 | *, 18 | *::before, 19 | *::after { 20 | box-sizing: border-box; 21 | } 22 | 23 | /* Remove default margin */ 24 | body, 25 | h1, 26 | h2, 27 | h3, 28 | h4, 29 | p, 30 | figure, 31 | blockquote, 32 | dl, 33 | dd { 34 | margin: 0; 35 | } 36 | 37 | /* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */ 38 | ul[role='list'], 39 | ol[role='list'] { 40 | list-style: none; 41 | } 42 | 43 | /* Set core root defaults */ 44 | html:focus-within { 45 | scroll-behavior: smooth; 46 | } 47 | 48 | /* Set core body defaults */ 49 | body { 50 | min-height: 100vh; 51 | text-rendering: optimizeSpeed; 52 | line-height: 1.5; 53 | } 54 | 55 | /* A elements that don't have a class get default styles */ 56 | a:not([class]) { 57 | text-decoration-skip-ink: auto; 58 | } 59 | 60 | /* Make images easier to work with */ 61 | img, 62 | picture { 63 | max-width: 100%; 64 | display: block; 65 | } 66 | 67 | /* Inherit fonts for inputs and buttons */ 68 | input, 69 | button, 70 | textarea, 71 | select { 72 | font: inherit; 73 | } 74 | 75 | /* Remove all animations, transitions and smooth scroll for people that prefer not to see them */ 76 | @media (prefers-reduced-motion: reduce) { 77 | html:focus-within { 78 | scroll-behavior: auto; 79 | } 80 | 81 | *, 82 | *::before, 83 | *::after { 84 | animation-duration: 0.01ms !important; 85 | animation-iteration-count: 1 !important; 86 | transition-duration: 0.01ms !important; 87 | scroll-behavior: auto !important; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /@docs/website/taddy-plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | (pluginOptions = {}) => 3 | (nextConfig = {}) => { 4 | const extension = pluginOptions.extension || /\.mdx$/; 5 | 6 | return Object.assign({}, nextConfig, { 7 | webpack(config, options) { 8 | config.module.rules.push({ 9 | test: /@taddy\/babel-plugin.*\.css/, 10 | use: [], 11 | }); 12 | 13 | if (typeof nextConfig.webpack === 'function') { 14 | return nextConfig.webpack(config, options); 15 | } 16 | 17 | return config; 18 | }, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /@docs/website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "baseUrl": ".", 17 | "noEmit": true 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /@docs/website/utils/code.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import LZString from 'lz-string'; 3 | import history from './history'; 4 | 5 | const NEWLINE = '❤'; 6 | 7 | export const encode = (value) => 8 | LZString.compressToEncodedURIComponent( 9 | value.replace(/\n/g, NEWLINE).replace(/\s/g, ' '), 10 | ); 11 | export const decode = (value) => 12 | LZString.decompressFromEncodedURIComponent(value).replace( 13 | new RegExp(NEWLINE, 'g'), 14 | '\n', 15 | ); 16 | 17 | class CodeHandler { 18 | value: string; 19 | timerId: any; 20 | 21 | constructor(initial) { 22 | this.value = decode(initial); 23 | } 24 | 25 | getHash = () => { 26 | return encode(this.value); 27 | }; 28 | 29 | onChange = (code) => { 30 | this.value = code; 31 | }; 32 | 33 | updateCodeLink = () => { 34 | if (!this.value) return; 35 | 36 | history.replace({ 37 | pathname: globalThis.location.pathname, 38 | query: { 39 | ...history.location.query, 40 | code: this.getHash(), 41 | }, 42 | }); 43 | }; 44 | 45 | scheduleLinkUpdate(timeout = 1000) { 46 | if (this.timerId) { 47 | clearTimeout(this.timerId); 48 | } 49 | 50 | this.timerId = setTimeout(this.updateCodeLink, timeout); 51 | } 52 | } 53 | 54 | export const code = new CodeHandler(history.location.query.code); 55 | 56 | export const useCode = (value?: string | null): string => { 57 | React.useEffect(() => { 58 | if (value === null) return; 59 | 60 | code.onChange(value); 61 | 62 | code.scheduleLinkUpdate(); 63 | }, [value]); 64 | 65 | /** 66 | * Code will subscribe only if there is an initial code 67 | */ 68 | React.useEffect(() => { 69 | if (value === null) return; 70 | 71 | window.addEventListener('blur', code.updateCodeLink); 72 | 73 | return () => { 74 | window.removeEventListener('blur', code.updateCodeLink); 75 | }; 76 | }, []); 77 | 78 | return code.value; 79 | }; 80 | -------------------------------------------------------------------------------- /@docs/website/utils/history.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createBrowserHistory, createMemoryHistory} from 'history'; 3 | import type {History, State, To} from 'history'; 4 | import qhistory from 'qhistory'; 5 | import {stringify, parse} from 'qs'; 6 | 7 | type QueryParams = object & {code: string}; 8 | 9 | type QueryOptions = { 10 | query: QueryParams; 11 | }; 12 | 13 | type QTo = To & QueryOptions; 14 | 15 | interface QHistory extends History { 16 | replace(to: QTo, state?: State): ReturnType; 17 | location: History['location'] & QueryOptions; 18 | } 19 | 20 | const history = qhistory( 21 | typeof window === 'undefined' 22 | ? createMemoryHistory() 23 | : createBrowserHistory(), 24 | stringify, 25 | parse, 26 | ) as QHistory; 27 | 28 | export const Location = ({children}) => { 29 | const [state, setState] = React.useState({history}); 30 | 31 | history.listen(() => { 32 | setState({history}); 33 | }); 34 | 35 | return children(state.history); 36 | }; 37 | 38 | export default history; 39 | -------------------------------------------------------------------------------- /@docs/website/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "alias": ["taddy.dev", "taddy"] 4 | } 5 | -------------------------------------------------------------------------------- /@examples/astro/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /@examples/astro/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Basics 2 | 3 | ``` 4 | npm create astro@latest -- --template basics 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 14 | 15 | 16 | ## 🚀 Project Structure 17 | 18 | Inside of your Astro project, you'll see the following folders and files: 19 | 20 | ``` 21 | / 22 | ├── public/ 23 | │ └── favicon.svg 24 | ├── src/ 25 | │ ├── components/ 26 | │ │ └── Card.astro 27 | │ ├── layouts/ 28 | │ │ └── Layout.astro 29 | │ └── pages/ 30 | │ └── index.astro 31 | └── package.json 32 | ``` 33 | 34 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 35 | 36 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 37 | 38 | Any static assets, like images, can be placed in the `public/` directory. 39 | 40 | ## 🧞 Commands 41 | 42 | All commands are run from the root of the project, from a terminal: 43 | 44 | | Command | Action | 45 | | :--------------------- | :----------------------------------------------- | 46 | | `npm install` | Installs dependencies | 47 | | `npm run dev` | Starts local dev server at `localhost:3000` | 48 | | `npm run build` | Build your production site to `./dist/` | 49 | | `npm run preview` | Preview your build locally, before deploying | 50 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 51 | | `npm run astro --help` | Get help using the Astro CLI | 52 | 53 | ## 👀 Want to learn more? 54 | 55 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 56 | -------------------------------------------------------------------------------- /@examples/astro/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import taddy from '@taddy/vite-plugin'; 2 | import {defineConfig} from 'astro/config'; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | vite: { 7 | plugins: [taddy()], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /@examples/astro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@examples/astro", 4 | "version": "0.0.1", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "astro": "^2.0.18", 15 | "taddy": "^0.1.0-alpha.0" 16 | }, 17 | "devDependencies": { 18 | "@taddy/vite-plugin": "^0.1.0-alpha.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /@examples/astro/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /@examples/astro/src/components/Card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import {css} from 'taddy'; 3 | 4 | export interface Props { 5 | title: string; 6 | body: string; 7 | href: string; 8 | } 9 | 10 | const {href, title, body} = Astro.props; 11 | --- 12 | 13 | 25 | 68 | -------------------------------------------------------------------------------- /@examples/astro/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /@examples/astro/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | title: string; 4 | } 5 | 6 | const { title } = Astro.props; 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {title} 17 | 18 | 19 | 20 | 21 | 22 | 36 | -------------------------------------------------------------------------------- /@examples/astro/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../layouts/Layout.astro'; 3 | import Card from '../components/Card.astro'; 4 | import {css} from 'taddy'; 5 | --- 6 | 7 | 8 |
9 |

Welcome to Astro

10 |

11 | Hello world! 12 |

13 |

14 | To get started, open the directory src/pages in your project.
16 | Code Challenge: Tweak the "Welcome to Astro" message 17 | above. 18 |

19 | 41 |
42 |
43 | 44 | 88 | -------------------------------------------------------------------------------- /@examples/astro/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.json", "astro/tsconfigs/strict"], 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /@examples/nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /@examples/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /@examples/nextjs/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 20 | 21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 39 | -------------------------------------------------------------------------------- /@examples/nextjs/app/SuperComponent.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | export const SuperComponent = () => { 4 | return ( 5 |
6 | hello, super!!! 7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /@examples/nextjs/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET(request: Request) { 2 | return new Response('Hello, Next.js!') 3 | } 4 | -------------------------------------------------------------------------------- /@examples/nextjs/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/nextjs/app/favicon.ico -------------------------------------------------------------------------------- /@examples/nextjs/app/layout.tsx: -------------------------------------------------------------------------------- 1 | // import '@taddy/babel-plugin/lib/cache/taddy.css'; 2 | 3 | export const metadata = { 4 | title: 'Create Next App', 5 | description: 'Generated by create next app', 6 | }; 7 | 8 | export default function RootLayout({children}: {children: React.ReactNode}) { 9 | return ( 10 | 11 | {children} 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /@examples/nextjs/app/page.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | import {SuperComponent} from './SuperComponent'; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | hello, hoh!!! 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /@examples/nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | const withTaddy = require('@taddy/next-plugin'); 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | experimental: { 6 | appDir: true, 7 | }, 8 | }; 9 | 10 | module.exports = withTaddy()(nextConfig); 11 | -------------------------------------------------------------------------------- /@examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "18.15.3", 13 | "@types/react": "18.0.28", 14 | "@types/react-dom": "18.0.11", 15 | "eslint": "8.36.0", 16 | "eslint-config-next": "13.2.4", 17 | "next": "13.2.4", 18 | "react": "18.2.0", 19 | "react-dom": "18.2.0", 20 | "taddy": "^0.1.0-alpha.0" 21 | }, 22 | "devDependencies": { 23 | "@taddy/next-plugin": "^0.1.0-alpha.0", 24 | "tslib": "2.5.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /@examples/nextjs/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /@examples/nextjs/public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /@examples/nextjs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /@examples/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | 6 | "target": "es5", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": ["./*"] 27 | } 28 | }, 29 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 30 | "exclude": ["node_modules"] 31 | } 32 | -------------------------------------------------------------------------------- /@examples/nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /@examples/nuxt/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /@examples/nuxt/README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3 Minimal Starter 2 | 3 | Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. 4 | 5 | ## Setup 6 | 7 | Make sure to install the dependencies: 8 | 9 | ```bash 10 | # yarn 11 | yarn install 12 | 13 | # npm 14 | npm install 15 | 16 | # pnpm 17 | pnpm install 18 | ``` 19 | 20 | ## Development Server 21 | 22 | Start the development server on http://localhost:3000 23 | 24 | ```bash 25 | npm run dev 26 | ``` 27 | 28 | ## Production 29 | 30 | Build the application for production: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | Locally preview production build: 37 | 38 | ```bash 39 | npm run preview 40 | ``` 41 | 42 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. 43 | -------------------------------------------------------------------------------- /@examples/nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /@examples/nuxt/components/Test.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | export default defineComponent({ 4 | render: () => { 5 | return ( 6 |
7 | Hello World 8 |
9 | ); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /@examples/nuxt/components/Test2.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | export default defineComponent({ 4 | render: () => { 5 | return ( 6 |
7 | Hello World 2 8 |
9 | ); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /@examples/nuxt/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import taddyPlugin from '@taddy/vite-plugin'; 2 | 3 | // https://nuxt.com/docs/api/configuration/nuxt-config 4 | export default defineNuxtConfig({ 5 | vite: { 6 | plugins: [taddyPlugin()], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /@examples/nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@examples/nuxt", 4 | "version": "0.0.0", 5 | "scripts": { 6 | "build": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview", 10 | "prepare": "nuxt prepare" 11 | }, 12 | "dependencies": { 13 | "taddy": "^0.1.0-alpha.0" 14 | }, 15 | "devDependencies": { 16 | "nuxt": "^3.3.1", 17 | "@taddy/babel-plugin": "^0.1.0-alpha.0", 18 | "@taddy/vite-plugin": "^0.1.0-alpha.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /@examples/nuxt/pages/index.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /@examples/nuxt/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/nuxt/public/favicon.ico -------------------------------------------------------------------------------- /@examples/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": ["../../tsconfig.json", "./.nuxt/tsconfig.json"], 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /@examples/parcel/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/react"] 3 | } 4 | -------------------------------------------------------------------------------- /@examples/parcel/App.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | import Test from './Test'; 4 | 5 | function App() { 6 | return ( 7 |

8 | Hello world 9 | 10 |

11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /@examples/parcel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Taddy Parcel Example 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /@examples/parcel/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import 'taddy/css'; 5 | 6 | import App from './App'; 7 | 8 | const render = () => { 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root'), 14 | ); 15 | }; 16 | 17 | render(); 18 | -------------------------------------------------------------------------------- /@examples/parcel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@examples/parcel", 4 | "version": "0.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "parcel index.html", 9 | "build": "parcel build index.html" 10 | }, 11 | "dependencies": { 12 | "react": "18.2.0", 13 | "react-dom": "18.2.0", 14 | "taddy": "^0.1.0-alpha.0" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.11.1", 18 | "@types/react": "^18.0.28", 19 | "@types/react-dom": "^18.0.11", 20 | "parcel": "^2.8.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /@examples/parcel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /@examples/react-native-expo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | # Temporary files created by Metro to check the health of the file watcher 17 | .metro-health-check* 18 | -------------------------------------------------------------------------------- /@examples/react-native-expo/App.tsx: -------------------------------------------------------------------------------- 1 | import {StatusBar} from 'expo-status-bar'; 2 | import {StyleSheet, Text, View} from 'react-native'; 3 | 4 | import {css} from 'taddy'; 5 | 6 | export default function App() { 7 | return ( 8 | 9 | 10 | Open up App.js to start working on your app! 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | const styles = StyleSheet.create({ 18 | container: { 19 | flex: 1, 20 | backgroundColor: '#fff', 21 | alignItems: 'center', 22 | justifyContent: 'center', 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /@examples/react-native-expo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-expo", 4 | "slug": "react-native-expo", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": ["**/*"], 15 | "ios": { 16 | "supportsTablet": true 17 | }, 18 | "android": { 19 | "adaptiveIcon": { 20 | "foregroundImage": "./assets/adaptive-icon.png", 21 | "backgroundColor": "#ffffff" 22 | } 23 | }, 24 | "web": { 25 | "favicon": "./assets/favicon.png" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /@examples/react-native-expo/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/react-native-expo/assets/adaptive-icon.png -------------------------------------------------------------------------------- /@examples/react-native-expo/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/react-native-expo/assets/favicon.png -------------------------------------------------------------------------------- /@examples/react-native-expo/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/react-native-expo/assets/icon.png -------------------------------------------------------------------------------- /@examples/react-native-expo/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/react-native-expo/assets/splash.png -------------------------------------------------------------------------------- /@examples/react-native-expo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | [ 7 | 'module-resolver', 8 | { 9 | alias: { 10 | crypto: 'expo-crypto', 11 | }, 12 | }, 13 | ], 14 | ], 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /@examples/react-native-expo/index.tsx: -------------------------------------------------------------------------------- 1 | import {registerRootComponent} from 'expo'; 2 | 3 | import App from './App'; 4 | 5 | registerRootComponent(App); 6 | -------------------------------------------------------------------------------- /@examples/react-native-expo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "react-native-expo", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "expo": "~48.0.6", 13 | "expo-crypto": "^12.2.1", 14 | "expo-status-bar": "~1.4.4", 15 | "react": "18.2.0", 16 | "react-native": "0.71.3", 17 | "taddy": "^0.1.0-alpha.0" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.20.0", 21 | "@types/react-native": "0.71.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /@examples/react-native-expo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.json", "expo/tsconfig.base"], 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /@examples/remix/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Linter.Config} */ 2 | module.exports = { 3 | extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], 4 | }; 5 | -------------------------------------------------------------------------------- /@examples/remix/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | /public/build 6 | .env 7 | -------------------------------------------------------------------------------- /@examples/remix/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix! 2 | 3 | - [Remix Docs](https://remix.run/docs) 4 | 5 | ## Development 6 | 7 | From your terminal: 8 | 9 | ```sh 10 | npm run dev 11 | ``` 12 | 13 | This starts your app in development mode, rebuilding assets on file changes. 14 | 15 | ## Deployment 16 | 17 | First, build your app for production: 18 | 19 | ```sh 20 | npm run build 21 | ``` 22 | 23 | Then run the app in production mode: 24 | 25 | ```sh 26 | npm start 27 | ``` 28 | 29 | Now you'll need to pick a host to deploy it to. 30 | 31 | ### DIY 32 | 33 | If you're familiar with deploying node applications, the built-in Remix app server is production-ready. 34 | 35 | Make sure to deploy the output of `remix build` 36 | 37 | - `build/` 38 | - `public/build/` 39 | 40 | ### Using a Template 41 | 42 | When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server. 43 | 44 | ```sh 45 | cd .. 46 | # create a new project, and pick a pre-configured host 47 | npx create-remix@latest 48 | cd my-new-remix-app 49 | # remove the new project's app (not the old one!) 50 | rm -rf app 51 | # copy your app over 52 | cp -R ../my-old-remix-app/app app 53 | ``` 54 | -------------------------------------------------------------------------------- /@examples/remix/app/root.tsx: -------------------------------------------------------------------------------- 1 | import type {MetaFunction, LinksFunction} from '@remix-run/node'; 2 | import { 3 | Links, 4 | LiveReload, 5 | Meta, 6 | Outlet, 7 | Scripts, 8 | ScrollRestoration, 9 | } from '@remix-run/react'; 10 | import {cssBundleHref} from '@remix-run/css-bundle'; 11 | 12 | export const meta: MetaFunction = () => ({ 13 | charset: 'utf-8', 14 | title: 'New Remix App', 15 | viewport: 'width=device-width,initial-scale=1', 16 | }); 17 | 18 | export const links: LinksFunction = () => { 19 | return [ 20 | ...(cssBundleHref ? [{rel: 'stylesheet', href: cssBundleHref}] : []), 21 | // ... 22 | ]; 23 | }; 24 | 25 | export default function App() { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /@examples/remix/app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | export default function Index() { 4 | return ( 5 |
6 |

Welcome to Remix

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /@examples/remix/app/routes/test/index.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | export default function Index() { 4 | return ( 5 |
6 |

7 | Test Page 8 |

9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /@examples/remix/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/remix", 3 | "version": "0.0.0", 4 | "private": true, 5 | "sideEffects": false, 6 | "scripts": { 7 | "build": "remix build", 8 | "dev": "remix dev", 9 | "start": "remix-serve build", 10 | "typecheck": "tsc", 11 | "postinstall": "remix-esbuild-override" 12 | }, 13 | "dependencies": { 14 | "@remix-run/css-bundle": "^1.14.3", 15 | "@remix-run/node": "^1.14.3", 16 | "@remix-run/react": "^1.14.3", 17 | "@remix-run/serve": "^1.14.3", 18 | "D": "^1.0.0", 19 | "isbot": "^3.6.5", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "remix-esbuild-override": "^3.0.4", 23 | "taddy": "^0.1.0-alpha.0" 24 | }, 25 | "devDependencies": { 26 | "@remix-run/dev": "^1.14.3", 27 | "@remix-run/eslint-config": "^1.14.3", 28 | "@taddy/babel-plugin": "^0.1.0-alpha.0", 29 | "@taddy/esbuild-plugin": "^0.1.0-alpha.0", 30 | "@types/react": "^18.0.25", 31 | "@types/react-dom": "^18.0.8", 32 | "eslint": "^8.27.0" 33 | }, 34 | "engines": { 35 | "node": ">=14" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /@examples/remix/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/remix/public/favicon.ico -------------------------------------------------------------------------------- /@examples/remix/remix.config.js: -------------------------------------------------------------------------------- 1 | const {withEsbuildOverride} = require('remix-esbuild-override'); 2 | const taddyPlugin = require('@taddy/esbuild-plugin'); 3 | 4 | withEsbuildOverride((option) => { 5 | option.plugins.unshift(taddyPlugin()); 6 | 7 | return option; 8 | }); 9 | 10 | /** @type {import('@remix-run/dev').AppConfig} */ 11 | module.exports = { 12 | ignoredRouteFiles: ['**/.*'], 13 | 14 | serverDependenciesToBundle: [/@taddy/], 15 | 16 | future: { 17 | unstable_cssSideEffectImports: true, 18 | }, 19 | 20 | // appDirectory: "app", 21 | // assetsBuildDirectory: "public/build", 22 | // serverBuildPath: "build/index.js", 23 | // publicPath: "/build/", 24 | }; 25 | -------------------------------------------------------------------------------- /@examples/remix/remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /@examples/remix/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.json"], 3 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | 7 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 8 | "isolatedModules": true, 9 | "esModuleInterop": true, 10 | "jsx": "react-jsx", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "target": "ES2019", 14 | "strict": true, 15 | "allowJs": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "paths": { 18 | "~/*": ["./app/*"] 19 | }, 20 | // Remix takes care of building everything in `remix build`. 21 | "noEmit": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /@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@latest 10 | 11 | # create a new project in my-app 12 | npm init solid@latest 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`. 31 | -------------------------------------------------------------------------------- /@examples/solid-start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@examples/solid-start", 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "solid-start dev", 7 | "build": "solid-start build", 8 | "start": "solid-start start" 9 | }, 10 | "type": "module", 11 | "devDependencies": { 12 | "@taddy/vite-plugin": "^0.1.0-alpha.0", 13 | "@types/node": "^18.11.18", 14 | "esbuild": "^0.14.54", 15 | "postcss": "^8.4.21", 16 | "solid-start-node": "^0.2.19", 17 | "vite": "^4.1.4" 18 | }, 19 | "dependencies": { 20 | "@solidjs/meta": "^0.28.2", 21 | "@solidjs/router": "^0.7.0", 22 | "solid-js": "^1.6.11", 23 | "solid-start": "^0.2.23", 24 | "undici": "^5.15.1", 25 | "taddy": "^0.1.0-alpha.0" 26 | }, 27 | "engines": { 28 | "node": ">=16.8" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /@examples/solid-start/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/solid-start/public/favicon.ico -------------------------------------------------------------------------------- /@examples/solid-start/src/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | import {createSignal} from 'solid-js'; 2 | import {css} from 'taddy'; 3 | 4 | export default function Counter() { 5 | const [count, setCount] = createSignal(0); 6 | return ( 7 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /@examples/solid-start/src/components/Hello.tsx: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | export default function Hello() { 4 | return ( 5 |

6 | Hello World 7 |

8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /@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 | 16 | export default function Root() { 17 | return ( 18 | 19 | 20 | SolidStart - Bare 21 | 22 | 26 | 27 | 28 | 29 | 30 | Index 31 | About 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /@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 {Title} from 'solid-start'; 2 | 3 | import Hello from '../components/Hello'; 4 | import Counter from '../components/Counter'; 5 | 6 | export default function Home() { 7 | return ( 8 |
9 | Hello World 10 | 11 | 12 |

13 | Visit{' '} 14 | 19 | start.solidjs.com 20 | {' '} 21 | to learn how to build SolidStart apps. 22 |

23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /@examples/solid-start/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "target": "ESNext", 9 | "module": "ESNext", 10 | "moduleResolution": "node", 11 | "jsxImportSource": "solid-js", 12 | "jsx": "preserve", 13 | "strict": true, 14 | "types": ["solid-start/env"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /@examples/solid-start/vite.config.ts: -------------------------------------------------------------------------------- 1 | import taddy from '@taddy/vite-plugin'; 2 | import solid from 'solid-start/vite'; 3 | import {defineConfig} from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [solid(), taddy()], 7 | }); 8 | -------------------------------------------------------------------------------- /@examples/svelte-kit/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /@examples/svelte-kit/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /@examples/svelte-kit/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /@examples/svelte-kit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/svelte-kit", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 11 | }, 12 | "dependencies": { 13 | "taddy": "^0.1.0-alpha.0" 14 | }, 15 | "devDependencies": { 16 | "@sveltejs/adapter-auto": "^2.0.0", 17 | "@sveltejs/kit": "^1.5.0", 18 | "@taddy/babel-plugin": "^0.1.0-alpha.0", 19 | "svelte": "^3.54.0", 20 | "svelte-check": "^3.0.1", 21 | "svelte-preprocess": "^5.0.3", 22 | "tslib": "^2.4.1", 23 | "vite": "^4.0.0" 24 | }, 25 | "type": "module" 26 | } 27 | -------------------------------------------------------------------------------- /@examples/svelte-kit/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /@examples/svelte-kit/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /@examples/svelte-kit/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /@examples/svelte-kit/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Welcome to SvelteKit

8 |

Visit kit.svelte.dev to read the documentation

9 | -------------------------------------------------------------------------------- /@examples/svelte-kit/src/routes/test/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |

Welcome to SvelteKit

8 |

Visit kit.svelte.dev to read the documentation

9 | 10 | -------------------------------------------------------------------------------- /@examples/svelte-kit/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lttb/taddy/44700650aa6000138104af1b43df944d82e0971d/@examples/svelte-kit/static/favicon.png -------------------------------------------------------------------------------- /@examples/svelte-kit/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: preprocess({ 9 | babel: { 10 | plugins: [['@taddy']], 11 | }, 12 | }), 13 | 14 | kit: { 15 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 16 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 17 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 18 | adapter: adapter(), 19 | }, 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /@examples/svelte-kit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.json", "./.svelte-kit/tsconfig.json"], 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | 6 | "allowJs": true, 7 | "checkJs": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "sourceMap": true, 13 | "strict": true 14 | } 15 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /@examples/svelte-kit/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | cache/* 2 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/README.md: -------------------------------------------------------------------------------- 1 | # @taddy/babel-plugin 2 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/cache/index.js: -------------------------------------------------------------------------------- 1 | // require('./taddy.css'); 2 | 3 | // console.log(require.context(__dirname, true, /.*\.css/)); 4 | 5 | // function requireAll(requireContext) { 6 | // return requireContext.keys().map(requireContext); 7 | // } 8 | 9 | // requireAll(require.context(__dirname, true, /.*\.css/)); 10 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './src'; 2 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/macro.ts: -------------------------------------------------------------------------------- 1 | export {macro} from './src/macro-plugin'; 2 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@taddy/babel-plugin", 3 | "version": "0.1.0-alpha.10", 4 | "author": "Kenzhaev Artur ", 5 | "license": "MIT", 6 | "main": "lib/index.js", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/lttb/taddy.git", 13 | "directory": "@taddy/babel-plugin" 14 | }, 15 | "scripts": { 16 | "clean": "rm -rf lib", 17 | "check:ts": "tsc --noEmit", 18 | "precompile": "yarn clean", 19 | "compile": "tsc", 20 | "prestart": "yarn clean", 21 | "start": "yarn compile --watch", 22 | "prebuild": "yarn clean", 23 | "build": "yarn compile" 24 | }, 25 | "dependencies": { 26 | "@babel/core": "^7.21.0", 27 | "@babel/helper-module-imports": "^7.10.1", 28 | "@babel/preset-env": "^7.10.4", 29 | "@babel/preset-react": "^7.18.6", 30 | "@babel/preset-typescript": "^7.10.4", 31 | "@babel/register": "^7.21.0", 32 | "@babel/standalone": "^7.21.2", 33 | "@babel/types": "^7.11.0", 34 | "@taddy/core": "^0.1.0-alpha.3", 35 | "babel-helper-evaluate-path": "^0.5.0", 36 | "common-tags": "^1.8.2", 37 | "convert-source-map": "^2.0.0", 38 | "find-cache-dir": "3.3.2", 39 | "lilconfig": "^2.1.0", 40 | "postcss": "^8.4.21", 41 | "postcss-js": "^4.0.1", 42 | "resolve": "^1.22.1", 43 | "source-map": "^0.7.4", 44 | "string-hash": "^1.1.3", 45 | "sync-rpc": "^1.3.6", 46 | "taddy": "^0.1.0-alpha.5", 47 | "ts-morph": "^17.0.1" 48 | }, 49 | "devDependencies": { 50 | "@types/babel__register": "^7.17.0", 51 | "@types/babel__standalone": "^7.1.4", 52 | "@types/find-cache-dir": "3.2.1", 53 | "@types/node": "18.15.0", 54 | "@types/string-hash": "1.1.1", 55 | "@types/workerpool": "^6.4.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/config.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | export const MACRO_NAME = 'taddy.macro'; 4 | export const PACKAGE_NAME = 'taddy'; 5 | 6 | /** 7 | * @param {import('@babel/core').ConfigAPI} babel 8 | * @returns {import('./types').Env} 9 | */ 10 | export function getEnv(babel) { 11 | try { 12 | return babel.env(); 13 | } catch (e) { 14 | // console.log('error', e); 15 | } 16 | 17 | const DEFAULT_ENV = 'production'; 18 | 19 | if (!(typeof process && process.env)) { 20 | return DEFAULT_ENV; 21 | } 22 | 23 | return process.env.BABEL_ENV || process.env.NODE_ENV || DEFAULT_ENV; 24 | } 25 | 26 | // TODO: add config resolution 27 | 28 | /* 29 | function loadConfig(filepath: string): object { 30 | const {Project} = require('ts-morph'); 31 | 32 | // empty project to parse the config 33 | const project: TSProject = new Project(); 34 | 35 | const sourceFile = project.addSourceFileAtPath(filepath); 36 | 37 | const decl = sourceFile.getDefaultExportSymbol(); 38 | 39 | const properties = getType(decl) 40 | ?.getProperties() 41 | .find((x) => x.getEscapedName() === 'properties'); 42 | 43 | const result = parseObject(getType(properties)); 44 | 45 | const compiled = project.emitToMemory().getFiles()[0].text; 46 | 47 | // eslint-disable-next-line 48 | const emit = new Function('exports', `${compiled};return exports;`); 49 | 50 | return { 51 | ...emit({}).default, 52 | _properties: result, 53 | }; 54 | } 55 | 56 | export const config = lilconfigSync(PACKAGE_NAME, { 57 | searchPlaces: [`${PACKAGE_NAME}.config.ts`], 58 | loaders: { 59 | '.ts': loadConfig, 60 | }, 61 | }).search()?.config; 62 | */ 63 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/BindingOptimizer.ts: -------------------------------------------------------------------------------- 1 | import type {NodePath, Binding} from '@babel/traverse'; 2 | import * as t from '@babel/types'; 3 | 4 | import {findBindings, addBinding} from './findBindings'; 5 | import type {BindingMap} from './findBindings'; 6 | 7 | function isTaddy(binding: Binding) { 8 | const {path} = binding; 9 | 10 | if (!path.isImportSpecifier()) return false; 11 | 12 | const {imported} = path.node; 13 | 14 | return ( 15 | t.isIdentifier(imported) && 16 | (((path.parentPath as NodePath).node.source 17 | .value === 'taddy' && 18 | imported.name === 'css') || 19 | imported.name === 'mixin') 20 | ); 21 | } 22 | 23 | export class BindingOptimizer { 24 | cache: BindingMap = new Map(); 25 | 26 | process(referentPath: NodePath) { 27 | let bindings: ReturnType; 28 | 29 | try { 30 | bindings = findBindings(referentPath); 31 | } catch (error) { 32 | return; 33 | } 34 | 35 | for (const [binding, paths] of bindings) { 36 | if (isTaddy(binding)) continue; 37 | 38 | if (binding.path.removed) continue; 39 | 40 | this.process(binding.path); 41 | 42 | addBinding(this.cache, binding, paths); 43 | 44 | if (this.cache.get(binding)!.size < binding.references) { 45 | continue; 46 | } 47 | 48 | binding.path.remove(); 49 | 50 | const {parentPath} = binding.path; 51 | 52 | const isImportToRemove = 53 | parentPath?.isImportDeclaration() && 54 | parentPath.node.specifiers.length === 0; 55 | 56 | // // keep imports for development build 57 | // if (env === 'development') { 58 | // return; 59 | // } 60 | 61 | if (!isImportToRemove) { 62 | continue; 63 | } 64 | 65 | parentPath.remove(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/buildCodeByPath.ts: -------------------------------------------------------------------------------- 1 | import type {NodePath} from '@babel/traverse'; 2 | 3 | import {findBindings} from './findBindings'; 4 | 5 | function getPathsByBindings( 6 | bindings: ReturnType, 7 | ): Set> { 8 | const paths = new Set>(); 9 | 10 | for (const x of bindings.keys()) { 11 | if (x.path.isImportSpecifier() || x.path.isVariableDeclarator()) { 12 | paths.add(x.path.parentPath); 13 | continue; 14 | } 15 | 16 | paths.add(x.path); 17 | } 18 | 19 | return paths; 20 | } 21 | 22 | export function buildCodeByPath(path: NodePath): string { 23 | return Array.from( 24 | getPathsByBindings(findBindings(path, {throwError: true})), 25 | ) 26 | .sort((a, b) => a.node.start - b.node.start) 27 | .map((x) => x.toString()) 28 | .join('\n'); 29 | } 30 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/evaluate.ts: -------------------------------------------------------------------------------- 1 | import type {NodePath} from '@babel/core'; 2 | 3 | import evaluatePath from 'babel-helper-evaluate-path'; 4 | 5 | import {MACRO_NAME, PACKAGE_NAME} from '../config'; 6 | 7 | import {findBindings} from './findBindings'; 8 | import {buildCodeByPath} from './buildCodeByPath'; 9 | 10 | import ev from './evaluate.worker.cjs'; 11 | 12 | // NOTE: bun doesn't support sync-rps 13 | // TODO: support bun environment and avoid transpiling 14 | const evaluateChunk = process.versions.bun 15 | ? ev() 16 | : require('sync-rpc')(__dirname + '/evaluate.worker.cjs', 'Evaluate'); 17 | 18 | const macroRe = new RegExp(MACRO_NAME.replace('.', '\\.'), 'g'); 19 | 20 | export function evaluate( 21 | currentPath: NodePath, 22 | {exec = true}: {exec?: boolean}, 23 | ): 24 | | { 25 | value: any; 26 | } 27 | | {error: Error} { 28 | const result = evaluatePath(currentPath); 29 | 30 | if (result.confident) { 31 | findBindings(currentPath); 32 | 33 | return {value: result.value}; 34 | } 35 | 36 | if (!exec) return {error: new Error('EXEC_REQUIRED')}; 37 | 38 | let content = ''; 39 | 40 | try { 41 | const callbackName = '__taddy__'; 42 | 43 | content = buildCodeByPath(currentPath) 44 | // hack to avoid processing by babel-macro 45 | .replace(macroRe, PACKAGE_NAME) 46 | .concat(`\n\n;${callbackName}(${currentPath.toString()})`); 47 | 48 | // TODO: improve Hub type 49 | const {opts} = (currentPath.hub as any).file; 50 | 51 | const {value, error} = evaluateChunk({ 52 | content, 53 | filename: opts.filename, 54 | callbackName, 55 | }); 56 | 57 | if (error) return {error}; 58 | 59 | return {value: JSON.parse(value)}; 60 | } catch (error: any) { 61 | // console.log('evaluate error', {content, error}); 62 | 63 | return {error}; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/evaluate.worker.cjs: -------------------------------------------------------------------------------- 1 | const {transform, registerPreset} = require('@babel/standalone'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const resolve = require('resolve'); 5 | 6 | const EXTENSIONS = ['.es6', '.es', '.js', '.mjs', '.jsx', '.tsx', '.ts']; 7 | const DEFAULT_PRESETS = [ 8 | ['@babel/typescript', {allExtensions: true, isTSX: true}], 9 | ['@babel/env', {targets: {node: '12'}, modules: 'cjs'}], 10 | ]; 11 | 12 | const {config} = require('taddy'); 13 | 14 | const {EVAL_FILENAME_POSTFIX} = require('./utils.cjs'); 15 | 16 | // webpack "require" critical dependency issue workaround 17 | const nodeRequire = new Function( 18 | 'require', 19 | `return typeof require !== 'undefined' 20 | ? require 21 | : (typeof globalThis !== 'undefined' ? globalThis : global).require; `, 22 | )(module.require); 23 | 24 | const transformCode = async ({content, filename}) => { 25 | /** @type {import('@babel/core').TransformOptions} */ 26 | const transformOptions = { 27 | filename, 28 | presets: DEFAULT_PRESETS, 29 | }; 30 | 31 | // TODO: switch to async transform (need to support sync browser version) 32 | return transform(content, transformOptions); 33 | }; 34 | 35 | const evaluate = async ({content, filename, callbackName}) => { 36 | const ext = path.extname(filename); 37 | const basename = path.basename(filename, ext); 38 | const dirname = path.dirname(filename); 39 | const evalFilename = path.join( 40 | dirname, 41 | basename + EVAL_FILENAME_POSTFIX + ext, 42 | ); 43 | 44 | const {code} = await transformCode({content, filename: evalFilename}); 45 | 46 | if (!code) return {error: new Error('TRANSPILATION_ERROR')}; 47 | 48 | // console.log({code}); 49 | 50 | const exec = new Function('require', callbackName, code); 51 | 52 | let value; 53 | try { 54 | const currentTarget = config.unstable_target; 55 | 56 | config({unstable_target: 'compiler'}); 57 | exec( 58 | (filepath) => { 59 | const requirePath = resolve.sync(filepath, { 60 | extensions: EXTENSIONS, 61 | basedir: path.dirname(filename), 62 | }); 63 | 64 | return nodeRequire(requirePath); 65 | }, 66 | (result) => { 67 | value = result; 68 | }, 69 | ); 70 | config({unstable_target: currentTarget}); 71 | 72 | // console.log('eval success', value); 73 | } catch (error) { 74 | // console.log('eval error', error); 75 | 76 | return {error}; 77 | } 78 | 79 | // console.log('VALUE', {value}); 80 | 81 | // for some reason, there is an additional ":" prefix on deserialisation/serialisation 82 | // for example, {':hover': {color: 'red'}} becomes {'::hover': {color: 'red'}} 83 | return {value: JSON.stringify(value)}; 84 | }; 85 | 86 | module.exports = () => { 87 | registerPreset('@babel/env', require('@babel/preset-env')); 88 | registerPreset('@babel/typescript', require('@babel/preset-typescript')); 89 | 90 | if (!process.version.bun) { 91 | require('@babel/register')({ 92 | ignore: [ 93 | (filename) => { 94 | // consider symlinks 95 | const realpath = fs.realpathSync(filename); 96 | 97 | if (realpath.includes('node_modules')) return true; 98 | 99 | return false; 100 | }, 101 | ], 102 | presets: DEFAULT_PRESETS, 103 | cache: false, 104 | extensions: EXTENSIONS, 105 | }); 106 | } 107 | 108 | return evaluate; 109 | }; 110 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/getObjectPropertyKey.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import type {NodePath} from '@babel/traverse'; 3 | 4 | export function getObjectPropertyKey(path: NodePath) { 5 | if (path.isIdentifier()) { 6 | return path.node.name; 7 | } 8 | 9 | if (path.isLiteral() && 'value' in path.node) { 10 | return String(path.node.value); 11 | } 12 | 13 | return null; 14 | } 15 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/mergeObjectProperties.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | 3 | type Properties = t.ObjectExpression['properties']; 4 | 5 | export function mergeObjectProperties(properties: Properties) { 6 | const map = new Map(); 7 | 8 | function move(key, value) { 9 | map.delete(key); 10 | map.set(key, value); 11 | } 12 | 13 | for (const x of properties) { 14 | if (!t.isObjectProperty(x)) { 15 | move(x, x); 16 | continue; 17 | } 18 | 19 | const {key} = x; 20 | 21 | if (t.isIdentifier(key)) { 22 | move(key.name, x); 23 | continue; 24 | } 25 | 26 | if (t.isStringLiteral(key)) { 27 | move(key.value, x); 28 | continue; 29 | } 30 | 31 | move(x, x); 32 | } 33 | 34 | // TODO: there was a problem with typescript compiler with the variant [...map.values()] 35 | 36 | return Array.from(map.values()); 37 | } 38 | 39 | export function mergeObjects(objects: t.ObjectExpression[]) { 40 | const properties: Properties = []; 41 | objects.forEach((obj) => { 42 | properties.push(...obj.properties); 43 | }); 44 | return mergeObjectProperties(properties); 45 | } 46 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/optimizeStaticStyles.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | 3 | import type {NodePath} from '@babel/traverse'; 4 | 5 | import {VARS_KEY} from '@taddy/core'; 6 | 7 | import {getObjectPropertyKey} from './getObjectPropertyKey'; 8 | 9 | export function optimizeStaticStyles(path: NodePath) { 10 | const props: t.ObjectProperty[] = []; 11 | 12 | let quasis: t.TemplateElement[] = []; 13 | let expressions: t.Expression[] = []; 14 | 15 | let currQuasi = ''; 16 | 17 | for (const propPath of path.get('properties')) { 18 | if (!propPath.isObjectProperty()) { 19 | throw new Error('Cant optimize this path'); 20 | } 21 | 22 | const keyPath = (propPath as NodePath).get('key'); 23 | const key = getObjectPropertyKey(keyPath); 24 | 25 | if (!key) continue; 26 | 27 | const valuePath = (propPath as NodePath).get('value'); 28 | 29 | if (key === 'className' || key === 'style' || key === VARS_KEY) { 30 | if (currQuasi) { 31 | quasis.push(t.templateElement({raw: currQuasi})); 32 | 33 | props.push( 34 | t.objectProperty( 35 | t.templateLiteral(quasis, expressions), 36 | t.booleanLiteral(true), 37 | ), 38 | ); 39 | } 40 | 41 | props.push(propPath.node); 42 | 43 | quasis = []; 44 | expressions = []; 45 | currQuasi = ''; 46 | 47 | continue; 48 | } 49 | 50 | // append " " delimiter for the continuous value or if that's not the first quasi 51 | if (currQuasi || quasis.length !== 0) { 52 | currQuasi += ' '; 53 | } 54 | 55 | if (valuePath.isStringLiteral()) { 56 | currQuasi += key + valuePath.node.value; 57 | } else if (valuePath.isBooleanLiteral()) { 58 | currQuasi += key; 59 | } else { 60 | currQuasi += key; 61 | 62 | quasis.push(t.templateElement({raw: currQuasi})); 63 | expressions.push(valuePath.node as t.Expression); 64 | 65 | currQuasi = ''; 66 | } 67 | } 68 | 69 | if (currQuasi) { 70 | quasis.push(t.templateElement({raw: currQuasi})); 71 | } 72 | 73 | if (expressions.length === quasis.length) { 74 | quasis.push(t.templateElement({raw: ''})); 75 | } 76 | 77 | if (quasis.length) { 78 | if (props.length === 0) { 79 | path.replaceWith(t.templateLiteral(quasis, expressions)); 80 | 81 | return; 82 | } 83 | 84 | props.push( 85 | t.objectProperty( 86 | t.templateLiteral(quasis, expressions), 87 | t.booleanLiteral(true), 88 | ), 89 | ); 90 | } 91 | 92 | path.node.properties = props; 93 | } 94 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/taggedTemplateToObject.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import type {NodePath} from '@babel/traverse'; 3 | 4 | export function taggedTemplateToObject( 5 | path: NodePath, 6 | ): t.ObjectExpression { 7 | const postcss = require('postcss'); 8 | const postcssJS = require('postcss-js'); 9 | const {quasis, expressions} = path.node.quasi; 10 | 11 | type Expression = (typeof expressions)[number]; 12 | 13 | const cache: {[placeholder: string]: Expression} = {}; 14 | 15 | // this tricky placeholder has special syntax to avoid any conflicts with css parsing 16 | const re = /@\^var__\w+__/; 17 | const getPlaceholder = (index) => `@^var__${index}__`; 18 | 19 | let pseudoCSS = ''; 20 | for (let i = 0; i < quasis.length; i++) { 21 | pseudoCSS += quasis[i].value.raw; 22 | if (expressions[i]) { 23 | const placeholder = getPlaceholder(i); 24 | cache[placeholder] = expressions[i]; 25 | pseudoCSS += placeholder; 26 | } 27 | } 28 | 29 | const root = postcss.parse(pseudoCSS); 30 | const CSSObject = postcssJS.objectify(root); 31 | 32 | function parseString(str) { 33 | const expr: Expression[] = []; 34 | str.replace(new RegExp(re, 'g'), (match) => { 35 | expr.push(cache[match]); 36 | }); 37 | 38 | if (expr.length === 0) { 39 | return t.stringLiteral(str); 40 | } 41 | 42 | return t.templateLiteral( 43 | str.split(re).map((q) => t.templateElement({raw: q, cooked: q})), 44 | expr, 45 | ); 46 | } 47 | 48 | function traverse(obj: object): t.ObjectExpression { 49 | const props: t.ObjectExpression['properties'] = []; 50 | 51 | for (const key in obj) { 52 | const value = obj[key]; 53 | 54 | if (value === true) { 55 | props.push(t.spreadElement(cache[key] as t.Expression)); 56 | 57 | continue; 58 | } 59 | 60 | const cssKey = key.replace(/^&/, ''); 61 | 62 | const keyNode = parseString(cssKey); 63 | let valueNode; 64 | 65 | if (typeof value === 'object') { 66 | valueNode = traverse(value); 67 | } else { 68 | valueNode = parseString(String(value)); 69 | } 70 | 71 | props.push( 72 | t.objectProperty( 73 | keyNode, 74 | valueNode, 75 | !t.isStringLiteral(keyNode), 76 | ), 77 | ); 78 | } 79 | 80 | return t.objectExpression(props); 81 | } 82 | 83 | return traverse(CSSObject); 84 | } 85 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/helpers/utils.cjs: -------------------------------------------------------------------------------- 1 | const EVAL_FILENAME_POSTFIX = '@__TADDY_EVALUATE__'; 2 | 3 | /** 4 | * @param {import('@babel/core').PluginPass} state 5 | * @returns {boolean} 6 | */ 7 | function isTaddyEvaluation(state) { 8 | return !!state.filename?.includes(EVAL_FILENAME_POSTFIX); 9 | } 10 | 11 | module.exports = {EVAL_FILENAME_POSTFIX, isTaddyEvaluation}; 12 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import plugin from './plugin'; 2 | 3 | export default plugin; 4 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | types as t, 3 | PluginPass, 4 | NodePath, 5 | ConfigAPI, 6 | PluginObj, 7 | } from '@babel/core'; 8 | import type {NodePaths} from '@babel/traverse'; 9 | 10 | import {PACKAGE_NAME, getEnv} from './config'; 11 | import * as utils from './helpers/utils.cjs'; 12 | import {macro, type MacroConfig} from './macro-plugin'; 13 | 14 | type ImportSpecifiers = NodePaths; 15 | 16 | function getImportNames(path: ImportSpecifiers[number]): { 17 | localName: string; 18 | importedName: string; 19 | } { 20 | const localName = path.node.local.name; 21 | 22 | if (path.isImportSpecifier()) { 23 | const imported = path.node.imported as t.Identifier; 24 | 25 | return { 26 | localName, 27 | importedName: imported.name, 28 | }; 29 | } 30 | 31 | if (path.isImportDefaultSpecifier()) { 32 | return {localName, importedName: 'default'}; 33 | } 34 | 35 | return {localName, importedName: 'namespace'}; 36 | } 37 | 38 | function findReferences(paths: ImportSpecifiers) { 39 | const references = {}; 40 | for (const path of paths) { 41 | const {localName, importedName} = getImportNames(path); 42 | const binding = path.scope.getBinding(localName); 43 | if (binding?.referencePaths) { 44 | references[importedName] = binding.referencePaths; 45 | } 46 | } 47 | return references; 48 | } 49 | 50 | interface TaddyPluginPass extends PluginPass { 51 | references: Record; 52 | } 53 | 54 | export default function plugin( 55 | babel: ConfigAPI, 56 | options: MacroConfig, 57 | ): PluginObj { 58 | const env = getEnv(babel); 59 | 60 | return { 61 | name: '@taddy/babel-plugin', 62 | 63 | pre() { 64 | this.references = {}; 65 | }, 66 | 67 | visitor: { 68 | // TODO: support require expression 69 | 70 | ImportDeclaration(path, state) { 71 | // console.log('run', state.file.code, state.filename); 72 | 73 | if (utils.isTaddyEvaluation(state)) { 74 | return; 75 | } 76 | 77 | if (path.node.source.value !== PACKAGE_NAME) { 78 | return; 79 | } 80 | 81 | Object.assign( 82 | this.references, 83 | findReferences(path.get('specifiers')), 84 | ); 85 | 86 | if (!(this.references.css || this.references.mixin)) { 87 | return; 88 | } 89 | 90 | macro({ 91 | references: this.references, 92 | babel, 93 | state, 94 | config: { 95 | env, 96 | ...options, 97 | }, 98 | }); 99 | }, 100 | }, 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/source-maps.ts: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {SourceMapGenerator} from 'source-map'; 3 | import * as convert from 'convert-source-map'; 4 | 5 | export {SourceMapGenerator}; 6 | 7 | export function getGeneratorOpts(file) { 8 | return file.opts.generatorOpts ? file.opts.generatorOpts : file.opts; 9 | } 10 | 11 | // export function makeSourceMapGenerator(file) { 12 | // const generator = new SourceMapGenerator(); 13 | 14 | // return generator; 15 | // } 16 | 17 | export function makeSourceMapGenerator(file) { 18 | const generatorOpts = getGeneratorOpts(file); 19 | const filename = generatorOpts.sourceFileName; 20 | const generator = new SourceMapGenerator({ 21 | file: filename, 22 | sourceRoot: generatorOpts.sourceRoot, 23 | }); 24 | 25 | // console.log('root', generatorOpts.sourceRoot); 26 | 27 | generator.setSourceContent(filename, file.code); 28 | return generator; 29 | } 30 | 31 | export function convertGeneratorToComment(generator) { 32 | return convert.fromObject(generator).toComment({multiline: true}); 33 | } 34 | 35 | export function getSourceMap( 36 | offset: { 37 | line: number; 38 | column: number; 39 | }, 40 | state, 41 | ): string { 42 | const generator = makeSourceMapGenerator(state.file); 43 | const generatorOpts = getGeneratorOpts(state.file); 44 | if ( 45 | generatorOpts.sourceFileName && 46 | generatorOpts.sourceFileName !== 'unknown' 47 | ) { 48 | generator.addMapping({ 49 | generated: { 50 | line: 1, 51 | column: 0, 52 | }, 53 | source: generatorOpts.sourceFileName, 54 | original: offset, 55 | }); 56 | return convert.fromObject(generator).toComment({multiline: true}); 57 | } 58 | return ''; 59 | } 60 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/tests/common.ts: -------------------------------------------------------------------------------- 1 | import {transformAsync, createConfigItem} from '@babel/core'; 2 | import type {TransformOptions} from '@babel/core'; 3 | 4 | import {stripIndent} from 'common-tags'; 5 | 6 | import {$css, StyleSheet} from 'taddy'; 7 | 8 | import taddyPlugin from '../plugin'; 9 | import type {MacroConfig} from '../macro-plugin'; 10 | 11 | export {PACKAGE_NAME} from '../config'; 12 | 13 | export function getStyles(): string { 14 | return [...$css.ruleInjector.styleSheet.rules] 15 | .map((rule) => rule.cssText) 16 | .join('\n'); 17 | } 18 | 19 | export function resetStyles() { 20 | if ($css.ruleInjector.styleSheet instanceof StyleSheet) { 21 | $css.ruleInjector.styleSheet.node.remove(); 22 | } 23 | 24 | $css.ruleInjector.reset(); 25 | } 26 | 27 | export const getBabelOptions = ( 28 | options: MacroConfig = {}, 29 | ): TransformOptions => ({ 30 | filename: `test.virtual.tsx`, 31 | babelrc: false, 32 | configFile: false, 33 | plugins: [ 34 | ['@babel/plugin-syntax-typescript', {isTSX: true}], 35 | 36 | createConfigItem((babel) => 37 | taddyPlugin(babel, { 38 | compileOptions: { 39 | evaluate: false, 40 | unstable_typescript: false, 41 | ...options.compileOptions, 42 | }, 43 | outputOptions: { 44 | ...options.outputOptions, 45 | }, 46 | }), 47 | ), 48 | ], 49 | }); 50 | 51 | type Optional = T | null | void; 52 | 53 | export async function transform( 54 | code: string, 55 | babelOptions?: MacroConfig, 56 | transformOptions?: TransformOptions, 57 | ): Promise> { 58 | return ( 59 | await transformAsync(stripIndent(code), { 60 | ...getBabelOptions(babelOptions), 61 | ...transformOptions, 62 | }) 63 | )?.code; 64 | } 65 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/tests/data/mixins.ts: -------------------------------------------------------------------------------- 1 | import {css} from 'taddy'; 2 | 3 | export const box = css.mixin({fontWeight: 'bold'}); 4 | 5 | export const typo = css.mixin({lineHeight: 1, ':hover': {color: 'red'}}); 6 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/tests/typescript.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import {PACKAGE_NAME, transform, getStyles, resetStyles} from './common'; 4 | 5 | const options = { 6 | compileOptions: {unstable_typescript: true}, 7 | }; 8 | 9 | describe('taddy.macro.typescript', () => { 10 | beforeEach((done) => { 11 | resetStyles(); 12 | 13 | done(); 14 | }); 15 | 16 | // TODO: fix the filename consistency between local/CI tests 17 | test.skip('typed mixins', async () => { 18 | expect( 19 | await transform( 20 | ` 21 | import {css} from '${PACKAGE_NAME}' 22 | 23 | function box({direction = 'column' as D}: {direction?: D}) { 24 | return css.mixin({ 25 | display: 'flex', 26 | flexDirection: direction, 27 | captionSide: 'block-end', 28 | 29 | composes: [ 30 | css.mixin({':hover': { 31 | color: 'red', 32 | }}), 33 | 34 | css.mixin({':focus': { 35 | border: '1px solid red', 36 | }}), 37 | ], 38 | }); 39 | } 40 | 41 | export default css({ 42 | ...box({direction: 'row'}), 43 | }) 44 | `, 45 | options, 46 | {filename: './test.infer-mixins.tsx'}, 47 | ), 48 | ).toMatchInlineSnapshot(` 49 | "import { css } from "@taddy/core"; 50 | import ".cache/taddy/kukjmn/1rx2e9b.taddy.css"; 51 | export default css(\`_rnbphe_1vf95 _-g4lbay_2fa2 _-wnpzmr_eeql5n _t3u24i_1kgt43_2f0x _t2q38e_-mvl0b8_2c7gol\`, "__1p9m90k");" 52 | `); 53 | 54 | expect(getStyles()).toMatchInlineSnapshot(` 55 | "._rnbphe_1vf95 { display: flex; } 56 | ._-g4lbay_2fa2 { flex-direction: row; } 57 | ._-wnpzmr_eeql5n { caption-side: block-end; } 58 | ._t3u24i_1kgt43_2f0x:hover { color: red; } 59 | ._t2q38e_-mvl0b8_2c7gol:focus { border: 1px solid red; }" 60 | `); 61 | }); 62 | 63 | // TODO: fix the filename consistency between local/CI tests 64 | test.skip('should support infer types', async () => { 65 | expect( 66 | await transform( 67 | ` 68 | import {css} from '${PACKAGE_NAME}' 69 | import {box, typo} from '@taddy/babel-plugin/src/tests/data/mixins' 70 | 71 | function mixin(size: T) { 72 | return css.mixin({ 73 | ...box, 74 | ...typo, 75 | display: 'flex', 76 | fontSize: size 77 | }) 78 | } 79 | 80 | const display = 'flex' 81 | 82 | export default css({ 83 | ...mixin('smaller'), 84 | ...box, 85 | ...typo, 86 | color: 'red', 87 | display, 88 | }) 89 | `, 90 | options, 91 | {filename: './test.infer-types.tsx'}, 92 | ), 93 | ).toMatchInlineSnapshot(` 94 | "import { css } from "@taddy/core"; 95 | import ".cache/taddy/o2loos/hqitgk.taddy.css"; 96 | export default css("_-q8b8sh_-yoym18 _9wido6_1sxol _-k3s8v4_1d _t3u24i_1kgt43_2f0x _1kgt43_2f0x _rnbphe_1vf95", "__2hq8osn");" 97 | `); 98 | 99 | expect(getStyles()).toMatchInlineSnapshot(` 100 | "._rnbphe_1vf95 { display: flex; } 101 | ._-q8b8sh_-yoym18 { font-size: smaller; } 102 | ._-k3s8v4_1d { line-height: 1; } 103 | ._t3u24i_1kgt43_2f0x:hover,._1kgt43_2f0x { color: red; } 104 | ._9wido6_1sxol { font-weight: bold; }" 105 | `); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Env = 'development' | 'production' | 'test'; 2 | 3 | export type Target = 'auto' | 'vue' | 'remix'; 4 | -------------------------------------------------------------------------------- /@taddy/babel-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "module": "CommonJS", 6 | 7 | "baseUrl": ".", 8 | "rootDir": "." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /@taddy/core/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | 4 | node_modules 5 | 6 | dist 7 | *.code-workspace 8 | .vscode 9 | .cache 10 | 11 | !packages/taddy/.cache 12 | taddy.css 13 | 14 | .vercel 15 | 16 | *.tsbuildinfo 17 | -------------------------------------------------------------------------------- /@taddy/core/NameGenerator/index.ts: -------------------------------------------------------------------------------- 1 | import {isInvalidValue} from '../common'; 2 | 3 | function hash(s: string) { 4 | let h = 0; 5 | for (let i = 0; i < s.length; i++) 6 | h = (Math.imul(31, h) + s.charCodeAt(i)) | 0; 7 | 8 | return h.toString(36); 9 | } 10 | 11 | type NameOptions = { 12 | postfix?: string; 13 | at?: {query?: string; name: string}; 14 | }; 15 | 16 | function generateHash(value: string) { 17 | // if (__DEV__) { 18 | // const cssesc = require('cssesc'); 19 | // return cssesc(value, {isIdentifier: true}).replace(/\s/g, '-'); 20 | // } 21 | 22 | return hash(String(value)); 23 | } 24 | 25 | export class NameGenerator { 26 | cache: {[name: string]: string} = {}; 27 | 28 | getHash = (value?: string): string => { 29 | if (isInvalidValue(value)) return ''; 30 | if (value[0] === '_') return value; 31 | 32 | const key = `_${value}`; 33 | 34 | if (!(key in this.cache)) { 35 | this.cache[key] = generateHash(value); 36 | } 37 | 38 | return `_${this.cache[key]}`; 39 | }; 40 | 41 | getName = ( 42 | prop: string, 43 | value: string, 44 | {postfix = '', at}: NameOptions = {}, 45 | ) => { 46 | return [ 47 | this.getHash(at?.name), 48 | this.getHash(at?.query), 49 | this.getHash(postfix), 50 | this.getHash(prop), 51 | this.getHash(value), 52 | ] as const; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /@taddy/core/README.md: -------------------------------------------------------------------------------- 1 | # @taddy/core 2 | -------------------------------------------------------------------------------- /@taddy/core/common.ts: -------------------------------------------------------------------------------- 1 | export const VARS_KEY = '__VARS__'; 2 | 3 | export const MIXIN_KEY = Symbol('__MIXIN__'); 4 | 5 | export const ID_KEY = Symbol('ID_KEY'); 6 | 7 | type InvalidValue = '' | false | null | void; 8 | export function isInvalidValue(value: any): value is InvalidValue { 9 | return !(!!value || value === 0); 10 | } 11 | -------------------------------------------------------------------------------- /@taddy/core/config.ts: -------------------------------------------------------------------------------- 1 | import {NameGenerator} from './NameGenerator'; 2 | 3 | const nameGenerator = new NameGenerator(); 4 | 5 | type MapStylesOpts = {className: string; style?: object}; 6 | 7 | export type TaddyConfig = { 8 | nameGenerator: NameGenerator; 9 | 10 | /** map "style" and "className" to the needed value */ 11 | unstable_mapStyles: (value: MapStylesOpts) => any; 12 | 13 | /** can be used to pregenarate atoms */ 14 | unstable_properties?: { 15 | [key: string]: 16 | | void 17 | | string 18 | | number 19 | | object 20 | | ((...args: any[]) => object | string | number | null); 21 | }; 22 | 23 | unstable_target?: 'react' | 'react-native' | 'vue' | 'svelte' | 'compiler'; 24 | }; 25 | 26 | type ValidatedShape = T & { 27 | [key in keyof T]: key extends keyof Shape ? T[key] : never; 28 | }; 29 | 30 | declare function setConfig>( 31 | value: ValidatedShape, 32 | ): T; 33 | 34 | export const config: typeof setConfig & TaddyConfig = Object.assign( 35 | >(value: T) => { 36 | Object.assign(config, value); 37 | return value; 38 | }, 39 | { 40 | nameGenerator, 41 | unstable_mapStyles: (x: MapStylesOpts) => x, 42 | }, 43 | ); 44 | -------------------------------------------------------------------------------- /@taddy/core/h.ts: -------------------------------------------------------------------------------- 1 | import {config} from './config'; 2 | 3 | export const h = (x: any) => config.nameGenerator.getHash(x); 4 | -------------------------------------------------------------------------------- /@taddy/core/index.test.ts: -------------------------------------------------------------------------------- 1 | import {expect, describe, it} from '@jest/globals'; 2 | 3 | import {config, css} from '.'; 4 | 5 | const getClassName = (key, value) => 6 | config.nameGenerator.getName(key, value).join(''); 7 | 8 | describe('@taddy/core', () => { 9 | it('should merge styles', () => { 10 | const elem = css( 11 | `${getClassName('color', 'red')} ${getClassName( 12 | 'background', 13 | 'blue', 14 | )}`, 15 | '__id1 _', 16 | ); 17 | 18 | expect( 19 | css( 20 | { 21 | ...elem, 22 | [getClassName('color', 'blue')]: true, 23 | }, 24 | '__id2', 25 | ).className, 26 | ).toEqual( 27 | `${getClassName('color', 'blue')} ${getClassName( 28 | 'background', 29 | 'blue', 30 | )} __id1 __id2 _`, 31 | ); 32 | 33 | expect( 34 | css( 35 | { 36 | [getClassName('color', 'blue')]: true, 37 | ...elem, 38 | }, 39 | '__id2', 40 | ).className, 41 | ).toEqual( 42 | `${getClassName('color', 'red')} ${getClassName( 43 | 'background', 44 | 'blue', 45 | )} __id1 __id2 _`, 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /@taddy/core/index.ts: -------------------------------------------------------------------------------- 1 | export {NameGenerator} from './NameGenerator'; 2 | 3 | export * from './static'; 4 | export * from './common'; 5 | export * from './config'; 6 | export * from './h'; 7 | -------------------------------------------------------------------------------- /@taddy/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@taddy/core", 3 | "version": "0.1.0-alpha.3", 4 | "author": "Kenzhaev Artur ", 5 | "license": "MIT", 6 | "main": "lib/index.cjs", 7 | "module": "lib/index.js", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/lttb/taddy.git" 14 | }, 15 | "scripts": { 16 | "clean": "rm -rf lib", 17 | "check:ts": "tsc --noEmit", 18 | "compile": "rollup -c --bundleConfigAsCjs", 19 | "prestart": "yarn clean", 20 | "start": "yarn compile --watch", 21 | "prebuild": "yarn clean", 22 | "build": "yarn compile" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /@taddy/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonConfig from '../../rollup.config.common'; 2 | 3 | const config = { 4 | ...commonConfig, 5 | 6 | input: ['index.ts'], 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /@taddy/core/static/index.ts: -------------------------------------------------------------------------------- 1 | import {VARS_KEY, MIXIN_KEY, ID_KEY} from '../common'; 2 | 3 | import {config} from '../config'; 4 | 5 | export const staticCache = {}; 6 | export const mapStaticClassName = (className?: string): object => { 7 | if (!className) return {}; 8 | const v = staticCache[className]; 9 | if (v) return v; 10 | return (staticCache[className] = className.split(' ').reduce((acc, v) => { 11 | if (v === '_') return acc; 12 | 13 | // ignore classes which don't have '_' prefix or have '__' prefix 14 | if (v[0] !== '_' || v[1] === '_') { 15 | acc[v] = true; 16 | return acc; 17 | } 18 | 19 | const hashes = v.split('_'); 20 | if (hashes.length === 2) { 21 | acc[v] = true; 22 | } else { 23 | const lastHash = hashes.pop(); 24 | acc[hashes.join('_')] = '_' + lastHash; 25 | } 26 | 27 | return acc; 28 | }, {})); 29 | }; 30 | 31 | export const joinClassName = (className: object): string => { 32 | let classNameString = ''; 33 | for (const key in className) { 34 | if (!className[key]) continue; 35 | 36 | classNameString += (classNameString ? ' ' : '') + key; 37 | if (typeof className[key] === 'string') { 38 | classNameString += className[key]; 39 | } 40 | } 41 | return classNameString; 42 | }; 43 | 44 | export const withId = (result, id?: string | void) => { 45 | if (!result) return result; 46 | 47 | /** 48 | * For the reference between different styles 49 | */ 50 | const value = 51 | id || '__' + config.nameGenerator.getHash('id' + result.className); 52 | 53 | result[ID_KEY] = value; 54 | result[Symbol?.toPrimitive || 'toString'] = () => '.' + value; 55 | result.className += (result.className ? ' ' : '') + value; 56 | 57 | // append "_" to the final className to maintain specificity 58 | result.className += ' _'; 59 | 60 | return result; 61 | }; 62 | 63 | const _css = ( 64 | rule?: 65 | | string 66 | | Partial<{ 67 | className: string; 68 | style: object; 69 | [VARS_KEY]: object; 70 | [key: string]: any; 71 | }>, 72 | id?: string, 73 | ): {className: string; style?: object} => { 74 | if (!rule) return {className: ''}; 75 | 76 | if (typeof rule === 'string') { 77 | return withId({className: rule}, id); 78 | } 79 | 80 | const result: any = {className: ''}; 81 | 82 | let style; 83 | 84 | const className = {}; 85 | 86 | for (const key in rule) { 87 | if (!rule[key]) continue; 88 | 89 | if (rule[key] === true) { 90 | Object.assign(className, mapStaticClassName(key)); 91 | 92 | continue; 93 | } 94 | if (key === 'className') { 95 | Object.assign(className, mapStaticClassName(rule[key])); 96 | 97 | continue; 98 | } 99 | if (key === 'style' || key === VARS_KEY) { 100 | Object.assign((style = style || {}), rule[key]); 101 | 102 | continue; 103 | } 104 | } 105 | 106 | result.className = joinClassName(className); 107 | 108 | if (style) { 109 | result.style = style; 110 | } 111 | 112 | return withId(result, id); 113 | }; 114 | 115 | export const css = ( 116 | ...args: Parameters 117 | ): ReturnType => config.unstable_mapStyles(_css(...args)); 118 | 119 | css.h = (x) => config.nameGenerator.getHash(x); 120 | // eslint-disable-next-line no-sequences 121 | css.mixin = (x: object) => ((x[MIXIN_KEY] = _css(x)), x); 122 | 123 | css.static = css; 124 | // @ts-expect-error TODO: fix 125 | css.mixin.static = css.mixin; 126 | 127 | export function $(strs: TemplateStringsArray, ...values: any[]): string { 128 | let selector = ''; 129 | strs.forEach((chunk, index) => { 130 | selector += chunk; 131 | if (values[index]) { 132 | selector += '.' + values[index][ID_KEY]; 133 | } 134 | }); 135 | return selector; 136 | } 137 | -------------------------------------------------------------------------------- /@taddy/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /@taddy/esbuild-plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # 0.1.0-alpha.1 (2023-03-19) 7 | 8 | ### Features 9 | 10 | - **esbuild-plugin:** initial implementation ([3635a81](https://github.com/lttb/taddy/commit/3635a816605821caae0800f77680cb0db42a1fdf)) 11 | - make it work with vue jsx ([334939e](https://github.com/lttb/taddy/commit/334939e203f5e5a5a6afd34ec093eee429e490cc)) 12 | -------------------------------------------------------------------------------- /@taddy/esbuild-plugin/index.js: -------------------------------------------------------------------------------- 1 | const babel = require('@babel/core'); 2 | const taddyBabelPlugin = require('@taddy/babel-plugin'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | function taddyPlugin() { 7 | /** @type {import('esbuild').Plugin} */ 8 | const plugin = { 9 | name: '@taddy/esbuild-plugin', 10 | setup({onLoad}) { 11 | const root = process.cwd(); 12 | 13 | onLoad({filter: /\.[tj]sx$/}, async (args) => { 14 | const id = args.path; 15 | 16 | if (id.includes('.taddy.js')) return; 17 | 18 | const code = await fs.promises.readFile(id, 'utf8'); 19 | 20 | const extname = path.extname(id); 21 | 22 | const isTypescript = extname === '.tsx' || extname === '.ts'; 23 | 24 | const result = await babel.transformAsync(code, { 25 | babelrc: false, 26 | configFile: false, 27 | ast: false, 28 | root, 29 | filename: id, 30 | parserOpts: { 31 | allowAwaitOutsideFunction: true, 32 | plugins: [ 33 | 'importMeta', 34 | 'topLevelAwait', 35 | 'classProperties', 36 | 'classPrivateProperties', 37 | 'classPrivateMethods', 38 | 'jsx', 39 | ].concat(isTypescript ? ['typescript'] : []), 40 | }, 41 | generatorOpts: { 42 | decoratorsBeforeExport: true, 43 | }, 44 | plugins: [taddyBabelPlugin], 45 | sourceMaps: true, 46 | inputSourceMap: false, 47 | }); 48 | 49 | return { 50 | contents: 51 | result.code + 52 | `//# sourceMappingURL=data:application/json;base64,` + 53 | Buffer.from(JSON.stringify(result.map)).toString( 54 | 'base64', 55 | ), 56 | loader: isTypescript ? 'tsx' : 'jsx', 57 | resolveDir: path.dirname(id), 58 | }; 59 | }); 60 | }, 61 | }; 62 | 63 | return plugin; 64 | } 65 | 66 | module.exports = taddyPlugin; 67 | -------------------------------------------------------------------------------- /@taddy/esbuild-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@taddy/esbuild-plugin", 4 | "version": "0.1.0-alpha.1", 5 | "author": "Kenzhaev Artur ", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/lttb/taddy.git" 14 | }, 15 | "dependencies": { 16 | "@taddy/babel-plugin": "^0.1.0-alpha.1" 17 | }, 18 | "devDependencies": { 19 | "esbuild": "^0.17.12" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /@taddy/esbuild-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /@taddy/next-plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.1.0-alpha.12](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.11...@taddy/next-plugin@0.1.0-alpha.12) (2024-04-10) 7 | 8 | ### Bug Fixes 9 | 10 | - **next-plugin:** fix bun check ([7e44b92](https://github.com/lttb/taddy/commit/7e44b9268345e8a63079536be56e162603724989)) 11 | 12 | # [0.1.0-alpha.11](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.10...@taddy/next-plugin@0.1.0-alpha.11) (2024-04-10) 13 | 14 | ### Features 15 | 16 | - **next-plugin:** import typescript module for bun ([cfe799b](https://github.com/lttb/taddy/commit/cfe799bea7b92c5fa692dd8646b979309b13194d)) 17 | 18 | # [0.1.0-alpha.10](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.9...@taddy/next-plugin@0.1.0-alpha.10) (2024-04-10) 19 | 20 | **Note:** Version bump only for package @taddy/next-plugin 21 | 22 | # [0.1.0-alpha.9](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.8...@taddy/next-plugin@0.1.0-alpha.9) (2024-04-10) 23 | 24 | **Note:** Version bump only for package @taddy/next-plugin 25 | 26 | # [0.1.0-alpha.8](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.7...@taddy/next-plugin@0.1.0-alpha.8) (2024-03-18) 27 | 28 | **Note:** Version bump only for package @taddy/next-plugin 29 | 30 | # [0.1.0-alpha.7](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.6...@taddy/next-plugin@0.1.0-alpha.7) (2023-06-05) 31 | 32 | **Note:** Version bump only for package @taddy/next-plugin 33 | 34 | # [0.1.0-alpha.6](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.5...@taddy/next-plugin@0.1.0-alpha.6) (2023-06-05) 35 | 36 | **Note:** Version bump only for package @taddy/next-plugin 37 | 38 | # [0.1.0-alpha.5](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.4...@taddy/next-plugin@0.1.0-alpha.5) (2023-04-30) 39 | 40 | **Note:** Version bump only for package @taddy/next-plugin 41 | 42 | # [0.1.0-alpha.4](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.3...@taddy/next-plugin@0.1.0-alpha.4) (2023-04-29) 43 | 44 | **Note:** Version bump only for package @taddy/next-plugin 45 | 46 | # [0.1.0-alpha.3](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.2...@taddy/next-plugin@0.1.0-alpha.3) (2023-04-14) 47 | 48 | ### Features 49 | 50 | - **next-plugin:** support mdx ([47d4a41](https://github.com/lttb/taddy/commit/47d4a41a502d491e11fa78d95fa902be09cab895)) 51 | 52 | # [0.1.0-alpha.2](https://github.com/lttb/taddy/compare/@taddy/next-plugin@0.1.0-alpha.1...@taddy/next-plugin@0.1.0-alpha.2) (2023-04-05) 53 | 54 | ### Bug Fixes 55 | 56 | - **taddy/next-plugin:** add @babel/core as a dependency ([de352e2](https://github.com/lttb/taddy/commit/de352e29aa00c8af0a4460756a9d2ef3665c6031)) 57 | 58 | ### Features 59 | 60 | - **taddy/next-plugin:** add lib ([a0f89d2](https://github.com/lttb/taddy/commit/a0f89d29ebd1fde3c8120660f842f2323d008e1d)) 61 | 62 | # 0.1.0-alpha.1 (2023-04-05) 63 | 64 | ### Bug Fixes 65 | 66 | - **taddy/next-plugin:** use taddy subdir in .next ([d855ea0](https://github.com/lttb/taddy/commit/d855ea094747ceac849dc7e7fd79345b84b7f2f5)) 67 | 68 | ### Features 69 | 70 | - **next-plugin:** make next-plugin public ([75ccabf](https://github.com/lttb/taddy/commit/75ccabf9aac3d11d2aafe378011003178f96c9dd)) 71 | - **taddy/next-plugin:** initial implementation ([7b6963a](https://github.com/lttb/taddy/commit/7b6963aec98c9d369b6d2cc64078b9102e14b797)) 72 | - **taddy/next-plugin:** pass options to babel plugin ([3ce8489](https://github.com/lttb/taddy/commit/3ce8489b971a801671f5a4abd4a5a251b328bac1)) 73 | - **taddy/next-plugin:** support .next for caching ([0ffd051](https://github.com/lttb/taddy/commit/0ffd051fb3e2491da0a5ea1b807ea5b67c091714)) 74 | -------------------------------------------------------------------------------- /@taddy/next-plugin/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** @param {{}} [pluginOptions] */ 4 | module.exports = function (pluginOptions = {}) { 5 | /** @param {import('next').NextConfig} nextConfig */ 6 | return (nextConfig) => { 7 | /** @type {import('next').NextConfig} */ 8 | const config = { 9 | webpack(config, options) { 10 | const {dev, dir} = options; 11 | 12 | const taddyOptions = { 13 | ...pluginOptions, 14 | outputOptions: { 15 | cacheDir: path.join(dir, '.next/cache/taddy'), 16 | 17 | ...pluginOptions.outputOptions, 18 | }, 19 | }; 20 | 21 | if (!dev) { 22 | // @see https://github.com/webpack-contrib/mini-css-extract-plugin#extracting-all-css-in-a-single-file 23 | config.optimization.splitChunks.cacheGroups = { 24 | ...config.optimization.splitChunks.cacheGroups, 25 | styles: { 26 | name: 'styles', 27 | type: 'css/mini-extract', 28 | test: /\.taddy\.css$/, 29 | chunks: 'all', 30 | enforce: true, 31 | }, 32 | }; 33 | } 34 | 35 | config.module.rules.push({ 36 | test: /\.(tsx|ts|js|mjs|jsx|mdx|md)$/, 37 | exclude: /node_modules/, 38 | use: { 39 | loader: require.resolve('./loader.cjs'), 40 | options: taddyOptions, 41 | }, 42 | }); 43 | 44 | const cssRules = config.module.rules.find( 45 | (rule) => 46 | Array.isArray(rule.oneOf) && 47 | rule.oneOf.some(({test}) => test.test?.('global.css')), 48 | ).oneOf; 49 | 50 | let globalCSSLoader; 51 | for (const rule of cssRules) { 52 | if (rule.test.test?.('global.css') && rule.sideEffects) { 53 | globalCSSLoader = rule; 54 | 55 | break; 56 | } 57 | } 58 | 59 | if (globalCSSLoader) { 60 | cssRules.unshift({ 61 | test: /\.taddy\.css$/i, 62 | sideEffects: true, 63 | use: globalCSSLoader.use, 64 | }); 65 | } 66 | 67 | if (typeof nextConfig.webpack === 'function') { 68 | return nextConfig.webpack(config, options); 69 | } 70 | 71 | return config; 72 | }, 73 | }; 74 | 75 | return Object.assign({}, nextConfig, config); 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /@taddy/next-plugin/loader.cjs: -------------------------------------------------------------------------------- 1 | const babel = require('@babel/core'); 2 | const taddyBabelPlugin = process.versions.bun 3 | ? // import typescript module for bun 4 | require('@taddy/babel-plugin/index') 5 | : require('@taddy/babel-plugin'); 6 | const path = require('path'); 7 | 8 | /** @type {import('webpack').LoaderDefinition} */ 9 | const loader = function (code) { 10 | const callback = this.async(); 11 | const id = this.resourcePath; 12 | const options = this.getOptions(); 13 | 14 | if (id.includes('.taddy.js')) { 15 | callback(null, code); 16 | 17 | return; 18 | } 19 | 20 | const root = process.cwd(); 21 | 22 | const extname = path.extname(id); 23 | 24 | const isTypescript = extname === '.tsx' || extname === '.ts'; 25 | 26 | babel 27 | .transformAsync(code, { 28 | babelrc: false, 29 | configFile: false, 30 | ast: false, 31 | root, 32 | filename: id, 33 | parserOpts: { 34 | allowAwaitOutsideFunction: true, 35 | plugins: [ 36 | 'importMeta', 37 | 'topLevelAwait', 38 | 'classProperties', 39 | 'classPrivateProperties', 40 | 'classPrivateMethods', 41 | 'jsx', 42 | ].concat(isTypescript ? ['typescript'] : []), 43 | }, 44 | generatorOpts: { 45 | decoratorsBeforeExport: true, 46 | }, 47 | plugins: [[taddyBabelPlugin, options]], 48 | sourceMaps: true, 49 | inputSourceMap: false, 50 | }) 51 | .then((result) => { 52 | callback(null, result.code, result.map); 53 | }) 54 | .catch((err) => { 55 | callback(err); 56 | }); 57 | }; 58 | 59 | module.exports = loader; 60 | -------------------------------------------------------------------------------- /@taddy/next-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@taddy/next-plugin", 3 | "version": "0.1.0-alpha.12", 4 | "author": "Kenzhaev Artur ", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/lttb/taddy.git", 13 | "directory": "@taddy/next-plugin" 14 | }, 15 | "scripts": { 16 | "clean": "rm -rf lib", 17 | "check:ts": "tsc --noEmit", 18 | "compile": "rollup -c --bundleConfigAsCjs", 19 | "prestart": "yarn clean", 20 | "start": "yarn compile --watch", 21 | "prebuild": "yarn clean", 22 | "build": "yarn compile" 23 | }, 24 | "dependencies": { 25 | "@babel/core": "^7.0.0", 26 | "@taddy/babel-plugin": "^0.1.0-alpha.10" 27 | }, 28 | "devDependencies": { 29 | "next": "13.2.4", 30 | "webpack": "^5.77.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /@taddy/next-plugin/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonConfig from '../../rollup.config.common'; 2 | 3 | const config = { 4 | ...commonConfig, 5 | 6 | input: ['index.js', 'loader.cjs'], 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /@taddy/next-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /@taddy/vite-plugin/index.js: -------------------------------------------------------------------------------- 1 | const babel = require('@babel/core'); 2 | const taddyBabelPlugin = require('@taddy/babel-plugin'); 3 | const path = require('path'); 4 | 5 | const SUPPORTED_EXTENSIONS = new Set(['.tsx', '.ts', '.jsx', '.js', '.astro']); 6 | 7 | /** 8 | * @param {object} options 9 | * @param {string} [options.unstable_target] 10 | **/ 11 | function taddyPlugin({unstable_target} = {}) { 12 | /** @type {import('vite').Plugin} */ 13 | const plugin = { 14 | name: '@taddy/vite-plugin', 15 | 16 | async transform(code, id) { 17 | if (id.includes('.taddy.js')) return; 18 | 19 | const extname = path.extname(id); 20 | 21 | if (!SUPPORTED_EXTENSIONS.has(extname)) { 22 | return; 23 | } 24 | 25 | const root = process.cwd(); 26 | 27 | const isTypescript = extname === '.tsx' || extname === '.ts'; 28 | 29 | const result = await babel.transformAsync(code, { 30 | babelrc: false, 31 | configFile: false, 32 | ast: false, 33 | root, 34 | filename: id, 35 | parserOpts: { 36 | allowAwaitOutsideFunction: true, 37 | plugins: [ 38 | 'importMeta', 39 | 'topLevelAwait', 40 | 'classProperties', 41 | 'classPrivateProperties', 42 | 'classPrivateMethods', 43 | 'jsx', 44 | ].concat(isTypescript ? ['typescript'] : []), 45 | }, 46 | generatorOpts: { 47 | decoratorsBeforeExport: true, 48 | }, 49 | plugins: [[taddyBabelPlugin, {unstable_target}]], 50 | sourceMaps: true, 51 | inputSourceMap: false, 52 | }); 53 | 54 | return { 55 | code: result.code, 56 | map: result.map, 57 | }; 58 | }, 59 | }; 60 | 61 | return plugin; 62 | } 63 | 64 | module.exports = taddyPlugin; 65 | -------------------------------------------------------------------------------- /@taddy/vite-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@taddy/vite-plugin", 4 | "version": "0.1.0-alpha.0", 5 | "author": "Kenzhaev Artur ", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/lttb/taddy.git" 14 | }, 15 | "dependencies": { 16 | "@taddy/babel-plugin": "^0.1.0-alpha.0" 17 | }, 18 | "devDependencies": { 19 | "vite": "^4.2.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /@taddy/vite-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /@types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Declare global variables for TypeScript and VSCode. 2 | // Do not rename this file or move these types into index.d.ts 3 | // @see https://code.visualstudio.com/docs/nodejs/working-with-javascript#_global-variables-and-type-checking 4 | declare const __DEV__: boolean; 5 | declare const __VERSION__: string; 6 | declare const $FixMe: any; 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Artur Kenzhaev 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 | ./taddy/README.md -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | The list of key ideas and features that would be great to implement to improve the overall usage experience. 4 | 5 | Please fill free to share any your idea and [add your proposal](https://github.com/lttb/taddy/issues/new?labels=enhancement&title=[dx]%20my%20proposal) to help `taddy` become better. 6 | 7 | ## Overall 8 | 9 | - [ ] Support `styled` function for components, by `@taddy/styled` (?) 10 | - [ ] Provide tools and adaptors for easier `taddy` adoption 11 | - [ ] support `keyframes` and `media` 12 | - [ ] support global styles (?) 13 | 14 | ## Runtime 15 | 16 | - [ ] support runtime vendor prefixes 17 | - [ ] optimize `class` atomic merge (?) 18 | - [ ] improve the detection of styles that were already declared 19 | - [ ] improve styles object caching (eg `css({ color: 'red' })` === `css({ color: 'red' }`) 20 | 21 | ## Compiler 22 | 23 | - [ ] Optimize `babel-plugin`: 24 | - [ ] Improve the way plugin schedules the persistent cache updates 25 | - [ ] Research the ways to make the pre-evaluation more efficient 26 | - [ ] Support `taddy.config.ts` 27 | - [ ] Support atoms pregeneration based on the `config` 28 | - [ ] Support theming (?) 29 | - [ ] Improve `typescript` usage: 30 | - [ ] improve dynamic types infer 31 | - [ ] improve types usage for the complex pre-evaluation (e.g. calculate all the combinations of the calculation based on dynamic typed values) 32 | - [ ] make it stable 33 | - [ ] Optional bindings optimization (?) (at the moment that's always enabled) 34 | - [ ] Support `tagged template literals` by default (at the moment, it's under the unstable flag) 35 | - [ ] Optional `taddy/css` auto-import 36 | - [ ] Support custom paths in `taddy/css` 37 | 38 | ## Developer Experience 39 | 40 | - [ ] Check the usage of `taddy/css` and report if styles were not included to the app 41 | - [ ] Provide better compiler errors and tips 42 | - For example, show errors on `&` usage in selectors 43 | - [ ] Serialize `class` values as readable names instead of the hashes for the `DEV` mode 44 | - [ ] Provide optional warnings, errors and tips if compiler can't statically extract the css code 45 | - [ ] Provide `css source maps` for the declared css-in-js styles 46 | - [ ] Improve and document the Developer Experience with popular frameworks like Next.js 47 | 48 | ## Documentation 49 | 50 | - [ ] Describe the tradeoffs and edge cases 51 | - [ ] Provide different examples and tips for different environments (CRA, next.js, Svelte etc.) 52 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}, modules: 'auto'}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | module.exports = { 3 | testEnvironment: '@happy-dom/jest-environment', 4 | modulePathIgnorePatterns: ['lib', '.cache'], 5 | }; 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "registry": "https://registry.npmjs.org/", 3 | "publishConfig": { 4 | "access": "public" 5 | }, 6 | "packages": ["@taddy/*", "taddy", "taddy.macro", "@docs/*"], 7 | "version": "independent", 8 | "npmClient": "yarn", 9 | "useWorkspaces": true, 10 | "command": { 11 | "publish": { 12 | "conventionalCommits": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "root", 4 | "workspaces": { 5 | "packages": [ 6 | "@examples/*", 7 | "@taddy/*", 8 | "@docs/*", 9 | "taddy", 10 | "taddy.macro" 11 | ], 12 | "nohoist": [ 13 | "tslib", 14 | "**/@remix-run/**" 15 | ] 16 | }, 17 | "license": "MIT", 18 | "engines": { 19 | "node": "18.x" 20 | }, 21 | "scripts": { 22 | "start": "lerna run start --stream --parallel --no-private", 23 | "lint": "eslint --ext .js,.ts,.tsx", 24 | "test": "jest --runInBand", 25 | "check:ts": "lerna run check:ts --parallel --no-private", 26 | "build": "lerna run build --parallel --no-private", 27 | "website": "yarn lerna run --scope @docs/website", 28 | "prewebsite:build": "yarn build", 29 | "website:build": "yarn website build", 30 | "prepub": "lerna run build --no-private", 31 | "pub": "lerna publish from-package --contents lib --exact --no-private", 32 | "prepub:version": "lerna version --no-private", 33 | "pub:version": "yarn pub" 34 | }, 35 | "devDependencies": { 36 | "@babel/cli": "^7.21.0", 37 | "@babel/core": "7.21.0", 38 | "@babel/preset-env": "^7.20.2", 39 | "@babel/preset-typescript": "^7.21.0", 40 | "@happy-dom/jest-environment": "^8.9.0", 41 | "@jest/globals": "29.5.0", 42 | "@rollup/plugin-typescript": "^11.0.0", 43 | "@types/jest": "^29.4.0", 44 | "@typescript-eslint/eslint-plugin": "^5.54.1", 45 | "@typescript-eslint/parser": "^5.54.1", 46 | "babel-jest": "^29.5.0", 47 | "eslint": "^8.36.0", 48 | "eslint-config-prettier": "^8.7.0", 49 | "eslint-import-resolver-typescript": "^3.5.3", 50 | "eslint-plugin-import": "^2.27.5", 51 | "eslint-plugin-react": "^7.32.2", 52 | "eslint-plugin-react-hooks": "^4.6.0", 53 | "happy-dom": "^8.9.0", 54 | "husky": "8.0.3", 55 | "jest": "^29.5.0", 56 | "jest-environment-jsdom": "^29.5.0", 57 | "lerna": "6.5.1", 58 | "prettier": "2.8.4", 59 | "rollup": "^3.19.1", 60 | "rollup-plugin-copy": "^3.4.0", 61 | "rollup-plugin-node-externals": "^5.1.2", 62 | "typescript": "5.0.3" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 4, 3 | singleQuote: true, 4 | trailingComa: 'all', 5 | bracketSpacing: false, 6 | }; 7 | -------------------------------------------------------------------------------- /rollup.config.common.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import externals from 'rollup-plugin-node-externals'; 3 | import copy from 'rollup-plugin-copy'; 4 | 5 | /** @type {import('rollup').RollupOptions} */ 6 | const config = 7 | // ES module build (replaces broken basic TypeScript compilation) 8 | // * ref: , 9 | // * ref: 10 | // * ref: 11 | { 12 | output: [ 13 | { 14 | dir: 'lib', 15 | format: 'esm', 16 | entryFileNames: '[name].js', 17 | sourcemap: true, 18 | preserveModules: true, // or `false` to bundle as a single file 19 | }, 20 | { 21 | dir: 'lib', 22 | format: 'cjs', 23 | entryFileNames: '[name].cjs', 24 | sourcemap: true, 25 | preserveModules: true, // or `false` to bundle as a single file 26 | }, 27 | ], 28 | plugins: [ 29 | externals(), 30 | 31 | typescript({ 32 | exclude: ['**/tests/**', '**/*.test.*'], 33 | compilerOptions: { 34 | incremental: false, 35 | }, 36 | }), 37 | 38 | copy({ 39 | targets: [ 40 | {src: 'README.md', dest: 'lib'}, 41 | {src: 'CHANGELOG.md', dest: 'lib'}, 42 | { 43 | src: 'package.json', 44 | dest: 'lib', 45 | transform: (contents) => { 46 | const packageJson = JSON.parse(contents.toString()); 47 | 48 | return JSON.stringify({ 49 | ...packageJson, 50 | 51 | main: 'index.cjs', 52 | module: 'index.js', 53 | exports: { 54 | ...packageJson.exports, 55 | 56 | ...(packageJson.name === 57 | '@taddy/babel-plugin' && { 58 | './lib/': './', 59 | './cache/': './cache/', 60 | './macro': { 61 | import: './macro.js', 62 | require: './macro.cjs', 63 | }, 64 | }), 65 | 66 | ...(packageJson.name === 'taddy' && { 67 | './vue': { 68 | import: './vue/index.js', 69 | require: './vue/index.cjs', 70 | }, 71 | }), 72 | 73 | '.': { 74 | import: './index.js', 75 | require: './index.cjs', 76 | }, 77 | './package.json': './package.json', 78 | }, 79 | }); 80 | }, 81 | }, 82 | ], 83 | }), 84 | ], 85 | }; 86 | 87 | export default config; 88 | -------------------------------------------------------------------------------- /taddy.macro/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | 4 | node_modules 5 | 6 | dist 7 | *.code-workspace 8 | .vscode 9 | .cache 10 | 11 | !packages/taddy/.cache 12 | taddy.css 13 | 14 | .vercel 15 | 16 | *.tsbuildinfo 17 | -------------------------------------------------------------------------------- /taddy.macro/README.md: -------------------------------------------------------------------------------- 1 | # taddy.macro 2 | -------------------------------------------------------------------------------- /taddy.macro/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from 'taddy'; 2 | -------------------------------------------------------------------------------- /taddy.macro/index.ts: -------------------------------------------------------------------------------- 1 | import {createMacro} from 'babel-plugin-macros'; 2 | import {macro} from '@taddy/babel-plugin/macro'; 3 | 4 | // TODO: make compatible types 5 | export default createMacro(macro as any, {configName: 'taddy'}); 6 | -------------------------------------------------------------------------------- /taddy.macro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taddy.macro", 3 | "version": "0.1.0-alpha.10", 4 | "author": "Kenzhaev Artur ", 5 | "license": "MIT", 6 | "main": "lib/index.cjs", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/lttb/taddy.git" 13 | }, 14 | "scripts": { 15 | "clean": "rm -rf lib", 16 | "check:ts": "tsc --noEmit", 17 | "compile": "rollup -c --bundleConfigAsCjs", 18 | "prestart": "yarn clean", 19 | "start": "yarn compile --watch", 20 | "prebuild": "yarn clean", 21 | "build": "yarn compile" 22 | }, 23 | "dependencies": { 24 | "@taddy/babel-plugin": "^0.1.0-alpha.10", 25 | "taddy": "^0.1.0-alpha.5" 26 | }, 27 | "peerDependencies": { 28 | "babel-plugin-macros": "^3.0.0" 29 | }, 30 | "devDependencies": { 31 | "@types/babel-plugin-macros": "^3.1.0", 32 | "babel-plugin-macros": "^3.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /taddy.macro/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonConfig from '../rollup.config.common'; 2 | 3 | const config = { 4 | ...commonConfig, 5 | 6 | input: ['index.ts'], 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /taddy.macro/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /taddy/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | 4 | node_modules 5 | 6 | dist 7 | *.code-workspace 8 | .vscode 9 | .cache 10 | 11 | !packages/taddy/.cache 12 | taddy.css 13 | 14 | .vercel 15 | 16 | *.tsbuildinfo 17 | -------------------------------------------------------------------------------- /taddy/README.md: -------------------------------------------------------------------------------- 1 |

2 | taddy 3 |

4 | 5 |

6 | taddy 7 |

8 | 9 |

10 | Compile-time Atomic CSS-in-JS 11 |

12 | 13 |

14 | 15 | taddy npm 16 | 17 | 18 | @taddy/core npm bundle size 19 | 20 | 21 | taddy npm bundle size 22 | 23 | 24 |

25 | 26 | 27 | 28 | ## Quick Start 29 | 30 | ```sh 31 | npm install --save taddy 32 | ``` 33 | 34 | 35 | ```jsx 36 | import React from 'react' 37 | 38 | import {css} from 'taddy' 39 | 40 | export function Title() { 41 | return ( 42 |

43 | Hello, taddy! 44 |

45 | ) 46 | } 47 | ``` 48 | 49 | ## Usage 50 | 51 | ### css 52 | 53 | There is an agnostic `css` function, that returns an object of `className` and `style`. 54 | 55 | That's a framework-agnostic function, so it's ready for the usage at any environment. 56 | 57 | ```js 58 | // button = {className: 'hash1 hash2', style: {}} 59 | const button = css({padding: '10px', border: 'none'}); 60 | ``` 61 | 62 | #### pseudo classes 63 | 64 | ```js 65 | const button = css({ 66 | padding: '10px', 67 | border: 'none', 68 | color: 'red', 69 | 70 | ':hover': { 71 | color: 'blue', 72 | }, 73 | }); 74 | ``` 75 | 76 | ### css.mixin 77 | 78 | In terms of `taddy`, mixin is a special styling object, that can be used as a part of styles by `css`. 79 | 80 | To declare the mixin styles, there is a special function `css.mixin`: 81 | 82 | ```js 83 | const heading = css.mixin({ 84 | fontSize: '20px', 85 | fontWeight: 'bold', 86 | }); 87 | ``` 88 | 89 | `mixin` also could be used as a named export: 90 | 91 | ```js 92 | import {mixin} from 'taddy'; 93 | 94 | const heading = mixin({ 95 | fontSize: '20px', 96 | fontWeight: 'bold', 97 | }); 98 | ``` 99 | 100 | #### merge 101 | 102 | Mixin can be applied by spreading to the styles, consumed by `css`: 103 | 104 | ```js 105 | const heading = css.mixin({ 106 | fontSize: '20px', 107 | fontWeight: 'bold', 108 | }); 109 | 110 | const Title = ({children}) => ( 111 |

{children}

112 | ); 113 | ``` 114 | 115 | Mixins also could be used on the nested level: 116 | 117 | ```js 118 | const halfTransparent = css.mixin({ 119 | opacity: 0.5, 120 | }); 121 | 122 | const Title = ({children}) => ( 123 |

130 | {children} 131 |

132 | ); 133 | ``` 134 | 135 | #### composes 136 | 137 | Mixins are cool, but they have some restrictions. 138 | 139 | For example, let's consider two mixins: 140 | 141 | ```js 142 | const colorStateful = css.mixin({ 143 | color: 'red', 144 | 145 | ':hover': { 146 | color: 'blue', 147 | }, 148 | }); 149 | 150 | const opacityStateful = css.mixin({ 151 | opacity: 1, 152 | 153 | ':hover': { 154 | opacity: 0.5, 155 | }, 156 | }); 157 | ``` 158 | 159 | In terms of merge, the result of `css({...colorStateful, ...opacityStateful})` would be `{color: 'red', opacity: 1, ':hover': {opacity: 0.5}}` 160 | 161 | But what if we want to apply both mixins together? 162 | 163 | There is `composes` interface for that (mixins and styles as `css` arguments): 164 | 165 | ```js 166 | const Title = ({children}) => ( 167 |

172 | {children} 173 |

174 | ); 175 | ``` 176 | -------------------------------------------------------------------------------- /taddy/RuleInjector/Sheet.ts: -------------------------------------------------------------------------------- 1 | import {config} from '@taddy/core'; 2 | 3 | import {camelToKebab} from './common'; 4 | 5 | export type SheetOptions = { 6 | mergeDeclarations?: boolean; 7 | virtual?: boolean; 8 | }; 9 | 10 | abstract class Sheet { 11 | options: SheetOptions; 12 | cache: Map; 13 | rulesCache: Map; 14 | 15 | abstract insertAtRule(key: {name: string; query: string}): number; 16 | 17 | abstract insertAtomicRule( 18 | className: string, 19 | key: string, 20 | value: string, 21 | options: {postfix?: string; atRuleIndex?: number}, 22 | ): number; 23 | 24 | abstract appendSelector( 25 | ruleIndex: number, 26 | selector: string, 27 | options?: {atRuleIndex?: number}, 28 | ): void; 29 | 30 | constructor(options: SheetOptions = {}) { 31 | this.options = Object.assign({mergeDeclarations: true}, options); 32 | 33 | this.cache = new Map(); 34 | this.rulesCache = new Map(); 35 | } 36 | 37 | insert( 38 | key: string, 39 | value: any, 40 | { 41 | postfix = '', 42 | at, 43 | hash = '', 44 | }: { 45 | postfix?: string; 46 | at?: {name: string; query: string}; 47 | hash?: string; 48 | }, 49 | ) { 50 | hash = hash ? '-' + hash : ''; 51 | const {nameGenerator} = config; 52 | 53 | const name = nameGenerator.getName(key, value, { 54 | postfix, 55 | at, 56 | }); 57 | 58 | const cssKey = camelToKebab(key); 59 | 60 | const result = Object.create(null); 61 | // result[propHash + postfixHash] = value + hash 62 | result[name[0] + name[1] + name[2] + name[3]] = name[4] + hash; 63 | 64 | const nameHash = name.join(''); 65 | 66 | if (this.cache.has(nameHash)) { 67 | return result; 68 | } 69 | 70 | const atHash = at ? at.name + at.query : ''; 71 | let atRuleIndex = atHash ? this.rulesCache.get(atHash) : undefined; 72 | 73 | if (at && atRuleIndex === undefined) { 74 | atRuleIndex = this.insertAtRule(at); 75 | this.rulesCache.set(atHash, atRuleIndex); 76 | } 77 | 78 | const originalName = nameGenerator.getName(cssKey, value, {at}); 79 | const originalHash = originalName.join(''); 80 | 81 | let ruleIndex; 82 | 83 | const className = `${nameHash}${hash}${postfix}`; 84 | 85 | if (this.rulesCache.has(originalHash)) { 86 | ruleIndex = this.rulesCache.get(originalHash); 87 | 88 | this.appendSelector(ruleIndex, `.${className}`, {atRuleIndex}); 89 | } 90 | 91 | if (ruleIndex === undefined) { 92 | ruleIndex = this.insertAtomicRule(className, cssKey, value, { 93 | postfix, 94 | atRuleIndex, 95 | }); 96 | } 97 | 98 | this.cache.set(nameHash, { 99 | name, 100 | hash, 101 | key, 102 | value, 103 | postfix, 104 | at, 105 | ruleIndex, 106 | }); 107 | 108 | if ( 109 | this.options.mergeDeclarations && 110 | ruleIndex !== undefined && 111 | ruleIndex >= 0 112 | ) { 113 | this.rulesCache.set(originalHash, ruleIndex); 114 | } 115 | 116 | return result; 117 | } 118 | } 119 | 120 | export default Sheet; 121 | -------------------------------------------------------------------------------- /taddy/RuleInjector/VirtualStyleSheet.ts: -------------------------------------------------------------------------------- 1 | import {buildAtomicRule} from './common'; 2 | 3 | import Sheet from './Sheet'; 4 | import type {SheetOptions} from './Sheet'; 5 | 6 | interface VirtualCSSStyleRule extends Partial { 7 | $className: string; 8 | $key: string; 9 | $value: string; 10 | $postfix: string; 11 | } 12 | 13 | type VirtualCSSConditionRule = Partial; 14 | 15 | export class VirtualStyleSheet extends Sheet { 16 | cssRules: (VirtualCSSStyleRule | VirtualCSSConditionRule)[]; 17 | 18 | sheet: {cssRules: VirtualStyleSheet['cssRules']}; 19 | 20 | constructor(options?: SheetOptions) { 21 | super(options); 22 | 23 | this.sheet = { 24 | cssRules: [], 25 | }; 26 | this.cssRules = this.sheet.cssRules; 27 | } 28 | 29 | get rules() { 30 | return this.cssRules; 31 | } 32 | 33 | insertDevRule(rule) { 34 | this.cssRules.push({ 35 | cssText: rule, 36 | }); 37 | } 38 | 39 | insertAtomicRule( 40 | className: string, 41 | key: string, 42 | value: string, 43 | { 44 | postfix = '', 45 | atRuleIndex, 46 | }: {postfix?: string; atRuleIndex?: number} = {}, 47 | ): number { 48 | const isAtRule = atRuleIndex !== undefined; 49 | 50 | const selectorPrefix = isAtRule ? '._' : ''; 51 | const selectorText = selectorPrefix + `.${className}`; 52 | const cssText = buildAtomicRule(selectorText, key, value); 53 | 54 | let insertSheet = this.sheet; 55 | 56 | if (isAtRule) { 57 | // cast media rule type 58 | insertSheet = this.sheet.cssRules[ 59 | atRuleIndex 60 | ] as any as typeof insertSheet; 61 | } 62 | 63 | const index = insertSheet.cssRules.length; 64 | 65 | insertSheet.cssRules.push({ 66 | cssText, 67 | selectorText, 68 | $className: className, 69 | $key: key, 70 | $value: value, 71 | $postfix: postfix, 72 | }); 73 | return index; 74 | } 75 | 76 | insertAtRule(key: {name: string; query: string}): number { 77 | const index = this.sheet.cssRules.length; 78 | const cssRules = [] as any; 79 | this.sheet.cssRules.push({ 80 | get cssText() { 81 | return `${key.name} (${key.query}) {${this.cssRules 82 | .map((x) => x.cssText || '') 83 | .join('')}}`; 84 | }, 85 | cssRules, 86 | conditionText: key.query, 87 | }); 88 | return index; 89 | } 90 | 91 | appendSelector( 92 | ruleIndex: number, 93 | selector: string, 94 | {atRuleIndex}: {atRuleIndex?: number} = {}, 95 | ): void { 96 | let sheet = this.sheet; 97 | 98 | if (atRuleIndex !== undefined) { 99 | // cast media rule type 100 | sheet = this.cssRules[atRuleIndex] as any as typeof sheet; 101 | } 102 | 103 | const rule = sheet.cssRules[ruleIndex] as VirtualCSSStyleRule; 104 | 105 | const selectorText = `${rule.selectorText},${selector}`; 106 | rule.selectorText = selectorText; 107 | rule.cssText = buildAtomicRule(selectorText, rule.$key, rule.$value); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /taddy/RuleInjector/common.ts: -------------------------------------------------------------------------------- 1 | export function buildAtomicRule( 2 | selector: string, 3 | key: string, 4 | value: string, 5 | ): string { 6 | return `${selector}{${key}:${value}}`; 7 | } 8 | 9 | const camelToKebabRe = /([a-z0-9]|(?=[A-Z]))([A-Z])/g; 10 | export function camelToKebab(string): string { 11 | return string.replace(camelToKebabRe, '$1-$2').toLowerCase(); 12 | } 13 | 14 | export function getStyleNodeById(id: string): HTMLStyleElement { 15 | let node = document.getElementById(id) as HTMLStyleElement; 16 | if (!node) { 17 | node = document.createElement('style'); 18 | node.id = id; 19 | document.head.appendChild(node); 20 | } 21 | return node; 22 | } 23 | -------------------------------------------------------------------------------- /taddy/at.ts: -------------------------------------------------------------------------------- 1 | import type {TaddyRule, SupportedAtRulesNames} from './types'; 2 | 3 | export function at( 4 | ruleName: SupportedAtRulesNames, 5 | query: string, 6 | rule: TaddyRule, 7 | ) { 8 | const name = `@${ruleName}`; 9 | return {[name + query]: {'@at': {name, query}, rule}}; 10 | } 11 | -------------------------------------------------------------------------------- /taddy/css.js: -------------------------------------------------------------------------------- 1 | require('@taddy/babel-plugin/cache'); 2 | -------------------------------------------------------------------------------- /taddy/index.native.ts: -------------------------------------------------------------------------------- 1 | import {processRules} from './react-native/processStyles'; 2 | 3 | export function css(...rule) { 4 | return processRules(rule); 5 | } 6 | -------------------------------------------------------------------------------- /taddy/index.ts: -------------------------------------------------------------------------------- 1 | import type {Properties} from 'csstype'; 2 | 3 | import { 4 | css as staticCSS, 5 | $, 6 | withId, 7 | config, 8 | MIXIN_KEY, 9 | ID_KEY, 10 | joinClassName, 11 | } from '@taddy/core'; 12 | import {$css} from './$css'; 13 | import type {TaddyRule} from './types'; 14 | 15 | import {mixin} from './mixin'; 16 | import {at} from './at'; 17 | import {processRules} from './react-native/processStyles'; 18 | 19 | export type ExactProp = Exclude< 20 | Properties[T], 21 | object 22 | >; 23 | 24 | export type TaddyStyle = {style?: any; className: string}; 25 | 26 | export * from './RuleInjector'; 27 | 28 | export {$css, config}; 29 | 30 | /* 31 | 32 | // TODO: at the moment, this kind of opaque type will not work for external modules 33 | // for example, const styles = {base: css({color: 'red'})} 34 | 35 | const TADDY: unique symbol = Symbol('TADDY'); 36 | */ 37 | 38 | type CSSResult = TaddyStyle & Record; 39 | 40 | const getId = (rule: any[]): string | void => { 41 | if (rule.length <= 1) return; 42 | const maybeId = rule[rule.length - 1]; 43 | if (maybeId && maybeId[0] === '_' && maybeId[1] === '_') { 44 | return rule.pop(); 45 | } 46 | }; 47 | 48 | function _css( 49 | rule: (T | TaddyRule | false | void | null | string)[], 50 | ): CSSResult { 51 | const id = getId(rule); 52 | 53 | const result = $css(rule.length <= 1 ? rule[0] : {composes: rule}); 54 | 55 | if (result.className) { 56 | delete result.className[MIXIN_KEY]; 57 | 58 | // @ts-expect-error fix types 59 | result.className = joinClassName(result.className); 60 | } 61 | 62 | return withId(result, id); 63 | } 64 | 65 | /** 66 | * tagged template literal interface works only with babel-plugin 67 | */ 68 | export function css(str: TemplateStringsArray, ...values: any[]): CSSResult; 69 | export function css( 70 | ...rule: Parameters[0] 71 | ): ReturnType; 72 | 73 | export function css(...rule) { 74 | if (config.unstable_target === 'react-native') { 75 | return processRules(rule); 76 | } 77 | 78 | return config.unstable_mapStyles(_css(rule)); 79 | } 80 | 81 | css.mixin = mixin; 82 | css.at = at; 83 | 84 | export const h = (x) => config.nameGenerator.getHash(x); 85 | 86 | css.h = h; 87 | 88 | css.static = (...args: any[]) => staticCSS(...args); 89 | 90 | // @ts-expect-error "static" doesn't exist 91 | css.mixin.static = staticCSS.mixin; 92 | 93 | export {mixin, at, $}; 94 | -------------------------------------------------------------------------------- /taddy/mixin.ts: -------------------------------------------------------------------------------- 1 | import {MIXIN_KEY} from '@taddy/core'; 2 | 3 | import {$css} from './$css'; 4 | import type {TaddyRule} from './types'; 5 | 6 | type TaddyMixin = T; 7 | 8 | // The union hack to improve autocomplete 9 | export function mixin(rule: TaddyRule | T): TaddyMixin; 10 | 11 | export function mixin(rule) { 12 | rule[MIXIN_KEY] = $css(rule); 13 | return rule; 14 | } 15 | -------------------------------------------------------------------------------- /taddy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taddy", 3 | "version": "0.1.0-alpha.5", 4 | "author": "Kenzhaev Artur ", 5 | "license": "MIT", 6 | "main": "lib/index.cjs", 7 | "module": "lib/index.js", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/lttb/taddy.git" 14 | }, 15 | "scripts": { 16 | "clean": "rm -rf lib", 17 | "check:ts": "tsc --noEmit", 18 | "compile": "rollup -c --bundleConfigAsCjs", 19 | "prestart": "yarn clean", 20 | "start": "yarn compile --watch", 21 | "prebuild": "yarn clean", 22 | "build": "yarn compile" 23 | }, 24 | "dependencies": { 25 | "@taddy/core": "^0.1.0-alpha.3", 26 | "csstype": "3.1.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /taddy/react-native/processStyles.ts: -------------------------------------------------------------------------------- 1 | export function processRules(rules: any[]) { 2 | return { 3 | style: rules 4 | .flatMap((x) => (x ? x.style || x : [])) 5 | .reduce((acc, x) => { 6 | for (const key in x) { 7 | if (key[0] === ':' || key[0] === '@') continue; 8 | 9 | acc[key] = x[key]; 10 | } 11 | return acc; 12 | }, {}), 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /taddy/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonConfig from '../rollup.config.common'; 2 | 3 | const config = { 4 | ...commonConfig, 5 | 6 | input: ['index.ts', 'index.native.ts', 'vue/index.ts'], 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /taddy/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import {RuleInjector} from '../RuleInjector'; 2 | import {getStyleNodeById} from '../RuleInjector/common'; 3 | 4 | import {$css} from '..'; 5 | 6 | export function getStyles(): string { 7 | return Array.from(getStyleNodeById('taddy').sheet?.cssRules || []) 8 | .map((rule) => rule.cssText) 9 | .join('\n'); 10 | } 11 | 12 | export function resetStyles() { 13 | getStyleNodeById('taddy').remove(); 14 | 15 | // @ts-expect-error - fix compiled/source types resolution 16 | $css.ruleInjector = new RuleInjector(); 17 | } 18 | -------------------------------------------------------------------------------- /taddy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /taddy/types.ts: -------------------------------------------------------------------------------- 1 | import type {Properties} from 'csstype'; 2 | 3 | export type SupportedAtRulesNames = 'media' | 'supports' | 'container'; 4 | export type SupportedAtRules = `@${SupportedAtRulesNames}`; 5 | 6 | /* that could be {[key in SimplePseudos]: TaddyRule}, but there would be problems with autocomplete */ 7 | type TaddyRuleBase = Properties & { 8 | ':-khtml-any-link'?: TaddyRuleBase; 9 | ':-moz-any-link'?: TaddyRuleBase; 10 | ':-moz-focusring'?: TaddyRuleBase; 11 | ':-moz-full-screen'?: TaddyRuleBase; 12 | ':-moz-placeholder'?: TaddyRuleBase; 13 | ':-moz-read-only'?: TaddyRuleBase; 14 | ':-moz-read-write'?: TaddyRuleBase; 15 | ':-ms-fullscreen'?: TaddyRuleBase; 16 | ':-ms-input-placeholder'?: TaddyRuleBase; 17 | ':-webkit-any-link'?: TaddyRuleBase; 18 | ':-webkit-full-screen'?: TaddyRuleBase; 19 | '::-moz-placeholder'?: TaddyRuleBase; 20 | '::-moz-progress-bar'?: TaddyRuleBase; 21 | '::-moz-range-progress'?: TaddyRuleBase; 22 | '::-moz-range-thumb'?: TaddyRuleBase; 23 | '::-moz-range-track'?: TaddyRuleBase; 24 | '::-moz-selection'?: TaddyRuleBase; 25 | '::-ms-backdrop'?: TaddyRuleBase; 26 | '::-ms-browse'?: TaddyRuleBase; 27 | '::-ms-check'?: TaddyRuleBase; 28 | '::-ms-clear'?: TaddyRuleBase; 29 | '::-ms-fill'?: TaddyRuleBase; 30 | '::-ms-fill-lower'?: TaddyRuleBase; 31 | '::-ms-fill-upper'?: TaddyRuleBase; 32 | '::-ms-input-placeholder'?: TaddyRuleBase; 33 | '::-ms-reveal'?: TaddyRuleBase; 34 | '::-ms-thumb'?: TaddyRuleBase; 35 | '::-ms-ticks-after'?: TaddyRuleBase; 36 | '::-ms-ticks-before'?: TaddyRuleBase; 37 | '::-ms-tooltip'?: TaddyRuleBase; 38 | '::-ms-track'?: TaddyRuleBase; 39 | '::-ms-value'?: TaddyRuleBase; 40 | '::-webkit-backdrop'?: TaddyRuleBase; 41 | '::-webkit-input-placeholder'?: TaddyRuleBase; 42 | '::-webkit-progress-bar'?: TaddyRuleBase; 43 | '::-webkit-progress-inner-value'?: TaddyRuleBase; 44 | '::-webkit-progress-value'?: TaddyRuleBase; 45 | '::-webkit-slider-runnable-track'?: TaddyRuleBase; 46 | '::-webkit-slider-thumb'?: TaddyRuleBase; 47 | '::after'?: TaddyRuleBase; 48 | '::backdrop'?: TaddyRuleBase; 49 | '::before'?: TaddyRuleBase; 50 | '::cue'?: TaddyRuleBase; 51 | '::cue-region'?: TaddyRuleBase; 52 | '::first-letter'?: TaddyRuleBase; 53 | '::first-line'?: TaddyRuleBase; 54 | '::grammar-error'?: TaddyRuleBase; 55 | '::marker'?: TaddyRuleBase; 56 | '::placeholder'?: TaddyRuleBase; 57 | '::selection'?: TaddyRuleBase; 58 | '::spelling-error'?: TaddyRuleBase; 59 | ':active'?: TaddyRuleBase; 60 | ':after'?: TaddyRuleBase; 61 | ':any-link'?: TaddyRuleBase; 62 | ':before'?: TaddyRuleBase; 63 | ':blank'?: TaddyRuleBase; 64 | ':checked'?: TaddyRuleBase; 65 | ':default'?: TaddyRuleBase; 66 | ':defined'?: TaddyRuleBase; 67 | ':disabled'?: TaddyRuleBase; 68 | ':empty'?: TaddyRuleBase; 69 | ':enabled'?: TaddyRuleBase; 70 | ':first'?: TaddyRuleBase; 71 | ':first-child'?: TaddyRuleBase; 72 | ':first-letter'?: TaddyRuleBase; 73 | ':first-line'?: TaddyRuleBase; 74 | ':first-of-type'?: TaddyRuleBase; 75 | ':focus'?: TaddyRuleBase; 76 | ':focus-visible'?: TaddyRuleBase; 77 | ':focus-within'?: TaddyRuleBase; 78 | ':fullscreen'?: TaddyRuleBase; 79 | ':hover'?: TaddyRuleBase; 80 | ':in-range'?: TaddyRuleBase; 81 | ':indeterminate'?: TaddyRuleBase; 82 | ':invalid'?: TaddyRuleBase; 83 | ':last-child'?: TaddyRuleBase; 84 | ':last-of-type'?: TaddyRuleBase; 85 | ':left'?: TaddyRuleBase; 86 | ':link'?: TaddyRuleBase; 87 | ':only-child'?: TaddyRuleBase; 88 | ':only-of-type'?: TaddyRuleBase; 89 | ':optional'?: TaddyRuleBase; 90 | ':out-of-range'?: TaddyRuleBase; 91 | ':placeholder-shown'?: TaddyRuleBase; 92 | ':read-only'?: TaddyRuleBase; 93 | ':read-write'?: TaddyRuleBase; 94 | ':required'?: TaddyRuleBase; 95 | ':right'?: TaddyRuleBase; 96 | ':root'?: TaddyRuleBase; 97 | ':scope'?: TaddyRuleBase; 98 | ':target'?: TaddyRuleBase; 99 | ':valid'?: TaddyRuleBase; 100 | ':visited'?: TaddyRuleBase; 101 | }; 102 | 103 | export type TaddyRule = TaddyRuleBase & { 104 | composes?: object[]; 105 | className?: string; 106 | style?: object; 107 | } & Partial<{ 108 | [key in SupportedAtRules]: Record; 109 | }>; 110 | -------------------------------------------------------------------------------- /taddy/vue/index.ts: -------------------------------------------------------------------------------- 1 | import {config} from 'taddy'; 2 | 3 | config({ 4 | unstable_mapStyles: (result) => ({ 5 | class: result.className, 6 | style: result.style, 7 | }), 8 | }); 9 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "alwaysStrict": true, 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "importHelpers": true, 9 | "jsx": "react-jsx", 10 | "lib": ["dom", "dom.iterable", "esnext"], 11 | "moduleResolution": "nodenext", 12 | "noFallthroughCasesInSwitch": false, 13 | "noImplicitAny": false, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noUnusedLocals": false, 17 | "noUnusedParameters": false, 18 | "pretty": true, 19 | "sourceMap": true, 20 | "strict": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": true, 24 | "stripInternal": true, 25 | "target": "ESNext", 26 | "downlevelIteration": true, 27 | "skipLibCheck": true, 28 | "outDir": "lib" 29 | }, 30 | "exclude": ["**/node_modules/**", "**/lib/**"] 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "incremental": true 5 | } 6 | } 7 | --------------------------------------------------------------------------------