Giter Site home page Giter Site logo

vanjs-org / van Goto Github PK

View Code? Open in Web Editor NEW
3.6K 31.0 84.0 2.75 MB

🍦 VanJS: World's smallest reactive UI framework. Incredibly Powerful, Insanely Small - Everyone can build a useful UI app in an hour.

Home Page: https://vanjs.org

License: MIT License

JavaScript 84.15% Shell 0.38% HTML 0.68% TypeScript 14.78%
ui-framework vanilla-dom-manipulation vanilla-javascript vanilla-js data-binding dom dom-manipulation minimalist grab-n-go reactive-ui

van's Introduction

🍦 VanJS: The Smallest Reactive UI Framework in the World

πŸ“£ Introducing VanX β†’
πŸ“£ Introducing VanJS App Builder β†’

Enable everyone to build useful UI apps with a few lines of code, anywhere, any time, on any device.

VanJS is an ultra-lightweight, zero-dependency and unopinionated Reactive UI framework based on pure vanilla JavaScript and DOM. Programming with VanJS feels like building React apps in a scripting language, without JSX. Check-out the Hello World code below:

// Reusable components can be just pure vanilla JavaScript functions.
// Here we capitalize the first letter to follow React conventions.
const Hello = () => div(
  p("πŸ‘‹Hello"),
  ul(
    li("πŸ—ΊοΈWorld"),
    li(a({href: "https://vanjs.org/"}, "🍦VanJS")),
  ),
)

van.add(document.body, Hello())
// Alternatively, you can write:
// document.body.appendChild(Hello())

Try on jsfiddle

You can convert any HTML or MD snippet into VanJS code with our online converter.

VanJS helps you manage states and UI bindings as well, with a more natural API:

const Counter = () => {
  const counter = van.state(0)
  return div(
    "❀️ ", counter, " ",
    button({onclick: () => ++counter.val}, "πŸ‘"),
    button({onclick: () => --counter.val}, "πŸ‘Ž"),
  )
}

van.add(document.body, Counter())

Try on jsfiddle

Why VanJS?

Reactive Programming without React/JSX

Declarative DOM tree composition, reusable components, reactive state binding - VanJS offers every good aspect that React does, but without the need of React, JSX, transpiling, virtual DOM, or any hidden logic. Everything is built with simple JavaScript functions and DOM.

Grab 'n Go

No installation, no configuration, no dependencies, no transpiling, no IDE setups. Adding a line to your script or HTML file is all you need to start coding. And any code with VanJS can be pasted and executed directly in your browser's developer console. VanJS allows you to focus on the business logic of your application, rather than getting bogged down in frameworks and tools.

Ultra-Lightweight

VanJS is the smallest reactive UI framework in the world, with just 1.0kB in the gzipped minified bundle. It's 50~100 times smaller than most popular alternatives. Guess what you can get from this 1.0kB framework? All essential features of modern web frameworks - DOM templating, state, state binding, state derivation, effect, SPA, client-side routing and even hydration!

Size comparison

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.

-- Antoine de Saint-ExupΓ©ry, Airman's Odyssey

Easy to Learn

Simplicity at its core. Only 5 functions (van.tags, van.add, van.state, van.derive, van.hydrate). The entire tutorial plus the API reference is just one single web page, and can be learned within 1 hour for most developers.

Performance

VanJS is among the fastest web frameworks, according to the results by krausest/js-framework-benchmark. For SSR, Mini-Van is 1.75X to 2.25X more efficient compared to React.

TypeScript Support

VanJS provides first-class support for TypeScript. With the .d.ts file in place, you'll be able to take advantage of type-checking, IntelliSense, large-scale refactoring provided by your preferred development environment. Refer to the Download Table to find the right .d.ts file to work with.

Want to Learn More?

IDE Plug-ins

See Also

A Guide to Reading VanJS Codebase

Support & Feedback

πŸ™ VanJS aims to build a better world by reducing the entry barrier of UI programming, with no intention or plan on commercialization whatsoever. If you find VanJS interesting, or could be useful for you some day, please consider starring the project. It takes just a few seconds but your support means the world to us and helps spread VanJS to a wider audience.

In the name of Vanilla of the House JavaScript, the First of its name, Smallest Reactive UI Framework, 1.0kB JSX-free Grab 'n Go Library, Scripting Language for GUI, GPT-Empowered Toolkit, by the word of Tao of the House Xin, Founder and Maintainer of VanJS, I do hereby grant you the permission of VanJS under MIT License.

Contact us: @taoxin / [email protected] / Tao Xin

Community Add-ons

VanJS can be extended via add-ons. Add-ons add more features to VanJS and/or provide an alternative styled API. Below is a curated list of add-ons built by VanJS community:

Add-on Description Author
Van Cone An SPA framework add-on for VanJS b-rad-c
van_element Web Components with VanJS Atmos4
VanJS Feather Feather Icons for VanJS thednp
van_dml.js A new flavour of composition for VanJS Eckehard
van-jsx A JSX wrapper for VanJS, for people who like the JSX syntax more cqh963852
vanjs-router A router solution for VanJS (README.md in simplified Chinese) 欧阳鹏
VanJS Routing Yet another routing solution for VanJS Kwame Opare Asiedu
VanJS Form Fully typed form state management library (with validation) for VanJS Kwame Opare Asiedu
vanjs-bootstrap VanJS Bootstrap Components Willi Commer
vanrx An ultra-lightweight Redux addon for VanJS Meddah Abdallah
VanFS 1:1 bindings from F# to VanJS Ken Okabe

