diff --git a/package.json b/package.json index a7171c21039..cac3ce74670 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "qwik-monorepo", - "version": "0.0.20-2", + "version": "0.0.20-3", "scripts": { "build": "yarn node scripts --tsc --build --api --eslint --platform-binding --wasm", "build.platform": "yarn node scripts --platform-binding", diff --git a/packages/create-qwik/package.json b/packages/create-qwik/package.json index 58d956127dc..11a23bf7114 100644 --- a/packages/create-qwik/package.json +++ b/packages/create-qwik/package.json @@ -1,6 +1,6 @@ { "name": "create-qwik", - "version": "0.0.20-2", + "version": "0.0.20-3", "description": "Interactive CLI and API for generating Qwik projects.", "bin": "create-qwik", "main": "index.js", diff --git a/packages/docs/functions/[[path]].ts b/packages/docs/functions/[[path]].ts index d2e8f158ae8..52cf45427a7 100644 --- a/packages/docs/functions/[[path]].ts +++ b/packages/docs/functions/[[path]].ts @@ -40,7 +40,7 @@ export const onRequestGet: PagesFunction = async ({ request, next, waitUntil }) const opts: RenderToStringOptions = { url: request.url, base: '/build/', - manifest, + manifest: manifest as any, prefetchStrategy: { symbolsToPrefetch: 'events-document', implementation: 'link-prefetch', diff --git a/packages/docs/pages/docs/README.md b/packages/docs/pages/docs/README.md new file mode 100644 index 00000000000..9dd75862aed --- /dev/null +++ b/packages/docs/pages/docs/README.md @@ -0,0 +1,36 @@ +# Guide + +## Guides + +- [Overview](overview.mdx) +- [Getting Started](getting-started.mdx) +- [Think Qwik](think-qwik.mdx) + +## Component API + +- [Overview](components/overview.mdx) +- [Anatomy](components/anatomy.mdx) +- [Hooks](components/hooks.mdx) +- [Events](components/events.mdx) +- [Lite elements](components/lite-components.mdx) +- [Content projection](components/projection.mdx) +- [Rendering](components/rendering.mdx) + +## Concepts + +- [Resumable](concepts/resumable.mdx) +- [Progressive](concepts/progressive.mdx) +- [Reactive](concepts/reactivity.mdx) + +## Advanced + +- [Containers](advanced/container.mdx) +- [Optimizer](advanced/optimizer.mdx) +- [QRL](advanced/qrl.mdx) +- [Qwikloader](advanced/qwikloader.mdx) + +## Community + +- [Github](https://github.com/BuilderIO/qwik) +- [@QwikDev](https://twitter.com/QwikDev) +- [Discord](https://qwik.builder.io/chat) diff --git a/packages/docs/pages/guide/containers/overview.mdx b/packages/docs/pages/docs/advanced/containers.mdx similarity index 98% rename from packages/docs/pages/guide/containers/overview.mdx rename to packages/docs/pages/docs/advanced/containers.mdx index 1cc3f82695b..f63140bfef5 100644 --- a/packages/docs/pages/guide/containers/overview.mdx +++ b/packages/docs/pages/docs/advanced/containers.mdx @@ -4,8 +4,6 @@ title: Containers # Containers -**\*NOTE**: Work in progress / Not fully implemented\* - Containers are a way to break up an application into smaller parts. We call these parts containers. Containers bring several benefits. Each container on the page can independently be: - **resumed**: Each container can be resumed independently from all other components on the page. Independent resumability further reduces the amount of state which resume deserializes. diff --git a/packages/docs/pages/docs/advanced/optimizer.mdx b/packages/docs/pages/docs/advanced/optimizer.mdx new file mode 100644 index 00000000000..9cc7f04c417 --- /dev/null +++ b/packages/docs/pages/docs/advanced/optimizer.mdx @@ -0,0 +1,356 @@ +--- +title: Optimizer Rules +fetch: https://hackmd.io/@mhevery/rkydmxJZ9 +--- + + +# Optimizer + +Qwik's philosophy is to delay loading code for as long as possible. To do that, Qwik relies on Optimizer to re-arrange the code for lazy loading. The Optimizer is code level transformation that runs as part of the rollup. (Optimizer is written in Rust (and available as WASM) for instant performance) + +The Optimizer looks for `$` and applies a transformation that extracts the expression following the `$` and turns it into a lazy-loadable and importable symbol. + +Let's start by looking at a simple `Counter` example: + +```tsx +const Counter = component$(() => { + const store = useStore({ count: 0 }); + + return ; +}); +``` + +The above code represents what a developer would write to describe the component. Below are the transformations that the Optimizer applies to the code to make the code lazy-loadable. + +```tsx +const Counter = component(qrl('./chunk-a.js', 'Counter_onMount')); +``` + +`chunk-a.js`: + +```tsx +export const Counter_onMount = () => { + const store = useStore({ count: 0 }); + return qrl('./chunk-b.js', 'Counter_onRender', [store]); +}; +``` + +`chunk-b.js`: + +```tsx +const Counter_onRender = () => { + const [store] = useLexicalScope(); + return ( + + ); +}; +``` + +`chunk-c.js`: + +```tsx +const Counter_onClick = () => { + const [store] = useLexicalScope(); + return store.count++; +}; +``` + + +# `$` 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: + +```tsx +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: + +```tsx +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. + +```tsx +function onScroll(fnQrl: QRL<() => void>) { + document.addEventListener('scroll', async () => { + fn = await qImport(document, fnQrl); + fn(); + }); +} + +onScroll($(() => alert('clicked'))); +``` + +The Optimizer will generate: + +```tsx +onScroll(qrl('./chunk-a.js', 'onScroll_1')); +``` + +`chunk-a.js`: + +```tsx +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`.) + +```tsx +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: + +```tsx +const MyComp = component$(() => { + /* my component definition */ +}); +``` + +The Optimizer breaks the code up into two files: + +The original file: + +```tsx +const MyComp = component(qrl('./chunk-a.js', 'MyComp_onMount')); +``` + +`chunk-a.js`: + +```tsx +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. + +# Capturing the lexical scope + +The Optimizer extracts expressions (usually functions) into new files and leaves behind a `QRL` pointing to the lazy-loaded location. + +Let's look at a simple case: + +```tsx +const Greeter = component$(() => { + return Hello World!; +}); +``` + +this will result in: + +```tsx +const Greeter = component(qrl('./chunk-a.js', 'Greeter_onMount')); +``` + +`chunk-a.js`: + +```tsx +const Greeter_onMount = () => { + return qrl('./chunk-b.js', 'Greeter_onRender'); +}; +``` + +`chunk-b.js`: + +```tsx +const Greeter_onRender = () => Hello World!; +``` + +The above is for simple cases where the extracted function closure does not capture any variables. Let's look at a more complicated case where the extracted function closure lexically captures variables. + +```tsx +const Greeter = component$((props: { name: string }) => { + const salutation = 'Hello'; + + return ( + + {salutation} {props.name}! + + ); +}); +``` + +The naive way to extract functions will not work. + +```tsx +const Greeter = component(qrl('./chunk-a.js', 'Greeter_onMount')); +``` + +`chunk-a.js`: + +```tsx +const Greeter_onMount = (props) => { + const salutation = 'Hello'; + return qrl('./chunk-b.js', 'Greeter_onRender'); +}; +``` + +`chunk-b.js`: + +```tsx +const Greeter_onRender = () => ( + + {salutation} {props.name}! + +); +``` + +The issue can be seen in `chunk-b.js`. The extracted function refers to `salutation` and `props`, which are no longer in the lexical scope of the function. For this reason, the generated code must be slightly different. + +`chunk-a.js`: + +```tsx +const Greeter_onMount = (props) => { + const salutation = 'Hello'; + return qrl('./chunk-b.js', 'Greeter_onRender', [salutation, props]); +}; +``` + +`chunk-b.js`: + +```tsx +const Greeter_onRender = () => { + const [salutation, props] = useLexicalScope(); + + return ( + + {salutation} {props.name}! + + ); +}; +``` + +Notice two changes: + +1. The `QRL` in `Greeter_onMount` now stores the `salutation` and `props`. This performs the role of capturing the constants inside closures. +2. The generated closure `Greeter_onRender` now has a preamble which restores the `salutation` and `props` (`const [salutation, props] = useLexicalScope()`.) + +The ability for the Optimizer (and Qwik runtime) to capture lexically scoped constants significantly improves which functions can be extracted into lazy-loaded resources. It is a powerful tool for breaking up complex applications into smaller lazy-loadable chunks. + +# Optimizer Rules + +The Optimizer can break up large applications into lots of small lazy-loadable chunks. In addition, the Optimizer can lazy-load function closure, which lexically captures variables. However, there are limits to what can be achieved, and therefore the Optimizer comes with a set of rules. Not all valid Javascript is valid Optimizer code. This section describes the rules that developer needs to follow for successful Optimizer transformation. + +The `$` is not only a marker for the Optimizer but also a marker for the developer to follow these rules. + +NOTE: There are plans for a linter that will be able to enforce these rules eagerly. + +## Imports + +**RULE**: If a function that is being extracted by Optimizer refers to a top-level symbol, that symbol must either be imported or exported. + +```tsx +import { importedFn } from '...'; +export exportedFn = () => {...}; + +const salutation = "Hello"; + +someApi$(() => { + importedFn(); // OK + exportedFn(); // OK + salutation; // Error: salutation not imported/exported +}) +``` + +The reason for the above rule becomes obvious when the output is examined. + +```tsx +import { importedFn } from '...'; +export exportedFn = () => { ... }; + +const salutation = "Hello"; + +someApi(qrl('./chunk-a.js', 'someApi_1')); +``` + +`chunk-a.js`: + +```tsx +import { importedFn } from '...'; +import { exportedFn } from './originalFile'; + +export const someApi_1 = () => { + importedFn(); // OK + exportedFn(); // OK + salutation; // Error: no way to get reference to this. +}; +``` + +## Closures + +**RULE**: If a function lexically captures a variable (or parameter), that variable must be (1) a `const` and (2) the value must be serializable. + +```tsx +function somefn() { + let count = 0; + list.foreach((item) => { + count++; + const currentCount = count; + someApi$(() => { + item; // OK (assuming serializable) + count; // ERROR: count not const + currentCount; // OK (assuming serializable) + }); + }); +} +``` + +Again looking at the generated code reveals why these rules must be so: + +```tsx +function somefn() { + let count = 0; + list.foreach((item) => { + count++; + const currentCount = count; + someApi$(qrl('./chunk-a.js', '_1', [item, count, currentCount])); + }); +} +``` + +`chunk-a.js`: + +```tsx +export _1 = () => { + const [item, count, currentCount] = useLexicalScope(); + + item; // OK (assuming serializable) + count; // ERROR: count not const + currentCount; // OK (assuming serializable) +}; +``` + +See serialization for discussion of what is serializable. diff --git a/packages/docs/pages/guide/components/qrl.mdx b/packages/docs/pages/docs/advanced/qrl.mdx similarity index 100% rename from packages/docs/pages/guide/components/qrl.mdx rename to packages/docs/pages/docs/advanced/qrl.mdx diff --git a/packages/docs/pages/guide/qwikloader.mdx b/packages/docs/pages/docs/advanced/qwikloader.mdx similarity index 100% rename from packages/docs/pages/guide/qwikloader.mdx rename to packages/docs/pages/docs/advanced/qwikloader.mdx diff --git a/packages/docs/pages/guide/components/host-element.mdx b/packages/docs/pages/docs/components/anatomy.mdx similarity index 61% rename from packages/docs/pages/guide/components/host-element.mdx rename to packages/docs/pages/docs/components/anatomy.mdx index d4a0a8ce893..271ac5598d2 100644 --- a/packages/docs/pages/guide/components/host-element.mdx +++ b/packages/docs/pages/docs/components/anatomy.mdx @@ -3,33 +3,78 @@ title: Host element fetch: https://hackmd.io/@mhevery/ryD4BhAg9 --- -# Host Element +# Component anatomy -The host element is an element in the DOM that represents component boundaries. +A component is a small, reusable piece of code that can be used to build a UI. -Lite-components do not have host elements. +In Qwik, they are declared using the `component$` method: ```tsx -const Child = () => child; +import { component$, useStore } from '@builder.io/qwik'; -const Parent = () => ( -
- -
-); +// Qwik components can be asynchronous +export const MyCmp = component$(async (props: MyCmpProps) => { + // Declare local state + const state = useStore({ + count: 0, + }); + + // Returns JSX + return ( + + Hello, {props.name} {state.count} +
Times: {state.count}
+ +
+ ); +}); ``` -Will result in: +## Props -```html -
- child -
+Props are used to pass data into the a component. Props are declared as named arguments of the component. + +In this example a component `Item` declares optional `name`, `quantity`, `description`, and `price`. + +```tsx +interface ItemProps { + name?: string, + quantity?: number, + description?: string, + price?: number +} + +export const Item = component$((props: ItemProps) => { + return ...; +}); ``` -Looking at the resulting HTML, it is not possible to tell if a single component or multiple components produced the HTML. It is also not possible to discern where the component boundaries are. +The resulting component can be used like so: -On the other hand, Qwik-components rely on host elements because it must be possible by looking on the HTML to determine where one component starts and another ends. Without knowing the boundaries, it would not be possible to render components independently without forcing parent/child components to render as well. This is a crucial feature of Qwik. +```tsx +const MyApp = component$(() => { + return ( + <> + - With no props: + - With some props: + - With all props: + + ); +}); +``` + +## Host element + +The host element is an element in the DOM that represents component boundaries. + +Qwik-components rely on host elements because it must be possible by looking on the HTML to determine where one component starts and another ends. Without knowing the boundaries, it would not be possible to render components independently without forcing parent/child components to render as well. This is a crucial feature of Qwik. ```tsx const Child = component$(() => child); @@ -53,72 +98,38 @@ Will result in: ``` -Qwik host elements are marked with `q:host` attribute. (Default element is `div` for host element, but that can be changed with `component$` options argument.) Qwik uses host elements in various ways. For example when using `useHostElement()` function which retrieves it. It is also used to attach props to the components for serialization. - -## Lazy Loading - -The host component also serves an important role when breaking parent-child relationships for bundling purposes. - -```tsx -const Child = () => child; - -const Parent = () => ( -
- -
-); -``` +Qwik host elements are marked with `q:host` attribute. -In the above example, referring to the `Parent` component implies a transitive reference to the `Child` component. When bundler is creating chunk, a reference to `Parent` necessitates bundling `Child` as well. (`Parent` internally refers to `Child`.) These transitive dependencies are a problem because it means that having a reference to the root component will transitively refer to the remainder of the application—something which Qwik tries to avoid explicitly. +As you can see, `component$` will always create an extra element, by default is `div`, but that can be changed with `component$` options argument: ```tsx -const Child = component$(() => { - return child; -}); - -const Parent = component$(() => { - return ( -
- -
- ); +const MyArticle = component$(() => ( + My article +), { + tagName: 'article' }); ``` -In the above example the Optimizer transforms the above to: +Will result in: -```tsx -const Child = component$(qrl('./chunk-a', 'Child_onMount')); -const Parent = component$(qrl('./chunk-b', 'Parent_onMount')); -const Parent_onMount = () => qrl('./chunk-c', 'Parent_onRender'); -const Parent_onRender = () => ( -
- -
-); +```html +
+ + My article + +
``` -NOTE: for simplicity, not all of the transformations are shown; all resulting symbols are kept in the same file for succinctness. +### `useHostElement()` -Notice that after the optimizer transforms the code, the `Parent` no longer directly references `Child`. This is important because it allows the bundler (and tree shakers) to freely move the symbols into different chunks without pulling the rest of the application with it. - -So what happens when the `Parent` component renders and `Child` component has not yet been downloaded? First, the `Parent` component renders its JSX like so. +Since the host element is implicitally created by `component$`, it is not possible to access it directly. Instead, you can use `useHostElement()` to get the host element. -```html -
-
-
-
-
-``` + Qwik uses host elements in various ways. For example when using `useHostElement()` function which retrieves it. It is also used to attach props to the components for serialization. -As you can see in the above example, the `
` acts as a marker where the `Child` component will be inserted once it is lazy-loaded. -## Mental Model -The optimizer splits Qwik components into the host element and the behavior of the component. The host element gets bundled with the parent components OnRender function, whereas the component's behavior is something that gets lazy-loaded on an as-needed basis. -## Host ELement Attributes & Styling +## Host Element Attributes & Styling Assume you have a component defined as so: @@ -151,7 +162,13 @@ Because the host element is an actual element, there may be a desire to place HT What if you wanted to add a `name` attribute to the host element? The issue is that the `name` is already used by the component props. For this reason, we use `host:` prefix to refer to the host element's attributes. ```tsx - {}} name="world" /> + {}} + + name="world" +/> ``` would render as: @@ -174,11 +191,15 @@ would render:
``` -However, many IDEs will not recognize `host:class` and `host:style` and would not trigger code completion. For this reason, `class` and `styles` are special, and they will automatically map to `host:class` and `host:styles` +However, for convenience `class` and `styles` are special, and they will automatically map to `host:class` and `host:styles` when placed on the host element. ```jsx - + ``` would also render to the some output. @@ -208,3 +229,68 @@ will result in: Hello World
``` + + +## Lazy Loading + +The host component also serves an important role when breaking parent-child relationships for bundling purposes. + +```tsx +const Child = () => child; + +const Parent = () => ( +
+ +
+); +``` + +In the above example, referring to the `Parent` component implies a transitive reference to the `Child` component. When bundler is creating chunk, a reference to `Parent` necessitates bundling `Child` as well. (`Parent` internally refers to `Child`.) These transitive dependencies are a problem because it means that having a reference to the root component will transitively refer to the remainder of the application—something which Qwik tries to avoid explicitly. + + +```tsx +const Child = component$(() => { + return child; +}); + +const Parent = component$(() => { + return ( +
+ +
+ ); +}); +``` + +In the above example the Optimizer transforms the above to: + +```tsx +const Child = component$(qrl('./chunk-a', 'Child_onMount')); +const Parent = component$(qrl('./chunk-b', 'Parent_onMount')); +const Parent_onMount = () => qrl('./chunk-c', 'Parent_onRender'); +const Parent_onRender = () => ( +
+ +
+); +``` + +NOTE: for simplicity, not all of the transformations are shown; all resulting symbols are kept in the same file for succinctness. + +Notice that after the optimizer transforms the code, the `Parent` no longer directly references `Child`. This is important because it allows the bundler (and tree shakers) to freely move the symbols into different chunks without pulling the rest of the application with it. + +So what happens when the `Parent` component renders and `Child` component has not yet been downloaded? First, the `Parent` component renders its JSX like so. + +```html +
+
+
+
+
+``` + +As you can see in the above example, the `
` acts as a marker where the `Child` component will be inserted once it is lazy-loaded. + +## Mental Model + +The optimizer splits Qwik components into the host element and the behavior of the component. The host element gets bundled with the parent components OnRender function, whereas the component's behavior is something that gets lazy-loaded on an as-needed basis. diff --git a/packages/docs/pages/docs/components/events.mdx b/packages/docs/pages/docs/components/events.mdx new file mode 100644 index 00000000000..8a4ccc53920 --- /dev/null +++ b/packages/docs/pages/docs/components/events.mdx @@ -0,0 +1,255 @@ +--- +title: Events +--- + +# Events + +For a web application to be interactive, there needs to be a way to respond to user events. This is done by registering callback functions in the JSX template. + +```tsx +const Counter = component$(() => { + const store = useStore({ count: 0 }); + + return ; +}); +``` + +In the above example, the `onClick$` attribute of the ` + store.cmpCount++}>{store.count} + + ); +}); +``` + +Notice that both ` +
...
+
+``` + +1. For the HTML ` + ); +}); +``` + +As far as Qwik is concerned, passing events to a component is equivalent to passing props. In our example, we declare all props in `CmpButtonProps` interface. Specifically, notice `onClickQrl?: QRL<() => void>` declaration. + +`` would like to receive an `onClick` closure which it invokes at some later point in time. Qwik mandates that all props to a component need to be serializable. For this reason, we can't ask for `onClick?: () => void`. Instead, we need to ask for a serializable and lazy loadable version of the closure in the form of `onClickQrl?: QRL<() => void>`. `QRL<() => void>` can be read as lazy-loadable reference to a `() => void` closure. + +On the usage side, when referring to the ``, it would be a lot more convenient to pass in a closure rather than `QRL` of the closure. The translation from closure to`QRL` closure is what Qwik Optimizer performs for us. For this reason, the usage is in the format where the closure is inlined like so: + +```tsx + store.cmpCount++}>{store.count} +``` + +Here the prop is `onClick$` rather than `onClickQrl`. We rely on the Qwik Optimizer to perform the translation. The above is roughly translated to: + +```tsx + + {store.count} + +``` + +Assume: `chunk-a.js`: + +```tsx +export const Counter_onRender_CmpButton_onClick = () => { + const [store] = useLexicalScope(); + store.cmpCount++; +}; +``` + +Notice that: + +- `onClick$` was translated to `onClickQrl`. +- The closure `() => store.cmpCount++` was replaced by `qrl('./chunk-a.js', 'Counter_onRender_CmpButton_onClick', [state]`. +- The closure was exported as `Counter_onRender_CmpButton_onClick`. +- A `const [store] = useLexicalScope();` was generated to restore closure state. + +Also, what is not immediately apparent is that TypeScript generates this interface for `` that allows usage of both properties depending on convenience: + +```tsx +interface CmpButtonProps { + onClickQrl?: QRL<() => void>; + onClick$?: () => void; +} +``` + +Notice that TypeScript automatically creates a correct prop with `$` suffix, which generates the parameterized `T` of `QRL`. In our case `T` is `() => void`. This type information makes sure that you correctly pass `QRL` to `onQrl` suffix and closures to `on$` suffix. + +## Working with QRLs + +Let's look at a variation of `` implementation. In this example, we would like to demonstrate working with `Qrl` vs `$`. For this reason, we have created an additional listener `onClick$` + +```tsx +interface CmpButtonProps { + onClickQrl?: QRL<() => void>; +} + +const CmpButton = component$((props: CmpButtonProps) => { + return ( + + ); +}); +``` + +Notice that we can pass the `props.onClickQrl` directly to the `onDblclickQrl` as seen on ` + ); +}); +``` + +The purpose of the `onWindow`/`onDocument` is to register the event at a current DOM location of the component but have it receive events from the `window`/`document`. There are two advantages to it: + +1. The events can be registered declaratively in your JSX +2. The events get automatically cleaned up when the component is destroyed. (No explicit bookkeeping and cleanup is needed.) + + +## Advanced: Events and qwikloader + + +In the above example, the `onClick$` is placed on ` + +``` + +The critical thing to notice is that Qwik generated an `on:click` attribute, containing the value `./chunk-a.js#Counter_button_onClick[0]`. In the above example the `on:click` attribute solves the listener location problem, and the attribute value solves the listener code location problem. By serializing the listeners into the HTML Qwik, applications do not need to perform hydration on application startup. + +## Qwikloader + +For the browser to understand the `on:click` attribute syntax, a small JavaScript known as Qwikloader is needed. The Qwikloader is small (about 1kb) and fast (about 5ms) to execute. The Qwikloader is inlined into the HTML so that it can be executed quickly. +When a user interacts with the application, the browser fires relevant events that bubble up the DOM. At the root of the DOM, Qwikloader listens for the events and then tries to locate the corresponding `on:` attribute. If such an attribute is found, then the value of the attribute is used to resolve the location where code can be downloaded from and then executed. + + +## State recovery + +```tsx +const Counter = component$(() => { + const store = useStore({ count: 0 }); + + return ; +}); +``` + +At first sight, it may appear that the Qwik simply lazy loads the `onClick$` function. But upon closer inspection, it is important to realize that the Qwik lazy loads a closure rather than a function. (A closure is a function that lexically captures the state inside its variables. In other words, closures carry state, whereas functions do not.) The capturing of the state is what allows the Qwik application to simply resume where the server left off because the recovered closure carries the state of the application with it. + +In our case, the `onClick$` closure captures `store`. Capturing of `store` allows the application to increment the `count` property on `click` without having to re-run the whole application. Let's look at how closure capturing works in Qwik. + +The HTML generated by the above code is something like this: + +```html +
+ +
+``` + +Notice that `on:click` attribute contains three pieces of information: + +1. `./chunk-a.js`: The file which needs to be lazy-loaded. +2. `Counter_button_onClick`: The symbol which needs to be retrieved from the lazy-loaded chunk. +3. `[0]`: An array of lexically capture variable references (State of the closure). + +In our case `() => store.count++` only captures `store`, and hence it contains only a single reference `0`. `0` is an index into the `q:obj` attribute which contains a reference to the actual serialized object referring to `store`. (The exact mechanisms and syntax is an implementation detail that can change at any time.) + +## Comparison to `import()` + +JavaScript supports dynamic `import()`. At first glance, it may seem that the same can be achieved by `import()`, but there are a few differences worth mentioning. + +Dynamic `import()`: + +- Is relative to the file which contains it. This works great for `file-a.js` trying to load `file-b.js` as `import('./file-b.js')`. However, when the `./file-a.js` gets serialized into HTML then we lose its relative nature. It is the framework that reads the `./file-b.js` from HTML and performs the `import()`. This means that all imports now become relative to the framework, which is incorrect. +- Requires that the developer writes `import('./file-a.js')`, which means the developer is in charge of deciding where the lazy-loaded boundaries are. This limits our ability of the tooling to move code around in an automated way. +- Supports import of top-level functions only which don't capture the state. This is the biggest difference. Qwik allows the imported symbol to be a closure that carries all of its state with it. + + diff --git a/packages/docs/pages/docs/components/hooks.mdx b/packages/docs/pages/docs/components/hooks.mdx new file mode 100644 index 00000000000..fc559680c6e --- /dev/null +++ b/packages/docs/pages/docs/components/hooks.mdx @@ -0,0 +1,231 @@ +--- +title: Hooks +--- + +# Hooks + +## useStore() + +Creates a object that Qwik can track across serializations. + +Use `useStore` to create state for your application. The return object is a proxy which has a unique ID. The ID of the object is used in the `QRL`s to refer to the store. + +### Example + +Example showing how `useStore` is used in Counter example to keep track of count. + +```tsx +const Stores = component$(() => { + const counter = useCounter(1); + // Reactivity happens even for nested objects and arrays + const userData = useStore({ + name: 'Manu', + address: { + address: '', + city: '' + }, + orgs: [] + }); + // useStore() can also accept a function to calculate the initial value + const state = useStore(() => { + return { + value: expensiveInitialValue() + } + }) + return ( + +
Counter: {counter.value}
+ +
+ ) +}); +function useCounter(step: number) { + // Multiple stores can be created in custom hooks for convenience and composability + const counterStore = useStore({ + value: 0 + }); + useClientEffect$(() => { + // Only runs in the client + const timer = setInterval(() => { + counterStore.value += step; + }, 500); + return () => { + clearInterval(timer); + }; + }); + return counterStore; +} +``` + +## useRef() + +It's a very thin wrapper around `useStore()` including the proper type signature to be passed to the `ref` property in JSX. + +```tsx +export function useRef(current?: T): Ref { + return useStore({ current }); +} +``` + +### Example + +```tsx +const Cmp = component$(() => { + const input = useRef(); + useClientEffect$((track) => { + const el = track(input, 'current')!; + el.focus(); + }); + return ( + + + + ) +}); +``` + +## useWatch$() + +Reruns the `watchFn` when the observed inputs change. + +Use `useWatch` to observe changes on a set of inputs, and then re-execute the `watchFn` when those inputs change. + +The `watchFn` only executes if the observed inputs change. To observe the inputs use the `obs` function to wrap property reads. This creates subscriptions which will trigger the `watchFn` to re-run. + + +### Example + +The `useWatch` function is used to observe the `state.count` property. Any changes to the `state.count` cause the `watchFn` to execute which in turn updates the `state.doubleCount` to the double of `state.count`. + +```tsx +const Cmp = component$(() => { + const store = useStore({ + count: 0, + doubleCount: 0, + debounced: 0 + }); + // Double count watch + useWatch$((track) => { + const count = track(store, 'count'); + store.doubleCount = 2 * count; + }); + // Debouncer watch + useWatch$((track) => { + const doubleCount = track(store, 'doubleCount'); + const timer = setTimeout(() => { + store.debounced = doubleCount; + }, 2000); + return () => { + clearTimeout(timer); + }; + }); + return ( + +
{store.count} / {store.doubleCount}
+
{store.debounced}
+
+ ) +}); +``` + +## useClientEffect$() + +```tsx +const Timer = component$(() => { + const store = useStore({ + count: 0 + }); + useClientEffect$(() => { + // Only runs in the client + const timer = setInterval(() => { + store.count++; + }, 500); + return () => { + clearInterval(timer); + }; + }); + return ( + + {store.count} + + ) +}); +``` + +## useServerMount$() + +Register's a server mount hook, that runs only in server when the component is first mounted. `useWatch` will run once in the server, and N-times in the client, only when the **tracked** state changes. + +### Example + +```tsx +const Cmp = component$(() => { + const store = useStore({ + users: [] + }); + // Double count watch + useServerMount$(async () => { + // This code will ONLY run once in the server, when the component is mounted + store.users = await db.requestUsers(); + }); + return ( + + {store.users.map((user => ( + + )))} + + ) +}); +interface User { + name: string; +} +function User(props: {user: User}) { + return ( +
Name: {props.user.name}
+ ) +} + +interface User { + name: string; +} + +function User(props: {user: User}) { + return ( +
Name: {props.user.name}
+ ) +} +``` + +## useHostElement() + +Retrieves the Host Element of the current component. + +NOTE: `useHostElement` method can only be used in the synchronous portion of the callback (before any `await` statements.) + +```tsx +const Section = component$(() => { + const hostElement = useHostElement(); + console.log(hostElement); // hostElement is a HTMLSectionElement + return ( + + I am a section + + ) +}, { + tagName: 'section' +}); +``` + +## useStyles$() + +A lazy-loadable reference to a component's styles. + +Component styles allow Qwik to lazy load the style information for the component only when needed. (And avoid double loading it in case of SSR hydration.) + +```tsx +import styles from './code-block.css?inline'; +export const CmpStyles = component$(() => { + useStyles$(styles); + return Some text; +}); +``` \ No newline at end of file diff --git a/packages/docs/pages/guide/components/lite-components.mdx b/packages/docs/pages/docs/components/lite-components.mdx similarity index 100% rename from packages/docs/pages/guide/components/lite-components.mdx rename to packages/docs/pages/docs/components/lite-components.mdx diff --git a/packages/docs/pages/guide/components/overview.mdx b/packages/docs/pages/docs/components/overview.mdx similarity index 64% rename from packages/docs/pages/guide/components/overview.mdx rename to packages/docs/pages/docs/components/overview.mdx index c7468ec343d..9d8f78985e1 100644 --- a/packages/docs/pages/guide/components/overview.mdx +++ b/packages/docs/pages/docs/components/overview.mdx @@ -8,7 +8,7 @@ fetch: https://hackmd.io/@mhevery/H1Rzr30l5 Components are basic building blocks of Qwik Applications. Qwik components are unique in that: - Qwik components automatically get broken down into lazy-loaded chunks by the Optimizer. (See Optimizer discussion) -- Are resumable. (A component can get created on a server and continue its execution on the client.) (See resumable discussion) +- Are [resumable](../concepts/resumable.mdx). (A component can get created on a server and continue its execution on the client.) (See resumable discussion) - Can render independently of other components on the page. (See rendering discussion) ## `component$` @@ -60,24 +60,6 @@ When components get invalidated, they are added to the invalidation queue, which For a detailed discussion of reactivity, see related discussion. -## Events - -Events handlers are closures placed in the JSX. - -```tsx -const Counter = component$(() => { - const store = useStore({ count: 0 }); - - return ; -}); -``` - -In the above example, `() => store.count++` is the event handler that is invoked on the user clicking on the `; +}); +``` + +Notice the presence of `$` in the code. `$` is a marker that tells the Optimizer that the function +following it should be lazy-loaded. (For a detailed discussion see [$ and Optimizer Rules](../advanced/optimizer.mdx).) +The `$` is a single character that hints to the Optimizer and the developer to let them know +that asynchronous lazy-loading occurs here. diff --git a/packages/docs/pages/guide/reactivity/overview.mdx b/packages/docs/pages/docs/concepts/reactivity.mdx similarity index 74% rename from packages/docs/pages/guide/reactivity/overview.mdx rename to packages/docs/pages/docs/concepts/reactivity.mdx index 216e1c7db0a..3c383fa39a1 100644 --- a/packages/docs/pages/guide/reactivity/overview.mdx +++ b/packages/docs/pages/docs/concepts/reactivity.mdx @@ -1,3 +1,4 @@ + --- title: Overview fetch: https://hackmd.io/@mhevery/SyYrShReq @@ -113,3 +114,45 @@ const MyComp = component$(() => { } }); ``` + + +## Out-of-order rendering + +Qwik components can render out of order. A component can render without forcing a parent component to render first or to force child components to render as a consequence of the component render. This is an important property of Qwik because it allows Qwik applications to only re-render components which have been invalidated due to state change rather than re-rendering the whole component tree on change. + +When components render, they need to have access to their props. Parent components create props. The props must be serializable for the component to render independently from the parent. (See serialization for an in-depth discussion on what is serializable.) + +## Invalidating child components + +When re-rendering a component, the child component props can stay the same or be updated. A child component only invalidates if the child component props change. + +```tsx +const Child = component$((props: { count: number }) => { + return {props.count}; +}); + +const MyApp = component$(() => { + const store = useStore({ a: 0, b: 0, c: 0 }); + + return ( + <> + + + + {JSON.stringify(store)} + + + + + ); +}); +``` + +In the above example, there are two `` components. + +- Every time a button is clicked, one of the three counters is incremented. A change of counter state will cause the `MyApp` component to re-render on each click. +- If `store.c` has been incremented, none of the child components get re-render. (And therefore, their code does not get lazy-loaded.) +- If `store.a` has been incremented than only `` will re-render. +- If `store.b` has been incremented than only `` will re-render. + +Notice that the child components only re-render when their props change. This is an important property of Qwik applications as it significantly limits the amount of re-rendering the application must do on state change. While less re-rendering has performance benefits, the real benefit is that large portions of the applications do not get downloaded if they don't need to be re-rendered. diff --git a/packages/docs/pages/guide/resumable.mdx b/packages/docs/pages/docs/concepts/resumable.mdx similarity index 53% rename from packages/docs/pages/guide/resumable.mdx rename to packages/docs/pages/docs/concepts/resumable.mdx index 14a20e94283..783b72548af 100644 --- a/packages/docs/pages/guide/resumable.mdx +++ b/packages/docs/pages/docs/concepts/resumable.mdx @@ -3,28 +3,40 @@ title: Resumable fetch: https://hackmd.io/@mhevery/rkYkHhCxq --- -# Resumable + +# Resumable vs. Hydration + +A key concept of Qwik applications is that they are resumable from server-side-rendered state. The best way to explain resumability is to understand how the current generation of frameworks are replayable (hydration). When an SSR/SSG application boots up on a client, it requires that the framework on the client restore two pieces of information: -1. Frameworks need to locate event listeners and install them on the DOM nodes to make the application interactive; -2. Frameworks need to build up an internal data structure representing the application component tree. -3. Frameworks need to restore the application state. +1. Locate event listeners and install them on the DOM nodes to make the application interactive; +2. Build up an internal data structure representing the application component tree. +3. Restore the application state. Collectively, this is known as hydration. All current generations of frameworks require this step to make the application interactive. -The above step is expensive for two reasons: +[Hydration is expensive](https://www.builder.io/blog/hydration-is-pure-overhead) for two reasons: 1. The frameworks have to download all of the component code associated with the current page. 2. The frameworks have to execute the templates associated with the components on the page to rebuild the listener location and the internal component tree. -The above describes why the current generation of frameworks have an expensive startup which is proportional to the complexity of the application which is being hydrated. The more complex the application is, the more expensive the hydration cost, both in terms of the code to download and the code to execute. +![Resumable vs Hydration](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F04681212764f4025b2b5f5c6a258ad6e?format=webp&width=2000) + +Qwik is different because it does not require hydration to resume an application on the client. Not requiring hydration is what makes the Qwik application startup instantaneous. -Hydration is a fundamental property of the current generation of frameworks. +All frameworks hydration **replay** all the application logic in the client, instead Qwik pauses execution in the server, and resumes execution in the client, -Qwik is different because it does not require hydration to resume an application on the client. Not requiring hydration is what makes the Qwik application startup instantaneous. Qwik needs to solve the event listener and component tree state in a way that is compatible with a no-code startup. -## Event Listeners +# Introducing Resumability + +Resumability is about pausing execution in the server, and resuming execution in the client without having to replay and download all of the application logic. + +A good mental model is that Qwik applications at any point in their lifecycle can be serialized and moved to a different VM instance. (Server to browser). There, the application simply resumes where the serialization stopped. No hydration is required. This is why we say that Qwik applications don't hydrate; they resume. + +In other to achieve this, qwik needs to solve the 3 problems (listeners, componenttree, application state) in a way that is compatible with a no-code startup. + +## Listeners DOM without event listeners is just a static page; it is not an application. Today's standard for all sites is quite a high level of interactivity, so even the most static-looking sites are full of event listeners. These include menus, hovers, expanding details, or even full-on interactive apps. @@ -58,12 +70,44 @@ Qwik collects component boundary information as part of the SSR/SSG and then ser 2. Qwik can do this lazily only for the components which need to be rerendered rather than all upfront. 3. Qwik collects relationship information between stores and components. This creates a subscription model that informs Qwik which components need to be re-rendered as a result of state change. The subscription information also gets serialized into HTML. -### Application State +## Application State Existing frameworks usually have a way of serializing the application state into HTML so that the state can be restored as part of hydration. In this way, they are very similar to Qwik. However, Qwik has state management more tightly integrated into the lifecycle of the components. In practice, this means that component can be delayed loaded independently from the state of the component. This is not easily achievable in existing frameworks because component props are usually created by the parent component. This creates a chain reaction. In order to restore component X, its parents need to be restored as well. Qwik allows any component to be resumed without the parent component code being present. -## Resumability +### Serialization -The above describes the differences that make Qwik resumable. Qwik startup performance is fast because there is no startup. There is nothing that Qwik needs to do eagerly to resume the application. +The simplest way to think about serialization is through `JSON.stringify`. However, JSON has several limitations. Qwik can overcome some limitations, but some can't be overcome, and they place limitations on what the developer can do. Understanding these limitations is important when building Qwik applications. + +Limitations of JSON which Qwik solves: + +- JSON produces DAG. DAG stands for directed acyclic graph, which means that the object which is being serialized can't have circular references. This is a big limitation because the application state is often circular. Qwik ensures that when the graph of objects gets serialized, the circular references get properly saved and then restored. +- JSON can't serialize some object types. For example, DOM references, Dates, etc... Qwik serialization format ensures that such objects can correctly be serialized and restored. Here is a list of types that can be serialized with Qwik: + - DOM references + - Dates (not yet implemented) + - Function closures (if wrapped in QRL). + +Limitations of JSON that Qwik does not solve: + +- Serialization of classes (`instanceof` and prototype) +- Serialization of `Promise`s, Streams, etc... + + +## Writing applications with serializability in mind + +The resumability property of the framework must extend to resumability of the application as well. This means that the framework must provide mechanisms for the developer to express Components and Entities of the applications in a way which can be serialized and then resumed (without re-bootstrapping). This necessitates that applications are written with resumability constraints in mind. It is simply not possible for developers to continue to write applications in a heap-centric way and expect that a better framework can somehow make up for this sub-optimal approach. + +Developers must write their applications in a DOM-centric way. This will require a change of behavior and retooling of web-developers skills. Frameworks need to provide guidance and APIs to make it easy for developers to write the applications in this way. + + +## Other benefits of resumability + +The most obvious benefit of using resumability is for server-side-rendering. However, there are secondary benefits: + +- Serializing existing PWA apps so that users don't loose context when they return to the application +- Improved rendering performance because only changed components need to be re-rendered +- Fine-grained-lazy loading +- Decreased memory pressure, especially on mobile devices +- Progressive interactivity of existing static websites + +Reactivity is a key component of Qwik. Reactivity allows Qwik to track which components are subscribed to which state. This information enables Qwik to invalidate only the relevant component on state change, which minimizes the number of components that need to be rerendered. Without reactivity, a state change would require rerendering from the root component, which would force the whole component tree to be eagerly downloaded. -A good mental model is that Qwik applications at any point in their lifecycle can be serialized and moved to a different VM instance. (Server to browser). There, the application simply resumes where the serialization stopped. No hydration is required. This is why we say that Qwik applications don't hydrate; they resume. diff --git a/packages/docs/pages/docs/getting-started.mdx b/packages/docs/pages/docs/getting-started.mdx new file mode 100644 index 00000000000..46dba9e4d64 --- /dev/null +++ b/packages/docs/pages/docs/getting-started.mdx @@ -0,0 +1,101 @@ +--- +title: Getting Started +fetch: https://hackmd.io/@mhevery/S1_pV3Cl9 +--- + +# Getting Started + +Qwik is a new kind of frameworks +- [Stackblitz Qwik + Vite](https://stackblitz.com/edit/qwik-vite-todoapp?file=index.html) +- [Examples playground](/examples) +- [Step by step tutorial](/tutorial) + + +## Prerequisites + +- `node.js` v14 or higher (with `npm`) +- your favorite IDE (vscode recommended) + +## Creating an app + +The first step is to create an application. Qwik comes with a CLI that allows you to create a basic working skeleton of an application. We will use the CLI to create a Todo sample app, and we will use that application to do a walk-through of Qwik so that you can familiarize yourself with it. + +1. Ask Qwik CLI to create a project: + +```shell +$ npm init qwik@latest +``` + +The CLI will guide you through an interactive menu to set the project-name and select one of the starters: + + +[![asciicast](https://asciinema.org/a/ni3UZdIPYPNPNgaGlXrxcl1Bj.svg)](https://asciinema.org/a/ni3UZdIPYPNPNgaGlXrxcl1Bj) + + +After your new app is created, you will see an output like the following in your terminal: + +```shell +💫 Let's create a Qwik project 💫 + +✔ Project name … qwik-todo +✔ Select a starter › Todo +✔ Select a server › Express + +⭐️ Success! Project saved in qwik-todo directory + +📟 Next steps: + cd qwik-todo + npm install + npm start +``` + +At this point, you will have `qwik-todo` directory, which contains the starter app. + +## Running in development + +The easiest way to get running application is to follow the steps from the `npm create qwik@latest`: + +1. Change into the directory created by the `npm create qwik@latest`. + +```shell +cd qwik-todo +``` + +2. Install NPM modules: + +```shell +npm install +``` + +3. Invoke the server + +```shell +npm start +``` + +4. You should see a server running with your To-do application + +```shell + vite v2.8.6 dev server running at: + + > Local: http://localhost:3000/ + > Network: use `--host` to expose + + ready in 157ms. +``` + +5. Visit http://localhost:3000/ to explore the To-do app. + +![](https://i.imgur.com/O72rnhe.png) + +The application is running in development mode using [Vite](https://vitejs.dev/). This is a special mode that supports Hot-Module-Reloading (HMR.) + +While HMR is great for development, Qwik runs like a traditional framework, where all of the work is done in the browser. If you look into the network tab of the dev-tools, you will see that all of the code is eagerly downloaded into the browser and executed. To understand how Qwik is different, we need to run in production mode to see the magic happen. + +## Commands + +- `npm start`: alias to `npm run dev` +- `npm run dev`: starts the dev server in client bootstrap mode +- `npm run dev.ssr`: starts the dev server with SSR +- `npm run build`: builds the application for production + diff --git a/packages/docs/pages/guide/overview.mdx b/packages/docs/pages/docs/overview.mdx similarity index 95% rename from packages/docs/pages/guide/overview.mdx rename to packages/docs/pages/docs/overview.mdx index fd97c6a817b..10e433b8601 100644 --- a/packages/docs/pages/guide/overview.mdx +++ b/packages/docs/pages/docs/overview.mdx @@ -10,7 +10,7 @@ Qwik is a new kind of web framework that can deliver instant loading web applica ## Qwik is: - **General-purpose**: Qwik can be used to build any type of web site or application -- **Instant-on**: Unlike other frameworks, Qwik is [resumable](./resumable.mdx) which means Qwik applications require **0 hydration**. This allows Qwik apps to have instant-on interactivity, regardless of size or complexity +- **Instant-on**: Unlike other frameworks, Qwik is [resumable](./concepts/resumable.mdx) which means Qwik applications require **0 hydration**. This allows Qwik apps to have instant-on interactivity, regardless of size or complexity - **Optimized for speed**: Qwik has unprecedented performance, offering sub-second full page loads even on mobile devices. Qwik achieves this by delivering pure HTML, and incrementally loading JS only as-needed. Delay execution of JavaScript as much as possible. + +Qwik applications startup fast because there is a minimal amount of JavaScript code to execute. (At its simplest, a Qwik application only needs about 1KB of JavaScript to become interactive.) + +By aggressively delaying the application download and execution, Qwik can provide near-instant startup performance that current generations of web frameworks can't match. + +Qwik is fast not because it uses clever algorithms but because it is designed in a way where most of the JavaScript never needs to be downloaded or executed. Its speed comes from not doing things (such as hydration) that other frameworks have to do. + +## Resumability & Serialization + +Resumability is discussed in detail [here](./concepts/resumable.mdx). Resumability allows Qwik applications to continue execution where the server left off. All frameworks need to keep track of internal data structures about the application's state. The current generation of frameworks does not preserve this information from the server to browser transition. As a result, the framework's data structures need to be rebuilt in the browser. The rebuilding of data structures and attaching of listeners is called hydration. + +Qwik serializes listeners, internal data structures, and application state into the HTML on server browser handoff. Because all of the information is serialized in HTML, the client can just resume execution where the server left of. + +# FAQ + +## Why Qwik? The JavaScript ecosystem already has a large selection of frameworks. Why build another one? @@ -19,24 +49,6 @@ As our applications get more complex with a higher fidelity of interactivity, th To make matters worse, JavaScript is single-threaded; therefore, our complex sites can't take advantage of modern multi-core CPUs. -## Why is the problem worth solving? - -Because there is a lot of evidence that says that site startup performance affects the bottom line. Slow sites: - -- Frustrate users -- Lower conversion rates -- Decrease profits - -Put simply: slow sites deter visitors, costing businesses millions. Fast sites have better SEO, better UX, and are more profitable. - -Some examples from [web.dev](https://web.dev): - -| | | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Every 100ms faster → 1% more conversions**
For Mobify, every 100ms decrease in homepage load speed worked out to a 1.11% increase in session-based conversion, yielding an average annual revenue increase of nearly $380,000. | **50% faster → 12% more sales**
When AutoAnything reduced page load time by half, they saw a boost of 12% to 13% in sales. | -| **20% faster → 10% more conversions**
Retailer Furniture Village audited their site speed and developed a plan to address the problems they found, leading to a 20% reduction in page load time and a 10% increase in conversion rate. | **40% faster → 15% more sign-ups**
Pinterest reduced perceived wait times by 40% and this increased search engine traffic and sign-ups by 15%. | -| **850ms faster → 7% more conversions**
COOK reduced average page load time by 850 milliseconds which increased conversions by 7%, decreased bounce rates by 7%, and increased pages per session by 10%. | **1 seconds slowness → 10% less users**
The BBC found they lost an additional 10% of users for every additional second their site took to load. | -| | | ## How did we get here? @@ -50,7 +62,7 @@ Do you need to solve rendering, styling, animation, A/B testing, analytics, etc. As an industry, we have failed to think about the implication of bundle size. Each tool solves a specific problem individually, but the size is not part of the equation. Size is the problem that emerges when you put all of the tools together, and by that point, there is very little the developer can do about it. -# Solution +## What's the solution? Qwik is designed from the ground up to address the size problem. Small bundle size is its initial goal, and all other design decisions are subservient to that goal. @@ -68,15 +80,8 @@ The mantra of Qwik is that bundle size should not be something that developers s Qwik is designed from the ground up to produce lots of lazy loadable boundaries. Tooling can break up your application into many lazy-loadable chunks, and the runtime can download them only when needed. -## What is Qwik - -Qwik is a framework that, above all else, desires to have instant-on application. Qwik achieves this through: - -1. **Philosophy**: Qwik's philosophy extends into API designed in such a way so that lazy loading is introduced into the codebase without undue burden on the developer. -2. **Optimizer**: A Build system that can take advantage of the philosophy to produce a lot of entry points for your application (a large number of entry points allows you to break the application into many small chunks.) -3. **runtime**: A runtime that understands the bundling strategy to deliver on the promise of instant-on applications (In many cases, the application can be interactive with near-zero JavaScript.) -## Why not fix existing frameworks/tools +## Why not fix existing frameworks/tools? In short, the lazy loading philosophy is at a low level and can not be retroactively added to the existing frameworks/tools without changing them fundamentally. Such fundamental change would be incompatible with the framework/tools and their respective ecosystems, rendering them useless. diff --git a/packages/docs/pages/examples/counter/app.tsx b/packages/docs/pages/examples/counter/app.tsx new file mode 100644 index 00000000000..59fb94682b3 --- /dev/null +++ b/packages/docs/pages/examples/counter/app.tsx @@ -0,0 +1,14 @@ +import { component$, useStore } from '@builder.io/qwik'; + +export const App = component$(() => { + const store = useStore({ count: 0 }); + + return ( +
+

Count: {store.count}

+

+ +

+
+ ); +}); diff --git a/packages/docs/pages/examples/counter/entry.server.tsx b/packages/docs/pages/examples/counter/entry.server.tsx new file mode 100644 index 00000000000..c7fcf6bf8e6 --- /dev/null +++ b/packages/docs/pages/examples/counter/entry.server.tsx @@ -0,0 +1,6 @@ +import { renderToString, RenderToStringOptions } from '@builder.io/qwik/server'; +import { Root } from './root'; + +export function render(opts: RenderToStringOptions) { + return renderToString(, opts); +} diff --git a/packages/docs/pages/examples/counter/root.tsx b/packages/docs/pages/examples/counter/root.tsx new file mode 100644 index 00000000000..72d9c413835 --- /dev/null +++ b/packages/docs/pages/examples/counter/root.tsx @@ -0,0 +1,14 @@ +import { App } from './app'; + +export const Root = () => { + return ( + + + Counter + + + + + + ); +}; diff --git a/packages/docs/pages/examples/examples-menu.json b/packages/docs/pages/examples/examples-menu.json new file mode 100644 index 00000000000..85e6cc32475 --- /dev/null +++ b/packages/docs/pages/examples/examples-menu.json @@ -0,0 +1,12 @@ +[ + { + "id": "hello-world", + "title": "Hello World", + "description": "The simplest qwik app" + }, + { + "id": "counter", + "title": "Counter", + "description": "A simple standard counter example" + } +] diff --git a/packages/docs/pages/examples/hello-world/app.tsx b/packages/docs/pages/examples/hello-world/app.tsx new file mode 100644 index 00000000000..46e5b732433 --- /dev/null +++ b/packages/docs/pages/examples/hello-world/app.tsx @@ -0,0 +1,5 @@ +import { component$ } from '@builder.io/qwik'; + +export const App = component$(() => { + return
Hello Qwik
; +}); diff --git a/packages/docs/pages/examples/hello-world/entry.server.tsx b/packages/docs/pages/examples/hello-world/entry.server.tsx new file mode 100644 index 00000000000..c7fcf6bf8e6 --- /dev/null +++ b/packages/docs/pages/examples/hello-world/entry.server.tsx @@ -0,0 +1,6 @@ +import { renderToString, RenderToStringOptions } from '@builder.io/qwik/server'; +import { Root } from './root'; + +export function render(opts: RenderToStringOptions) { + return renderToString(, opts); +} diff --git a/packages/docs/pages/examples/hello-world/root.tsx b/packages/docs/pages/examples/hello-world/root.tsx new file mode 100644 index 00000000000..c3d0864b5b1 --- /dev/null +++ b/packages/docs/pages/examples/hello-world/root.tsx @@ -0,0 +1,14 @@ +import { App } from './app'; + +export const Root = () => { + return ( + + + Hello Qwik + + + + + + ); +}; diff --git a/packages/docs/pages/guide/README.md b/packages/docs/pages/guide/README.md deleted file mode 100644 index bb7a9625c0b..00000000000 --- a/packages/docs/pages/guide/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Guide - -## Introduction - -- [Overview](overview.mdx) -- [Why Qwik](why-qwik.mdx) -- [Getting Started](getting-started.mdx) -- [Mental Model](mental-model.mdx) -- [Philosophy](philosophy.mdx) -- [Resumable](resumable.mdx) -- [Rendering](rendering.mdx) -- [Lazy Loading](lazy-loading.mdx) -- [Qwikloader](qwikloader.mdx) - -## Components - -- [Overview](components/overview.mdx) -- [Host elements](components/host-element.mdx) -- [Props](components/props.mdx) -- [Projection](components/projection.mdx) -- [QRL](components/qrl.mdx) -- [Lite elements](components/lite-components.mdx) - -## Events - -- [Overview](events/overview.mdx) -- [Window/Document](events/on-window.mdx) -- [Components](events/component.mdx) - -## Reactivity - -- [Overview](reactivity/overview.mdx) - -## Optimizer - -- [Overview](optimizer/overview.mdx) -- [$ and Optimizer Rules](optimizer/dollar.mdx) -- [Lexical scope](optimizer/lexical-scope.mdx) - -## Serialization - -- [Overview](serialization/overview.mdx) - -## Containers - -- [Overview](containers/overview.mdx) - -## Community - -- [Github](https://github.com/BuilderIO/qwik) -- [@QwikDev](https://twitter.com/QwikDev) -- [Discord](https://qwik.builder.io/chat) diff --git a/packages/docs/pages/guide/components/props.mdx b/packages/docs/pages/guide/components/props.mdx deleted file mode 100644 index 6d2e3062425..00000000000 --- a/packages/docs/pages/guide/components/props.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Props -fetch: https://hackmd.io/@mhevery/SJRM4p1W5 ---- - -# Props - -Use props to pass data into the child component. Props are declared as named arguments to the OnMount function. - -In this example a component `Item` declares optional `name`, `quantity`, `description`, and `price`. - -```tsx -const Item = component$((props: { - name?: string, - quantity?: number, - description?: string, - price?: number - }) => { - return ...; - } -); -``` - -The resulting component can be used like so: - -```tsx -const MyApp = component$(() => { - return ( - <> - - With no props: - - With some props: - - With all props: - - ); -}); -``` - -## Type information - -Using TypeScript, the props type information correctly flows into the component. - -```tsx -interface ItemProps { - name?: string, - quantity?: number, - description?: string, - price?: number -}; - - -const Item: JSXNode = component$((props: ItemProps) => { - return ...; -}); -``` - -Notice that `Item` is of type `JSXNode`, which constraints the `` usage to only declared properties. (`` would produce a type error as `foo` is not part of the `Item`'s props.) - -It is possible to extract the type interface from the `Item` using `PropsOf`. - -```tsx -const props: PropsOf = { - name: 'Item', - ... -}; -``` - -## Serialization - -Qwik components can render out of order. A component can render without forcing a parent component to render first or to force child components to render as a consequence of the component render. This is an important property of Qwik because it allows Qwik applications to only re-render components which have been invalidated due to state change rather than re-rendering the whole component tree on change. - -When components render, they need to have access to their props. Parent components create props. The props must be serializable for the component to render independently from the parent. (See serialization for an in-depth discussion on what is serializable.) - -## Invalidating child components - -When re-rendering a component, the child component props can stay the same or be updated. A child component only invalidates if the child component props change. - -```tsx -const Child = component$((props: { count: number }) => { - return {props.count}; -}); - -const MyApp = component$(() => { - const store = useStore({ a: 0, b: 0, c: 0 }); - - return ( - <> - - - - {JSON.stringify(store)} - - - - - ); -}); -``` - -In the above example, there are two `` components. - -- Every time a button is clicked, one of the three counters is incremented. A change of counter state will cause the `MyApp` component to re-render on each click. -- If `store.c` has been incremented, none of the child components get re-render. (And therefore, their code does not get lazy-loaded.) -- If `store.a` has been incremented than only `` will re-render. -- If `store.b` has been incremented than only `` will re-render. - -Notice that the child components only re-render when their props change. This is an important property of Qwik applications as it significantly limits the amount of re-rendering the application must do on state change. While less re-rendering has performance benefits, the real benefit is that large portions of the applications do not get downloaded if they don't need to be re-rendered. diff --git a/packages/docs/pages/guide/events/component.mdx b/packages/docs/pages/guide/events/component.mdx deleted file mode 100644 index fea6a554189..00000000000 --- a/packages/docs/pages/guide/events/component.mdx +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: Events and Components ---- - -# Events and Components - -So far, we have discussed how one can listen to DOM events in JSX. A similar mechanism exists with components. Let's assume we have two kinds of buttons to aid the discussion. A HTML button (` - store.cmpCount++}>{store.count} - - ); -}); -``` - -Notice that both ` -
...
- -``` - -1. For the HTML ` - ); -}); -``` - -As far as Qwik is concerned, passing events to a component is equivalent to passing props. In our example, we declare all props in `CmpButtonProps` interface. Specifically, notice `onClickQrl?: QRL<() => void>` declaration. - -`` would like to receive an `onClick` closure which it invokes at some later point in time. Qwik mandates that all props to a component need to be serializable. For this reason, we can't ask for `onClick?: () => void`. Instead, we need to ask for a serializable and lazy loadable version of the closure in the form of `onClickQrl?: QRL<() => void>`. `QRL<() => void>` can be read as lazy-loadable reference to a `() => void` closure. - -On the usage side, when referring to the ``, it would be a lot more convenient to pass in a closure rather than `QRL` of the closure. The translation from closure to`QRL` closure is what Qwik Optimizer performs for us. For this reason, the usage is in the format where the closure is inlined like so: - -```tsx - store.cmpCount++}>{store.count} -``` - -Here the prop is `onClick$` rather than `onClickQrl`. We rely on the Qwik Optimizer to perform the translation. The above is roughly translated to: - -```tsx - - {store.count} - -``` - -Assume: `chunk-a.js`: - -```tsx -export const Counter_onRender_CmpButton_onClick = () => { - const [store] = useLexicalScope(); - store.cmpCount++; -}; -``` - -Notice that: - -- `onClick$` was translated to `onClickQrl`. -- The closure `() => store.cmpCount++` was replaced by `qrl('./chunk-a.js', 'Counter_onRender_CmpButton_onClick', [state]`. -- The closure was exported as `Counter_onRender_CmpButton_onClick`. -- A `const [store] = useLexicalScope();` was generated to restore closure state. - -Also, what is not immediately apparent is that TypeScript generates this interface for `` that allows usage of both properties depending on convenience: - -```tsx -interface CmpButtonProps { - onClickQrl?: QRL<() => void>; - onClick$?: () => void; -} -``` - -Notice that TypeScript automatically creates a correct prop with `$` suffix, which generates the parameterized `T` of `QRL`. In our case `T` is `() => void`. This type information makes sure that you correctly pass `QRL` to `onQrl` suffix and closures to `on$` suffix. - -## Working with QRLs - -Let's look at a variation of `` implementation. In this example, we would like to demonstrate working with `Qrl` vs `$`. For this reason, we have created an additional listener `onClick$` - -```tsx -interface CmpButtonProps { - onClickQrl?: QRL<() => void>; -} - -const CmpButton = component$((props: CmpButtonProps) => { - return ( - - ); -}); -``` - -Notice that we can pass the `props.onClickQrl` directly to the `onDblclickQrl` as seen on ` - ); -}); -``` - -The purpose of the `onWindow`/`onDocument` is to register the event at a current DOM location of the component but have it receive events from the `window`/`document`. There are two advantages to it: - -1. The events can be registered declaratively in your JSX -2. The events get automatically cleaned up when the component is destroyed. (No explicit bookkeeping and cleanup is needed.) diff --git a/packages/docs/pages/guide/events/overview.mdx b/packages/docs/pages/guide/events/overview.mdx deleted file mode 100644 index 37db5486fb2..00000000000 --- a/packages/docs/pages/guide/events/overview.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Events Overview ---- - -# Events - -For a web application to be interactive, there needs to be a way to respond to user events. This is done by registering callback functions in the JSX template. - -```tsx -const Counter = component$(() => { - const store = useStore({ count: 0 }); - - return ; -}); -``` - -In the above example, the `onClick$` attribute of the `; -}); -``` - -In the above example, the `onClick$` is placed on ` - -``` - -The critical thing to notice is that Qwik generated an `on:click` attribute, containing the value `./chunk-a.js#Counter_button_onClick[0]`. In the above example the `on:click` attribute solves the listener location problem, and the attribute value solves the listener code location problem. By serializing the listeners into the HTML Qwik, applications do not need to perform hydration on application startup. - -## Qwikloader - -For the browser to understand the `on:click` attribute syntax, a small JavaScript known as Qwikloader is needed. The Qwikloader is small (about 1kb) and fast (about 5ms) to execute. The Qwikloader is inlined into the HTML so that it can be executed quickly. - -When a user interacts with the application, the browser fires relevant events that bubble up the DOM. At the root of the DOM, Qwikloader listens for the events and then tries to locate the corresponding `on:` attribute. If such an attribute is found, then the value of the attribute is used to resolve the location where code can be downloaded from and then executed. - -## State recovery - -```tsx -const Counter = component$(() => { - const store = useStore({ count: 0 }); - - return ; -}); -``` - -At first sight, it may appear that the Qwik simply lazy loads the `onClick$` function. But upon closer inspection, it is important to realize that the Qwik lazy loads a closure rather than a function. (A closure is a function that lexically captures the state inside its variables. In other words, closures carry state, whereas functions do not.) The capturing of the state is what allows the Qwik application to simply resume where the server left off because the recovered closure carries the state of the application with it. - -In our case, the `onClick$` closure captures `store`. Capturing of `store` allows the application to increment the `count` property on `click` without having to re-run the whole application. Let's look at how closure capturing works in Qwik. - -The HTML generated by the above code is something like this: - -```html -
- -
-``` - -Notice that `on:click` attribute contains three pieces of information: - -1. `./chunk-a.js`: The file which needs to be lazy-loaded. -2. `Counter_button_onClick`: The symbol which needs to be retrieved from the lazy-loaded chunk. -3. `[0]`: An array of lexically capture variable references (State of the closure). - -In our case `() => store.count++` only captures `store`, and hence it contains only a single reference `0`. `0` is an index into the `q:obj` attribute which contains a reference to the actual serialized object referring to `store`. (The exact mechanisms and syntax is an implementation detail that can change at any time.) - -## Comparison to `import()` - -JavaScript supports dynamic `import()`. At first glance, it may seem that the same can be achieved by `import()`, but there are a few differences worth mentioning. - -Dynamic `import()`: - -- Is relative to the file which contains it. This works great for `file-a.js` trying to load `file-b.js` as `import('./file-b.js')`. However, when the `./file-a.js` gets serialized into HTML then we lose its relative nature. It is the framework that reads the `./file-b.js` from HTML and performs the `import()`. This means that all imports now become relative to the framework, which is incorrect. -- Requires that the developer writes `import('./file-a.js')`, which means the developer is in charge of deciding where the lazy-loaded boundaries are. This limits our ability of the tooling to move code around in an automated way. -- Supports import of top-level functions only which don't capture the state. This is the biggest difference. Qwik allows the imported symbol to be a closure that carries all of its state with it. diff --git a/packages/docs/pages/guide/getting-started.mdx b/packages/docs/pages/guide/getting-started.mdx deleted file mode 100644 index 2f6191f9911..00000000000 --- a/packages/docs/pages/guide/getting-started.mdx +++ /dev/null @@ -1,453 +0,0 @@ ---- -title: Getting Started -fetch: https://hackmd.io/@mhevery/S1_pV3Cl9 ---- - -# Getting Started - -This document takes you through creating a To-do application as popularized by https://todomvc.com/. This guide assumes that you have prior knowledge of other frameworks and JSX. - -## Prerequisite - -- `node.js` v14 or higher (with `npm`) -- your favorite IDE - -## Creating an app - -The first step is to create an application. Qwik comes with a CLI that allows you to create a basic working skeleton of an application. We will use the CLI to create a Todo sample app, and we will use that application to do a walk-through of Qwik so that you can familiarize yourself with it. - -1. Ask Qwik CLI to create a project: - -```shell -$ npm create qwik@latest -``` - -2. Create an application (`qwik-todo`) - -```shell -💫 Let's create a Qwik project 💫 - -? Project name › qwik-todo -``` - -3. Select `Todo` starter project: - -```shell -? Select a starter › - Use arrow-keys. Return to submit. - Starter - Blank Qwik starter app. - Starter Builder - Starter Partytown -❯ Todo -``` - -4. Qwik is a framework that starts on the server and then moves to the browser. Select serving technology: - -```shell -? Select a server › - Use arrow-keys. Return to submit. -❯ Express - Express.js server. - Cloudflare - Setup later -``` - -The resulting output should look like this: - -```shell -💫 Let's create a Qwik project 💫 - -✔ Project name … qwik-todo -✔ Select a starter › Todo -✔ Select a server › Express - -⭐️ Success! Project saved in qwik-todo directory - -📟 Next steps: - cd qwik-todo - npm install - npm start -``` - -At this point, you will have `qwik-todo` directory, which contains the starter app. - -## Running in client mode (development) - -The easiest way to get running application is to follow the steps from the `npm create qwik@latest`: - -1. Change into the directory created by the `npm create qwik@latest`. - -```shell -cd qwik-todo -``` - -2. Install NPM modules: - -```shell -npm install -``` - -3. Invoke the server - -```shell -npm start -``` - -4. You should see a server running with your To-do application - -```shell - vite v2.8.6 dev server running at: - - > Local: http://localhost:3000/ - > Network: use `--host` to expose - - ready in 157ms. -``` - -5. Visit http://localhost:3000/ to explore the To-do app. - -![](https://i.imgur.com/O72rnhe.png) - -The application is running in development mode using [Vite](https://vitejs.dev/). This is a special mode that supports Hot-Module-Reloading (HMR.) - -While HMR is great for development, Qwik runs like a traditional framework, where all of the work is done in the browser. If you look into the network tab of the dev-tools, you will see that all of the code is eagerly downloaded into the browser and executed. To understand how Qwik is different, we need to run in production mode to see the magic happen. - -## Running in production mode - -Qwik is SSR/SSG framework that can 1) start execution in `node.js` 2) serialize the application state into HTML and 3) resume the application from HTML in the browser. This section is a tour of those capabilities. - -1. Build the application: - -```shell -$ npm run build -``` - -2. Results in output similar to this: - -```shell -> qwik-todo@0.0.0 build /Users/misko/qwik-todo -> npm run typecheck && npm run build.client && npm run build.server - - -> qwik-todo@0.0.0 typecheck /Users/misko/qwik-todo -> tsc --noEmit - - -> qwik-todo@0.0.0 build.client /Users/misko/qwik-todo -> vite build --outDir server/public - -vite v2.8.6 building for production... -✓ 34 modules transformed. -server/public/index.html 0.37 KiB -server/public/assets/main.81a5c326.js 0.20 KiB / gzip: 0.15 KiB -server/public/assets/index.9d3fa03a.js 0.25 KiB / gzip: 0.18 KiB -server/public/q-b5deaed3.js 0.67 KiB / gzip: 0.42 KiB -server/public/q-cc9047c3.js 5.08 KiB / gzip: 1.47 KiB -server/public/q-3a2b0629.css 6.65 KiB / gzip: 2.07 KiB -server/public/q-59136a66.js 39.29 KiB / gzip: 13.07 KiB - -> qwik-todo@0.0.0 build.server /Users/misko/qwik-todo -> vite build --outDir server/build --ssr src/entry.express.tsx - -vite v2.8.6 building SSR bundle for production... -✓ 30 modules transformed. -server/build/entry.express.js 13.23 KiB -``` - -There are three parts to the build: - -1. TypeScript compilation which is performed with `tsc` -2. Bundling for the client -3. Bundling for the server - -Because Qwik applications start their execution on the server and then resume on the client, it is necessary to bundle the application twice. There are a few reasons for this: - -1. Browsers want ES modules, whereas `node.js` runs better with `commonJS`. -2. Browsers need to take advantage of lazy loading, and therefore browsers need many small files. Servers are long-running, so lazy loading does not have benefits. -3. Server code may execute different functions which allow the server to make direct connections to databases etc.. - -For these reasons, the bundling step is performed twice. - -After a successful build, the application can be served as it would be served in production: - -```shell -$ npm run serve -``` - -Output: - -```shell -> qwik-todo@0.0.0 serve /Users/misko/qwik-todo -> node server/build/entry.express.js - -http://localhost:8080/ -``` - -We can now see the application running by visiting http://localhost:8080/. - -## Tour of the production - -At this point, it is important to take a tour of the To-do application to understand the main differences with the current generation of frameworks. Understanding the differences will give you a better insight into many of the technical decisions of the framework. - -1. First, visit http://localhost:8080/ to familiarize yourself with the To-do application and convince yourself that the application is working as expected. - -NOTE: for the next steps, it is recommended that you open the application in an incognito window, as many browser extensions inject code into sites which may make it look like Javascript is being downloaded. - -2. Open the networking tab in DevTools in your browser and notice that the application did not download any JavaScript to startup. And yet the application is fully interactive. We call this property resumability, and it is the main feature of Qwik that allows even the most complex applications to start up instantaneously. - ![](https://i.imgur.com/QVyu5OX.png) - -3. The question to answer is how is it possible for the application to be interactive with no Javascript. The answer is that Qwik applications come with a small Qwikloader. The Qwikloader is responsible for setting up global event listeners for the application and then downloading the application code on user interaction. The Qwikloader is very small, less than 1kB, and therefore is inlined directly into `index.html` to save round-trip cost. - -4. The Qwikloader is responsible for setting up global event listeners to make the application interactive. Open the DevTools Performance tab and profile the application. What you should see is that the Qwikloader should execute in about 10 ms. (Note: this can be further reduced by explicitly limiting the events to listen to.) - -![](https://i.imgur.com/bnnCd2L.png) - -To resume a Qwik application, it takes less than 1kB of Javascript, which then executes on the client in about 10 ms. The thing to understand is that this code is in no way specific to the To-do application. The cost described above is fixed no matter the size or complexity of the application. Current generation frameworks must hydrate the application on the client to make it interactive. This requires downloading the framework and the application. The hydration cost is proportional to the size and complexity of the application. So it may start out small, but as the application grows, so will the hydration cost. With Qwik, the startup cost 1) is significantly smaller and 2) is fixed no matter the complexity of the application. - -## Understanding User Interactions - -So far, we have shown how to run the Qwik application and how little code is needed to make the application interactive. Now let's look into what happens upon interaction. - -1. Startup the application: - -```shell -$ npm run dev.ssr -``` - -This results in: - -```shell - vite v2.8.6 dev server running at: - - > Local: http://localhost:3000/ - > Network: use `--host` to expose - - ready in 471ms. -``` - -Open http://localhost:3000 and open the browser DevTools. - -NOTE: Opening up the network tab will add `client` and `env.mjs` files. These are from Vite and are used for HMR. They will not be present in the production build and are ignored for this discussion. - -With the network tab open, complete a to-do item by clicking on its checkmark button. - -![](https://i.imgur.com/dNsSsHp.png) - -Notice how interacting with the application caused the network tab to download the necessary code to process the user input. - -Now refresh the page to clear the network tab and enter text into the To-do app input box. - -![](https://i.imgur.com/ohvnFPy.png) - -Notice how entering the text caused the network tab to download the necessary code that is different from the previous interaction. Depending on how the user interacts with the application, different code gets downloaded. - -![](https://i.imgur.com/wiGaLZL.png) - -Now create a new item by hitting `Enter` key in the input box. Notice that even more code now downloads. - -There are a few things to notice: - -- The code downloaded depends on how the user interacts with the application. Click on completion will download completion code; entering new text will download code associated with new to-do item creation. -- The initial files then download other code, including the code needed to re-render the component. (See prefetching on the discussion of how to avoid waterfall requests.) -- Only the code needed to re-render the specific component is downloaded. -- As you further interact with the application, more code gets downloaded on an as-needed basis. Notice that Qwik delays the loading of code for as long as possible. - -### QRLs - -You may want to know how Qwik knows which code to download based on the user interaction. You can explore this by examining the DOM in the DevTools. Let's zoom in on the checkmark HTML. You should see something like this: - -```html - -``` - -There are a few things to notice: - -- `q:obj` is used when deserializing objects associated with your application. The attribute is a pointer to serialized JSON that contains the relevant data. -- `onClickQrl`: is a serialized version of the event handler. It describes: - - `click`: Qwikloader should listen on the `click` event. - - `..../h_item_item_onrender_input_onclick.js`: points to the URL that needs to be downloaded due to the click. - - `Item_onRender_input_onclick`: points to the symbol which `h_item_item_onrender_input_onclick.js` exports which represents the handler for the click listener. (See Optimizer to understand how Qwik breaks up your application into lazy-loadable chunks.) - - `[0]`: Index into `q:obj` used to restore the lexically scoped variables for the event handler. - -NOTE: `q:obj` and `[0]` are implementation details of Qwik and can change at any time. They are described here for illustrative purposes only to explain how Qwik discovers which code it should download and which state it should restore to process the event handlers. Please don't rely on them in your application. - -Because Qwik SSR serializes the event handlers into HTML, the Qwik application does not need to perform hydration on application start up. All of the necessary information to process the events is present in HTML. (This is what we is meant by Qwik applications being resumable, they can resume execution where the server left off without downloading any code for hydration.) - -## Understanding SSR/SSG - -### Server - -Server-side rendering (SSR) and static-site generation (SSG) are ways of precomputing the HTML for faster site loading. Existing frameworks require that for a site to become interactive, it must undergo hydration. Qwik is unique in that it can serialize the event handlers, application state, and framework state information into HTML. The serialized information can then be used to skip hydration. - -Let's dive into how Qwik generates HTML either in SSR or SSG. - -1. The entry point for SSR/SSG is `src/entry.server.tsx`. It exports `render` function which invokes `renderToString()` function. - -```tsx -export function render(opts: RenderToStringOptions) { - return renderToString( - - - - Qwik Demo: ToDo - - -
- - - , - opts - ); -} -``` - -2. Notice that the rendering starts with the `` tag. This is quite different from most frameworks which usually load the application into the existing `index.html`. The reason for this is that Qwik needs to inject `` (and other components not shown here.) -3. The application is included as `
` tag that transitively includes other components which need are rendered. -4. The result of `renderToString` is a `Promise`. Qwik rendering pipeline is asynchronous, and it understands how to wait on components until they finish rendering for the purposes of SSR/SSG and before they are serialized. - -### Resuming in Browser - -While the server has a clear entry point for your application, no such entry point exists for the Client/Browser. At first, this may seem surprising, but it is a natural consequence of resumability. There are as many entry points as there are serialized event handlers in the HTML. Any one of the event handlers may be invoked first, and hence different code will be downloaded that in turn will be responsible for bootstrapping the application and framework. - -## Understanding Serialization and Resumability - -For the application to be resumable, the framework needs to know everything about the application without downloading any application code first. In practice, this means that HTML must contain: - -- **Event Handlers**: Information on the location of all event handlers. This includes event name, code to download, symbol to retrieve, and application state to restore for the event handler. -- **Application state**: State which your application needs to function. -- **Subscriptions**: Qwik is reactive, so it needs to know which components hold subscriptions to which application state. This is necessary so that when the event handler modifies the application state, Qwik can re-render only the affected component. -- **Component Hierarchy**: Information where individual components start/end in the HTML as well as any projected children. -- **Component Rendering**: Information showing where rendering functions for each component can be downloaded. - -The above information is split into two parts: - -1. Structural and event handler information is encoded in HTML directly in form of attributes or element names. (`` or `
`, `; -}); -``` - -As a developer, it is desirable to put all parts of the component together into an easily readable format, as shown above. However, while the component is concise, it is not lazy-loadable. - -Let's rewrite the component and give each function a name for easier discussion. - -```tsx -const Counter = component$(function onMount() { - const store = useStore({count: 0}); - return $(function onRender() { - return ( - - ); - }); -}); -``` - -NOTE: Code written verbosely for easier discussion. - -Let's look at use cases: - -- **initial render**: will require `onMount` to create component state and `onRender` to render the component. `onClick` is not needed as no user interaction has occurred. -- **re-rendering**: (assume `store.count` changes by another component, not shown.) Only `onRender` is needed. `onMount` is not required as the component already exists (no new state needs to be created.) `onClick` is not necessary because the user did not interact with the component. -- **interaction**: Clicking on the button needs `onClick`, which will mutate the `state.count`, which will require `onRender` to update the component. `onMount` is only needed on component creation, and in this case, the component already exists. - -As you can see, different use cases need different parts of the code. In the above trivial code example, downloading all of the code together is inconsequential. However, In large applications, it is easy to create large islands that are coupled and hence can't be lazy-loaded independently. The result is that bandwidth is wasted downloading code that is not immediately needed. - -# Optimizer - -Optimizer (described in-depth [here](./optimizer/overview.mdx)) is a code transformation that extracts functions into top-level importable symbols, which allows the Qwik runtime to lazy-load the JavaScript on a needed basis. - -The Optimizer and Qwik runtime work together to achieve the desired result of fine-grained lazy loading. - -Without Optimizer either: - -- The code would have to be broken up by the developer into importable parts. This would be unnatural to write an application, making for a bad DX. -- The application would have to load a lot of unnecessary code as there would be no lazy-loaded boundaries. - -Qwik runtime must understand the Optimizer output. The biggest difference to comprehend is that by breaking up the component into lazy-loadable chunks, the lazy-loading requirement introduces asynchronous code into the framework. The framework has to be written differently to take asynchronicity into account. Existing frameworks assume that all code is available synchronously. This assumption prevents an easy insertion of lazy-loading into existing frameworks. (For example, when a new component is created, the framework assumes that its initialization code can be invoked in a synchronous fashion. If this is the first time component is referenced, then its code needs to be lazy-loaded, and therefore the framework must take that into account.) - -## Lazy-loading - -Lazy-loading is asynchronous. Qwik is an asynchronous framework. Qwik understands that at any time, it may not have a reference to a callback, and therefore, it may need to lazy-load it. (In contrast, most existing frameworks assume that all of the code is available synchronously, making lazy-loading non-trivial.) - -In Qwik everything is lazy-loadable: - -- Component on-mount (initialization block) -- Component on-watch (side-effects, only downloaded if inputs change) -- Component on-render (only downloaded when a component needs to be re-rendered) -- Listeners (only downloaded on interaction) -- Styles (Only downloaded if the server did not already provide them) - -Lazy-loading is a core property of the framework and not an afterthought. - -## Optimizer and `$` - -Let's look at our example again: - -```tsx -const Counter = component$(() => { - const store = useStore({ count: 0 }); - - return ; -}); -``` - -Notice the presence of `$` in the code. `$` is a marker that tells the Optimizer that the function -following it should be lazy-loaded. (For a detailed discussion see [$ and Optimizer Rules](./optimizer/rules.mdx).) -The `$` is a single character that hints to the Optimizer and the developer to let them know -that asynchronous lazy-loading occurs here. diff --git a/packages/docs/pages/guide/mental-model.mdx b/packages/docs/pages/guide/mental-model.mdx deleted file mode 100644 index 45edbf3011b..00000000000 --- a/packages/docs/pages/guide/mental-model.mdx +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Mental model -fetch: https://hackmd.io/@mhevery/Hyh0420l5 ---- - -# Mental Model - -Qwik is very similar to other web frameworks on a high level. Qwik is a framework that renders a tree of components resulting in an interactive application. - -The unique part of Qwik is not in what it does but in how it achieves its goals. Qwik's goal is to have instant-on applications, even on mobile devices. Qwik achieves this through two main strategies: - -1. Delay execution and download of Javascript for as long as possible. -2. Serialize the execution state of the application and the framework on the server and resume it on the client. - -The goal of Qwik is having only to download and execute the bare minimum of the application. The remainder of the document lists the problems the framework will encounter to minimize the amount of code that needs to be downloaded and executed. - -## 1. Qwikloader and event handler serialization - -The most expensive part for the startup for the current generation of frameworks is the cost of hydration. Hydration refers to attaching event listeners to the DOM elements to make the application interactive. Qwik does not require hydration because Qwik serializes the event handlers into HTML in the form of `on:event` attributes. Together with Qwikloader, Qwik can delay executing Javascript until user interaction. (See preloading for discussion on how to minimize the cost of loading code on interaction.) - -Without serializing event handlers into the HTML, the framework would be forced to resume on application startup. Hydration would require downloading all components currently in HTML and executing their templates to determine where the event listeners need to be attached. - -## 2. Code splitting - -Qwikloader and event handler serialization from above can delay the execution of javascript until later. However, serializing event handlers will not have benefits if the first interaction will have to download the whole application. Qwik puts a lot of emphasis on breaking up the application into many small lazy loadable chunks through Optimizer so that only a tiny portion of the application needs to be downloaded on the first interaction. The amount of code downloaded should be proportional to the complexity of the interaction rather than just forcing the download of all components currently in HTML. - -Without Optimizer, a typical application would end up a single chunk because all of the application parts are interconnected. Breaking up the application codebase requires explicit dynamic imports. Most current-generation frameworks make it best to place these lazy-loaded boundaries on routing boundaries only. Routing level lazy loading still results in chunks that are too big for the Qwik approach. - -## 3. Bootstrap - -All applications have a main entry point to get the application going. We call this the bootstrap code. Typically this involves importing the application root component and calling a framework method to render it. Rendering a root component, in turn, causes more components to be pulled in and rendered until the whole application renders. - -On hydration, the bootstrap again takes a root component and calls a framework method to resume the current HTML (meaning re-attach the listeners as well as rebuild the framework internal state about the application component tree.) - -Qwik does not need to do hydration. Qwik still has a bootstrap method, but that typically executes on the server to render the initial application. There is no rebootstrap on the client. Instead, Qwik serializes not only the application state into HTML but also the framework state as well. The serialized state allows the Qwik framework to continue execution where the server left off. Not requiring a rebootstrap is what makes Qwik resumable. - -If the Qwik application required a rebootstrap in the browser, they would require pulling the root component and, with it, all child components. This would require an early download of the application code, the exact thing that Qwik aims to avoid. - -## 4. State - -A user interaction causes the browser to fire an event processed by the Qwikloader, which in turn downloads the specific event handler to process the user interaction. When the event handler starts execution, it needs the application state; otherwise, it will not perform useful work. - -The current generation of frameworks rebuild application (and framework) state as part of the rehydration. Qwik explicitly avoids rehydration because doing so would require the download and execution of code. Instead, Qwik relies on serializing the application (and framework) state from the server into HTML and then restoring the state on the client. By deserializing (restoring) the state, the Qwik application can continue execution in the browser from where the server left off. - -Without restoring the state, the application would have to rebuild the state before the event handler runs. The rebuild of the state would require downloading and executing application initialization code that would not fit into a reasonable handler response. - -## 5. Reactivity - -At this point, Qwik was able to download a minimal amount of code to process the event. The event most likely modified the application state, and therefore UI needs to be updated. To do this efficiently, Qwik must determine which components are invalidated due to the state change. - -Most current-generation frameworks are not reactive, and they solve the above problem by simply rerendering all of the components from the root. (The frameworks can use tricks to limit the DOM updates and prune some tree branches to stay fast, but they still need to execute too many templates.) The consequence of this is that component templates are downloaded and executed even if no visible change is rendered. Qwik can't use this approach because doing so would require that most of the application would be downloaded on the first interaction. - -An alternative approach is to use reactivity. A reactive framework keeps a graph of each state property and which component uses it. In this way, a reactive framework can efficiently answer which components need to be invalidated due to state change. However, to build up the graph, a framework must execute the whole application at least once to collect the graph information. The initial collection of the graph means that whole applications must be downloaded and executed before a framework can efficiently answer which component is invalidated due to a specific state change. This approach would invalidate the benefits of Qwik as it would force an early download and execution of the application. - -Qwik is reactive on the component level. To avoid the early download and execution of the component, Qwik serializes the reactivity graph on the server and restores it on the client. In this way, Qwik can easily answer which components need to be invalidated without forcing the whole application to download to the browser eagerly. - -## 6. Out-of-order Rendering - -At this point, Qwik was able to download minimal code to process the event, restore the application state, and determine a minimal number of components that are invalidated due to the state change. The last major challenge is to rerender the invalidated components and not also require Qwik to render parent or child components. - -The component that needs to be rendered will require props from the parent component. The Qwik serialization mechanism must serialize props on the server and restore props in the browser so that the parent component presence is not required. (A more complicated form of this is when the parent component creates projection children that child component does not render on the server. In the browser, the client component rerendering causes the projected content to be rendered. Qwik must restore the content children without forcing the parent component to rerender.) - -Qwik also needs to prevent child components from being rerendered due to parent component rerendering. When the parent component rerenders, the child components do not get updated unless their props have been modified. - -Lastly, Qwik needs to ensure that downloading the parent component template does not force the download of a child template. Typically a component template directly references a child component as symbols—this forces bundler to include child component in the parent component bundle because there is a direct reference. In practice, this means that having a reference to a root component transitively includes all child components as well. Qwik optimizer is specifically designed to prevent this so that having a reference to any component does not transitively include child components as well. - -## Summary - -As you can see, Qwik is designed so that it can delay the download and execution of most of the code associated with the application for as long as possible. It achieves this through: - -1. **The Optimizer**: breaks up the application into many small lazy-loadable chunks. -2. **Resumability**: runtime is designed so that it does not require hydration; instead, it serializes all of the application and framework state into HTML, allowing the application to resume execution where the server left off. diff --git a/packages/docs/pages/guide/optimizer/dollar.mdx b/packages/docs/pages/guide/optimizer/dollar.mdx deleted file mode 100644 index ab05a0b8864..00000000000 --- a/packages/docs/pages/guide/optimizer/dollar.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Optimizer Rules -fetch: https://hackmd.io/@mhevery/rkydmxJZ9 ---- - -# `$` 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: - -```tsx -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: - -```tsx -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. - -```tsx -function onScroll(fnQrl: QRL<() => void>) { - document.addEventListener('scroll', async () => { - fn = await qImport(document, fnQrl); - fn(); - }); -} - -onScroll($(() => alert('clicked'))); -``` - -The Optimizer will generate: - -```tsx -onScroll(qrl('./chunk-a.js', 'onScroll_1')); -``` - -`chunk-a.js`: - -```tsx -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`.) - -```tsx -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: - -```tsx -const MyComp = component$(() => { - /* my component definition */ -}); -``` - -The Optimizer breaks the code up into two files: - -The original file: - -```tsx -const MyComp = component(qrl('./chunk-a.js', 'MyComp_onMount')); -``` - -`chunk-a.js`: - -```tsx -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. diff --git a/packages/docs/pages/guide/optimizer/lexical-scope.mdx b/packages/docs/pages/guide/optimizer/lexical-scope.mdx deleted file mode 100644 index da11065bb35..00000000000 --- a/packages/docs/pages/guide/optimizer/lexical-scope.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Capturing and lexical scopes -fetch: https://hackmd.io/@mhevery/rJ6dXxy-c ---- - -# Capturing the lexical scope - -The Optimizer extracts expressions (usually functions) into new files and leaves behind a `QRL` pointing to the lazy-loaded location. - -Let's look at a simple case: - -```tsx -const Greeter = component$(() => { - return Hello World!; -}); -``` - -this will result in: - -```tsx -const Greeter = component(qrl('./chunk-a.js', 'Greeter_onMount')); -``` - -`chunk-a.js`: - -```tsx -const Greeter_onMount = () => { - return qrl('./chunk-b.js', 'Greeter_onRender'); -}; -``` - -`chunk-b.js`: - -```tsx -const Greeter_onRender = () => Hello World!; -``` - -The above is for simple cases where the extracted function closure does not capture any variables. Let's look at a more complicated case where the extracted function closure lexically captures variables. - -```tsx -const Greeter = component$((props: { name: string }) => { - const salutation = 'Hello'; - - return ( - - {salutation} {props.name}! - - ); -}); -``` - -The naive way to extract functions will not work. - -```tsx -const Greeter = component(qrl('./chunk-a.js', 'Greeter_onMount')); -``` - -`chunk-a.js`: - -```tsx -const Greeter_onMount = (props) => { - const salutation = 'Hello'; - return qrl('./chunk-b.js', 'Greeter_onRender'); -}; -``` - -`chunk-b.js`: - -```tsx -const Greeter_onRender = () => ( - - {salutation} {props.name}! - -); -``` - -The issue can be seen in `chunk-b.js`. The extracted function refers to `salutation` and `props`, which are no longer in the lexical scope of the function. For this reason, the generated code must be slightly different. - -`chunk-a.js`: - -```tsx -const Greeter_onMount = (props) => { - const salutation = 'Hello'; - return qrl('./chunk-b.js', 'Greeter_onRender', [salutation, props]); -}; -``` - -`chunk-b.js`: - -```tsx -const Greeter_onRender = () => { - const [salutation, props] = useLexicalScope(); - - return ( - - {salutation} {props.name}! - - ); -}; -``` - -Notice two changes: - -1. The `QRL` in `Greeter_onMount` now stores the `salutation` and `props`. This performs the role of capturing the constants inside closures. -2. The generated closure `Greeter_onRender` now has a preamble which restores the `salutation` and `props` (`const [salutation, props] = useLexicalScope()`.) - -The ability for the Optimizer (and Qwik runtime) to capture lexically scoped constants significantly improves which functions can be extracted into lazy-loaded resources. It is a powerful tool for breaking up complex applications into smaller lazy-loadable chunks. diff --git a/packages/docs/pages/guide/optimizer/overview.mdx b/packages/docs/pages/guide/optimizer/overview.mdx deleted file mode 100644 index 2fa1ae46506..00000000000 --- a/packages/docs/pages/guide/optimizer/overview.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Overview -fetch: https://hackmd.io/@mhevery/HyP4Qx1-q ---- - -# Optimizer - -Qwik's philosophy is to delay loading code for as long as possible. To do that, Qwik relies on Optimizer to re-arrange the code for lazy loading. The Optimizer is code level transformation that runs as part of the rollup. (Optimizer is written in Rust (and available as WASM) for instant performance) - -The Optimizer looks for `$` and applies a transformation that extracts the expression following the `$` and turns it into a lazy-loadable and importable symbol. - -Let's start by looking at a simple `Counter` example: - -```tsx -const Counter = component$(() => { - const store = useStore({ count: 0 }); - - return ; -}); -``` - -The above code represents what a developer would write to describe the component. Below are the transformations that the Optimizer applies to the code to make the code lazy-loadable. - -```tsx -const Counter = component(qrl('./chunk-a.js', 'Counter_onMount')); -``` - -`chunk-a.js`: - -```tsx -export const Counter_onMount = () => { - const store = useStore({ count: 0 }); - return qrl('./chunk-b.js', 'Counter_onRender', [store]); -}; -``` - -`chunk-b.js`: - -```tsx -const Counter_onRender = () => { - const [store] = useLexicalScope(); - return ( - - ); -}; -``` - -`chunk-c.js`: - -```tsx -const Counter_onClick = () => { - const [store] = useLexicalScope(); - return store.count++; -}; -``` diff --git a/packages/docs/pages/guide/optimizer/rules.mdx b/packages/docs/pages/guide/optimizer/rules.mdx deleted file mode 100644 index ebf7222ae8e..00000000000 --- a/packages/docs/pages/guide/optimizer/rules.mdx +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: Rules -fetch: https://hackmd.io/@mhevery/S1-U7l1b9 ---- - -# Optimizer Rules - -The Optimizer can break up large applications into lots of small lazy-loadable chunks. In addition, the Optimizer can lazy-load function closure, which lexically captures variables. However, there are limits to what can be achieved, and therefore the Optimizer comes with a set of rules. Not all valid Javascript is valid Optimizer code. This section describes the rules that developer needs to follow for successful Optimizer transformation. - -The `$` is not only a marker for the Optimizer but also a marker for the developer to follow these rules. - -NOTE: There are plans for a linter that will be able to enforce these rules eagerly. - -## Imports - -**RULE**: If a function that is being extracted by Optimizer refers to a top-level symbol, that symbol must either be imported or exported. - -```tsx -import { importedFn } from '...'; -export exportedFn = () => {...}; - -const salutation = "Hello"; - -someApi$(() => { - importedFn(); // OK - exportedFn(); // OK - salutation; // Error: salutation not imported/exported -}) -``` - -The reason for the above rule becomes obvious when the output is examined. - -```tsx -import { importedFn } from '...'; -export exportedFn = () => { ... }; - -const salutation = "Hello"; - -someApi(qrl('./chunk-a.js', 'someApi_1')); -``` - -`chunk-a.js`: - -```tsx -import { importedFn } from '...'; -import { exportedFn } from './originalFile'; - -export const someApi_1 = () => { - importedFn(); // OK - exportedFn(); // OK - salutation; // Error: no way to get reference to this. -}; -``` - -## Closures - -**RULE**: If a function lexically captures a variable (or parameter), that variable must be (1) a `const` and (2) the value must be serializable. - -```tsx -function somefn() { - let count = 0; - list.foreach((item) => { - count++; - const currentCount = count; - someApi$(() => { - item; // OK (assuming serializable) - count; // ERROR: count not const - currentCount; // OK (assuming serializable) - }); - }); -} -``` - -Again looking at the generated code reveals why these rules must be so: - -```tsx -function somefn() { - let count = 0; - list.foreach((item) => { - count++; - const currentCount = count; - someApi$(qrl('./chunk-a.js', '_1', [item, count, currentCount])); - }); -} -``` - -`chunk-a.js`: - -```tsx -export _1 = () => { - const [item, count, currentCount] = useLexicalScope(); - - item; // OK (assuming serializable) - count; // ERROR: count not const - currentCount; // OK (assuming serializable) -}; -``` - -See serialization for discussion of what is serializable. diff --git a/packages/docs/pages/guide/philosophy.mdx b/packages/docs/pages/guide/philosophy.mdx deleted file mode 100644 index 4bf039b6f2d..00000000000 --- a/packages/docs/pages/guide/philosophy.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Philosophy -fetch: https://hackmd.io/@mhevery/Hk5cgTCg9 ---- - -# Philosophy - -Qwik's goal is to have applications that have a fast startup. We believe that fast startup has these benefits: - -- Users use a fast startup time as a proxy for the site quality. -- The faster a site can become interactive, the lower the bounce rate and higher the conversions (and hence profit). - -During site startup, the browser has to do a lot of things that negatively impact startup performance: - -- Browser has to render the initial HTML. -- Browser has to execute your application (rehydration). -- Browser has to execute third-party code. - -Because JavaScript is single-threaded, all of the above tasks compete for a single CPU core. Your site startup performance is directly proportional to how much JavaScript the browser needs to execute to get the site interactive. - -## Core tenant - -Qwik is designed around this core tenant: - -> Delay execution of JavaScript as much as possible. - -Qwik applications startup fast because there is a minimal amount of JavaScript code to execute. (At its simplest, a Qwik application only needs about 1KB of JavaScript to become interactive.) - -By aggressively delaying the application download and execution, Qwik can provide near-instant startup performance that current generations of web frameworks can't match. - -Qwik is fast not because it uses clever algorithms but because it is designed in a way where most of the JavaScript never needs to be downloaded or executed. Its speed comes from not doing things (such as hydration) that other frameworks have to do. - -## Resumability & Serialization - -Resumability is discussed in detail [here](./resumable.mdx). Resumability allows Qwik applications to continue execution where the server left off. All frameworks need to keep track of internal data structures about the application's state. The current generation of frameworks does not preserve this information from the server to browser transition. As a result, the framework's data structures need to be rebuilt in the browser. The rebuilding of data structures and attaching of listeners is called hydration. - -Qwik serializes listeners, internal data structures, and application state into the HTML on server browser handoff. Because all of the information is serialized in HTML, the client can just resume execution where the server left of. - -Hydration forces the current generation of frameworks to download JavaScript that is associated with the current set of components on the page to make them interactive. The more complex the HTML and the more complex the components, the more JavaScript needs to be downloaded and executed. Qwik does not need hydration, and therefore, non of the JavaScript needs to be eagerly downloaded to make the application interactive. diff --git a/packages/docs/pages/guide/resumable-vs-replayable.mdx b/packages/docs/pages/guide/resumable-vs-replayable.mdx deleted file mode 100644 index e0ae72e0a80..00000000000 --- a/packages/docs/pages/guide/resumable-vs-replayable.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Resumable vs. Replayable ---- - -# Resumable vs. Replayable - -A key concept of Qwik applications is that they are resumable from server-side-rendered state. The best way to explain resumability is to understand how the current generation of frameworks are replayable. - -## Frameworks must understand the application - -Frameworks have the need to understand the structure of the application. Examples of such knowledge are: - -- location of the event handlers -- understanding component structure (parent child relationship) -- bindings - -We call the above information the framework-internal-state. - -## Heap-centric frameworks are replayable - -The current generation of frameworks store the framework-internal-state in the Javascript heap as a set of objects, and closures. The frameworks build up their internal-state by bootstrapping the application. Because the frameworks are not designed with serializability in mind, the framework-internal-state is not serializable and needs to be re-built on application bootstrap. The consequence of this is that if a site is server-side-rendered then the framework must re-bootstrap the application in order to rebuild the framework-internal-state. Bootstrapping the application is slow because: - -- Most of the framework needs to be downloaded and executed. -- All component templates on the page need to be downloaded. (Proportional to the size of the application.) -- The browser must parse and execute the code (usually in slow interpreted mode as JIT has not had sufficient time to warm up). -- On bootstrap the application often performs complex initialization and data fetching. -- The newly bootstrapped application generates DOM which needs to be reconciled with the server-side-rendered DOM (usually the new DOM just replaces the SSR DOM). - -The consequence of the above constraints is that the application initializes twice. Once on the server, and than once again on the client. We say that the application is **replayable** because the application must _replay_ its bootstrap on the client to get the framework-internal-state into the same state as it was on the server. - -The **re-playability** property of the framework is what makes the applications built with the current generation of frameworks have less than ideal [time to interactive](https://web.dev/interactive/) performance. Usually the performance is proportional to the application size. An application may start with good time-to-interactive and as the application gets bigger its time-to-interactive performance progressively gets worse. - -## DOM centric frameworks are resumable - -If time-to-interactive is your top concern then you want to have a framework which is **resumable**. By **resumable** we mean that the application bootstraps on the server, gets serialized into HTML, and can continue execution on the client without re-bootstrapping itself on the client. The application simply _resumes_ from where the server left off. - -In order for the framework to be resumable it must store the framework-internal-state in an easily serializable format. The most obvious location is to store framework-internal-state directly on the DOM in the form of attributes as they are serializable. - -Examples of information which the framework needs to store in the DOM are: - -- DOM listeners -- Component state -- Pointers to component templates for re-rendering. -- Entity state -- Entity component relationships. - -By keeping the above state in the DOM the framework does not have any additional information (other than what is stored in the DOM) and as a result the framework can continue executing from where the server left off. Because the framework provides a mechanism for application component and entities to also be serialized into the DOM the result is that both the framework as well as application state can be serialized into HTML and the application can fully be resumed on the client. - -## Writing applications with serializability in mind - -The resumability property of the framework must extend to resumability of the application as well. This means that the framework must provide mechanisms for the developer to express Components and Entities of the applications in a way which can be serialized and then resumed (without re-bootstrapping). This necessitates that applications are written with resumability constraints in mind. It is simply not possible for developers to continue to write applications in a heap-centric way and expect that a better framework can somehow make up for this sub-optimal approach. - -Developers must write their applications in a DOM-centric way. This will require a change of behavior and retooling of web-developers skills. Frameworks need to provide guidance and APIs to make it easy for developers to write the applications in this way. - -## Other benefits of resumability - -The most obvious benefit of using resumability is for server-side-rendering. However, there are secondary benefits: - -- Serializing existing PWA apps so that users don't loose context when they return to the application -- Improved rendering performance because only changed components need to be re-rendered -- Fine-grained-lazy loading -- Decreased memory pressure, especially on mobile devices -- Progressive interactivity of existing static websites diff --git a/packages/docs/pages/guide/serialization/overview.mdx b/packages/docs/pages/guide/serialization/overview.mdx deleted file mode 100644 index 5f365d790be..00000000000 --- a/packages/docs/pages/guide/serialization/overview.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Overview ---- - -# Serialization - -Qwik is resumable. Resumable means that the server can serialize the state of the application as well as the state of the framework into HTML. The browser can then deserialize the state and continue (resume) where the server left over. Serialization is the process of taking the object graph in a heap (memory) and turning it into strings that can be written into HTML. - -## JSON - -The simplest way to think about serialization is through `JSON.stringify`. However, JSON has several limitations. Qwik can overcome some limitations, but some can't be overcome, and they place limitations on what the developer can do. Understanding these limitations is important when building Qwik applications. - -Limitations of JSON which Qwik solves: - -- JSON produces DAG. DAG stands for directed acyclic graph, which means that the object which is being serialized can't have circular references. This is a big limitation because the application state is often circular. Qwik ensures that when the graph of objects gets serialized, the circular references get properly saved and then restored. -- JSON can't serialize some object types. For example, DOM references, Dates, etc... Qwik serialization format ensures that such objects can correctly be serialized and restored. Here is a list of types that can be serialized with Qwik: - - DOM references - - Dates (not yet implemented) - - Function closures (if wrapped in QRL). - -Limitations of JSON that Qwik does not solve: - -- Serialization of classes (`instanceof` and prototype) -- Serialization of `Promise`s, Streams, etc... diff --git a/packages/docs/src/components/app/global.css b/packages/docs/src/components/app/global.css index 22fb482e256..08a0a1237a9 100644 --- a/packages/docs/src/components/app/global.css +++ b/packages/docs/src/components/app/global.css @@ -22,7 +22,7 @@ pre[class*='language-'] { html { --header-height: 56px; --repl-tab-height: 56px; - --repl-tab-bg-color: rgb(208, 208, 208); + --repl-tab-bg-color: rgb(195 231 255); } a, diff --git a/packages/docs/src/components/header/header.css b/packages/docs/src/components/header/header.css index ec001d9ba96..d196491213f 100644 --- a/packages/docs/src/components/header/header.css +++ b/packages/docs/src/components/header/header.css @@ -1,12 +1,18 @@ header { - @apply text-slate-200 bg-gray-900; + @apply text-slate-200; + background: #0099ff; + box-shadow: 0px 2px 7px #0000003d; } header li { - @apply font-semibold hover:text-slate-400 px-2 mx-2 text-right; + @apply font-semibold px-2 mx-2 text-right; @apply py-4 md:py-0; } +header li:hover { + text-decoration: underline; +} + header ul { @apply hidden md:block; @apply pt-14 pb-6 md:pt-0; diff --git a/packages/docs/src/components/header/header.tsx b/packages/docs/src/components/header/header.tsx index dc90bf5b3ef..f56453bd6b4 100644 --- a/packages/docs/src/components/header/header.tsx +++ b/packages/docs/src/components/header/header.tsx @@ -41,22 +41,27 @@ export const Header = component$( -