# Design: `$` marker ## Overview Qwik relies on Optimizer to break up the source code of the application into fine-grained lazy loadable chunks. The breaking up of the application is not 100% transparent and has runtime implications which the developer needs to understand. This document describes how the Optimizer works and what the DX implications are for the developer. ## Developer Mental Model Looking at the source code of the application the developer needs to be able to quickly identify where: 1. Optimizer performs its transformations AND 2. What are the constraints that the developer must follow at these locations. What is needed as a convention which will quickly mark locations in the source code where the Optimizer is being applied to. The convention should: 1. Be syntactically succinct: So that the code is not bloated 2. Be consistent across all API: So that developer needs to learn only one set of rules. 3. Be IDE friendly: Provide the correct API for the developer on completion. 4. Be typesafe: So that TypeScript can help the developer ## Proposal **Use `$(...)` method as a marker function for Optimizer transformation.** ### Example ```typescript= import {component, useStore, onRender, $} from '@builder.io/qwik'; const MyCounter = component($(() => { const store = useStore({count: 0}); return onRender($(() => ( <button on:click={$(() => store.count++})> {store.count} </button> ))); }); ``` **NOTICE:** Every location marked with `$`: - Represents a location where Optimizer will perform its transformation. - Therefore only a subset of valid JavaScript is accepted at those locations. (discussed later) - Represents a lazy loaded location, therefore `async` invocation. - The `$` can not be a local identifier, it needs to come from an import statement. The transformed code is: ```typescript= import {component, useStore, onRender, qrl} from '@builder.io/qwik'; const MyCounter = component(qrl('./chunk', 'MyCounter_onMount')); export const MyCounter_onMount = () => { const store = useStore({count: 0}); return onRender(qrl('./chunk', 'MyCounter_onRender', [store])); } export const MyCounter_onRender = () => { const [store] = useLexicalScope(); return ( <button on:click={qrl('./chunk', 'MyCounter_onRender_click', [store])}> {store.count} </button> ); } export const MyCounter_onRender_click = () => { const [store] = useLexicalScope(); return store.count++; } ``` **NOTE:** That each location of `$(...)` has been turned into `qrl(...)`; After the execution of the Optimizer no `$(...)` functions should remain. ## Short hand Calling `someFn($(...))` is a very common pattern. For this reason Optimizer supports a short hand as a `$` suffix. This means that `someFn$(...)` is short hand for `someFn($(...))`. ```typescript= someFn($(arg1), arg2, arg3) someFn$(arg1, arg2, arg3) // shorthand ``` The above example can be re-written like so with equivalent output: ```typescript= import {component$, onRender$, useStore} from '@builder.io/qwik'; const MyCounter = component$(() => { const store = useStore({count: 0}); return onRender$(() => ( <button on$:click={() => store.count++}> {store.count} </button> )); }); ``` **NOTE:** In JSX `on$`/`onDocument$`/`onWindow$` converts to `on`/`onDocument`/`onWindow`. ## Extensions The Optimizer only special marker function is `$(...)`. For better DX the Optimizer also supports `$` suffix. The`$` suffix methods are not limited to Qwik API only but can be used by any third party to create their own lazy loaded APIs. Use `implicit$FirstArg(...)` to convert your method into `$` suffix method like so: ```typescript= // Error at compiler time, functions MUST be exported const local = (qrlFn: QRL<() => void>) =>{...} const local$ = implicit$FirstArg(local); // Success! functions are exported export const myApi = (qrlFn: QRL<() => void>) =>{...} export const myApi$ = implicit$FirstArg(myApi); ``` **NOTE:** The implicit `$` only works for first argument. Any additional arguments must be explicitly wrapped in `$()`. **NOTE:** Your custom functions that want to use `$` sufix need to be exported. ## `$` Constraints The Optimizer extracts anything marked with `$(...)` into a lazy loadable chunk. Not all valid JS can undergo the transformation. For this reason Optimizer enforces additional rules that the developer needs to understand. The rule can be summed up as: The optimizer must be able to refactor the expression in `$(...)` into a top level `export`ed function. In practice this means that `$(...)` expression must: 1. Refer to imported symbol 2. If inlined function => Function must refer to - Importable symbols AND - Functions can only capture serializable objects. Examples of code: ```typescript= import {importedFn} from './utils'; function myFunction() { function doSomething() {...}; const arrowFn = () => {...}; const store = useStore(...); // OK someFn$(importedFn); // Importable someFn$(() => 123); // Inlined someFn$(() => importedFn()); // Inlined + Importable someFn$(() => store); // Inlined + serializabel Object. // Compile Errors someFn$(localVar); // Not importable someFn$(() => localVar); // 'foo' => Not importable someFn$(() => doSomething()); // `doSomething` will fail serialization since its initialized as a function // Runtime Errors someFn$(() => arrowFn()); // `arrowFn` will fail serialization } function localVar() { console.log('other'); } ``` ## Implications of not having a marker `$` This section lists consequences of not having a marker functions: 1. Optimizer would have to have a list of special functions 1. Developers would have to memorize which functions are special. 1. Cursory glance of the source code would not easily identify where the lazy loaded locations are 1. Developers would not be able to create their own `foo$` functions with transformation semantics. --- ## Open questions - Do we want to allow third party to create their own `$` methods? - YES - Should `$` functions transform the first or last argument? - `component$()` transform the last one (making it special) - Rest, transform the first arg - Advantages of only explicit `$(...)` - No special methods. Simple / easy mental model to explain to users. - Any argument can be a hook (also applies to ) - Disadvantages of only explicit `$(...)` - Extremely annoying DX ```typescript= const MyComp = component$(() => {}); const MyComp = component('my-comp', $(() => { useSomethi({ onStart: $(()=>null) onEnd: $(()=>null) }) })); ``` # FAQ Add your questions here, and we will add the answers - Does `$` mean this function *must* be lazy-loaded or *may* be lazy-loaded? A: `$` is equivalent to dynamic import, therefore the resource will be lazy loaded. However, It may be lazy loaded as part of other resources, in other words it may already be loaded. In either case the function will always be called asynchronously.