$ and Optimizer Rules

Optimizer runs as part of the bundling step of building the application. The purpose of the Optimizer is to break up the application into many small lazy-loadable chunks. The Optimizer moves expressions (usually functions) into new files and leaves behind a reference pointing to where the expression was moved.

The meaning of $

The Optimizer needs to know which expression should be extracted into a new file. Extracting a symbol is complicated because the reference to the symbol changes from direct to asynchronous loading. This means that Optimizer needs to cooperate with the runtime to know which symbols can be extracted and how the runtime can then load them.

Let's look at hypothetical problem of performing an action on scroll. You may be tempted to write the code like so:

function onScroll(fn: () => void) {
  document.addEventListener('scroll', fn);
}

onScroll(() => alert('scroll'));

The problem with this approach is that the event handler is eagerly loaded, even if the scroll event never triggers. What is needed is a way to refer to code in a lazy loadable way.

The developer could write:

export scrollHandler = () => alert('scroll');

onScroll(() => (await import('./some-chunk')).scrollHandler());

This works but is a lot of work. The developer is responsible for putting the code in a different file and hard coding the chunk name. Instead, we use Optimizer to perform the work for us automatically. But we need a way to tell Optimizer that we want to perform such a refactoring. We use $() as a marker function for this purpose.

function onScroll(fnQrl: QRL<() => void>) {
  document.addEventListener('scroll', async () => {
    fn = await qImport(document, fnQrl);
    fn();
  });
}

onScroll($(() => alert('clicked')));

The Optimizer will generate:

onScroll(qrl('./chunk-a.js', 'onScroll_1'));

chunk-a.js:

export const onScroll_1 = () => alert('scroll');

Notice:

  1. All that the developer had to do was to wrap the function in the $() to signal to the Optimizer that the function should be moved to a new file and therefore lazy-loaded.
  2. The onScroll had to be implemented slightly differently as it needs to take into account the fact that the QRL of the function needs to be loaded before it can be used. In practice using qImport is rare in Qwik application as the Qwik framework provide higher-level APIs that rarely expect the developer to work with qImport directly.

However, wrapping code in $() is a bit inconvenient. For this reason, Optimizer implicitly wraps the first argument of any function call, which ends with $. (Additionally, one can use implicit$FirstArg() to automatically perform the wrapping and type matching of the function taking the QRL.)

const onScroll$ = implicit$FirstArg(onScroll);

onScroll$(() => alert('scroll'));

Now the developer has a very easy syntax for expressing that a particular function should be lazy-loaded.

Symbol extraction

Assume that you have this code:

const MyComp = component$(() => {
  /* my component definition */
});

The Optimizer breaks the code up into two files:

The original file:

const MyComp = component(qrl('./chunk-a.js', 'MyComp_onMount'));

chunk-a.js:

export const MyComp_onMount = () => {
  /* my component definition */
});

The result of Optimizer is that the MyComp's onMount method was extracted into a new file. There are a few benefits to doing this:

  • A Parent component can refer to MyComp without pulling in MyComp implementation details.
  • The application now has more entry points, giving the bundler more ways to chunk up the codebase.

See also: Capturing Lexical Scope.