diff --git a/README.md b/README.md index d55d79d77fa..09244bec730 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Qwik Logo

+ + WWC22 - Qwik + Partytown: How to remove 99% of JavaScript from main thread + + # The HTML-first framework Qwik is designed for the fastest possible page load time, by delivering pure HTML with near 0 JavaScript for your pages to become interactive, regardless of how complex your site or app is. It achieves this via [resumability](https://github.com/BuilderIO/qwik/blob/main/packages/docs/src/pages/docs/concepts/resumable.mdx) of HTML and [ultra fine-grained lazy-loading](https://github.com/BuilderIO/qwik/blob/main/packages/docs/src/pages/docs/concepts/progressive.mdx) of code. diff --git a/cspell.json b/cspell.json index 2c3933cf3fd..840b5bd78ab 100644 --- a/cspell.json +++ b/cspell.json @@ -34,5 +34,5 @@ "Pinterest", "Serializability" ], - "enableFiletypes": ["!mdx"] + "enableFiletypes": ["mdx"] } diff --git a/packages/docs/src/pages/docs/INDEX b/packages/docs/src/pages/docs/INDEX index c02e0f90f5d..95924c9acdb 100644 --- a/packages/docs/src/pages/docs/INDEX +++ b/packages/docs/src/pages/docs/INDEX @@ -5,6 +5,8 @@ - [Overview](overview.mdx) - [Getting Started](getting-started.mdx) - [Think Qwik](think-qwik.mdx) +- [Cheat Sheet](cheat-sheet.mdx) + ## Component API @@ -12,10 +14,16 @@ - [Anatomy](components/anatomy.mdx) - [Hooks](components/hooks.mdx) - [Events](components/events.mdx) -- [Lite elements](components/lite-components.mdx) +- [Context](components/context.mdx) +- [Lite components](components/lite-components.mdx) - [Content projection](components/projection.mdx) - [Rendering](components/rendering.mdx) +## Cheat Sheet + +- [Components](cheat/components.mdx) +- [Serialization](cheat/serialization.mdx) + ## Concepts - [Resumable](concepts/resumable.mdx) diff --git a/packages/docs/src/pages/docs/advanced/containers.mdx b/packages/docs/src/pages/docs/advanced/containers.mdx index f63140bfef5..62327d36ec3 100644 --- a/packages/docs/src/pages/docs/advanced/containers.mdx +++ b/packages/docs/src/pages/docs/advanced/containers.mdx @@ -40,7 +40,7 @@ A typical site is composed of two logical parts: 1. The navigation that tends to stay constant across many pages, and 2. The outlet, which is the part of the page that changes based on which route the user navigated to. -We can model the two parts as two navigation and outlet containers. When the user first navigates to a route, the server responds with HTML, which contains containers for both the navigation and the outlet. Once the user navigates to the second route, there are three ways to solve the navigation: +We can model the two parts as two navigation and outlet containers. When the user first navigates to a route, the server responds with HTML, that contains containers for both the navigation and the outlet. Once the user navigates to the second route, there are three ways to solve the navigation: 1. The simplistic approach is to make a full round trip and download an entirely new page. The main downside is that the application loses all of its states on the client. 1. The classical approach is to treat any further navigation in JavaScript. We replace the current outlet component with the new outlet component and let the new component render. The disadvantage is that we need to download and execute the JavaScript. diff --git a/packages/docs/src/pages/docs/advanced/optimizer.mdx b/packages/docs/src/pages/docs/advanced/optimizer.mdx index 9ce839287b0..88d67f3e33d 100644 --- a/packages/docs/src/pages/docs/advanced/optimizer.mdx +++ b/packages/docs/src/pages/docs/advanced/optimizer.mdx @@ -31,22 +31,15 @@ const Counter = component(qrl('./chunk-a.js', 'Counter_onMount')); ```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`: +`chunk-b.js`: ```tsx const Counter_onClick = () => { @@ -55,6 +48,8 @@ const Counter_onClick = () => { }; ``` +Notice that every occurence of `$` results in a new lazy loadable symbol. + # `$` and Optimizer Rules diff --git a/packages/docs/src/pages/docs/cheat-sheet.md b/packages/docs/src/pages/docs/cheat-sheet.mdx similarity index 97% rename from packages/docs/src/pages/docs/cheat-sheet.md rename to packages/docs/src/pages/docs/cheat-sheet.mdx index 28eb6dc173d..1f3bd178c46 100644 --- a/packages/docs/src/pages/docs/cheat-sheet.md +++ b/packages/docs/src/pages/docs/cheat-sheet.mdx @@ -21,7 +21,7 @@ export function Component() { Hello world ) -}) +} ``` ## Button with a click handler @@ -47,9 +47,9 @@ export function Component() { return ( ) -}) +} ``` ## Declare local state @@ -75,13 +75,11 @@ export const Component = component$(() => { export function Component() { const [value, setValue] = useState(0); return ( - -
- Value is: ${value} -
-
+
+ Value is: ${value} +
) -}) +} ``` ## Create a counter component @@ -95,7 +93,7 @@ export const Counter = component$(() => { }); return ( -
Value is: ${state.value}
+
Value is: ${state.count}
); @@ -117,7 +115,7 @@ export function Counter() { ) -}) +} ``` ## Create a clock that increments every second @@ -157,10 +155,10 @@ export function Clock() { }); return (
- Seconds: ${state.seconds} + Seconds: ${seconds}
) -}) +} ``` ## Perform a fetch request every time the state changes @@ -202,10 +200,10 @@ export function Fetch() { .then((json) => setResponseJson(json)); }, [url]); return ( - + <>
${responseJson.name}
setUrl(ev.target.value)} /> -
+ ); } ``` @@ -301,7 +299,7 @@ export function DynamicBackground() { const [green, setGreen] = useState(0); const [blue, setBlue] = useState(0); return ( - - + ) -}) +} ``` ## Create a component that renders a list of the presidents diff --git a/packages/docs/src/pages/docs/cheat/components.mdx b/packages/docs/src/pages/docs/cheat/components.mdx new file mode 100644 index 00000000000..204a602e151 --- /dev/null +++ b/packages/docs/src/pages/docs/cheat/components.mdx @@ -0,0 +1,98 @@ +--- +title: Components +--- + +# Common Component Patterns Cheat Sheet + +## Declartion + +```tsx +import {component$, useStore} from "@builder.io/qwik"; + +export const Greeter = component$(() => { + return Hello World!; +}); +``` + +## Props + +```tsx +import {component$, useStore} from "@builder.io/qwik"; + +interface GreeterProps { + salutation?: string; + name?: string; +} +export const Greeter = component$((props: GreeterProps) => { + const salutation = props.salutation || 'Hello'; + const name = props.name || 'World'; + return {salutation} {name}!; +}); +``` + +### Event Props + +Component props must be serializable, and therefore can not directly reffer to functions. + +```tsx +import {component$, useStore, Qrl} from "@builder.io/qwik"; + +export const Parent = component$(() => { + return ( + console.log('Hello')}> + click + + ); +}); + +interface MyButtonProps { + doSomethingQrl: QRL<() => void> +} +export const MyButton = component$((props: MyButtonProps) => { + return ; +}); +``` + +## Events + +## Watching for Changes + +## Server +### Fetching Data + +```tsx +import {component$, useStore, useServerMount$} from "@builder.io/qwik"; + +export const Greeter = component$(() => { + const store = useStore<{list: null|string[]}>({list: null}); + useServerMount$(async () => { + store.list = await doSomethingToFetchDataOnServer(); + }); + + return ( + + ); +}); +``` + +## Client +### Eagerly Executing Code + +```tsx +import {component$, useStore, useClientEffet} from "@builder.io/qwik"; + +export const Greeter = component$(() => { + const store = useStore<{list: null|string[]}>({list: null}); + useClientEffet$(async () => { + store.list = await doSomethingToFetchDataOnServer(); + }); + + return ( + + ); +}); +``` diff --git a/packages/docs/src/pages/docs/cheat/serialization.mdx b/packages/docs/src/pages/docs/cheat/serialization.mdx new file mode 100644 index 00000000000..8bbc2528614 --- /dev/null +++ b/packages/docs/src/pages/docs/cheat/serialization.mdx @@ -0,0 +1,53 @@ +--- +title: Serialization & Serialization Boundaries +--- + +# `$` Boundaries + +## Rules + +- Only serializable data can cross a `$` boundery. + +## Serialization Boundery + +A serialization boundery occures whenever you cross a lexical scope of a function that is converted into lazy loadable form. It is always denoted by `$(...)` (or `____$(...)`) See example: + +```tsx +import {component$} from "@builder.io/qwik"; + +export const topLevel = Promise.resolve('nonserializable data'); + +export const Greeter = component$(() => { + // BEGIN component serialization boundery + + // Referring to top level symbols that are exported is always allowed. + console.log(topLevel); // OK + + const captureSerializable = 'serializable data'; + const captureNonSerializable = Promise.resolve('nonserializable data'); + return ( + + ); + // BEGIN component serialization boundery +}); + +``` \ No newline at end of file diff --git a/packages/docs/src/pages/docs/components/anatomy.mdx b/packages/docs/src/pages/docs/components/anatomy.mdx index eb41a2a972b..07a32180b87 100644 --- a/packages/docs/src/pages/docs/components/anatomy.mdx +++ b/packages/docs/src/pages/docs/components/anatomy.mdx @@ -265,8 +265,8 @@ const Parent = component$(() => { 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 Child = componentQrl(qrl('./chunk-a', 'Child_onMount')); +const Parent = componentQrl(qrl('./chunk-b', 'Parent_onMount')); const Parent_onMount = () => qrl('./chunk-c', 'Parent_onRender'); const Parent_onRender = () => (
diff --git a/packages/docs/src/pages/docs/components/context.mdx b/packages/docs/src/pages/docs/components/context.mdx new file mode 100644 index 00000000000..7a31b2e1ca5 --- /dev/null +++ b/packages/docs/src/pages/docs/components/context.mdx @@ -0,0 +1,167 @@ +--- +title: Context +--- + +# Context + +Qwik provides a context API, very similar to React's functional `useContext()`. In fact the context API is the most efficient way to pass data down, reducing overhead, generating less code and allowing Qwik to treeshake unused data more heavily. + +Qwik's context API is made of 3 methods, importable from `@builder.io/qwik`: + +- `createContext(contextName: string): Context` +- `useContextProvider(ctx: Context, value: VALUE): Context` +- `useContext(ctx: Context): VALUE` + +## Example + +This example is made of two components, the `Parent` and the `Child`. The parent component creates some new state and assigns it to a context, allowing any descendent component to get a reference to the "state". + +At the same time, the parent component is rendering `
Count: {state.count}
`, creating a reactive subscription, that will rerender when the Child changes the value in the click handler: ` + + ); +}); +``` + +Let's dig into every API involved: + +## API + +### `createContext()` + +This method takes a string that gives the context unique name, if your application uses a lot of different context or you are writting a component library, we recommend to follow a name convention that avoids conflicts, such as: + +```tsx +export const QwikCityContext = createContext("io.builder.qwik.city"); +``` + +Notice that the value returned by `createContext()` does not hold any state, and its immutable. It's only used to describe the name and type of the context, like the address or an identifier. + +Because it does not hold any state, it's ok to call it and make it a singleton, exported in some shared module. + +### `useContextProvider()` + +Like all use- methods, it can only be called at the root of `component$()` (not inside branches). This method is called by some higher level component and it's what assigns (provides) a value to the context. The provided value will not be globally available across the whole render tree, but only to descendant components in the tree. + +The value passed to `useContextProvider()` can be any primitive, useStores, object or even array that contains serializable values. + +```tsx +export const Parent = component$(() => { + const reactiveObject = useStore({ + count: 0, + }); + useContextProvider(MyContextReactive, reactiveObject); + + const plainArray = listOfUSPresidents(); + useContextProvider(MyContextArray, plainArray); + + const appName = "My super app"; + useContextProvider(MyContextString, appName); + + return ( + + + + ); +}); +``` + +Let's see how `Children` can consume the values: + +### `useContext()` + +Again, like all use- methods, only usable at the root of a `component$()`. This methods allows to get the **provided** value to a named context. + + +```tsx +export const Children = component$(() => { + const reactiveObject = useContext(MyContextReactive); + const plainArray = useContext(MyContextArray); + const appName = useContext(MyContextString); + + return ( + +
Child components can use any of the provided values, such as {appName}
+
+ ); +}); +``` + +## Typed contexts + +When a context is created using `createContext()` a type can be provided, in fact, it's very recommended to do so, in order to reduce errors and typos: + +```tsx +export interface SharedState { + count: number; +} +export const MyContext = createContext('my-context'); +``` + +This way, when using `MyContext` in `useContextProvider()` and `useContext()` the provided value will have the `SharedState` type. + +Let's see a working example: + +```tsx +import { component$, useStore, useContext, useContextProvider, createContext} from '@builder.io/qwik'; + +export interface SharedState { + count: number; +} +export const MyContext = createContext('my-context'); + +export const Parent = component$(() => { + const state = useStore({ + count: 0, + }); + + useContextProvider(MyContext, state); // type checker will ensure the second param is SharedState + return ( + + +
Count: {state.count}
+
+ ); +}); + +export const Child = component$(() => { + const state = useContext(MyContext); // type of "state" will be `SharedState` + return ( + + + + ); +}); +``` + diff --git a/packages/docs/src/pages/docs/components/events.mdx b/packages/docs/src/pages/docs/components/events.mdx index d8b9edff26e..2db7de65ec7 100644 --- a/packages/docs/src/pages/docs/components/events.mdx +++ b/packages/docs/src/pages/docs/components/events.mdx @@ -67,7 +67,7 @@ The main point here is that while the syntax of the events is consistent between ## Prevent default -Because of the async nature of Qwik, event's handler execution might be delayed because the implementation is not downloaded yet. This introduces a problem when the event's handler needs to prevent the default behavior of the event. Tradicional `event.preventDefault()` will not work, instead use the qwik's `preventdefault:{eventName}` attribute: +Because of the async nature of Qwik, event's handler execution might be delayed because the implementation is not downloaded yet. This introduces a problem when the event's handler needs to prevent the default behavior of the event. Traditional `event.preventDefault()` will not work, instead use the qwik's `preventdefault:{eventName}` attribute: ```tsx const Counter = component$(() => { @@ -269,7 +269,7 @@ Notice that `on:click` attribute contains three pieces of information: 2. `Counter_button_onClick`: The symbol which needs to be retrieved from the lazy-loaded chunk. 3. `[0]`: An array of lexically captured 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.) +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 that 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()` @@ -277,7 +277,7 @@ JavaScript supports dynamic `import()`. At first glance, it may seem that the sa 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. +- Is relative to the file that 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/src/pages/docs/getting-started.mdx b/packages/docs/src/pages/docs/getting-started.mdx index dbb1ad3acc2..04df0320d4e 100644 --- a/packages/docs/src/pages/docs/getting-started.mdx +++ b/packages/docs/src/pages/docs/getting-started.mdx @@ -50,7 +50,7 @@ After your new app is created, you will see an output like the following in your npm start ``` -At this point, you will have `qwik-todo` directory, which contains the starter app. +At this point, you will have `qwik-todo` directory, that contains the starter app. ## Running in development diff --git a/packages/docs/src/pages/docs/overview.mdx b/packages/docs/src/pages/docs/overview.mdx index cb7448c282a..fd9f9155527 100644 --- a/packages/docs/src/pages/docs/overview.mdx +++ b/packages/docs/src/pages/docs/overview.mdx @@ -5,16 +5,16 @@ fetch: https://hackmd.io/@mhevery/Sy52N2Ax9 # Overview -Qwik is a new kind of web framework that can deliver instant loading web applications at any size or complexity. Your sites and apps can boot with less than 1kb of JS (_including_ your code, regardless of complexity), and achieve unheard of performance at scale. +Qwik is a new kind of web framework that can deliver instantly load web applications at any size or complexity. Your sites and apps can boot with about 1kb of JS (regardless of application complexity), and achieve consistent performance at scale. ## Qwik is: - **General-purpose**: Qwik is familiar for React developers and can be used to build any type of web site or application. -- **Resumable**: Qwik is [resumable](./concepts/resumable.mdx) which means Qwik applications require **0 hydration**. This allows Qwik apps to have instant-on interactivity. +- **Resumable**: Qwik is [resumable](./concepts/resumable.mdx) which means Qwik applications require **no hydration**. This allows Qwik apps to have instant-on interactivity. - **Progressive**: Qwik takes [full responsibility of how to load and download JS](https://www.builder.io/blog/dont-blame-the-developer-for-what-the-frameworks-did). No more manual code splitting. - **Reactive**: Qwik semantics allow for [fully reactive and efficient rendering](./concepts/reactivity.mdx). -- **Fast**: 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. -- **Scalable**: Qwik application have [O(1) constant scalability](https://www.builder.io/blog/our-current-frameworks-are-on-we-need-o1). It does not matter if your application has 1 million components, boot time is unaffected. +- **Fast**: Qwik has consistantly fast performance no matter the application complexity, offering sub-second full page loads even on mobile devices. Qwik achieves this by delivering pure HTML, and incrementally loading JS only as-needed. +- **Scalable**: Qwik application have [O(1) constant scalability](https://www.builder.io/blog/our-current-frameworks-are-on-we-need-o1). It does not matter the size of your application, boot time is constantly fast. Qwik Diagram { return ( <>