All Projects → harrysolovay → Sowing Machine

harrysolovay / Sowing Machine

🌱A React UI toolchain & JSX alternative

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Sowing Machine

Babel Plugin Jsx Adopt
Stars: ✭ 94 (+46.88%)
Mutual labels:  babel, babel-plugin, jsx
Eslint Import Resolver Babel Module
Custom eslint resolve for babel-plugin-module-resolver
Stars: ✭ 236 (+268.75%)
Mutual labels:  eslint-plugin, babel, babel-plugin
Babel Plugin React Persist
Automatically useCallback() & useMemo(); memoize inline functions
Stars: ✭ 91 (+42.19%)
Mutual labels:  babel, babel-plugin, jsx
Jsx Control Statements
Neater If and For for React JSX
Stars: ✭ 1,305 (+1939.06%)
Mutual labels:  babel, babel-plugin, jsx
react-enterprise-starter-kit
Highly Scalable Awesome React Starter Kit for an enterprise application with a very easy maintainable codebase. 🔥
Stars: ✭ 55 (-14.06%)
Mutual labels:  babel, eslint, jsx
Astexplorer.app
https://astexplorer.net with ES Modules support and Hot Reloading
Stars: ✭ 65 (+1.56%)
Mutual labels:  eslint, babel, babel-plugin
Babel Plugin React Html Attrs
Babel plugin which transforms HTML and SVG attributes on JSX host elements into React-compatible attributes
Stars: ✭ 170 (+165.63%)
Mutual labels:  babel, babel-plugin, jsx
Eslint Plugin Babel
An ESlint rule plugin companion to babel-eslint
Stars: ✭ 391 (+510.94%)
Mutual labels:  eslint, eslint-plugin, babel
eslint-plugin-test-selectors
Enforces that data-test-id attributes are added to interactive DOM elements (JSX) to help with UI testing. JSX only.
Stars: ✭ 19 (-70.31%)
Mutual labels:  eslint, jsx, eslint-plugin
eslint-plugin-layout-shift
ESLint plugin to force responsive media elements to set the width/height attributes
Stars: ✭ 15 (-76.56%)
Mutual labels:  eslint, jsx, eslint-plugin
Eslint Config Auto
Automatically configure ESLint based on project dependencies
Stars: ✭ 302 (+371.88%)
Mutual labels:  eslint, eslint-plugin, babel
Htm
Hyperscript Tagged Markup: JSX alternative using standard tagged templates, with compiler support.
Stars: ✭ 7,299 (+11304.69%)
Mutual labels:  babel, babel-plugin, jsx
Skyvow.github.io
🐶 My resume - 个人简历
Stars: ✭ 27 (-57.81%)
Mutual labels:  eslint, babel
Babel Plugin Jsx Two Way Binding
🍺 A two-way data binding solution for React
Stars: ✭ 15 (-76.56%)
Mutual labels:  babel-plugin, jsx
Express React Boilerplate
🚀🚀🚀 This is a tool that helps programmers create Express & React projects easily base on react-cool-starter.
Stars: ✭ 32 (-50%)
Mutual labels:  eslint, babel
Eslint Plugin Monorepo
ESLint Plugin for monorepos
Stars: ✭ 56 (-12.5%)
Mutual labels:  eslint, eslint-plugin
Babel Plugin Styled Components
Improve the debugging experience and add server-side rendering support to styled-components
Stars: ✭ 878 (+1271.88%)
Mutual labels:  babel, babel-plugin
React Starter
React Starter repository: cache bursting, css extraction, production, hot, react, redux, router, babel, eslint, jsx, webpack 3, editorconfig
Stars: ✭ 33 (-48.44%)
Mutual labels:  eslint, babel
Tinker.macro
Evaluate Laravel code at build-time, via Laravel Tinker
Stars: ✭ 56 (-12.5%)
Mutual labels:  babel, babel-plugin
Postjss
Use the power of PostCSS in compiling with JSS
Stars: ✭ 40 (-37.5%)
Mutual labels:  babel, babel-plugin

Sowing Machine

Guide

Minimal Example

