Coder Social home page Coder Social logo

l3p3 / lui Goto Github PK

View Code? Open in Web Editor NEW
18.0 18.0 1.0 501 KB

Tiny, robust web framework in my taste.

Home Page: https://l3p3.de/dok/lui.html

License: zlib License

JavaScript 89.25% HTML 10.75%
framework functional html javascript webframework

lui's People

Contributors

johann-lr avatar l3p3 avatar thecapypara avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

thecapypara

lui's Issues

Allow early exit when there were childs before

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.

Reuse ssr elements

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.

Alias null

Benchmark showed 2 things:

IS

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.

SHOULD

null should be cached in a global constant and that constant should be used everywhere instead.

hook_assert and hook_await

IS

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.

SHOULD

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

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.

Basic and extended variants

Move hook_transition, hook_sub and hook_map into an extended variant called luix.

luix should also have existing hooks from other projects.

Rename node_list?

As pointed out by JL, node_list may be misleading since we already return "lists" (=arrays) from components.
Here are some alternatives:

  • node_list
    • Current name, easy meaning as it takes a list and creates a list of components
    • Not very precise
  • node_map
    • Same terminology as in other frameworks ([].map(itm => <Component />))
    • Could be confused with graphical maps
  • node_dynamic
    • It highlights the fact that its childs can be changed dynamically unlike it is the case otherwise
    • A bit long and other things could also be dynamic

I consider renaming it to node_map... Are there any opinions on that?

Order rerenders

IS

Component instances requested for a rerender get redrawn in the order they were requested in.

SHOULD

If an instance lower in the tree (parent) is also requested, it should be rendered before.

Reason

Rerenders up in the tree may use outdated props and could be rerendered twice then.

Stricter error checking

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.

Allow custom root in all variants

IS

Only in the requirejs variant, you can pass the root element as the second argument to init.

SHOULD

This should be possible in all variants, as this only takes very few bytes.

Implementation of node_list

IS

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.

SHOULD

node_list should be implemented.

Reason

Dynamically mapping data to nodes must be possible since it is a very important feature.

Selection for node_map

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

Example

node_map(ListItem, list, null, 10),
...
function ListItem({ I, selected }) {}

hook_sub and hook_map

IS

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.

SHOULD

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.

NOTES

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.

Provide access to DOM

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.

Change node interface

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.

Allow custom dom templates

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

Proper handling of dom collections

IS

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.

SHOULD

No unnecessary elements should be used. Also no DomCollections or similar.

Reason

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.

Better handling of event listener props

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.

hook_sub

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.

Build artifact not deployed

IS

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.

SHOULD

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.

Element rendered callback

IS

When a function needs the already rendered element, e.g. for scrolling, a setTimeout(..., 0) needs to be put into the R callback.

SHOULD

There should be an intuitive way to define a callback that is called after the dom was updated. Maybe hook_rendered(fn, deps)?

New reducer format

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.

Provide rendering time

There should be a function lui.now which returns the time point of the latest rerender call. This could be useful for custom animations.

Multiple rerender requests on same component

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.

Always pass props object

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!

Simplify html component descriptors

IS

Now, beside the tagName and properties, an #id can be specified in the descriptor. This is unneccessary.

SHOULD

IDs must be specified just like any other property.

Reason

Violation of minimalism, lower code size.

Smarter reordering algorithm

IS

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.

SHOULD

Just reinsert them if their relative position has changed!
Try to reinsert as fewest as possible without getting too complicated.

Note

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!

Use more sugar

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. ๐Ÿ˜ญ

Force a consistent number of props

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.

Support RequireJS

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.

defer renderings

IS

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.

SHOULD

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.

Notice

This should be easier to do than writing a discord bot.

Compile Object.assign calls

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.

Flag subtree that does not need unmounting

IS

When unmounting a subtree, all of it is checked for unmounting tasks to do, including removing every child node.

SHOULD

When unmounting a subtree, only its top-level element is removed and only its subtrees are unmounted that contain unmounting actions.

NOTE

This will speed up some benchmarks. ๐Ÿ˜‰

Types(cript)

Why?

  • Imagine you're eating a hot dog and there are no roasted onions on it... or you've prepared breakfast but there's only fruit tea - that's exactly why we need typing to build an app using perfectly typed typescript.
  • JS can become more messy than the house of the Ritter family

How?

idk

Clean up readme

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.

Compile prop diff functions

IS

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.

SHOULD

On first instance call, we generate (or reuse) a function that is explicitly comparing the contained properties.

Notice

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.

Remove hook_await

It will almost never be used and can be easily substituted from existing hooks.
And I don't like it.

Compile diffing functions

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.

Provide actual compatiblity with older browsers

IS

Right now, lui works on browsers released a few years ago.

SHOULD

Modern responsive apps running snappily in IE5! โœŠ

Reason

Would be funny. Demonstration to the public.

Notes

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

hook_map updating problems

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:

  1. When an argument (in deps) passed to the map function changes, the items do not get updated. Weird since I think I tested this before.
  2. Instead of updating in O(n) on subscription events (handled in #29), its O(nยฒ), causing noticeable freezing when changing list items.
  3. Better error handling for hook_sub and hook_map required, there is almost nothing right now.

This will also be much more complicated than creating a discord bot.

Routing?

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.

Proper props comparision

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.

Support multiple instances for RequireJS

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.