Contributors (54)

If I miss anyone's contribution here, apologies for my oversight πŸ™, please comment on #87 to let me know.

Emoji key

Tao Xin
Tao Xin

🎨 πŸ’» πŸ“– πŸ’‘
Wei Sun
Wei Sun

πŸ›
Ryan Olson
Ryan Olson

πŸ–‹
Tamotsu Takahashi
Tamotsu Takahashi

πŸ’»
icecream17
icecream17

πŸ’»
enpitsulin
enpitsulin

πŸ’‘ πŸ’»
Elliot Ford
Elliot Ford

πŸ’»
andrewgryan
andrewgryan

🎨 πŸ’» πŸ›
FredericH
FredericH

πŸ’‘ πŸ’»
ebraminio
ebraminio

πŸ’» ⚠️
Eckehard
Eckehard

πŸ’» πŸ”Œ
Austin Merrick
Austin Merrick

πŸ’» πŸ€” 🎨
Lee Byonghun
Lee Byonghun

πŸ’»
caputdraconis
caputdraconis

πŸ’»
Achille Lacoin
Achille Lacoin

πŸ’»
cqh
cqh

πŸ’» πŸ”Œ
awesome
awesome

πŸ“Ή
artydev
artydev

πŸ’‘ πŸ’¬
Neven DREAN
Neven DREAN

πŸ’‘ πŸ›
Stephen Handley
Stephen Handley

πŸ’‘
Ionut Stoica
Ionut Stoica

πŸ€”
Rasmus Schultz
Rasmus Schultz

πŸ€”
cloudspeech
cloudspeech

πŸ€”
Daniel Upshaw
Daniel Upshaw

πŸ”Œ
barrymun
barrymun

πŸ’‘
Giulio Malventi
Giulio Malventi

πŸ–‹ πŸ›
Yahia Berashish
Yahia Berashish

πŸ› πŸ’» πŸ”Œ πŸ€” πŸ’‘
Phil Schumann
Phil Schumann

πŸ›
RaphaΓ«l Gauthier
RaphaΓ«l Gauthier

πŸ’» πŸ”Œ
Nail
Nail

πŸ’» πŸ”Œ
Brian Takita
Brian Takita

πŸ› πŸ€”
Jonny Fillmore
Jonny Fillmore

πŸ›
Lima Neto
Lima Neto

πŸ–‹
b rad c
b rad c

πŸ’» πŸ”Œ
欧阳鹏
欧阳鹏

πŸ”Œ πŸ“Ή
Daniel Mazurkiewicz
Daniel Mazurkiewicz

πŸ’»
Atmos4
Atmos4

πŸ’» πŸ”Œ
Kwame Opare Asiedu
Kwame Opare Asiedu

πŸ”Œ πŸ’‘
ali-alnasser570
ali-alnasser570

πŸ€”
Auryn Engel
Auryn Engel

πŸ“Ή
Samuel Wyndham
Samuel Wyndham

πŸ“Ή
sekoyo
sekoyo

πŸ›
Owen Furnell
Owen Furnell

πŸ›
MrVoltz
MrVoltz

πŸ›
Kane
Kane

πŸ’‘
Vlad Sirenko
Vlad Sirenko

πŸ’‘ πŸ’»
θ‘£ε‡―
θ‘£ε‡―

πŸ’‘
Meddah Abdallah
Meddah Abdallah

πŸ”Œ
Miroslaw
Miroslaw

πŸ›
Jon Nyman
Jon Nyman

πŸ€”
ericraider33
ericraider33

πŸ›
Ken Okabe
Ken Okabe

πŸ”Œ
Nick
Nick

πŸ’‘
thednp
thednp

πŸ”Œ

van's People

Contributors

allcontributors[bot] avatar atmos4 avatar b-rad-c avatar caputdraconis050630 avatar cqh963852 avatar duffscs avatar ebraminio avatar eford36 avatar efpage avatar hunter-gu avatar onsclom avatar tamo avatar tao-vanjs avatar tolluset avatar yahia-berashish avatar

Stargazers

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

Watchers

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

van's Issues

TS2345 error when adding a DocumentFragment as a child node

I'm attempting to add raw html as a child node.

const Hello = () => div(
	p("πŸ‘‹Hello ", fragment_('<em>there</em>')),
	ul(
		li("πŸ—ΊοΈWorld"),
		li(a({href: "https://vanjs.org/"}, "🍦VanJS")),
	),
)
van.add(document.body, Hello())
function fragment_(html:string) {
	const frag = document.createDocumentFragment()
	const temp = document.createElement('div')
	temp.innerHTML = html
	while (temp.firstChild) {
		frag.appendChild(temp.firstChild)
	}
	return frag
}

The code above results with an error:

TS2345: Argument of type Β DocumentFragmentΒ  is not assignable to parameter of type Β ChildDom<Element, Text>Β 

It works in js-fiddle using just javascript.

vanX: "TypeError: Illegal invocation" when using JS-native `File` object instead of custom one

Hiya, I had a well-functioning vanx.Reactive<UpFile> (where type UpFile={name:string,type:string}) already fully working with vanx.list() and vanx.replace().

