All Projects → snackui → Snackui

snackui / Snackui

Licence: mit
SnackUI 🍑 - the final React style library. With an *optimizing compiler* that lets you write views naturally, with easier DX, working on native and web at once, all while being faster than hand-rolling your own CSS.

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Snackui

visage
Visage design system
Stars: ✭ 12 (-78.18%)
Mutual labels:  css-in-js, ui-kit, ui-components
Compiled
A familiar and performant compile time CSS-in-JS library for React.
Stars: ✭ 1,235 (+2145.45%)
Mutual labels:  webpack, babel-plugin, css-in-js
Vuetify
🐉 Material Component Framework for Vue
Stars: ✭ 33,085 (+60054.55%)
Mutual labels:  ui-kit, ui-components
Event Hooks Webpack Plugin
Event hooks plugin for webpack
Stars: ✭ 30 (-45.45%)
Mutual labels:  webpack, webpack-plugin
Postjss
Use the power of PostCSS in compiling with JSS
Stars: ✭ 40 (-27.27%)
Mutual labels:  babel-plugin, css-in-js
Webpack Common Shake
CommonJS Tree Shaker plugin for WebPack
Stars: ✭ 875 (+1490.91%)
Mutual labels:  webpack, webpack-plugin
Nebular
💥 Customizable Angular UI Library based on Eva Design System 🌚✨Dark Mode
Stars: ✭ 7,368 (+13296.36%)
Mutual labels:  webpack, ui-kit
Webpack Aliyun Oss
一个webpack(version >= 4)插件,上传资源到阿里云oss。可以作为webpack插件使用,也可独立使用
Stars: ✭ 36 (-34.55%)
Mutual labels:  webpack, webpack-plugin
Sugui Design System
A design system template for the SugUI components library based on styleguidist
Stars: ✭ 17 (-69.09%)
Mutual labels:  ui-kit, ui-components
Webpack Webextension Plugin
Webpack plugin that compiles WebExtension manifest.json files and adds smart auto reload
Stars: ✭ 47 (-14.55%)
Mutual labels:  webpack, webpack-plugin
Dotenv Webpack
A secure webpack plugin that supports dotenv and other environment variables and only exposes what you choose and use.
Stars: ✭ 1,022 (+1758.18%)
Mutual labels:  webpack, webpack-plugin
Html Inline Css Webpack Plugin
☄️ A webpack plugin for convert external stylesheet to the embedded stylesheet
Stars: ✭ 48 (-12.73%)
Mutual labels:  webpack, webpack-plugin
Ui Box
Blazing Fast React UI Primitive
Stars: ✭ 847 (+1440%)
Mutual labels:  ui-components, css-in-js
Base
React-UI-Kit - frontend library with ReactJS components
Stars: ✭ 18 (-67.27%)
Mutual labels:  ui-kit, ui-components
Elm Ui
UI library for making web applications with Elm
Stars: ✭ 878 (+1496.36%)
Mutual labels:  ui-kit, ui-components
Cloudflare Workers Webpack Plugin
Launch Cloudflare Workers to the Edge from the comfort of your build step 🚀
Stars: ✭ 18 (-67.27%)
Mutual labels:  webpack, webpack-plugin
Webpack Alioss Plugin
阿里 oss-webpack 自动上传插件
Stars: ✭ 35 (-36.36%)
Mutual labels:  webpack, webpack-plugin
Treat
🍬 Themeable, statically extracted CSS‑in‑JS with near‑zero runtime.
Stars: ✭ 1,058 (+1823.64%)
Mutual labels:  webpack-plugin, css-in-js
Error Overlay Webpack Plugin
Catch errors with style 💥✨
Stars: ✭ 821 (+1392.73%)
Mutual labels:  webpack, webpack-plugin
Prerender Spa Plugin
Prerenders static HTML in a single-page application.
Stars: ✭ 7,018 (+12660%)
Mutual labels:  webpack, webpack-plugin

SnackUI

The smart SwiftUI-inspired UI kit for React Native & Web.

SetupExampleDocumentationIssuesTradeoffsLicense

SnackUI is a UI kit for react native and react native web that builds on the ideas of JSXStyle and SwiftUI. It's a great way to build cross platform app UI's on React that scale well - with smaller bundle sizes and faster rendering performance than StyleSheet.create() on the web. SnackUI is light; it doesn't prescribe much beyond basic views, hooks, and an optimizing compiler.

Illustration of <HStack spacing='md' /> <VStack spacing='lg' />

Features

  • Stack views with flat, simpler RN TypeScript types
  • Optimizing compiler (forked from JSXStyle)
    • Flatten <View /> / <Text /> into <div /> / <span />.
    • Extract inline styles to optimized atomic CSS stylesheets similar to Facebook's internal style library.
    • Support constant imports.
    • Support conditionals like color={isLarge ? 'red' : 'blue'} and <Text {...isLarge && { color: 'red' }} />
  • Pseudo styles
    • Supports hoverStyle, pressStyle, and focusStyle
    • Normalizes tricky styling between native and web
  • Media Queries
    • Supports native + web
    • Simple useMedia hook, compiles away when possible, falls back gracefully
  • Themes
    • Supports native + web
    • Typed themes, useTheme hook
    • Compiles away when possible, falls back gracefully
    • Avoids re-rendering by tracking which theme keys used at runtime
  • Development tools
    • Shows component name in DOM elements.
    • Add // debug to the top of file for detailed optimization info.