import sow from 'sowing-machine'
import {useState} from 'react'

// wrap the component with 'sow'
const Counter = sow(() => {
  const [count, setCount] = useState(0)

  const decrement = () => setCount(count - 1)
  const increment = () => setCount(count + 1)

  // express your UI and its embedded logic
  return div({className: 'wrapper'})(
    h1('current count: ', count),
    div({className: 'controls'})(
      button({onClick: decrement})`decrement`,
      button({onClick: increment})`increment`,
    ),
  )
})

// use the component
const counterInstance = sow(Counter())

Packages

Background

How do we feel about the coupling of UI logic & markup in React? Well, JSX does the trick... but it was born out of familiarity as a guiding principal (to ease the transition from HTML). This has been helpful to many user; after all, describing UI based off state is a new pattern, and JSX lowered the barrier to entry. But JSX also created some problems: beyond its redundancy, JSX suggests HTML-like behavior under the hood. Prop-passing resembles HTML attributes, instead of what it really is (passing an object to a function). Along with the misleading DX comes an extra parser stage for transpilation (in addition to the transform).

But what it really comes down to is this: if you could have your pick of conventions, would you ever do the following?

// defining a function
function add({a, b}) {
  return a + b
}

// calling the function
const result = <add a={1} b={2} />

Probably not. Yet, this is what we do for every single piece of our UI in React.

We could get around using JSX by using React.createElement directly. The bootstrapped create-react-app App component––for example––would look like this:

import {createElement as e} from 'react'

const App = e(
  'div',
  {className: 'App'},
  e(
    'header',
    {className: 'App-header'},
    e('img', {src: logo, className: 'App-logo', alt: 'logo'}),
    e(
      'p',
      null,
      'Edit ',
      e('code', null, 'src/App.js'),
      ' and save to reload.',
    ),
    e(
      'a',
      {
        className: 'App-link',
        href: 'https://reactjs.org',
        target: '_blank',
        rel: 'noopener noreferrer',
      },
      'Learn React',
    ),
  ),
)

Although this lets us avoid JSX, it isn't very readable. React.createElement (e) is called 6 times. Depending on your formatter (prettier, for example), this could wrap in really unattractive ways. The current highlighting makes it difficult to distinguish grammar. JSX is––without a doubt––easier to work with.

But not as easy as Sowing Machine––a balance between preexisting options. It improves upon the function-call DX and lets you write less code to express the same UI. It's simple-enough to learn and master in minutes.

Here's the create-react-app example using Sowing Machine:

import sow from 'sowing-machine'

const App = sow(() =>
  div({class: 'App'})(
    header({class: 'App-header'})(
      img({src: logo, class: 'App-logo', alt: 'logo'}),
      p('Edit ', code`src/App.js`, ' and save to reload'),
      a({
        class: 'App-link',
        href: 'https://reactjs.org',
        target: '_blank',
        rel: 'noopener noreferrer',
      })`Learn React`,
    ),
  ),
)

Installation & Setup

Install the following dev dependencies:

yarn add -D babel-plugin-sowing-machine eslint-config-sowing-machine

Sowing Machine is also available in Babel Macro form (sowing-machine.macro)

Install the runtime library as a dependency

yarn add sowing-machine

there's no need to do this if you're using sowing-machine.macro

Add the plugin to your Babel config

.babelrc

{
  "plugins": [
+   "sowing-machine"
  ]
}

Add to your ESLint config as well

.eslintrc

{
  "extends": [
+   "sowing-machine"
  ]
}

And voila! You're good to go!

Describing UI

Wrapping calls with sow ensures that they're transformed into valid runtime code.

import sow from 'sowing-machine'

- const HelloWorld = props =>
-   span(props)`Hello World!`

+ const HelloWorld = sow(props =>
+   span(props)`Hello World!`
+ )

- const instance = HelloWorld()
+ const instance = sow(HelloWorld())

To use a 3rd-party component with Sowing Machine, we need to tell the compiler that it is in fact a component (and not just a function):

import sow from 'sowing-machine'
import {DatePicker} from '~/components'

