owned this note
owned this note
Published
Linked with GitHub
# Optimizer Code Transformations
## 1) Developer's Source Code
- Find all of the input (TypeScript) source files
- Starting at the root dir, walk the child directories to find all typescript files
- Cannot use tsconfig.json `files` property because there isn't a single entry
- Finding ts files doesn't need to be too accurate
- Just find the bare minimum ts compononent files
- Overfinding too many ts files is fine
- The entry file generation will only pull in what's used
- Skips over unit tests, node_modules, build, dist
> [name=Miško Hevery] any `qrlHook` function (or any function which is aliased to it `export const qrlBar = qrlHook;`)
>
> What does the method do?
>
> - _It replaces all references to the symbol with QRL pointing to the symbol_
**my-counter.tsx**
```typescript
import { qComponent, qrlComponenOnInit, qrlComponenOnRender, qrlHandler } from '@builder.io/qwik';
type MyCounter = QComponent<{ step?: number }, { count: number }>;
export const onInit = qrlComponentOnInit<MyCounter>(() => ({ count: 0 }));
export const onRender = qrlComponentOnRender<MyCounter>(({props, state}) => {
return (
<div>
<button on:click={increment}>+</button>
<span>{state.count}</span>
</div>
);
});
export const increment = qrlHandler<MyCounter>(({props, state}) => {
state.count += props.step || 1;
});
export const MyCounter = qComponent<MyCounter>({ onInit, onRender });
```
**my-app.tsx**
```typescript
import { qComponent, qrlView } from '@builder.io/qwik';
import { MyCounter } from './my-counter';
export const onRender = qrlOnRender<MyApp>(() => {
return (
<section>
<MyCounter />
</section>
);
});
export const MyApp = qComponent<MyApp>({ onRender });
```
## 2) Static Analysis: Gather module graph and generate entry files
- Do a `rollup.build()` to gather all data about each module
- Collects import/export data in order to build a module graph
- Stores data within the `BuildInfo` which is passed throughout to the build
- Uses the same rollup plugins as the client build, to include a user's typescript plugin
- Unnecessary to do `rollup.generate()` for this pass
- After this first build completes, generate entry files
- Uses the module graph from the `BuildInfo` after the first build
- Generated code groups certain modules together, re-exports variable names
- Can be further optimized to better group modules together
- Possible to have variable name collisions, so use "as" and ensure it has a unique export name
These are the generated entry files with an algorithm deciding how to best group exports together.
It's possible to have variable name collisions with mulitple imported variables using the same name, like `view` or `state`. If there's a collision, ensure they use export `as` and make sure each export has a unique name.
> Note: Below isn't the optimial bundling, that's for the algorithm and developer config to do a better job creating. Below is one example.
**@qwik-0**
```typescript
export { onInit as onInit1 } from './my-counter';
```
**@qwik-1**
```typescript
export { onRender as onRender1 } from './my-counter';
```
**@qwik-2**
```typescript
export { increment as increment1 } from './my-counter';
```
**@qwik-3**
```typescript
export { view as view1 } from './my-app';
```
```
src/my-counter#state
src/my-counter#view
---
src/my-counter#increment
src/my-counter#*
```
// # If QRL not found here then end up in [chunk-main]
## 3) Build: Transform and bundle modules
The entry file ids are set in Rollup's `input` option. While resolving each import, the unqiue id of each entry file is used to lookup the generated entry code, which was already created after the first-pass source code analysis completed.
```typescript
const clientBuild = await rollup({
input: ['@qwik-0', '@qwik-1', '@qwik-2', '@qwik-3'],
});
```
- Use the generated entry files for rollup's `input` build option
- Each entry has a unique id which `optimizer.resolve()` will correctly resolve to the generated code
- Entry files know export names and source files used to create it
- Resolve imports to the entry files instead
- If an import can instead be imported from an entry file, resolve to the generated entry
- Add the export name as a `#hash` in the import url, needed for the runtime to know the export name
- Could become recursive, so if the `importee` is any entry file, then resolve it normally
- Rewrite Qwik local variables to standard dynamic `import()`
- Let rollup be rollup in order for the bundler to parse/hash/chunk the build urls and output
- Trying to accomplish the same thing without the standard `import()` would be reinventing the bundler wheel
Below is AFTER the Qwik code transformation happens, but BEFORE javascript output files are generated.
- Local variable q functions updated to a dynamic `import()` with an entry file as the url
- TypeScript down-leveled to just JavaScript, JSX transformed to `h()`
**my-counter.js**
```typescript
import { qComponent, qrlHandler, qrlView, qrlState } from '@builder.io/qwik';
export const onInit = qrlComponentOnInit(() => ({ count:0 }));
export const onRender = qrlComponentOnRender(({props, state}) => {
return (
h("div", null,
h("button", { 'on:click': import("@qwik-2#increment1")}, "+"),
h("span", null, state.count)
);
);
});
export const increment = qrlHandler((props, state) => {
state.count += props.step || 1;
});
export const MyCounter = qComponent({
onInit: import('@qwik-0#state1'),
onRender: import('@qwik-1#view1'),
});
```
**my-app.js**
```typescript
import { MyCounter } from './my-counter';
export const onRender = qrlComponentOnRender(() => {
return h('section', null, h(MyCounter, null));
});
export const MyApp = qComponent({
onRender: import('@qwik-3#onRender'),
});
```
## 4) Generate: Output JavaScript Chunks
- For the most part the build is done, files are hashed and urls are linking correctly to one another
- Rollup has linked the modules together and created the chunk urls to sibling chunks
- Last hook is to rewrite `import()` calls to instead just be the url string itself, removing `import(` and `)`
- Value for `on:click` handler is just a string to a url now, not an `import()` call
**q-abcd.js**
```typescript
import { qrlComponentInit } from './q-core.js';
export const stateMyCounter = qrlComponentInit(() => ({ count: 0 }));
```
**q-cdef.js**
```typescript
import { qrlComponentOnRender } from './q-core.js';
export const viewMyCounter = qrlComponentOnRender((props, state) => {
return (
h("div", null,
h("button", { 'on:click': './q-efgh#increment1' }, "+"),
h("span", null, state.count)
);
);
});
```
**q-efgh.js**
```typescript
import { qrlHandler } from './q-core.js';
export const incrementMyCounter = qrlHandler(({props, state}) => {
state.count += props.step || 1;
});
```
**q-ghjk.js**
```typescript
import { qComponent, qrlView } from './q-core.js';
export const MyCounter = qComponent({
view: './q-cdef.js#view1',
});
export const MyAppOnRender = qrlComponentOnRender(() => {
return h('section', null, h(MyCounter, null));
});
```
**q-klmn.js**
```typescript
import { qComponent } from './q-core.js';
export const MyApp = qComponent({
view: './q-ghjk.js#MyAppOnRender1',
});
```