SnackUI views flatten all style props onto the base props so there's no separate style prop to use, if you want to read reasoning on why, see why JSXStyle does it, SnackUI has all the same upsides listed there.

Example

import { Text, VStack } from 'snackui'

export function Component() {
  return (
    <VStack
      marginHorizontal={10}
      backgroundColor="red"
      hoverStyle={{ backgroundColor: 'blue' }}
    >
      <Text color="green">Hello world</Text>
    </VStack>
  )
}

This will compile on the web to something like this:

const _cn1 = 'r-1awozwy r-y47klf r-rs99b7 r-h-1udh08x'
const _cn2 = 'r-4qtqp9 r-1i10wst r-x376lf'
export function Component() {
  return (
    <div className={_cn1}>
      <span className={_cn2}>Hello world</span>
    </div>
  )
}

And on native:

import { View, Text, StyleSheet } from 'react-native'

export function Component() {
  return (
    <View style={[sheet[0]]}>
      <Text style={[sheet[1]]}>Hello world</Text>
    </View>
  )
}

const sheet = StyleSheet.create({
  // ... styles for 0 and 1
})

Why do this? Beyond the joy and many benefits of Stack views with inline styling, react-native-web views like <View /> and <Text /> aren't free. Read the source of Text for example. When you're rendering a large page with many text and view elements, snackui saves React from having to process all of that logic on every render, for every Text and View.

Supported extractions

SnackUI has fairly advanced optimizations, it can extract this entire component to CSS and flatten the VStack into a div:

import { Text, VStack, useTheme, useMedia } from 'snackui'
import { redColor } from './colors'

// This entire component will be extracted to just a div + css!
// SnackUI will even remove the hooks

const height = 10

export function Component(props) {
  const theme = useTheme()
  const media = useMedia()
  return (
    <VStack
      // constant values
      height={height}
      // imported constants (using evaluateImportsWhitelist option)
      color={redColor}
      // theme values
      borderColor={theme.borderColor}
      // media queries inline conditional
      borderWidth={media.sm ? 1 : 2}
      // inline conditionals
      backgroundColor={props.highlight ? 'red' : 'blue'}
      // spread objects
      {...props.hoverable && {
        hoverStyle: { backgroundColor: 'blue' }
      }}
      // spread conditional objects
      {...props.condition ? {
          hoverStyle: { backgroundColor: 'blue' }
        } : {
          hoverStyle: { backgroundColor: 'red' }
        }
      }
      // media query spread conditional + themes
      {...media.lg && {
        hoverStyle: {
          backgroundColor: theme.backgroundColorAlt
        }
      }}
    />
  )
}

Setup

Add snackui to your project:

yarn add snackui @snackui/static @snackui/babel-plugin

From here, you can set it two ways: for extraction to CSS on web, you'll need the Webpack plugin. If you don't need that, or are just developing for React Native, then just set up the babel plugin.

Note: Don't use both the Webpack and Babel plugin together as they will conflict.

We hope to add plugins for rollup, esbuild and others soon as it should be relatively straightforward, PR's are welcome.

Babel - Native / Simple extraction (experimental)

This is useful for Native apps or if you're not using Webpack.

For a simpler setup you can just add @snackui/babel-plugin as a babel plugin to your babel config to get extraction just to StyleSheet.create(). This isn't as performant as going to CSS, but works with anything that supports babel.

You can technically just use the babel plugin, but if you want much better flattening and CSS extraction, read on.

Webpack - CSS extraction

To extract to CSS, SnackUI supports Webpack for now (v4 and v5). Add the loader to your webpack config after babel-loader. Note, don't use the babel plugin if you are doing this, you only need the loader for Webpack.

module.exports = {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: require.resolve('@snackui/static/loader'),
            options: {
              // use this to add files to be statically evaluated
              // full path or partial path supported
              // always use the ".js" extension (so colors.ts => colors.js)
              // default:
              evaluateImportsWhitelist: ['constants.js', 'colors.js'],
              // exclude files from processing
              // default null
              exclude: /node_modules/,
              // attempts to statically follow variables to compile
              // default true
              evaluateVars: true

            },
          },
        ].filter(Boolean),
      },
    ],
  },
}

Caveat

react-native-web is currently taking a hard stance against supporting className and removed support for it in v0.14. We've opened an issue, but received pushback. We are going to try and work with them to see if there's a way they can enable a workaround now that we've published SnackUI. You'll have to use patch-package to restore className support for now.

Documentation

Media Queries

Beta. Early support for media queries has landed via the hook useMedia. It's designed to work much the same as the advanced conditional statements do above. If SnackUI extracts all media query statements, it will remove the hook for you.

