l3p3 / lui Goto Github PK
View Code? Open in Web Editor NEWTiny, robust web framework in my taste.
Home Page: https://l3p3.de/dok/lui.html
License: zlib License
Tiny, robust web framework in my taste.
Home Page: https://l3p3.de/dok/lui.html
License: zlib License
A component may return null
to skip the rest of it. This can now be done at the beginning. But after the component once returned childs, it cannot early-exit anymore. This should be possible, of course, by unmounting all childs on that occasion.
Allow lui to hydrate pre-rendered html and allow lui apps to be rendered into html code on the server, since some people NEED that.
Benchmark showed 2 things:
null
is faster than implicit casting (https://jsben.ch/pKNKz)null
in a global const
is just as fast as using null
directly everywhere while reducing file size (https://jsben.ch/LpFcF)If something is optional, explicit comparision to null
is already used almost everywhere.
At any location null
is used, it is used by its native name.
null
should be cached in a global constant and that constant should be used everywhere instead.
Components that require some data must return null;
if they are not ready yet. When we have promises to await, we need hook_async
and return null;
if it returns null
.
Additionally to return null;
, there should be a hook_assert(condition: boolean)
that interrupts rendering when the condition is falsy.
And for promises that do not return anything, there should be hook_await(Promise)
which interrupts rendering if the promise is not resolved yet.
hook_dom(descriptor: string, attributes: object)
should allow to turn any function component into a html component. This improves performance since no new instance needs to get created.
The documentation should list that hook but let's better not explain it too much since the "average" user should not use it due to complexity.
Move hook_transition
, hook_sub
and hook_map
into an extended variant called luix
.
luix
should also have existing hooks from other projects.
As pointed out by JL, node_list
may be misleading since we already return "lists" (=arrays) from components.
Here are some alternatives:
node_list
node_map
[].map(itm => <Component />)
)node_dynamic
I consider renaming it to node_map
... Are there any opinions on that?
Component instances requested for a rerender get redrawn in the order they were requested in.
If an instance lower in the tree (parent) is also requested, it should be rendered before.
Rerenders up in the tree may use outdated props and could be rerendered twice then.
It is possible to mutate incoming props, to pass non-plain objects into states/reducers etc.
To prevent issues following these mistakes, there should be checking against these cases installed in the dev variant.
Only in the requirejs variant, you can pass the root element as the second argument to init
.
This should be possible in all variants, as this only takes very few bytes.
To map data to variable number of nodes, an array of node
must be generated. The node list's length and order must not change.
node_list
should be implemented.
Dynamically mapping data to nodes must be possible since it is a very important feature.
value
do not get renamed by closure compiler so just use them if neccessary.Array
, Object
and document
.Add an optional argument to node_map
which allows selecting an item.
On selection change, only the unselected and selected items are updated, optimizing selection from O(n) to O(1).
node_map(ListItem, list, null, 10),
...
function ListItem({ I, selected }) {}
Components can only return lui nodes which will be displayed. There is no way to have abstract data types with self-contained functionality.
When we think of components and hooks as classes and mixins, there is no way to have classes that do not directly return view definitions.
hook_sub
is barely working but it cannot be recalled from updates inside of it.
The way to define classes using functions including hooks is possible using hook_sub
for a single instance and hook_map(func, list, deps)
for a list of instances.
hook_sub
und hook_map
behave just like node
and node_map
with the exception that instead of a props object, a list of arguments is passed down and any kind of data is returned back up. The functions passed to these 2 hooks can do everything that can be done in component functions, except for having hook_dom
.
It will be very complicated to implement this. The code may contain much redundancy with existing code. Try to re-use as much as possible. Maybe the existing components interface must be changed to remain below the build size limit.
There may be the need of accessing the underlying HTML elements from components.
There should be an R
prop for HTML components which gets called once the html got created. The element gets passed as the only argument.
This way, a state's setter, reducer's dispatcher or some other handler can be passed.
Now, we use node
for html components and custom components. So the first parameter can be a string
or a function
. Instead of detecting the type in runtime, add node_html
for html components. This breaks compatiblity with apps using the previous version but lui is in beta stage so what do you expect?
This change will also make it slightly harder to add JSX support to lui, so we should remove JSX from feature list but still point out the possibility in the readme.
Offer a new method to register dom presets that can be used in node_dom
and hook_dom
as a base instead of having to specify everything in the descriptor like element[property=...]
.
Line 194 in 07dc1ab
If the type is HOOK_EFFECT
(=0
), the check is skipped. Compare to undefined
instead.
Components written in functions are made for virgin react developers. I get the idea behind the hooks but... no.
Why?
https://open.spotify.com/track/11RdQ7zGzEfRuXcZW5sb1Y?si=9C6QB5pVQYaSFzEK-wuyWA
Each component's instance has exactly 1 dom element. If there is no other specified, a span
node is used. This is just a temporary hack.
No unnecessary elements should be used. Also no DomCollections or similar.
DOM tree should be minimal and clean. Just as expected from a perfect framework. Performance, styling and illegal child nodes. Eg. inside <select>
, <span>
are not allowed.
Please add a license to lui, so others can use it:
https://choosealicense.com/
Currently, I recommend wrapping event callbacks in hook_static
so they won't get detached and reattached on each node call. function()!==function()
!
Instead, there should be a hook_callback(cb: function(event, ...deps):boolean, deps: ?Array):function(event):boolean
, taking the callback and a deps array. This hook returns a static function that will never change. If that function gets called, the callback passed to the hook gets called with the first original argument (usually event) and the deps items.
This approach has the big upside that the callback can be moved out of the component and can thus be defined just once per runtime and not on each render on each instance. Especially olders browsers were not designed to redefine functions all the time.
To enable further abstraction in components, hook_sub(function(...deps):T, deps=):T
should be added.
It behaves just like hook_memo
but allows using hooks inside of the getter function! And the getter function can be swapped too.
It says 2022 but it is 2023!
Thanks to GitHub Actions (and @Parakoopa), now a new build is automatically done on each new commit. The results can be downloaded in the Actions tab.
The build should only be done when the version
field in the package.json
got updated.
The resulting files lui.js
and lui.dev.js
should be automatically commited to the dist
branch, commit message is just the version, prefixed with v
.
When a function needs the already rendered element, e.g. for scrolling, a setTimeout(..., 0)
needs to be put into the R
callback.
There should be an intuitive way to define a callback that is called after the dom was updated. Maybe hook_rendered(fn, deps)
?
Instead of returning a dispatch method, return an object with methods for each action.
const [count, {increment}] = hook_model({
init: () => 0,
increment: n => n+1,
});
Both reducer hooks should be deprecated.
There should be a function lui.now
which returns the time point of the latest rerender call. This could be useful for custom animations.
function Parent() {
hook_rerender();
console.log('Render!');
return [
node(Child),
];
}
function Child() {
const [state, state_set] = hook_state(false);
hook_effect(() => {
setTimeout(() => {
state_set(true);
}, 0);
});
return null;
}
Expected: Message printed 60 times per second.
Got: Message printed 120 times per second, depending on whether state_set
was called. When commenting that call, it is 60 times per second again.
When no props are put into node
, the component function gets null
. If that component function has a deconstructor in its signature, it generates a js error.
Instead of null
, fall back to an empty object!
Now, beside the tagName
and properties, an #id
can be specified in the descriptor. This is unneccessary.
IDs must be specified just like any other property.
Violation of minimalism, lower code size.
Currently, node_map
items are ordered by reinserting them when their index (absolute position) has changed.
Currently, node_map
items are ordered by reinserting all of them.
Currently, node_map
items are ordered by reinserting all of them if their nextSibling
changes.
Just reinsert them if their relative position has changed!
Try to reinsert as fewest as possible without getting too complicated.
In practice, it works well enough. But as shown by benchmark, this makes lui one of the slowest candidates for "remove row", since all n rows get reinserted without any need. It just looks bad in the table!
In the default variant, lui uses Object.assign
instead of {...}
and removeChild
instead of remove
which is pointless and does not look as cool.
Also, in legacy variants, Object.assign is called instead of the cached one, wasting valuable bytes. ๐ญ
IS
The number of props passed to an instance can be changed from one rendering to the next one.
SHOULD
Assert a constant set of props keys for each instance.
Reason for that is a much faster props diffing with less code size. And having a varying number of props is pointless and could indicate a design flaw.
For using lui in blocks in (outdated) pages using RequireJS, in a new variant lui.r.js
, it should be possible to declare the root element as second init param.
And in that variant, it should be wrapped by rjs' define
call and there should not be a global lui
object anymore but it should be returned from the define
callback.
When a state (or reducer) is updated, the dom is updated synchronously. This is a very nice thing in most cases.
When multiple states are updated in a bunch (eg from a subscription), multiple redundant renderings happen. Since they are all done synchronously, the dom is redrawn only once anyway but there is too much overhead in my opinion.
The new defer()
should prevent renderings until the next frame is drawn, causing all changes being rendered in one go.
defer_end()
should allow updating instantly without waiting for the next frame.
This should be easier to do than writing a discord bot.
Since the set of props never changes, it may be worth it to compile a custom function that creates an object containing all the keys or setting all the keys.
When unmounting a subtree, all of it is checked for unmounting tasks to do, including removing every child node.
When unmounting a subtree, only its top-level element is removed and only its subtrees are unmounted that contain unmounting actions.
This will speed up some benchmarks. ๐
idk
The readme contains bad grammar, formatting errors and misses some relevant details.
Also the order is not nice. The list of functions should be last, but before Contribution/Support.
At each rendering, we iterate over all properties of the prop object with Object.keys. Accessing objects via a variable key is much slower and prevents some optimizations in the JS engine. The dynamicality is not needed since every instance will always get the same props.
On first instance call, we generate (or reuse) a function that is explicitly comparing the contained properties.
To reuse (cache) comparision functions, the props should be in a specific order. We could sort the properties before generating the lookup key but that annoys me. Even thou I would like to force developers to always sort their propertys alphabetically, it seems to silly for non-perfectionist devs.
So we just either only sort on cache miss and map wrong sortings to proper sortings or we just accept redundant function generations, have to benchmark for that.
Also, we could do the very same thing for deps comparision which is basically the same concept except for an array instead of an object.
It will almost never be used and can be easily substituted from existing hooks.
And I don't like it.
Only change detection is currently compiled.
To detect modified props, Object.keys
and a loop is used โ on each run.
Instead, the modified props should be detected by compiled code with the property names hardcoded into it.
For dom attributes, we could skip creating a copy but diff with the dom directly.
Right now, lui works on browsers released a few years ago.
Modern responsive apps running snappily in IE5! โ
Would be funny. Demonstration to the public.
It would be possible to change code in a way that we would get ES3 or even less than that. Closure compiler can already generate ES3 but it does so by generating polyfills which would take up more space than our app itself.
I consider using bake.js, a js toolchain I created years ago. It has a preprocessor and already supplies polyfills that compile seemlessly, including set
s. With bake, we could easily compile lui to different targets automatically. My server could send the proper variant depending on the clien's agent string. Downside of that is a lacking integration in IDEs such as VS Code. I may find a good compromise.
krausest/js-framework-benchmark#1139
krausest/js-framework-benchmark#1148
Either a variant or detect if eval
is allowed or not and fall back to non-generated diffing.
As expected with such a horribly complicated feature, while working on my horribly complicated app prog, I discovered some problems with reacting to updates inside hook_map
:
hook_sub
and hook_map
required, there is almost nothing right now.This will also be much more complicated than creating a discord bot.
I don't know if it makes sense, but maybe I should integrate some sort of routing, eg.:
function PageStart() {
return [
node_dom('h1[innerText=Hallo, Welt!]'),
node_dom('p', null, [
node_dom('a[href=#/blah][innerText=Blah]')
])
];
}
function PageBlah() {
const [sdl, sdl_set] = hook_route();
return [
node_dom('h1', {
innerText:
sdl !== ''
? `Seite ${sdl}`
: 'Sinnlose Seite'
}),
node_dom('p', null, [
node_dom('input[type=number]', {
value: sdl,
onchange: event => {
const value = parseInt(event.target.value);
!isNaN(value) &&
sdl_set(value)
}
})
])
];
}
init(() => {
return[
null,
[
node_route({
blah: PageBlah
}, PageStart)
]
];
});
With this approach, there would be a hook_route
that behaves like hook_state
but is bound to the matching url part. A node_route(Object<string, Component>, Component, props=)
could be used to handle pagination, including a default route.
To skip re-rendering when the props object has the same value as the previous one, I used JSON.stringify
to compare the resulting strings like a hash. This is pretty stable but very ugly in my taste.
The proper implementation should not only use a === b
comparision but alternatively check if all their properties are equal. This is critical since on each node
call, we pass a newly created {}
which will not be ===
the one we passed earlier.
When loading lui via RequireJS, it can be included in multiple places but the init
function may still be called only once globally.
It should be possible to mount more than one root per document, at least for the rjs variant.
If you don't use lui via RequireJS, you can totally ignore this issue.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.