// wrapping with `sow` marks the `DatePicker` as sowable
const SDatePicker = sow(DatePicker)

// use SDatePicker
const datePickerInstance = sow(SDatePicker())

Ways to write an element

note: in use, we need to wrap any of the following with sow (sow(div()))

bare

hr()

with props

div({some: 'prop'})

with children

div(div({className: 'first-child'}), div({className: 'second-child'}))

with text

h1`This is cool!`

with props and children

form({className: 'form'})(
  input({type: 'email', name: 'email'}),
  input({type: 'submit', value: 'Submit'}),
)

with props & text

span({className: 'warning')`beware of dog`

Creating vs. Cloning

Let's say you have a component instance that you'd like to use as the basis for a new component. There's no way of expressing this with JSX. You'd need to do the following:

import React from 'react'

const instance = <div>Hello</div>

const usingTheInstance = (
  <div>{React.cloneElement(instance, {}, 'Hello JSX')}</div>
)

With Sowing Machine, the difference between using components and component instances is trivial:

import sow from 'sowing-machine'

const instance = sow(div`Hello`)

const usingTheInstance = sow(instance`Hello Sowing Machine`)

Life Cycle

Compilation

During compilation, Sowing Machine code is broken down into a format that can be understood at runtime. There's no way––without the compilation step––to simultaneously support the following ways of describing your UI:

// these cannot co-exist
div(props)
div(children)
div(props)(children)
div`child text`
div(props)`child text`

Sowing Machine takes care of some additional complexity: differentiating between components, functions, and implicitly embedded logic.

When working with JSX, you need to mark your logic as off-limits to the compiler:

import React from 'react'

const List = ({list}) => (
  <div>
    {list.map(e => (
      <div>{e}</div>
    ))}
  </div>
)

...this isn't the case with Sowing Machine:.

import sow from 'sowing-machine'

// no need to manually distinguish between logic & markup
const List = sow(({list}) => div(list.map(e => div(e))))

The compiler extracts key information from each function or tag call within your sow calls, and places them in a lightweight, runtime-friendly wrapper.

This code:

sow(div({some: 'prop'})(span`neat`, span`syntax`))

Gets transformed into something like this:

_c(
  _s('div', [
    [{some: 'prop'}],
    [_s('span', _x, [['neat']]), _s('span', _x, [['syntax']])],
  ]),
)

Runtime

When executing the code above, the runtime library will analyze the arguments of each _s.

The runtime goes through some of the following questions about each call: is the callee a React component? If so, are props applied? How about children? Both? Is the source a Tagged Template Expression? If we're dealing with a React component, we need to recombine the quasis. Otherwise, we need to pass quasis and expressions into the function according to the Tagged Template Literal specification. That way, libraries such as common-tags will still work.

Future Direction

Compiling into a form that requires a runtime helper is heavily debatable (even though we do this pretty much everywhere, such as with ES6 classes). Without the runtime, it's impossible to support current sowing-machine syntax. However, there are other reasons that justify the (very slight) overhead. They mainly have to do with future direction for this toolchain:

  • allowing a 3rd-party to orchestrate component instanciation and function calls opens the door to some powerful optimizations. React users frequently fall into unintentional re-renders and re-calculations. A key goal for the runtime is to safeguard against these pitfalls.
  • styling! If you've ever used a CSS-in-JS library, chances are that you create a container component, assign it nested styles, and wrap your component root. Another key goal for the runtime is to support this pattern:
import sow from 'sowing-machine'

const BlueBoxWithHello = sow(() => div`Hello`)`
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  width: 100px;
  height: 100px;
  background-color: blue;
  color: #fff;
`

This API will be fairly minimal. It will include nested-css support and vendor-prefixing at runtime. That about covers it.

Roadmap

Status Goal package
in progress TypeScript definitions runtime
in progress Container memoization & invalidation runtime
in progress Applying nested styles tagged onto sow calls runtime
in progress Better errors babel-plugin, runtime
planning More ESLint rules eslint-plugin

Contributing

If you have a feature idea or want to contribute, please go ahead and file an issue 💡

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].