Skip to content

Commit f21a368

Browse files
committed
docs: Update component docs
1 parent 0918301 commit f21a368

File tree

7 files changed

+152
-326
lines changed

7 files changed

+152
-326
lines changed

src/core/component/COMPONENT.md

Lines changed: 46 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ export function Counter(props: {step?:number}) {
2626
}
2727
```
2828

29-
Note that the components view, state, and handler are all inlined together. The implication is that all of these parts (view, state, and handler) have to be downloaded, parsed, and executed together. This severely limits our lazy loading capability.
29+
Note that the components view, state, and handler are all inlined together and heavily rely on closing over variables in parent scope. The implication is that all of these parts (view, state, and handler) have to be downloaded, parsed, and executed together. This severely limits our lazy loading capability.
3030

31-
The example above might be trivial, but imagine a more complex version of the above, which requires many KB worth of code to be downloaded, parsed, and executed together. In such a case, requiring the view, state, and handler to be eagerly loaded together might be a problem. Let's look at some common user usage patterns to get a better idea as to why this is an issue:
31+
The example above may be trivial, but imagine a more complex version of the above, which requires many KB worth of code to be downloaded, parsed, and executed together. In such a case, requiring the view, state, and handler to be eagerly loaded together is a problem. Let's look at some common user usage patterns to get a better idea as to why this is an issue:
3232

3333
**User interacts with a component by clicking on it:**
3434

3535
- some of the `handler`s are needed: Only the specific handler which is triggered needs to be downloaded. All other handlers are not needed.
36-
- `view` is **not needed**: View may not be needed because the handler may not cause a re-render on may cause a re-render of a different component.
36+
- `view` is **not needed**: View may not be needed because the handler may not cause a re-render or may cause a re-render of a different component.
3737
- `state factory` is **not needed**: The component is being rehydrated and so no state initialization code is needed.
3838

3939
**Component state is mutated:**
@@ -56,161 +56,92 @@ Qwik solves this by only downloading and executing the code that is needed for t
5656

5757
It is not possible to "tool" our way out of this. It isn’t possible to write a statically analyzable tool that can separate these pieces into parts that can then be lazy loaded as needed. The developer must break up the component into the corresponding parts to allow fine-grain lazy loading.
5858

59-
Qwik has `qrlOnRender`, `qrlOnMount` and `qrlHandler` marker functions for this purpose.
59+
Qwik has `qHook` marker functions for this purpose.
6060

6161
**file:** `my-counter.tsx`
6262

6363
```typescript
64-
import { QComponent, qComponent, qrlOnRender, qrlHandler, qrlOnMount } from '@builder.io/qwik';
64+
import { qComponent, qHook } from '@builder.io/qwik';
6565

