potatejs 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/bin/potate-cli.mjs +66 -0
- package/dist/index-esbuild.js +13546 -0
- package/dist/index-vite.js +13534 -0
- package/dist/index.js +3 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 uniho
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/uniho/potate/main/assets/potate.svg" alt="potate" width="250">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Potate
|
|
6
|
+
|
|
7
|
+
**Po**wered by **Ta**gged **Te**mplate.
|
|
8
|
+
|
|
9
|
+
**Note**: This project is a fork of [brahmosjs/brahmos](https://github.com/brahmosjs/brahmos).
|
|
10
|
+
|
|
11
|
+
Supercharged JavaScript library to build user interfaces with modern React API and native templates.
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- Lightweight and Fast.
|
|
18
|
+
- Exact same React's Declarative APIs with JSX.
|
|
19
|
+
- Fast alternative to Virtual DOM. (JSX without VDOM).
|
|
20
|
+
- Smaller transpiled footprint of your source code, than traditional JSX.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Vite
|
|
25
|
+
|
|
26
|
+
Create your new app with "select a framework: > Vanilla".
|
|
27
|
+
|
|
28
|
+
``` bash
|
|
29
|
+
npm create vite@latest my-app
|
|
30
|
+
cd my-app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Add `potatejs` as a dependency.
|
|
34
|
+
```
|
|
35
|
+
npm install potatejs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Add Potate in your `vite.config.js|.ts` file.
|
|
39
|
+
|
|
40
|
+
``` js
|
|
41
|
+
import { defineConfig } from 'vite'
|
|
42
|
+
import potatejs from 'potatejs/vite'
|
|
43
|
+
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
plugins: [potatejs()],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Create `src/main-potate.jsx`.
|
|
51
|
+
|
|
52
|
+
``` jsx
|
|
53
|
+
import './style.css'
|
|
54
|
+
import javascriptLogo from './javascript.svg'
|
|
55
|
+
import viteLogo from '/vite.svg'
|
|
56
|
+
import Potate from 'potatejs'
|
|
57
|
+
|
|
58
|
+
const App = props => {
|
|
59
|
+
return (
|
|
60
|
+
<div>
|
|
61
|
+
<a href="https://vite.dev" target="_blank">
|
|
62
|
+
<img src={viteLogo} class="logo" alt="Vite logo" />
|
|
63
|
+
</a>
|
|
64
|
+
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
|
|
65
|
+
<img src={javascriptLogo} class="logo vanilla" alt="JavaScript logo" />
|
|
66
|
+
</a>
|
|
67
|
+
<h1>Hello Potate on Vite!</h1>
|
|
68
|
+
<p class="read-the-docs">
|
|
69
|
+
Click on the Vite logo to learn more
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const root = Potate.createRoot(document.querySelector('#app'))
|
|
76
|
+
root.render(Potate.createElement(App)) // ✖ root.render(<App/>) Please avoid JSX at the root
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Edit your `index.html`.
|
|
81
|
+
|
|
82
|
+
``` html
|
|
83
|
+
:
|
|
84
|
+
:
|
|
85
|
+
<script type="module" src="/src/main-potate.jsx"></script>
|
|
86
|
+
:
|
|
87
|
+
:
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
### Esbuild
|
|
92
|
+
|
|
93
|
+
Add `potatejs` as a dependency. And `esbuild` as a dev dependency.
|
|
94
|
+
```
|
|
95
|
+
npm install potatejs
|
|
96
|
+
npm install -D esbuild
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Build your app.
|
|
100
|
+
|
|
101
|
+
NOTE: This CLI is build-only. For watch / dev usage, use esbuild's JS API directly.
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
npx potatejs src/entry-point.js --outdir dist
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## Usage
|
|
109
|
+
|
|
110
|
+
The API is exact same as React so build how you build application with React, but instead of importing from `react` or `react-dom` import from `potate`;
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
import Potate from 'potatejs'
|
|
114
|
+
|
|
115
|
+
export default function App(props) {
|
|
116
|
+
const [state, setState] = Potate.useState(0)
|
|
117
|
+
|
|
118
|
+
Potate.useEffect(() => {
|
|
119
|
+
...
|
|
120
|
+
}, [])
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div>
|
|
124
|
+
...
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const root = Potate.createRoot(document.querySelector('#app'))
|
|
130
|
+
root.render(Potate.createElement(App)) // ✖ root.render(<App/>) Please avoid JSX at the root
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
### Using React 3rd party libraries
|
|
136
|
+
|
|
137
|
+
Just alias react and react-dom with brahmos. And you are good to go using 3rd party react libraries.
|
|
138
|
+
|
|
139
|
+
You need to add following aliases.
|
|
140
|
+
```js
|
|
141
|
+
alias: {
|
|
142
|
+
react: 'potatejs',
|
|
143
|
+
'react-dom': 'potatejs',
|
|
144
|
+
'react/jsx-runtime': 'potatejs'
|
|
145
|
+
},
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Idea
|
|
149
|
+
|
|
150
|
+
It is inspired by the rendering patterns used on hyperHTML and lit-html.
|
|
151
|
+
|
|
152
|
+
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.
|
|
153
|
+
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.
|
|
154
|
+
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.
|
|
155
|
+
|
|
156
|
+
Even though tagged template literals are the key to static and dynamic part separation, the developer has to code on well adopted JSX.
|
|
157
|
+
|
|
158
|
+
Using the babel-plugin-brahmos it transforms JSX into tagged template literals which are optimized for render/updates and the output size.
|
|
159
|
+
|
|
160
|
+
Consider this example,
|
|
161
|
+
|
|
162
|
+
```jsx
|
|
163
|
+
class TodoList extends Component {
|
|
164
|
+
state = { todos: [], text: '' };
|
|
165
|
+
setText = (e) => {
|
|
166
|
+
this.setState({ text: e.target.value });
|
|
167
|
+
};
|
|
168
|
+
addTodo = () => {
|
|
169
|
+
let { todos, text } = this.state;
|
|
170
|
+
this.setState({
|
|
171
|
+
todos: todos.concat(text),
|
|
172
|
+
text: '',
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
render() {
|
|
176
|
+
const { todos, text } = this.state;
|
|
177
|
+
return (
|
|
178
|
+
<form className="todo-form" onSubmit={this.addTodo} action="javascript:">
|
|
179
|
+
<input value={text} onChange={this.setText} />
|
|
180
|
+
<button type="submit">Add</button>
|
|
181
|
+
<ul className="todo-list">
|
|
182
|
+
{todos.map((todo) => (
|
|
183
|
+
<li className="todo-item">{todo}</li>
|
|
184
|
+
))}
|
|
185
|
+
</ul>
|
|
186
|
+
</form>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
It will be transformed to
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
class TodoList extends Component {
|
|
196
|
+
state = { todos: [], text: '' };
|
|
197
|
+
setText = (e) => {
|
|
198
|
+
this.setState({ text: e.target.value });
|
|
199
|
+
};
|
|
200
|
+
addTodo = () => {
|
|
201
|
+
let { todos, text } = this.state;
|
|
202
|
+
this.setState({
|
|
203
|
+
todos: todos.concat(text),
|
|
204
|
+
text: '',
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
render() {
|
|
208
|
+
const { todos, text } = this.state;
|
|
209
|
+
return html`
|
|
210
|
+
<form class="todo-form" ${{ onSubmit: this.addTodo }} action="javascript:">
|
|
211
|
+
<input ${{ value: text }} ${{ onChange: this.setText }} />
|
|
212
|
+
<button type="submit">Add</button>
|
|
213
|
+
<ul class="todo-list">
|
|
214
|
+
${todos.map((todo) =>
|
|
215
|
+
html`
|
|
216
|
+
<li class="todo-item">${todo}</li>
|
|
217
|
+
`(),
|
|
218
|
+
)}
|
|
219
|
+
</ul>
|
|
220
|
+
</form>
|
|
221
|
+
`("0|0|1,0|1|0,1|3|");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
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.
|
|
227
|
+
|
|
228
|
+
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.
|
|
229
|
+
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.
|
|
230
|
+
|
|
231
|
+
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.
|
|
232
|
+
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.
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
## Progress
|
|
236
|
+
|
|
237
|
+
- [x] Babel Plugin to transpile JSX to tagged template
|
|
238
|
+
- [x] Class components with all life cycle methods (Except deprecated methods)
|
|
239
|
+
- [x] Functional Component
|
|
240
|
+
- [x] List and Keyed list
|
|
241
|
+
- [x] Synthetic input events - onChange support
|
|
242
|
+
- [x] Hooks
|
|
243
|
+
- [x] Context API
|
|
244
|
+
- [x] Refs Api, createRef, ref as callback, forwardRef
|
|
245
|
+
- [x] SVG Support
|
|
246
|
+
- [x] Suspense, Lazy, Suspense for data fetch, Suspense List
|
|
247
|
+
- [x] Concurrent Mode
|
|
248
|
+
- [x] 3rd Party React library support (Tested React-router, redux, mobx, react-query, zustand, recharts)
|
|
249
|
+
- [x] React Utilities and Methods
|
|
250
|
+
- [x] Vite Plugin to transpile JSX to tagged templates
|
|
251
|
+
- [x] Esbuild Plugin to transpile JSX to tagged templates
|
|
252
|
+
- [x] The Lanes Light **(Though I haven't cleaned up the no-longer-needed TRANSITION_STATE_TIMED_OUT yet.)**
|
|
253
|
+
- [x] The Standalone `startTransition`
|
|
254
|
+
- [x] Enhanced `useTransition` hook
|
|
255
|
+
- [x] Enhanced `useDeferredValue` hook
|
|
256
|
+
- [x] `use(resource)` API
|
|
257
|
+
- [x] `watch(resource)` API
|
|
258
|
+
- [x] Provides React 19 style root management.
|
|
259
|
+
- [ ] `use(context)` API
|
|
260
|
+
- [ ] `use(store)` API
|
|
261
|
+
- [ ] Support for `ref` as a prop
|
|
262
|
+
- [ ] Cleanup functions for refs
|
|
263
|
+
- [ ] `<Context>` as a provider
|
|
264
|
+
- [ ] `startTransition(action)` for POST request.
|
|
265
|
+
- [ ] `useEffectEvent` hook
|
|
266
|
+
- [ ] `useImperativeHandle` hook
|
|
267
|
+
- [ ] `useInsertionEffect` hook
|
|
268
|
+
- [ ] Clean up
|
|
269
|
+
- [ ] Performance improvement
|
|
270
|
+
- [ ] Bug fixes
|
|
271
|
+
- [ ] Test Cases
|
|
272
|
+
- [ ] Rewrite core source code with [MoonBit](https://www.moonbitlang.com/)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
## Won't Do
|
|
276
|
+
|
|
277
|
+
*The following features are not planned for the core roadmap (though contributors are welcome to explore them):*
|
|
278
|
+
|
|
279
|
+
* Potate Server Components (PSC)
|
|
280
|
+
* Potate Compiler
|
|
281
|
+
* `useSyncExternalStore` hook
|
|
282
|
+
* `useOptimistic` hook
|
|
283
|
+
* Superficial Type Definitions: We do not provide type information solely to satisfy IDE warnings unless it directly impacts runtime execution safety.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import esbuild from 'esbuild';
|
|
4
|
+
import potate from '../dist/index-esbuild.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* CLI Argument Parsing
|
|
8
|
+
*/
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const entries = [];
|
|
11
|
+
let outdir = 'dist';
|
|
12
|
+
let sourcemap = false;
|
|
13
|
+
let minify = true; // Enabled by default
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < args.length; i++) {
|
|
16
|
+
const arg = args[i];
|
|
17
|
+
if (arg === '--outdir') {
|
|
18
|
+
const val = args[++i];
|
|
19
|
+
if (val) outdir = val;
|
|
20
|
+
} else if (arg === '--sourcemap') {
|
|
21
|
+
sourcemap = true;
|
|
22
|
+
} else if (arg === '--no-minify') {
|
|
23
|
+
minify = false;
|
|
24
|
+
} else if (!arg.startsWith('--')) {
|
|
25
|
+
entries.push(arg);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validation
|
|
31
|
+
*/
|
|
32
|
+
if (entries.length === 0) {
|
|
33
|
+
console.error('❌ Error: No entry files specified.');
|
|
34
|
+
console.log('Usage: node build.mjs <entry_file> [options]');
|
|
35
|
+
console.log('\nOptions:');
|
|
36
|
+
console.log(' --outdir <path> Output directory (default: dist)');
|
|
37
|
+
console.log(' --sourcemap Enable sourcemap generation');
|
|
38
|
+
console.log(' --no-minify Disable minification (enabled by default)');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Execute esbuild
|
|
44
|
+
*/
|
|
45
|
+
try {
|
|
46
|
+
await esbuild.build({
|
|
47
|
+
entryPoints: entries,
|
|
48
|
+
outdir: outdir,
|
|
49
|
+
bundle: true,
|
|
50
|
+
format: 'esm',
|
|
51
|
+
platform: 'browser',
|
|
52
|
+
target: 'es2018',
|
|
53
|
+
plugins: [potate()],
|
|
54
|
+
jsx: 'preserve',
|
|
55
|
+
sourcemap: sourcemap,
|
|
56
|
+
minify: minify,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log('✅ Build complete!');
|
|
60
|
+
console.log(`- Entries: ${entries.join(', ')}`);
|
|
61
|
+
console.log(`- Output: ${outdir}/`);
|
|
62
|
+
console.log(`- Sourcemap: ${sourcemap ? 'Enabled' : 'Disabled'}`);
|
|
63
|
+
console.log(`- Minify: ${minify ? 'Enabled' : 'Disabled'}`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|