I had to directly swap out that little custom type with the browser/EcmaScript-native File (that's in urFileInput.files[] β€” File also having those same 2 fields, and then some), and, with no other changes having been made to the code, now during replace() vanX throws like so:

van-1.2.3.js:17 TypeError: Illegal invocation
    at get (<anonymous>)
    at Object.get (van-x.js:29:11)
    at posts.js:76:65
    at van-x.js:57:13
    at runAndCaptureDeps (van-1.2.3.js:15:12)
    at bind (van-1.2.3.js:74:16)
    at add (van-1.2.3.js:94:32)
    at van-1.2.3.js:119:10
    at Module.replace (van-x.js:113:16)
    at onFilesAdded (posts.js:142:8)
runAndCaptureDeps @ van-1.2.3.js:17
bind @ van-1.2.3.js:74
add @ van-1.2.3.js:94
(anonymous) @ van-1.2.3.js:119
replace @ van-x.js:113
onFilesAdded @ posts.js:142
onchange @ posts.js:33

van-x.js:112 Uncaught TypeError: Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node'.
    at Module.replace (van-x.js:112:23)
    at onFilesAdded (posts.js:142:8)
    at HTMLInputElement.onchange (posts.js:33:90)
replace @ van-x.js:112
onFilesAdded @ posts.js:142
onchange @ posts.js:33

To prevent this, AFAICT I'd have to keep a separate File[] array and a my custom File-clone type and keep a vanx.Reactive<MyFileType> sync'd with the sibling File[] array. Doable, but enough of a situation to prompt the question whether you have any idea what causes this, whether there might be a nifty trick for the vanX lib user to employ for certain "browser-native/resource objects" where that would happen, etc. Eager to hear your thoughts =)

Updating UI

Hy Tao,

Thank you for your awesome framework.
I really love it and hope It will have the sucess it deserves.

I have a question about state.

How could I update the UI when passing an object to a state ? :

ObjectState

const {button, div} = van.tags

// Create a new State object with init value 1
const counter = van.state({
  count : 0
})

const viewcounter = div(counter.val.count)

const incrementBtn = button({onclick: () => ++counter.val.count}, "Increment")


function App () {
  const dom = div
  return dom (
    viewcounter,
    incrementBtn
  )
}

van.add(document.body, App())

I have managed this way, can you tell me if there is something wrong ? , thank you

StateObject

import van from 'https://vanjs.org/code/van-0.11.9.min.js'

const { div, button, input, "m-c": MC} = van.tags;

const counter = van.state({
  point : 89,
  name: "Hal"
})


function incCounter (s) {
  counter.val = {...counter.val, point : ++s.point }
}

function App (stateval) {
  let s = stateval
  const dom = div
  const view = dom (
     div(s.name),
     input({placeholder: "input..."}),
     div(s.point),
     button({onclick : () => incCounter(s)}, "inc")
  )
  return view
}

van.add(document.body, van.bind(counter, (stateval) => App(stateval)))

Regards

expose isProps

There is this fragment in tagsNS:

let [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args]

and for the sake of possibility of creating semi "custom tags" I would want to get exposed isProps to be able to detect if props are provided as first argument to my function:

let isProps = (o) => protoOf(o ?? 0) === objProto

and then line in tagsNS would look like that:

let [props, ...children] = isProps(args[0]) ? args : [{}, ...args]

If no one using isProps minifier should inline it back, so no size impact.

Improve type definition

I'm an absolutely heavy user of typescript, and glad to see how lightweight a library van-js is, but I'd find some type definitions can be improved

For the function bind, I use a series of generics instead of overload to improve that

typescript playground

PS: tags didn't contain all known element types for now(e.g. section and some other semantic tags), I'm also trying to do something on it

Array state

I have such function to render to-do list. But it doesn't re-render after click on button.
As I understand, it's because I use .map with .val. So state doesn't track changes in there.
How to use .map with array state?

function todo() {
  const todos = van.state(["AAA", "BBB", "CCC"])

  return el.ul(
    todos.val.map((x) =>
      el.li(
        el.button(
          {
            onclick: () => {
              todos.val = todos.val.filter((value) => value != x)
            },
          },
          x
        )
      )
    )
  )
}

Suggestion - readonly state proxy

This is more a dx issue, but I see a pattern frequently where I'd like to be able to bind to a state var, but prevent accidental writes to it because writes should be handled by a helper that adjusts multiple state variables or performs other side effects. It would be nice to have something like State.readonly() that returned a wrapped version where get stateVar.val worked but set stateVar.val threw an exception.

It might look something like this:

  "readonly"() {
    let _this=this
    return { 
      ..._this,
      set "val"(v) { throw new Error(`State is read-only`) },
    }
  }
export type State<T> = {
  val: T
  onnew(l: (val: T, oldVal: T) => void): void
  readonly(): State<T> & { readonly val:T}
}

Or, if you don't want to actually add that to the code, I can basically get what I need from TS type checking

export type ReadOnlyState<T> = Omit<State<T>, 'val'> & { readonly val: T }
export const toReadOnlyState = <T>(state: State<T>) => state as ReadOnlyState<T>

vanX: `Cannot read properties of null (reading 'Symbol()')` issue when `replace`ing an array of non-null objs with partially-null fields

Bear with me if I paste some slightly verbose (tho still trim & banal) code... the array's item type being reactive([])d here is this:

export type Post = {
	Id: number
	DtMade?: string
	DtMod?: string
	By: number // Id of User
	Files: string[]
	Md: string // text content of post
	Repl: number // Id of another Post, or 0
	To: number[] // Id of User(s)
}

(This is in a codegenerated-from-my-API .ts, no van let alone state fields here as you can see. Simple as it gets objs.)

This simplistic / prototypal component fails during replace as will be stacktrace-detailed right afterwards:

import van from '../../__yostatic/vanjs/van-1.2.3.debug.js'
import * as vanx from '../../__yostatic/vanjs/van-x.js'
import * as yo from '../yo-sdk.js'

const htm = van.tags

export type UiCtlPosts = {
    DOM: HTMLElement
    posts: vanx.Reactive<yo.Post[]>
    update: (_: yo.Post[]) => void
}

export function create(): UiCtlPosts {
    const me: UiCtlPosts = {
        DOM: htm.div({ 'class': 'haxsh-posts' }),
        posts: vanx.reactive([] as yo.Post[]),
        update: (posts) => update(me, posts),
    }
    van.add(me.DOM, vanx.list(htm.div, me.posts, (it) => {
        return htm.div({}, it.val.Md)
    }))
    return me
}

function update(me: UiCtlPosts, posts: yo.Post[]) {
    vanx.replace(me.posts, (_: yo.Post[]) => posts)
}

Now here's what happens on every update call β€” I have console-verified that posts is always filled with 123 non-null Post objects. No fields are ever null except occasionally To. And what is catched is this:

{
  "message":"Cannot read properties of null (reading 'Symbol()')",
  "stack"  :"TypeError: Cannot read properties of null (reading 'Symbol()')
    at toState (http://localhost:5252/__yostatic/vanjs/van-x.js:11:21)
    at http://localhost:5252/__yostatic/vanjs/van-x.js:16:74
    at Array.map (<anonymous>)
    at reactive (http://localhost:5252/__yostatic/vanjs/van-x.js:16:54)
    at toState (http://localhost:5252/__yostatic/vanjs/van-x.js:11:72)
    at http://localhost:5252/__yostatic/vanjs/van-x.js:96:25
    at Array.map (<anonymous>)
    at Module.replace (http://localhost:5252/__yostatic/vanjs/van-x.js:94:38)
    at update (http://localhost:5252/__static/ui/posts.js:26:8)
    at Object.update (http://localhost:5252/__static/ui/posts.js:12:24)"
}

To circumvent this, I have found that this alternative update impl would be necessary instead:

function update(me: UiCtlPosts, posts: yo.Post[]) {
    vanx.replace(me.posts, (_: yo.Post[]) => posts.map(it => {
        if (!it.To)
            it.To = []
        return it
    }))
}

Imho shouldn't be necessary, right? Might have minified away or forgotten a ? in the still-new, otherwise funky and already-greatly-appreciated vanX perhaps =)

Adding TypeScript support VanCone

Hello again.
I will be trying to open a PR to add TypeScript support, but I don't have enough information about how the vanjs-core works, I tried to get some things done with ChatGPT, but I think I still need help from @b-rad-c on how the code functions and what types are expected to be passed and result from the code.

"state functions" dependencies

hi, back in v0.x van.bind() asked for dependency array, since v1.x it's handled automatically, which is convenient, but not always what you want. it might not be the best practice, but sometimes you want to trigger the "state function" when some state you're not reffering in the function changes.

example: graph nodes w/ linked siblings. i want to output list of selected node's siblings. when i change change time, selected node's siblings might dis/appear, so i want to update list upon this change.

i don't want to create new array of selected nodes just because of this case and only other thing i can think of is referring the value and not using it, but does not feel "right" :) and also might stop working in future as it's kinda hacky way

() => {
    state2.val;  <-- is not used
    return state1.val;
}

DocumentFragment not Append to Dom

I am creating a todo list

const items = van.state<number[]>([1,2,3]);
let count = 3;

van.add(
  document.getElementById("app")!,
  van.tags.button(
    {
      onclick: () => {
        items.val = [...items.oldVal, count++];
      },
    },
    "add item",
    () => items.val.length,
  ),
 // todo list here
  () => {
    const parent = document.createDocumentFragment();

    items.val.forEach((item) => {
      const element = van.tags.div(item);
      parent?.append(element);
    });

    return parent;
  },
);

I want the todo items append to the app div. without a wrapper. So I tried DocumentFragment

This todo list will only be executed once and will not be executed a second time due to changes in items. May I ask why this is?

Context-API support

Discussed in #152

Originally posted by yahia-berashish October 25, 2023
Hello, I tried to migrate React Context API to VanJS. The key steps:

  • create a class name and unique id for context provider, and store provide value
  • find the ancestor with class name by element.closest() when use context
  • get the unique id from dom element id, then get the provide value from store

Current drawback: it will always get the default context when component render, because the framework hasn't bind the reactive state and dom, and it's a little tricky to find the ancestor context provider.

Try it on sandbox: https://codesandbox.io/p/sandbox/vanjs-context-provider-poc-qsgdr8?file=%2Fsrc%2Fmain.ts%3A5%2C1

I want to know if there any recommendations about this.

Add a swap method

Hello,

I suppose the main idea of Vanjs is to create a single page application, but you can use it also for handle few parts of a single html page

It's possible to add a new method like van.swap or improve the van.add method for swap a element?
It's useful when you create a component with a css-in-js librairie and you need customize "the container" depending on the context.
it's useful also when you fetch a element and you want update the UI.

Example when I create a nav component with VanJs and Panda-css:

index.html

<body>
  <nav id="navigation"></nav>
</body>

index.js

import { css } from '../styled-system/css';
import van from 'vanjs-core';

const { nav, div } = van.tags;

const containerStyle = css({
  display: flex,
  border: 'solid'
});

const itemStyle = css({
  display: flex,
  border: 'solid'        
});

const Menu= ()=> nav({id:"navigation", class:containerStyle}, 
                     [div({class:itemStyle},"Home"), 
                      div({class:itemStyle},"About"), 
                      div({class:itemStyle},"Contact")])

van.add(document.querySelector("#navigation"), Menu())

here when I append, I have :

index.html

<body>
   <nav id="navigation">
      <nav id="navigation">
         <div>Home</div>
         <div>About</div>
         <div>Menu</div>
      </nav>
    </nav>
</body>

with swap:
index.js

van.swap(document.querySelector("#navigation"), Menu())

index.html

<body>
      <nav id="navigation">
         <div>Home</div>
         <div>About</div>
         <div>Menu</div>
      </nav>
</body>

off course I can do:

const Menu= ()=>  [div({class:itemStyle},"Home"), 
                   div({class:itemStyle},"About"), 
                   div({class:itemStyle},"Contact")]
                   
 van.add(document.querySelector("#navigation"), Menu())

But I loose my container style an I must handle two css files.

State change debounce and supression

For my understanding, state management in VanJS is event driven. The event is triggered by the state setter and prpagated to all event listeners:

s.listeners.forEach(l => l(v, curV))

For frequent updates this may cause too many calls. A common way to prevent this is a timeout, that prevents retriggering before the timeout has fired.

It may also be useful to have an option to globally disable all events (e.g. during page update or deletion) programmatically with frunctions like van.inhibitUpdate() and van.restoreUpdate().

TypeScript error when using State inside JSX element

When using van_jsx, I noticed that using a state as an element child will cause a TypeScript error:
Type 'State' is not assignable to type 'ComponentChild'.

The issue was solved very simply by adding the Sate type imported from vanjs-core to the ComponentChild type in type.d.ts:
Before:
export type ComponentChild =
| FunctionChild
| Element
| string
| number
| bigint
| boolean
| null
| undefined;

After - this solves the problem:
export type ComponentChild =
| FunctionChild
| Element
| string
| number
| bigint
| boolean
| null
| undefined
| State

No license file for Van

Could you please tell us what License for Vanjs?
It would be important for enterprise early adoption. Thanks!

Feature - Ignore null or undefined for the rest parameters

I am trying this pattern, could the rest: ChildDom[] allow undefined or null, more like rest: (ChildDom|undefined|null)[]

export const Button = (props?: ButtonProps) => {
  const componentClasses = classNames(
    Classes.BUTTON,
    props?.active ? Classes.ACTIVE : null,
    props?.minimal ? Classes.MINIMAL : null,
    props?.outlined ? Classes.OUTLINED : null,
    props?.fill ? Classes.FILL : null,
    props?.small ? Classes.SMALL : null,
    props?.alignText ? AlignmentClassMap[props?.alignText] : null,
    props?.intent ? IntentClassMap[props?.intent] : null
  );
  const createText = () => {
    if (props?.text) {
      return span({ class: Classes.BUTTON_TEXT }, props.text);
    }
  };
  const createIcon = (icon?: IconName) => {
    if (icon) {
      return Icon({ icon });
    }
  };
  return button({ class: componentClasses }, createText(), createIcon());
};
Button.displayName = `${DISPLAYNAME_PREFIX}.Button`;

Otherwise I have to do this which is a bit uglier

export const Button = (props?: ButtonProps) => {
  const componentClasses = classNames(
    Classes.BUTTON,
    props?.active ? Classes.ACTIVE : null,
    props?.minimal ? Classes.MINIMAL : null,
    props?.outlined ? Classes.OUTLINED : null,
    props?.fill ? Classes.FILL : null,
    props?.small ? Classes.SMALL : null,
    props?.alignText ? AlignmentClassMap[props?.alignText] : null,
    props?.intent ? IntentClassMap[props?.intent] : null
  );
  const children: ChildDom[] = [];
  if (props?.text) {
    children.push(span({ class: Classes.BUTTON_TEXT }, props.text));
  }
  if (props?.icon) {
    children.push(Icon({ icon: props?.icon }));
  }
  return button({ class: componentClasses }, ...children);
};
Button.displayName = `${DISPLAYNAME_PREFIX}.Button`;

Sometimes components might return nothing due to logic inside so it would be useful to ignore undefined or null

`van.bind` and state-derived properties support both state and non-state (static) objects

Usecase

For reusable components like that:

const Component = (props: ComponentProps) => ...

it would be desirable for ComponentProps to have shape like that:

interface ComponentProps {
  prop1: string | State<string>
  prop2: number | State<number>
}

That is, while composing a Component object, you can pass in either a static string object, or a state object for string for prop1, and either a static number object, or a state object for number for prop2. In other words, the custom component can either be built statically, or reactively (whose appearance is reactive to underlying state changes).

If the property value is used as the child DOM node or property value of the tag function, everything will work properly. Since both child DOM node and property value can accept both static and state objects. However, when the property value is used in van.bind or state-derived properties, things will be problematic, as both van.bind and state-derived properties can only accept state objects, not static objects as deps argument.

The example below illustrate the problematic situation:

const Button = (color: string | State<string>) => button(
  {style: {deps: [color], f: color => `background-color: ${color};`}},
  "Click me",
)

The code above works when color is a string-typed state object, but doesn't work if color is a string, as state-derived properties don't support static object as values of deps argument.

Solution

Both van.bind and state-derived properties should support both state and static objects as values of deps argument. When a static object is passed in, the behavior would be the same as if a state object whose val never changes.

Workaround before official solution is released

Before the official support is in place, VanJS users are encouraged to use the following workaround:

const stateProto = van.state(0).__proto__

const toState = v => v.__proto__ === stateProto ? v : van.state(v)

const Button = (color: string | State<string>) => button(
  {style: {deps: [toState(color)], f: color => `background-color: ${color};`}},
  "Click me",
)

Incorrect attribute typing for van_jsx

This is another JSX type issue I haven't noticed in the previous issue.
The attributes used for the JSX elements in jsx-internal.d.ts are plain HTML attributes, which means that reactive states cannot be used as attributes = no reactive attributes.
This can be solved by creating a utility type to modify the attributes, something like this:

// modify attribute type
type VanAttrs<T> = {...}

// use utility type instead of HTMLAttributes
 export interface IntrinsicElements {
    a: VanAttrs<HTMLAnchorElement>;
    abbr: VanAttrs<HTMLElement>;
    address: VanAttrs<HTMLElement>;
    area: VanAttrs<HTMLAreaElement>;
    article: VanAttrs<HTMLElement>;
    ...
}

I will probably take a look at this issue later if it is still open

Real World Template

Is there a real world template that provides the below?

  • demo how SSR works
  • demo how interactivity is achieved with SSR, i.e. does this support hydration?
  • demo how components can be structured in individual files and imported to pages/routes

Thanks!

Example for use with a global/unified state machine

For Advanced Examples, it would be nice to see a way to use this against a state engine like Zustand, Jotai, Valtio, Redux or MobX. Since being able to integrate a larger shared state would be beneficial for use in larger application(s) development.

Example using multiple components together.

Would be good to see more examples using components together. Ideally something with a router based render, and async loading of components via import('./SomeComponent').then(({ SomeComponent }) => SomeComponent)

SVG has a slightly different DOM API for element creation than ordinary DOM elements. Referred to as "namespace" elements in the spec (https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS)

SVG has a slightly different DOM API for element creation than ordinary DOM elements. Referred to as "namespace" elements in the spec (https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS)

I noticed while trying to add icons to an app using VanJS that the element in the devtools <svg width="100" ...><circle cx="50" ...></circle></svg> while visually identical to the namespace element didn't render on my page.

// SVG namespace element API
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
el.setAttribute("width", "100")
// etc.

Would it be possible to add support for SVG in VanJS without sacrificing the amazing developer experience?

Either switch to createElementNS for known namespace tags, e.g. path, p, circle etc. or allow users to opt-in somehow?

Originally posted by @andrewgryan in #27

Proxy error using vanX.reactive() with lists of nested objects

I have an API that returns a list of objects, each of which has an inner object. This use case seems best handled using vanX with a combination of vanX.reactive, vanX.replace, and vanX.list to render a list of UI elements.

At the surface level, this appears to work fine if you don't access inner objects:

items = vanX.reactive(
  [
    {
      foo: 'bar',
      baz: {kind: 'dessert', amount: 'lots'}
    }
  ]
);

makeItemGood = (item) => van.tags.li(item.foo)
itemListGood = vanX.list(van.tags.ul, items, ({val: v}) => makeItemGood(v))
// yields: <ul><li>bar</li></ul>

However, the moment you attempt to access baz, you get the error:

TypeError: 'get' on proxy: property 'baz' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#<Object>' but got '#<Object>')

For example:

makeItemBad = (item) => van.tags.li(item.baz.kind)
itemListBad = vanX.list(van.tags.ul, items, ({val: v}) => makeItemBad(v))

As a work around I'm currently rewriting nested objects as JSON strings and then parsing them when it's time to make use of them.

Perhaps I've misunderstood something here, however? I don't actually need nested state management, but have struggled so far to get reactivity working using vanjs alone under this scenario.

TS Typings improvement

Hey!

Below added type Val (for convenience of typing arguments) and exposed PropsWithKnownKeys for anyone who would want to explore and expand sort of "custom tags" using typescript.

Currently:

const Button = (text: StateView<string> | string, color: StateView<string> | string) =>
  button({style: () => `background-color: ${van.val(color)};`, onclick: ()=> console.log("HELLO")}, van.val(text))

After adding and exposing Val type:

const Button = (text: Val<string>, color: Val<string>) =>
  button({style: () => `background-color: ${van.val(color)};`, onclick: ()=> console.log("HELLO")}, van.val(text))

van.d.ts

export interface State<T> {
  val: T
  readonly oldVal: T
}

// Defining readonly view of State<T> for covariance.
// Basically we want StateView<string> to implement StateView<string | number>
export type StateView<T> = Readonly<State<T>>

export type Val<T> = State<T> | T

export type Primitive = string | number | boolean | bigint

export type PropValue = Primitive | ((e: any) => void) | null

export type PropValueOrDerived = PropValue | StateView<PropValue> | (() => PropValue)

export type Props = Record<string, PropValueOrDerived> & { class?: PropValueOrDerived }

export type PropsWithKnownKeys<ElementType> = Partial<{[K in keyof ElementType]: PropValueOrDerived}>

export type ValidChildDomValue = Primitive | Node | null | undefined

export type BindingFunc = ((dom?: Node) => ValidChildDomValue) | ((dom?: Element) => Element)

export type ChildDom = ValidChildDomValue | StateView<Primitive | null | undefined> | BindingFunc | readonly ChildDom[]

export type TagFunc<Result> = (first?: Props & PropsWithKnownKeys<Result> | ChildDom, ...rest: readonly ChildDom[]) => Result

type Tags = Readonly<Record<string, TagFunc<Element>>> & {
  [K in keyof HTMLElementTagNameMap]: TagFunc<HTMLElementTagNameMap[K]>
}

export interface Van {
  readonly state: <T>(initVal: T) => State<T>
  readonly val: <T>(s: Val<T>) => T
  readonly oldVal: <T>(s: Val<T>) => T
  readonly derive: <T>(f: () => T) => State<T>
  readonly add: (dom: Element, ...children: readonly ChildDom[]) => Element
  readonly _: (f: () => PropValue) => () => PropValue
  readonly tags: Tags
  readonly tagsNS: (namespaceURI: string) => Readonly<Record<string, TagFunc<Element>>>
  readonly hydrate: <T extends Node>(dom: T, f: (dom: T) => T | null | undefined) => T
}

declare const van: Van

export default van

Remember local state

Hey,
I just wanted to try out VanJS by implementing a todo app.
While there are two examples - a procedural and a reactive one - I prefer the latter.

So here is a small snippet of my app:

const App = () => {
  const todos = van.state([{ task: "test", done: false }]);

  const onAddTodoClick = () => {
    todos.val = [...todos.val, { task: "new task", done: false }];
  };

  return van.tags.div(
    van.tags.button({ onClick: onAddTodoClick }, "+"),
    () => van.tags.ul(todos.val.map(todo => TodoItem({ todo })))
  );
};

const TodoItem = props => {
  const edit = van.state(false);

  const onEditClick = e => {
    e.stopPropagation();

    edit.val = true;
  };

  return () => van.tags.li(
    edit.val ? "edit" : props.todo.task,
    van.tags.button({ onClick: onEditClick }, "edit"),
  ); 
};

(this code is out of my head - can't test it right now)

Unfortunately if I click on an item to edit it and add a new item to the list, the list will be rerendered completely. So all items will be in the "normal" state again.

Asked in this thread, there is a procedural solution, but as a longtime react developer, I would like to know, if there is also a functional solution without saving the todo item state also in the app state.

SSR and Hydration Support for VanJS

Astro js support is a bit more complex one. But supporting Vite as bundle tool is really necessary nowadays.
I really like minimal frameworks like Van. But it's really hard to use them when you need SEO. Good SEO requires SSR, but SSR requires some kind of hydration.

state management? css?

how to manage state with vanjs, is this more of a SPA that can have client side routing, state etc?

also, an example with some well known css will be nice, e.g bootstrap 5.

vanX: `TypeError: 'get' on proxy` and `TypeError: 'set' on proxy`, what am I doing wrong?

Here's my dummy .ts component:

import van from '../../__yostatic/vanjs/van-1.2.3.debug.js'
import * as vanx from '../../__yostatic/vanjs/van-x.js'
import * as yo from '../yo-sdk.js'

const htm = van.tags

export type UiCtlBuddies = {
    DOM: HTMLElement
    buddies: vanx.Reactive<yo.User[]> // User type def not included here, the inits below show it anyway
}

export function create(): UiCtlBuddies {
    const me: UiCtlBuddies = {
        DOM: htm.div({ 'class': 'buddies' }),
        buddies: vanx.reactive([
            { Id: 1234, Btw: "Btw 1234", Nick: "user1234", PicFileId: "", Auth: 1234 } as yo.User,
            { Id: 2345, Btw: "Btw 2345", Nick: "user2345", PicFileId: "", Auth: 2345 } as yo.User,
            { Id: 4321, Btw: "Btw 4321", Nick: "user4321", PicFileId: "", Auth: 4321 } as yo.User,
        ])
    }

    setInterval(() => {
        const idx = Math.floor(me.buddies.length * Math.random())
        const buddy = me.buddies[idx]
        buddy.Btw = new Date().toLocaleTimeString() // take this line out and there are no errors (because there are no updates=)
    }, 1000)

    van.add(me.DOM, vanx.list(htm.ul, me.buddies, (it) => {
        const buddy = it.val
        return htm.li({}, buddy.Nick, buddy.LastSeen, buddy.Btw)
    }))

    return me
}

Every second, it aims to pick a random User in buddies and set its Btw: string to current time.

With that, every second Chrome dev-tools console spits out 2 errors together, first:

Uncaught TypeError: 'set' on proxy: trap returned truish for property 'Btw' which exists in the proxy target as a non-configurable and non-writable data property with a different value
at buddies.js:20:15
(anonymous) @ buddies.js:20

and second:

van-1.2.3.js:17 TypeError: 'get' on proxy: property 'Btw' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'Btw 4321' but got '9:35:37 AM')
at buddies.js:24:57
at van-x.js:52:13
at van-1.2.3.debug.js:60:27
at runAndCaptureDeps (van-1.2.3.js:15:12)
at bind (van-1.2.3.js:74:16)
at updateDoms (van-1.2.3.js:128:20)

Any ideas what I'm doing wrong? I thought I'd basically followed the vanjs.org/x examples.. somewhere must have messed up there tho.

Btw: an array-of-strings instead of an array-of-objects works for successful automagic reactive update-and-refresh. But that surely can't be the solution given we're talking vanX here =)

Reference comparison

The code uses !== to compare old and new values.
The problem is, if at least one of the values is NaN the condition will always be true

NaN !== 0; // is true
NaN !== NaN; // is also true!

We can fix the problem by using Object.is:

Object.is('0', 0); // false
Object.is(0, -0); // false
Object.is(NaN, 0); // false
Object.is(NaN, NaN); // true

Deeply nested state objects and arrays

Looking into a code and trying to wrap my head around a topic of dynamic deeply nested object and array structures. I'm pretty sure someone here thought it through and I don't have to rediscover things.

Lets say I have this example:

const items = vanX.reactive([])
return vanX.list(ul, items, v => SomeComponent(v))

and then I add item:

items.push({
    type: "some_sub_items",
    sub_items: []
})

can I use vanX.list on sub_items?

Allow lit-html-like templates as worthy, yet build-free JSX replacement

Discussed in #10

Originally posted by cloudspeech May 25, 2023
VanJS' HTML-markup-as-nested-JS-function-calls approach to templating is going to be a hard sell to the wider community, especially people that have seen React. They just want something like JSX.

Lit-html has a very nice syntax that comes very close to JSX, yet only uses browser-native means. See https://lit.dev/docs/templates/overview/.

I humbly suggest to not reinvent the wheel but provide an add-on to VanJS that supports unchanged lit-html template syntax (properties, attributes, events at minimum, perhaps also ref). This way the vanJS base size can be kept minimal for purists.

I expect the add-on's size to likewise be very small (using DOMParser).

What do you think?

Can state not be an object?

Sorry if I missed it in docs.

<!doctype html>
<html>
<head>
</head>
<body>
<script type="module">
import * as van from 'https://vanjs.org/code/van-0.11.0.js'

const { button, span } = van.tags

class Counter {
  constructor() {
    this.state = van.state({
      amount: 0
    })
  }

  onIncrementButtonClickFactory(self) {
    return (event) => {
      console.log(event)
      self.state.amount += 1
    }
  }

  onDecrementButtonClickFactory(self) {
    return (event) => {
      console.log(event)
      self.state.amount -= 1
    }
  }

  render() {
    const incrementButtonOnClick = this.onIncrementButtonClickFactory(this)
    const derementButtonOnClick = this.onDecrementButtonClickFactory(this)
    const incrementButton = button({ onclick: incrementButtonOnClick }, "πŸ‘")
    const decrementButton = button({ onclick: derementButtonOnClick }, "πŸ‘Ž")
    return span(`❀️ ${this.state.amount} `, incrementButton, decrementButton)
  }
}

window.addEventListener('load', function () {
  const node = document.body
  const counter = new Counter()
  van.add(node, counter.render())
})
</script>
</body>
</html>

I was trying to teach my friend who doesn't know programming how to use a really cool lightweight library like this while showing him basic concepts of HTML/JavaScript/ES6/frontend development. I couldn't discern from the docs how to make this work. this.state.amount is undefined(this.state.val.amount isn't but that feels wrong) and none of the events work.

onRender and/or onReRender triggers

Would be nice to see examples of and an option to run some script upon rendering, for example, wiring bootstrap or materializeweb.com based component handlers.

tags.input: readonly vs disabled

I know this is a real edge-case but would be totally nice if one could pass to van.tags.input({},...) a readonly: false (typically, would be a terniary conditional rather than false const of course =) similar to disabled, without the readonly-ness becoming true in-the-browser (well, certainly Chromium).

Ie.: when we pass {disabled:false}, the input is not disabled, but passing {readonly:false} seems to make it readonly still.

I know full well that it's really a quirk of HTML and/or the browsers' DOM APIs rather than van's, of course ... and special-casing like that in a crisp codebase such as van might just be a no-go for you... but like I said, here's a "would be too neat from the van user's point-of-view" =) β€” feel free to close issue if out-of-scope or going against the van design philosophy or something.

Neat lib, glad it exists! Really happy to not have to go heavyweight overbloated React-and-the-likes and can stay vanilla, and appreciate the .d.ts too.

bind() not picking up changes even when `onnew` does

I have a pattern like this:

const myStoreFactory = ()=> {
   const foo = state(false)
}

const myStore = myStoreFactory()

const myComponent = ()=>{
   const { foo } = myStore
   foo.onnew(console.log) // This works
   return bind(foo, foo=>{
      return div(foo ? `foo` : `no foo`)
   })
}

const toggle = ()=>{
   foo.val = !foo.val
   setTimeout(toggle, 1000)
}

In some cases, and I can't figure out exactly why, bind() is not picking up the changes even when onnew will fire. Any ideas?

Question: updating attributes (eg. 'className') without re-generating whole sub-DOM

Considering the case of a tabbed-container component with say 3 tabs, only any 1 of them to be ever visible, and a van state var named activeIdx.

The requirement: when switching active-tab, the other tabs should just become hidden of course β€” but remain in the DOM in their current UI state (like scroll position in tab itself or some textarea). No re-generating DOM on hiding or showing, by adding/removing a custom CSS class (eg. 'active') via script on tab switch.

Right now, I'm doing like document.getElement('tab_' + i).classList.add/remove('active') in the van.derive handler for activeIdx. And it works.

My question is whether there are any hidden gotchas with this approach in a VanJS context or whether this is unproblematic =) I read somewhere a warning about custom DOM manipulations in your docs or threads some days ago but can't find it again right now.

Custom Elements support

The concise but powerful API of VanJS would make it very good alternative to template literals for Web Components.

I've been evaluating it for this use case but there are a couple of advanced use cases that aren't supported that would be great to see.

  • Non primitive data types for properties (e.g. arrays, objects and functions)
  • Custom event handling

I've attempted at implementing the Custom Elements Everywhere tests:

Link to CodePen

If you consider this useful I'd happily create a PR to add these into the existing test suite, as well as assisting with any API changes.

Look forward to seeing how this library progresses.

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.