66-
// Declare the component type, defining prop and state shape.
67-
export type Counter = QComponent<{ step?: number }, { count: number }>;
68-
69-
// Declare the component's initialization hook. This will be used
70-
// when new component is being created to initialize the state.
71-
// (It will not be used on rehydration.)
72-
export const onMount = qrlOnMount<Counter>(() => {
73-
return { count: 0 };
74-
});
75-
76-
// Define the component's view used for rendering the component.
77-
export const onRender = qrlOnRender<Counter>(({ props, state }) => {
78-
return (
66+
export const Counter = qComponent<{ value?: number; step?: number }, { count: number }>({
67+
onMount: qHook((props) => ({ count: props.value || 0 })),
68+
onRender: qHook((props, state) => (
7969
<div>
80-
<button on:click={update.with({ direction: -1 })}>-</button>
8170
<span>{state.count}</span>
82-
<button on:click={update.with({ direction: 1 })}>+</button>
71+
<button
72+
on:click={qHook<typeof Counter>((props, state) => {
73+
state.count += props.step || 1;
74+
})}
75+
>
76+
+
77+
</button>
8378
</div>
84-
);
79+
)),
8580
});
86-
87-
// Component view may need handlers describing behavior.
88-
export const update = qrlHandler<Counter, { direction: number }>(({ props, state, params }) => {
89-
state.count += params.direction * (props.step || 1);
90-
});
91-
92-
// Finally tie it all together into a component.
93-
export const Counter = qComponent<Counter>({ onMount, onRender });
9481
```
9582

96-
Compared to other frameworks, the above is wordier. However, the cost of the explicit break up of components into their parts gives us the benefit of fine-grained lazy loading.
83+
Compared to other frameworks, the above is a bit wordier. However, the cost of the explicit break up of components into their parts gives us the benefit of fine-grained lazy loading.
9784

9885
- Keep in mind that this is a relatively fixed DevExp overhead per component. As the component complexity increases, the added overhead becomes less of an issue.
9986
- The benefit of this is that tooling now has the freedom to package up the component in multiple chunks which can be lazy loaded as needed.
10087

10188
## What happens behind the scenes
10289

103-
`qrlOnMount`, `qrlHandler`, `qrlOnRender` are all markers for Qwik Optimizer, which tell the tooling that it needs to transform any reference to it into a QRL. The resulting files can be seen here:
90+
`qHook` is a marker for Qwik Optimizer, which tells the tooling that it needs to transform any reference to it into a QRLs. The resulting files can be seen here:
10491

10592
**File:** `my-counter.js`
10693

10794
```typescript
108-
import {qComponent, qrlOnRender, qrlHandler, qrlOnMount} from '@builder.io/qwik';
95+
import { qComponent, qHook } from '@builder.io/qwik';
10996

110-
export const onMount = qrlOnMount(() => ({ count: 0 }));
111-
112-
export const onRender = qrlOnRender(({props, state}) => {
113-
return (
114-
<div>
115-
<button on:click="/chunk-pqr#update?direction=-1">
116-
// ^^^^^^^^^^^ OPTIMIZER ^^^^^^^^^^
117-
-
118-
</button>
119-
<span>{state.count}</span>
120-
<button on:click="/chunk-pqr#update?direction=1">
121-
// ^^^^^^^^^^^ OPTIMIZER ^^^^^^^^^^
122-
+
123-
</button>
124-
</div>
125-
);
97+
export const Counter = qComponent<{ value?: number; step?: number }, { count: number }>({
98+
onMount: qHook('entry-cde#Counter_onMount'),
99+
onRender: qHook('entry-abc#Counter_onRender'),
126100
});
127-
128-
export const update = qrlHandler(
129-
(props, state, params) => {
130-
state.count += params.direction * (props.step || 1);
131-
);
132-
133-
134-
export const Counter = qComponent({
135-
onMount: '/chunk-cde#onMount', // <<=== OPTIMIZER
136-
onRender: '/chunk-abc#onRender', // <<=== OPTIMIZER
137-
});
138-
```
139-
140-
In addition to the source file transformation, the optimizer removed any static references between the view, state, and handlers. Optimizer also generates entry point files for the rollup. These entry points match the QRLs above.
141-
142-
**File:** `chunk-abc.js`
143-
144-
```typescript
145-
export { onRender } from './my-counter';
146-
```
147-
148-
**File:** `chunk-pqr.js`
149-
150-
```typescript
151-
export { update } from './my-counter';
152101
```
153102

154-
**File:** `chunk-cde.js`
155-
156-
```typescript
157-
export { onMount } from './my-counter';
158-
```
103+
In addition to the source file transformation, the optimizer transformed references between the view, state, and handlers into QRLs. Optimizer also generates entry point files for the rollup. These entry points match the QRLs above.
159104

160-
The important thing to note is that Qwik has great freedom on how many entry files should be generated, as well as which export goes into which entry file. This is because the developer never specified where the lazy loading boundaries are. Instead, the framework guided the developer to write code in a way that introduced many lazy loading boundaries in the codebase. This gives Qwik the power to generate optimal file distribution based on actual application usage. For small applications, Qwik can generate a single file. As the application size grows, more entry files can be generated. If a particular feature is rarely used, it can be placed in its own bundle.
161-
162-
Once Rollup processes the entry files, the resulting files are as seen below:
163-
164-
**File:** `chunk-abc.js`
105+
**File:** `entry-abc.js`
165106

166107
```typescript
167-
import { qrlOnMount } from '@builder.io/qwik';
168-
169-
export const onRender = qrlOnMount(() => ({ count: 0 }));
108+
import { qHook } from '@builder.io/qwik';
109+
110+
export const Counter_onRender = qHook((props, state) => (
111+
<div>
112+
<span>{state.count}</span>
113+
<button on:click={qHook<typeof Counter>('entry-pqr#Counter_onClick')}>+</button>
114+
</div>
115+
));
170116
```
171117

172-
**File:** `chunk-pqr.js`
118+
**File:** `entry-pqr.js`
173119

174-
```typescript
175-
import { qrlHandler} from '@builder.io/qwik';
120+
```typescript=
121+
import { qHook } from '@builder.io/qwik';
122+
import type { Counter } from './my-counter';
176123
177-
export const update = qrlHandler(
178-
({props, state, params}) => {
179-
state.count += params.direction * (props.step || 1);
180-
);
124+
export const Counter_onClick = qHook<typeof Counter>((props, state) => {
125+
state.count += props.step || 1;
126+
});
181127
```
182128

183-
**File:** `chunk-cde.js`
129+
**File:** `entry-cde.js`
184130

185131
```typescript
186-
import { qrlOnRender } from '@builder.io/qwik';
132+
import { qHook } from '@builder.io/qwik';
187133

188-
export const onRender = qrlOnRender(({ props, state }) => {
189-
return (
190-
<div>
191-
<button on:click="/chunk-pqr#update?direction=-1">-</button>
192-
<span>{state.count}</span>
193-
<button on:click="/chunk-pqr#update?direction=1">+</button>
194-
</div>
195-
);
196-
});
134+
export const Counter_onMount = qHook((props) => ({ count: props.value || 0 }));
197135
```
198136

199-
Notice that Rollup flattened the contents of files into the entry files and removed any unneeded code, resulting in ideally sized bundles.
137+
The important thing to note is that Qwik has great freedom on how many entry files should be generated, as well as which export goes into which entry file. This is because the developer never specified where the lazy loading boundaries are. Instead, the framework guided the developer to write code in a way that introduced many lazy loading boundaries in the codebase. This gives Qwik the power to generate optimal file distribution based on actual application usage. For small applications, Qwik can generate a single file. As the application size grows, more entry files can be generated. If a particular feature is rarely used, it can be placed in its own bundle.
200138

201139
### Constraints
202140

203-
In order for the tooling to be able to move `qrlOnRender`, `qrlOnMount`, `qrlHandler` around the usage of these methods is restricted. (Not every valid JS program is a valid Qwik program.) The constraint is that all of the marker functions must be a top-level function that is `export`ed.
141+
In order for the tooling to be able to move `qrlOnRender`, `qrlOnMount`, `qrlHandler` around the usage of these methods is restricted. (Not every valid JS program is a valid Qwik program.) The constraint is that all functions which are enclosed in `qHook` must be moveable into different files. In practice this means that functions can only close over symbols which are:
204142

205-
Examples of invalid code:
206-
207-
```typescript
208-
import { someFn } from './some-place';
209-
210-
function main() {
211-
const MyStateFactory = qrlOnMount(() => ({})); // INVALID not top level
212-
}
213-
```
143+
1. `import`able (i.e. the symbol was `import`ed.)
144+
2. `export`ed (i.e. the symbol is already exported so that it can be imported from the entry file.)
214145

215146
## Tooling has choices
216147

src/core/component/HOST_ELEMENT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
[![hackmd-github-sync-badge](https://hackmd.io/byPxWWAMTmmHCJISBJiNvA/badge)](https://hackmd.io/byPxWWAMTmmHCJISBJiNvA)
2+
13
# Host Element

src/core/component/LIFE_CYCLE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
[![hackmd-github-sync-badge](https://hackmd.io/bQNLF9lySvyclywmM_Bd5g/badge)](https://hackmd.io/bQNLF9lySvyclywmM_Bd5g)
2+
3+
# Lifecycle Hooks
4+
15
Describes component lifecycle hooks.
26

37
In typical development, when discussing object lifespan, it is clear that objects either exist or they do not. Either an object has been instantiated, or it has not yet been instantiated (or it has been instantiated and has since been garbage collected. Hence it no longer exists.)

0 commit comments

Comments
 (0)