ulive's People
ulive's Issues
Add tests
@kethan as suggested, here's tests:
import t, { is, throws } from 'tst'
import { signal, computed, effect } from './signal.js'
// value
t('signal: readme', async t => {
let log = []
let v1 = signal(0)
is(v1.value, 0)
// subscribe
let unsub = effect(() => log.push(v1.value))
// set
v1.value = 1
is(v1.value, 1)
// is(log, [0, '-', 1])
unsub()
// from value
let v2 = computed(() => v1 * 2)
log = []
effect(() => log.push(v2.value))
is(log, [2])
is(v2.value, 2) // > 2
is(v1.value, 1)
is(log, [2])
console.log('v1.value = 2')
v1.value = 2
is(log, [2, 4])
// initialize value
let v3 = signal(v1)
is(v3.value, v1) // v5
// dispose
// v2.dispose()
// ;[v3, v2, v1].map(v => v[Symbol.dispose]())
})
t('signal: callstack trouble', t => {
let v1 = signal(0)
let v2 = computed(() => { console.log('v2.compute'); return v1.value })
effect(() => { console.log('v2.subscribed'), v2.value })
console.log('---- v1.value = 1')
v1.value = 1
})
t('signal: core API', t => {
// warmup
let v1 = signal(0)
let v2 = computed(() => v1 * 2)
effect(() => (v2.value))
v1.value = 2
console.log('---start')
let s = signal(0)
let log = []
effect(value => log.push(s.value))
is(log, [0], 'should publish the initial state')
is(+s, 0, 'toPrimitive')
is(s.valueOf(), 0, 'valueOf')
is(String(s.toString()), '0', 'toString')
is(s.value, 0, 's()')
s.value = 1
is(+s, 1, 'state.current = value')
s.value = 2
is(+s, 2, 'state(value)')
is(s.value, 2, 'state(value)')
s.value += 1
is(s.value, 3, 'state(state + value)')
// observer 2
let log2 = []
effect(() => log2.push(s.value))
is(log.slice(-1), [3], 'should track and notify first tick changes')
is(log2, [3], 'should properly init set')
s.value = 4
is(log.slice(-1), [4], 'arbitrary change 1')
s.value = 5
is(log.slice(-1), [5], 'arbitrary change 2')
is(log2.slice(-1), [5], 'secondary observer is fine')
})
t.skip('signal: should not expose technical/array symbols', async t => {
let s = signal({ x: 1 })
let log = []
is(s.map, undefined)
for (let p in s) { log.push(p) }
is(log, [])
})
t('signal: multiple subscriptions should not inter-trigger', async t => {
let value = signal(0)
let log1 = [], log2 = [], log3 = []
effect(v => log1.push(value.value))
effect(v => log2.push(value.value))
is(log1, [0])
is(log2, [0])
value.value = 1
is(log1, [0, 1])
is(log2, [0, 1])
effect(v => log3.push(value.value))
is(log1, [0, 1])
is(log2, [0, 1])
is(log3, [1])
value.value = 2
is(log1, [0, 1, 2])
is(log2, [0, 1, 2])
is(log3, [1, 2])
})
t('signal: stores arrays', async t => {
let a = signal([])
is(a.value, [])
a.value = [1]
is(a.value, [1])
a.value = [1, 2]
is(a.value, [1, 2])
a.value = []
is(a.value, [])
let b = signal(0)
a = signal([b])
is(a.value, [b])
b.value = 1
is(a.value, [b])
a.value = [b.value]
is(a.value, [1])
})
t('signal: stringify', async t => {
let v1 = signal(1), v2 = signal({ x: 1 }), v3 = signal([1, 2, 3])
is(JSON.stringify(v1), '1')
is(JSON.stringify(v2), `{"x":1}`)
is(JSON.stringify(v3), '[1,2,3]')
})
t('signal: subscribe value', async t => {
let v1 = signal(1), log = []
effect(v => log.push(v1.value))
is(log, [1])
v1.value = 2
is(log, [1, 2])
})
t('signal: internal effects', async t => {
const s1 = signal(1), s2 = signal(2)
let log1 = [], log2 = []
effect(() => {
log1.push(s1.value)
return effect(() => {
log2.push(s2.value)
})
})
is(log1, [1]), is(log2, [2])
s1.value++
is(log1, [1, 2]), is(log2, [2, 2])
s1.value++
is(log1, [1, 2, 3]), is(log2, [2, 2, 2])
s2.value++
is(log1, [1, 2, 3]), is(log2, [2, 2, 2, 3])
})
// error
t.todo('signal: error in mapper', async t => {
// NOTE: actually mb useful to have blocking error in mapper
let x = signal(1)
let y = x.map(x => { throw Error('123') })
t.ok(y.error)
})
t.todo('signal: error in subscription', async t => {
let x = signal(1)
x.subscribe(() => { throw new Error('x') })
})
t.todo('signal: error in init', async t => {
let x = signal(() => { throw Error(123) })
})
t.todo('signal: error in set', async t => {
let x = signal(1)
x(x => { throw Error(123) })
})
// effect
t('effect: single', t => {
// NOTE: we don't init from anything. Use strui/from
let log = [], v1 = signal(1)
effect(() => log.push(v1.value))
is(log, [1])
v1.value = 2
is(log, [1, 2])
})
t('effect: teardown', t => {
const a = signal(0)
const log = []
let dispose = effect(() => {
log.push('in', a.value)
const val = a.value
return () => log.push('out', val)
})
// is(log, [])
// a.value = 0
is(log, ['in', 0])
a.value = 1
is(log, ['in', 0, 'out', 0, 'in', 1])
dispose()
is(log, ['in', 0, 'out', 0, 'in', 1, 'out', 1])
})
// computed
t('computed: single', t => {
let v1 = signal(1), v2 = computed(() => v1.value)
is(v2.value, 1)
v1.value = 2
is(v2.value, 2)
})
t('computed: multiple', t => {
let v1 = signal(1), v2 = signal(1), v3 = computed(() => v1.value + v2.value)
is(v3.value, 2)
v1.value = 2
is(v3.value, 3)
v2.value = 2
is(v3.value, 4)
})
t('computed: chain', t => {
let a = signal(1),
b = computed(() => (console.log('b'), a.value + 1)),
c = computed(() => (console.log('c'), b.value + 1))
is(c.value, 3)
a.value = 2
is(c.value, 4)
a.value = 3
is(c.value, 5)
})
Sorry for not making a PR.
You can take any test runner to your flavor.
I'd suggest setting up a github action and bumping version to major.
Lmk if you need help with that.
Thanks @kethan
Benchmark of ulive
Hi, I liked your code, I did a benchmark on it please check it. I noticed it is very slow in compare with others, maybe you can fix the benchmark or the library.
https://codesandbox.io/s/ulive-vs-usignal-vs-preact-1gnmbu
USignal
Best,
Add untracked function
Hi @kethan !
Nice philosophy, smallest size so far!
I'd suggest removing o
/ memo
and adding untacked
function (since you've removed peek
) - all it needs is to run effect in zero context (example for usignal):
const untracked = (fn, prev) => (prev = current, current = null, fn(), current = prev)
Looking forward, thanks!
I would integrate it in sprae as direct dependency
Take over sprae/signal
Hi @kethan !
That might probably sound too bold, but would you like to just take sprae/signal implementation?
It was anyways based on ulive, just some bits of rewrite.
I believe o
and memo
functions don't belong to signals, mixing patterns doesn't do good here, and as you see from #2 they're not very elegant fit.
// Minimorum signals impl (almost _ulive_ without o/memo)
export let current,
signal = (v, s, obs = new Set) => (
s = {
get value() {
current?.deps.push(obs.add(current));
return v
},
set value(val) {
v = val
for (let sub of obs) sub(val) // notify effects
},
peek() { return v },
},
s.toJSON = s.then = s.toString = s.valueOf = () => s.value,
s
),
effect = (fn, teardown, run, deps) => (
run = (prev) => {
teardown?.call?.()
prev = current, current = run
try { teardown = fn() } finally { current = prev }
},
deps = run.deps = [],
run(),
(dep) => { teardown?.call?.(); while (dep = deps.pop()) dep.delete(run) }
),
computed = (fn, s = signal(), c, e) => (
c = {
get value() {
e ||= effect(() => s.value = fn())
return s.value
}
},
c.toJSON = c.then = c.toString = c.valueOf = () => c.value,
c
),
batch = (fn) => fn(),
untracked = (fn, prev, v) => (prev = current, current = null, v = fn(), current = prev, v)
Why I am asking - I want to make signals optional for sprae, and ulive would be the first library in the list, providing absolutely minimal implementation (306 bytes gzipped):
let e;export let signal=(t,l,a=new Set)=>((l={get value(){return e?.deps.push(a.add(e)),t},set value(e){t=e;for(let t of a)t(e)},peek:()=>t}).toJSON=l.then=l.toString=l.valueOf=()=>l.value,l),effect=(t,l,a,u)=>(u=(a=u=>{l?.call&&l(),u=e,e=a;try{l=t()}finally{e=u}}).deps=[],a(),e=>{for(;e=u.pop();)e.delete(a)}),computed=(e,t=signal(),l,a)=>((l={get value(){return a||=effect((()=>t.value=e())),t.value}}).toJSON=l.then=l.toString=l.valueOf=()=>l.value,l),batch=e=>e(),untracked=(t,l,a)=>(l=e,e=null,a=t(),e=l,a);
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.