Customizing the queries isn't supported quite yet, but planned to work without syntax changes. You'll have to make do with the defaults for the beta.

import { useMedia, VStack } from 'snackui'

export function Component(props) {
  const media = useMedia()
  const { sm } = useMedia()
  return (
    <VStack
      height={media.xs ? 100 : 200}
      color={sm ? 'red' : 'blue'}
      {...media.lg && {
        hoverStyle: { backgroundColor: 'blue' }
      }}
    />
  )
}

Using the hook syntax has the nice benefit of falling back gracefully when it's not supported, without any change in syntax. Only width/height media queries work for now. To see if your media query extracted successfully, add // debug to the top of the file.

On native media queries are not extracted and left to parse at runtime.

Themes

Beta. Early support for themes has also landed via the hook useTheme.

First, set up your themes in its own file:

// themes.ts

export type MyTheme = typeof dark
export type MyThemes = typeof themes

const dark = {
  backgroundColor: '#000',
  borderColor: '#222',
  color: '#fff',
}

const light: MyTheme = {
  backgroundColor: '#fff',
  borderColor: '#eee',
  color: '#000',
}

const themes = {
  dark,
  light,
}

export default themes

Then, add a themesFile property to your loader options. Please note, this means your themes file should be loadable by the node process.

module: {
  rules: [
    {
      test: /\.[jt]sx?$/,
      use: [
        {
          loader: 'babel-loader',
        },
        {
          loader: require.resolve('@snackui/static/loader'),
          options: {
            evaluateImportsWhitelist: ['constants.js', 'colors.js'],
            themesFile: require.resolve('./themes.ts'),
          },
        },
      ]
    }
  ]
}

Then, the root of your components:

import { configureThemes, ThemeProvider } from 'snackui'
import themes, { MyTheme, MyThemes } from './themes'

// configure the types
declare module 'snackui' {
  interface ThemeObject extends MyTheme {}
  interface Themes extends MyThemes {}
}

configureThemes(themes)

export function App() {
  return (
    <ThemeProvider themes={themes} defaultTheme="light">
      <Component />
    </ThemeProvider>
  )
}

Finally, in any component below that:

export function Component() {
  const theme = useTheme()
  return (
    <VStack color={theme.color} />
  )
}

SnackUI will extract this and other more complex use cases (logical expressions, ternaries), removing the hook. The CSS will be something like:

.light {
  --color: #000;
  --backgroundColor: #fff;
  --borderColor: #eee;
}
.theme-color {
  color: var(--color);
}

And the output component:

const _cn = `theme-color`
export function Component() {
  return (
    <div className={_cn} />
  )
}

Tradeoffs

Pros

  • Nicer base views: Stacks are easy to learn and use
  • Less up front time: No more jumping between style/view, no time spent thinking about naming things.
  • Less long term maintenance: No dead code to clean up, no thinking about merging shared styles.
  • Smaller bundle sizes: Because everything is extracted to atomic CSS and theres no managing duplicate styles, you ship less JS and lighten your bundle.
  • Faster runtime performance: Your browser can parse the CSS as it loads the page, view flattening means React trees are far more shallow.
  • Devtools: Compiler outputs helpful information to DOM

Cons

  • More setup: Need to configure a webpack plugin and babel plugin
  • Is Beta: Will run into edge cases and bugs
  • Testing: No testing library helpers as of yet
  • Requires checking output: Because we're analyzing somewhat complex statements to optimize, you'll have to keep an eye on the output to ensure it actually extracted. You can do so with the // debug pragma.

Issues

SnackUI is still early stage. It works well for us, and we've built a fairly large app with it, but it's needs wider testing and a couple more features before it really shines. The compiler can be wonky in ways and occasional CSS bugs do exist, but generally it's not hard to quickly see if it's a Snack issue, and further, easy to deopt out when it does happen.

Roadmap

See the roadmap for details:

  • [x] Themes
  • [x] Test performance of useMemo calls / splitProps
  • [ ] Media Queries test coverage, docs and configuration
  • [ ] Docs / docs site
  • [ ] improve props ease of use
    • [ ] media query shorthands
      • [ ] maxWidth={{ sm: 10 }}
      • [ ] maxWidth={{ sm: x ? 10 : 0 }}
    • [ ] flat transforms to prevent awkward spreads
      • scale={} x={} y={}
  • [ ] Support extraction of custom components that extend lower level ones
    • [ ] Support user-defined components that just spread props onto simple child thats extractable
  • [ ] Variants/Scaling
  • [x] Compiler contains memory leak(s) (mostly fixed with esbuild-loader)
  • [ ] Extraction - advanced traversals (see plan)
  • [ ] Support <Stack spacing /> extraction
  • [ ] Support <Input />, <Spacer flex />, <LinearGradient />, maybe <Image />
  • [ ] Compile a few directly-translatable HTML props: onPress, etc
  • [ ] Reload constants/themes during watch
  • [ ] Extract default styles to StyleSheet.create() for better fallback runtime speed
  • [ ] MaskView with web support

License

MIT License, see LICENSE

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].