Overview • Creating Live Programs • Implementation • Examples • License
UseLive ("use live") is a runtime extension to JavaScript that enables live execution mode in JavaScript.
What's that?
Where you normally would require reactive primitives to express reactive logic...
// Import reactive primitivesimport{createSignal,createMemo,createEffect}from"solid-js";// Declare valuesconst[count,setCount]=createSignal(5);constdoubleCount=createMemo(()=>count()*2);// Log this value livecreateEffect(()=>{console.log(doubleCount());});// Setup periodic updatessetInterval(()=>setCount(10),1000);The "use live" directive gives you same reactive behavior on top of ordinary imperative JavaScript:
"use live";// Declare valuesletcount=5;letdoubleCount=count*2;// Log this value liveconsole.log(doubleCount);// Setup periodic updatessetInterval(()=>{count+=10;},1000);To try:
- Add the following script to your page:
<scriptsrc="https://unpkg.com/@webqit/oohtml/dist/main.js"></script>- Write your logic with a
"use live"directive:
<script>"use live";// Declare valuesletcount=5;letdoubleCount=count*2;// Log this value liveconsole.log(doubleCount);// Setup periodic updatessetInterval(()=>{count+=10;},1000);</script>- Watch your console.
To go one step further, update your step 2 to split the logic into two separate scripts:
<script>"use live";// Declare valuesletcount=5;letdoubleCount=count*2;// Setup periodic updatessetInterval(()=>{count+=10;},1000);</script><script>"use live";// Log this value liveconsole.log(doubleCount);</script>Watch your console. Reactivity still works.
To define, live programs are JavaScript programs that stay sensitive to changes in program state in fine-grained details - and with no moving parts.
While that is the <script>"use live"</script> part of the HTML page above, there are many different forms of live programs. Examples are just ahead.
Note
This project pursues a futuristic, more efficient way to write reactive applocations. And it occupies a new category in the reactivity spectrum.
Note
You’re viewing @webqit/use-live — the newest iteration.
For the prev 4.6.x branch, see webqit/quantum-js@0.3.*.
As seen above, UseLive can run in the browser.
Up there, we've used a version of the UseLive implementation that supports HTML <script> elements. UseLive works in HTML via the OOHTML project (OOHTML) and it's the most direct way to use UseLive in the browser.
That said, UseLive is directly usable in many different ways — both in the browser and in Node.js.
Load from a CDN
└───────── 
<scriptsrc="https://unpkg.com/@webqit/use-live/dist/main.js"></script>└ This is to be placed early on in the document and should be a classic script without any defer or async directives.
// Destructure from the webqit namespaceconst{
LiveFunction,
AsyncLiveFunction,
LiveScript,
LiveModule,
LiveProgramHandle,
Observer,}=window.webqit;Install from NPM
└───────── 
// npm installnpmi @webqit/use-live// Import APIimport{LiveFunction,AsyncLiveFunction,LiveScript,AsyncLiveScript,LiveModule,LiveProgramHandle,Observer,}from"@webqit/use-live";It is possible to use a lighter version of UseLive where you want something further feather weight for your initial application load.
Load from a CDN
└───────── 
<scriptsrc="https://unpkg.com/@webqit/use-live/dist/main.lite.js"></script>└ This is to be placed early on in the document and should be a classic script without any defer or async directives!
// Destructure from the webqit namespaceconst{ AsyncLiveFunction, AsyncLiveScript, LiveModule, LiveProgramHandle, Observer }=window.webqit;Additional details
The Lite APIs initially come without the compiler and yet lets you work with UseLive ahead of that. Additionally, these APIs are able to do their compilation off the main thread by getting the UseLive compiler loaded into a Web Worker!
But if you may, the UseLive Compiler is all still loadable directly - as if short-circuiting the lazy-loading strategy of the Lite APIs:
<head><scriptsrc="https://unpkg.com/@webqit/use-live/dist/compiler.js"></script><!-- Must come before the polyfil --><scriptsrc="https://unpkg.com/@webqit/use-live/dist/main.lite.js"></script></head>
Install from NPM
└───────── 
// npm installnpmi @webqit/use-live// Import Lite APIimport{AsyncLiveFunction,AsyncLiveScript,LiveModule,LiveProgramHandle,Observer,}from"@webqit/use-live/lite";You declare live functions by declaring the "use live" directive as first statement in the function body. And you can also use the LiveFunction and AsyncLiveFunction APIs. (The first option requires a compile step, the second doesn't.)
Function Declarations
functionbar(){"use live";letcount=5;letdoubleCount=count*2;console.log(doubleCount);}bar();asyncfunctionbar(){"use live";letcount=await5;letdoubleCount=count*2;console.log(doubleCount);}awaitbar();Show more syntax examples
Function Expressions
constbar=function(){"use live";};constbar=asyncfunction(){"use live";};Object Properties
constfoo={bar: function(){"use live";},};constfoo={bar: asyncfunction(){"use live";},};Object Methods
constfoo={bar(){"use live";},};constfoo={asyncbar(){"use live";},};Class Methods
classFoo{bar(){"use live";},}classFoo{asyncbar(){"use live";},}Arrow Functions
constbar=()=>{"use live";};constbar=async()=>{"use live";};If you have a build process for your app, you can use the UseLive compiler to compile your live functions. It's easy:
import{compile}from"@webqit/use-live";constinputSource=` function bar() { "use live"; let count = 5; let doubleCount = count * 2; console.log(doubleCount); }`;constresultString=compile('function',inputSource);Or to compile files conditionally:
import{parse,compile}from"@webqit/use-live";constast=parse(inputSource);if(ast.isLiveProgram||ast.hasLiveFunctions){constresultString=compile('function',ast);}Plugins for bundlers like esbuild and rollup are available soon.
UseLive has the concept of function constructors that allow you to create functions at runtime — without a build step. You obtain the APIs as shown below:
// Import APIimport{LiveFunction,AsyncLiveFunction,}from"@webqit/use-live";or, if loaded in the browser:
// Destructure from the webqit namespaceconst{
LiveFunction,
AsyncLiveFunction,}=window.webqit;Here, LiveFunction and AsyncLiveFunction give you the equivalent of function() {} and async function() {} respectively.
constbar=LiveFunction(` let count = 5; let doubleCount = count * 2; console.log(doubleCount);`);bar();constbar=AsyncLiveFunction(` let count = await 5; let doubleCount = count * 2; console.log(doubleCount);`);awaitbar();Show more syntax examples
// With function parametersconstbar=LiveFunction(param1, ...paramN,functionBody);// With the new keywordconstbar=newLiveFunction(param1, ...paramN,functionBody);// As class propertyclassFoo{bar=LiveFunction(param1, ...paramN,functionBody);}Global variables are accessible in the body of these constructed functions.
// External dependencyglobalThis.externalVar=10;// LiveFunctionconstsum=LiveFunction(`a`,`b`,`return a + b + externalVar;`);conststate=sum(10,10);// Inspectconsole.log(state.value);// 30And note that as is the noraml behaviour of function constructors in JavaScript, only the global scope is accessible as shown. Variables in the surrounding scope are not accessible:
leta;globalThis.b=2;varc="c";// Equivalent to globalThis.c = 'c' assuming that we aren't running in a function scope or module scopeconstbar=LiveFunction(` console.log(typeof a); // undefined console.log(typeof b); // number console.log(typeof c); // string`);bar();Note that, unlike the main UseLive build, the UseLive Lite edition only implements the
AsyncLiveFunctionAPI.
UseLive has the concept of whole scripts as live programs — whether classic scripts or module scripts. You use either the "use live" directive (which requires a compile step) or the LiveScript and LiveModule APIs (which don't).
// script: index.js"use live";globalThis.count=0;setInterval(()=>{count++;},1000);// module: index.js"use live";// Import dependencies where neededimportmodule1,{module2}from'package-name';// Export a live variableexportletcount=0;// Update the variable every secondsetInterval(()=>{count++;},1000);As in the case of live functions above, these can be compiled as part of your application's build process. (In the compilation example above, you'll get ast.isLiveProgram === true for these scripts.)
These APIs give you the equivalent of <script> and <script type="module"> respectively. You obtain the APIs as shown below:
// Import APIimport{LiveModule,LiveScript,AsyncLiveScript,}from"@webqit/use-live";Or, if loaded in the browser:
const{
LiveModule,
LiveScript,
AsyncLiveScript,}=window.webqit;Here, LiveModule, LiveScript, and AsyncLiveScript give you the equivalent of <script type="module">, <script>, and <script async> respectively.
// Live moduleconstprogram=newLiveModule(` // Import dependencies where needed import module1, { module2 } from 'package-name'; // Export a live variable export let count = 0; // Update the variable every second setInterval(() => { count++; }, 1000);`);awaitprogram.execute();constprogram=newLiveScript(` globalThis.count = 0; setInterval(() => { count++; }, 1000);`);program.execute();Note that, unlike the main UseLive build, the UseLive Lite edition only implements the
AsyncLiveScriptandLiveModuleAPIs.
Each call to a Live function or script returns back a LiveProgramHandle object that lets you access values exposed by the program.
For Live functions:
consthandle=bar();For Live scripts:
consthandle=program.execute();For Live HTML scripts - <script>"use live"</script>, the LiveProgramHandle object is available as a direct property of the script element:
console.log(script.liveProgramHandle);For functions, the LiveProgramHandle object exposes a value property that carries the program's actual return value:
functionsum(a,b){"use live";returna+b;}consthandle=sum(5,4);console.log(handle.value);// 9But being a "live" program, handle.value is a "live" property that always reflects the program's return value at any point in time:
functioncounter(){"use live";letcount=0setInterval(()=>count++,500);returncount;}consthandle=counter();console.log(handle.value);// 0The general-purpose, object-observability API: Observer API may be used to observe changes to the value property:
Observer.observe(handle,"value",(mutation)=>{//console.log(handle.value); Or:console.log(mutation.value);// 1, 2, 3, 4, etc.});For module programs, the LiveProgramHandle object exposes an exports property that produces the module's exports:
consthandle=awaitprogram.execute();console.log(handle.exports);// { count }But being a "live" program, each property in the handle.exports object is a "live" property that always reflects an export's internal value at any point in time:
constprogram=newLiveModule(` export let localVar = 0; ... setInterval(() => localVar++, 500);`);conststate=awaitprogram.execute();console.log(state.exports);// { localVar }Again, the Observer API puts those changes in your hands:
Observer.observe(state.exports,'localVar',(mutation)=>{console.log(mutation.value);// 1, 2, 3, 4, etc.});// Observe "any" exportObserver.observe(state.exports,(mutations)=>{mutations.forEach((mutation)=>console.log(mutation.key,mutation.value));});Live programs may maintain many live relationships and should be aborted when their work is done! The LiveProgramHandle object they return exposes an abort() method that lets us do that:
handle.abort();For Live HTML Scripts - <script>"use live"</script>, this cleanup is automatic as script element leaves the DOM!
Live programs can read and write to the given scope in which they run; just in how a regular JavaScript function can reference outside variables and also make side effects:
leta=2,b;functionbar(){"use live";b=a*2;console.log('Total:',b);}bar();But as an extension to regular JavaScript, Live programs maintain a live relationship with the outside world! This means that:
Given the code above, the following will now be reflected:
// Update external dependencya=4;The above dependency and reactivity hold the same even if you had a in the place of a parameter's default value:
leta=2,b=0;functionbar(param=a){"use live";b=param*2;console.log('Total:',b);}bar();And you get the same automatic dependency tracking with object properties:
// External valueconstobj={a: 2,b: 0};functionbar(){"use live";obj.b=obj.a*2;console.log('Total:',obj.b);}bar();// Update external dependencyobj.a=4;Given the same data binding principles, you are able to observe updates the other way round as to the updates made from the inside of the function: b = 4, obj.b = 4!
For updates to object properties, you use the Observer API directly:
// Observe changes to object propertiesconstobj={a: 2,b: 0};Observer.observe(obj,"b",(mutation)=>{console.log("New value:",mutation.value);});The above also holds for global variables:
// Observe changes to global variablesb=0;// globalThis.b = 0;Observer.observe(globalThis,"b",(mutation)=>{console.log("New value:",mutation.value);});And for updates to local variables, while you can't use the Observer API directly (as local variables aren't associated with a physical object as we have of global variables)...
letb=0;Observer.observe(?,'b',()=>{ ... });...you can use a Live function itself to achieve the exact:
(function(){"use live";console.log('New value:',b);})();...and, where necessary, you could next map those changes to an object that you intend to use the Observer API on:
(function(){"use live";obj.b=b;})();Observer.observe(obj,'b',()=>{ ... });Coming soon! The docs in the wiki are for a previous version of UseLive, but may give an idea of advanced concepts.
Using the UseLive and the Observer API, the following examples work today. While we demonstrate the most basic forms of these scenarios, it takes roughly the same principles to build more intricate equivalents.
Example 1: A Custom Element-Based Counter
└─────────
This is a custom element that works as a counter. Notice that the magic is in its live render() method. Reactivity starts at connected time (on calling the render() method), and stops at disconnected time (on calling dispose)!
customElements.define("click-counter",classextendsHTMLElement{count=10;connectedCallback(){// Initial renderingthis._state=this.render();// Static reflection at click timethis.addEventListener("click",()=>{this.count++;});}disconnectCallback(){// Cleanupthis._state.abort();}// Using the LiveFunction constructorrender=LiveFunction(` let countElement = this.querySelector('#count'); countElement.innerHTML = this.count; let doubleCount = this.count * 2; let doubleCountElement = this.querySelector('#double-count'); doubleCountElement.innerHTML = doubleCount; let quadCount = doubleCount * 2; let quadCountElement = this.querySelector('#quad-count'); quadCountElement.innerHTML = quadCount; `);});<click-counterstyle="display: block; padding: 1rem;">
Click me<br/><spanid="count"></span><br/><spanid="double-count"></span><br/><spanid="quad-count"></span></click-counter>Example 2: A Custom URL API
└─────────
This is a simple replication of the URL API - where you have many interdependent properties! Notice that the magic is in its live compute() method which is called from the constructor.
constMyURL=class{constructor(href){// The raw urlthis.href=href;// Initial computationsthis.compute();}compute=LiveFunction(` // These will be re-computed from this.href always let { protocol, hostname, port, pathname, search, hash } = new URL(this.href); this.protocol = protocol; this.hostname = hostname; this.port = port; this.pathname = pathname; this.search = search; this.hash = hash; // These individual property assignments each depend on the previous this.host = this.hostname + (this.port ? ':' + this.port : ''); this.origin = this.protocol + '//' + this.host; let href = this.origin + this.pathname + this.search + this.hash; if (href !== this.href) { // Prevent unnecessary update this.href = href; } `);};// Instantiateconsturl=newMyURL("https://www.example.com/path");// Change a propertyurl.protocol="http:";//Observer.set(url, 'protocol', 'http:');console.log(url.href);// http://www.example.com/path// Change anotherurl.hostname="foo.dev";//Observer.set(url, 'hostname', 'foo.dev');console.log(url.href);// http://foo.dev/pathAll forms of contributions are welcome at this time. Also, implementation details are all up for discussion.
MIT.