- Minimal Example
- Packages
- Background
- Installation & Setup
- Describing UI
- Creating vs. Cloning
- Life Cycle
- Future Direction
- Roadmap
- Contributing
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())
- babel-plugin-sowing-machine: compiles sowing machine code into a format that can be understood at runtime
- sowing-machine: the runtime library which interprets compiled
sow
calls - eslint-config-sowing-machine: disables
no-undef
and enables a thesowing-machine/no-undef
rule, which marks html tag identifiers as defined within sow calls - sowing-machine.macro: makes use of the babel-plugin-macros API to provide a more seamless integration with tools such as create-react-app, NextJS and GatsbyJS
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`,
),
),
)
yarn add -D babel-plugin-sowing-machine eslint-config-sowing-machine
Sowing Machine is also available in Babel Macro form (
sowing-machine.macro
)
yarn add sowing-machine
there's no need to do this if you're using
sowing-machine.macro
.babelrc
{
"plugins": [
+ "sowing-machine"
]
}
.eslintrc
{
"extends": [
+ "sowing-machine"
]
}
And voila! You're good to go!
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())
note: in use, we need to wrap any of the following with
sow
(sow(div()))
hr()
div({some: 'prop'})
div(div({className: 'first-child'}), div({className: 'second-child'}))
h1`This is cool!`
form({className: 'form'})(
input({type: 'email', name: 'email'}),
input({type: 'submit', value: 'Submit'}),
)
span({className: 'warning')`beware of dog`
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`)
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']])],
]),
)
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.
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.
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 |
If you have a feature idea or want to contribute, please go ahead and file an issue 💡