potatejs
v1.0.0-beta.8
Published
Super charged UI library with modern React API and native templates.
Readme
Potate
Powered by Tagged Template.
Note: This project is a fork of brahmosjs/brahmos.
Supercharged JavaScript library to build user interfaces with modern React API and native templates.
Potate supports all the APIs of React including the upcoming concurrent mode APIs and the existing ones. It has its own custom fiber architecture and concurrent mode implementation to support the concurrent UI patterns.
Features
- Lightweight and Fast.
- Exact same React's Declarative APIs with JSX.
- Fast alternative to Virtual DOM. (JSX without VDOM).
- Smaller transpiled footprint of your source code, than traditional JSX.
LIVE DEMO with Astro
Here is a live demo with Astro on StackBlitz.
Installation
Astro
Create your new app.
npm create astro@latest my-app
cd my-appAdd potatejs as a dependency.
npm install potatejs
Note: Potate uses its own JSX runtime. To avoid JSX transformation conflicts, please ensure that
@astrojs/reactor other JSX-based integrations are not enabled in your Astro project.
Add the integration in astro.config.mjs.
import { defineConfig } from 'astro/config';
import potate from 'potatejs/astro';
export default defineConfig({
integrations: [potate()],
});Now, you can use not only Astro components (.astro) but also Potate JSX components (.jsx or .tsx).
Note: Potate components in Astro Islands
No directive (Server Only): Rendered as just static HTML tags. It results in zero client-side JavaScript. (I used to think there wasn't much point in writing static content in JSX components instead of just using Astro components. It seemed like standard Astro components was more than enough. However, I've realized one major advantage: SSR Emotion for Astro — the ultimate SSR Zero-Runtime CSS-in-JS solution, seamlessly integrated with Astro. By using Potate JSX components, your styles are automatically extracted into static CSS during the build process. This means you can enjoy the full power of CSS-in-JS while still shipping zero bytes of JS to the browser. In this regard, it's a significant upgrade over standard Astro components.)
client:only="potate"(Client Only): This is the mode where the relationship between Potate and Astro is the clearest. (In this context, Potate plays the same role as React in other integrations.) It skips server-side rendering and runs entirely in the browser.
client:load(and others likeclient:visibleorclient:idle) (SSR Hydration): Despite its cool and flashy name, "SSR Hydration" is not that complicated: it just creates a static HTML skeleton first, and once the JS is ready, the engine takes over the DOM as if it had been there from the start. If you are particular about the visual transition—like ensuring there is no layout shift by pre-setting an image's height—you might want to take control to make the swap feel completely natural.
Vite
Create your new app with select a framework: > Vanilla.
npm create vite@latest my-app
cd my-appAdd potatejs as a dependency.
npm install potatejsAdd Potate in your vite.config.ts file.
Note: What is the
clientOnlyoption? See SSR Emotion for Vite.
import { defineConfig } from 'vite'
import potate from 'potatejs/vite'
export default defineConfig({
plugins: [potate({
clientOnly: true,
})],
})
Create src/main-potate.jsx.
import './style.css'
import javascriptLogo from './javascript.svg'
import viteLogo from '/vite.svg'
import Potate from 'potatejs'
const App = props => {
return (
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} class="logo" alt="Vite logo" />
</a>
<a href="https://github.com/uniho/potate" target="_blank">
<img class="logo" alt="potate" src="https://raw.githubusercontent.com/uniho/potate/main/assets/potate.svg" />
</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
<img src={javascriptLogo} class="logo vanilla" alt="JavaScript logo" />
</a>
<h1>Hello Potate on Vite!</h1>
<p class="read-the-docs">
Click on the Vite logo to learn more
</p>
</div>
)
}
const root = Potate.createRoot(document.querySelector('#app'))
root.render(<App/>)
Edit your index.html.
:
:
<script type="module" src="/src/main-potate.jsx"></script>
:
:Finally, run the development server to see your Potate app in action!
npm run devEsbuild
Add potatejs as a dependency. And esbuild as a dev dependency.
npm install potatejs
npm install -D esbuildBuild your app.
Note: This CLI is build-only. For watch / dev usage, use esbuild's JS API directly.
npx potatejs src/entry-point.js --outdir distUsage
The API is basically the same as React, allowing you to build applications with your existing knowledge. Simply import from potatejs instead of react or react-dom.
import Potate from 'potatejs'
export default function App(props) {
const [state, setState] = Potate.useState(0)
Potate.useEffect(() => {
...
}, [])
return (
<div>
...
</div>
)
}
const root = Potate.createRoot(document.querySelector('#app'))
root.render(<App/>)
However, Potate components have several key differences from React to maximize native performance. For more details, see Potate Native Component (PNC) Guide.
Using React 3rd party libraries
If you want to use existing 3rd party React libraries or write your own components using React syntax (like style objects, custom events and more), just wrap them with reacty().
Potate will automatically handle the "React-isms" for that component and its children.
For more details, see Potate API Documentation.
Idea
Excerpt from brahmosjs/brahmos:
It is inspired by the rendering patterns used on hyperHTML and lit-html.
It has the same declarative API like React, but instead of working with VDOM, it uses tagged template literals and HTML's template tag for faster rendering and updates. It divides the HTML to be rendered into static and dynamic parts, and in next render, it has to compare the values of only dynamic parts and apply the changes optimally to the connected DOM. It's unlike the VDOM which compares the whole last rendered VDOM to the new VDOM (which has both static and dynamic parts) to derive the optimal changes that are required on the actual DOM.
Even though tagged template literals are the key to static and dynamic part separation, the developer has to code on well adopted JSX.
Using the babel-plugin-brahmos it transforms JSX into tagged template literals which are optimized for render/updates and the output size.
Consider this example,
class TodoList extends Component {
state = { todos: [], text: '' };
setText = (e) => {
this.setState({ text: e.target.value });
};
addTodo = () => {
let { todos, text } = this.state;
this.setState({
todos: todos.concat(text),
text: '',
});
};
render() {
const { todos, text } = this.state;
return (
<form className="todo-form" onSubmit={this.addTodo} action="javascript:">
<input value={text} onChange={this.setText} />
<button type="submit">Add</button>
<ul className="todo-list">
{todos.map((todo) => (
<li className="todo-item">{todo}</li>
))}
</ul>
</form>
);
}
}It will be transformed to
class TodoList extends Component {
state = { todos: [], text: '' };
setText = (e) => {
this.setState({ text: e.target.value });
};
addTodo = () => {
let { todos, text } = this.state;
this.setState({
todos: todos.concat(text),
text: '',
});
};
render() {
const { todos, text } = this.state;
return html`
<form class="todo-form" ${{ onSubmit: this.addTodo }} action="javascript:">
<input ${{ value: text }} ${{ onChange: this.setText }} />
<button type="submit">Add</button>
<ul class="todo-list">
${todos.map((todo) =>
html`
<li class="todo-item">${todo}</li>
`(),
)}
</ul>
</form>
`("0|0|1,0|1|0,1|3|");
}
}With the tagged template literal we get a clear separating of the static and dynamic part. And on updates it needs to apply changes only on the changed dynamic parts.
Tagged template literals also have a unique property where the reference of the literal part (array of static strings) remain the same for every call of that tag with a given template. Taking advantage of this behavior Brahmos uses literal parts as a cache key to keep the intermediate states to avoid the work done to process a template literal again.
Tagged template is natively supported by the browser, unlike the React's JSX which has to be transformed to React.createElement calls. So the output generated to run Brahmos has a smaller footprint than the output generated for the react. For the above example, the Brahmos output is 685 bytes, compared to 824 bytes from the React output. More the static part of an HTML, greater the difference will be.
Progress
- [x] Babel Plugin to transpile JSX to tagged template
- [x] Class components with all life cycle methods (Except deprecated methods)
- [x] Functional Component
- [x] List and Keyed list
- [x] Synthetic input events - onChange support
- [x] Hooks
- [x] Context API
- [x] Refs Api, createRef, ref as callback, forwardRef
- [x] SVG Support
- [x] Suspense, Lazy, Suspense for data fetch, Suspense List
- [x] Concurrent Mode
- [x] 3rd Party React library support (Tested React-router, redux, mobx, react-query, zustand, recharts)
- [x] React Utilities and Methods
- [x] ⭐⭐⭐ Vite Plugin to transpile JSX to tagged templates
- [x] ⭐⭐⭐ Esbuild Plugin to transpile JSX to tagged templates
- [x] ⭐⭐⭐ Potate Native Component(PNC)
- [x] ⭐⭐⭐ SSR Emotion for Astro
- [x] ⭐⭐⭐ SSR Emotion for Vite
- [x] ⭐⭐⭐ reacty(ReactComponent) API
- [x] ⭐⭐⭐ watch(resource) API
- [x] ⭐ The Lanes Light (Though I haven't cleaned up the no-longer-needed TRANSITION_STATE_TIMED_OUT yet.)
- [x] ⭐ The Standalone
startTransition - [x] ⭐ Enhanced
useTransitionhook - [x] ⭐ Enhanced
useDeferredValuehook - [x] ⭐
use(resource)API - [x] ⭐ React 19 style root management
- [x] Allow using class instead of className
- [x] ⭐
<Context>as a provider - [x] ⭐ ref as a prop
- [x] ⭐ startTransition(action) for POST request
- [x] ⭐ Cleanup functions for refs
- [x] ⭐
useImperativeHandlehook - [x] ⭐ Handle server rendering(SSR/SSG/renderToString)
- [x] ⭐
useIdhook - [ ] ⭐
use(context)API - [ ] ⭐ use(store) API
- [ ] ⭐ useEffectEvent hook
- [ ] ⭐
useInsertionEffecthook - [ ] Clean up
- [ ] Performance improvement
- [ ] Bug fixes
- [ ] Test Cases
- [ ] ⭐ Rewrite core source code with MoonBit
Won't Do
The following features are not planned for the core roadmap (though contributors are welcome to explore them):
- Potate Server Components (PSC)
- Potate Compiler
- Async SSR
useSyncExternalStorehookuseOptimistichook- Superficial Type Definitions: We do not provide type information solely to satisfy IDE warnings unless it directly impacts runtime execution safety.
