theme | marp |
---|---|
gaia |
true |
@mizchi | PWA Night
- mizchi | Plaid, Inc
- Node.js, Frontend エンジニア
- この資料は Workers Tech Talk の LT(5min) を膨らませたもの
- 最近は League of Legends を10年ぶりにやってる
- https://qwik.builder.io/
- BuilderIO社 (ノーコードプラットフォーム)が開発
- パフォーマンスに全振りした過激な設計が特徴
- Pros
- React JSX 方言でノウハウを転用できる
- 採用するだけでパフォーマンス問題は(ほぼ)解決
- 使いこなせれば理論上最強
- Cons
- 最適化のためにかなり特殊な制約がかかる
$ npm create vite@latest
✔ Project name: … qwik-app
✔ Select a framework: › Qwik
? Select a variant: ›
TypeScript
JavaScript
❯ QwikCity ↗
- 運用上 SSR 必須なので最初から QwikCity がおすすめ
- 一応 Static Export もある
import { component$ } from "@builder.io/qwik";
export default component$(() => {
console.log("render");
return <button onClick$={() => console.log("hello")}>
Hello Qwik
</button>;
});
- 見た目は React JSX だが...
- 素朴なマークアップなら React とほとんど同じコードになる
...$
で終わると、チャンク分割の合図 (詳細は後述)
- Qwik Optimizer を前提としたコーディング(後述)
- 運用上クラサバが緊密に連携する為 QwikCity(Qwik版Next) か Astro が前提
- Qwik の主要なコンセプト
- SSR 前提で、必要なチャンクを都度渡す
<script type="qwik/json">...
に状態をシリアライズ- イベントハンドラごとに チャンクを取得して実行
- Hydration(React etc...) との違い
- クラサバの冪等性の為に同一テンプレートを再展開のが Hydration
- Resumable はSSR前提でクライアントの差分だけ渡す
import { component$ } from "@builder.io/qwik";
export default component$(() => {
console.log("render");
return <button onClick$={() => console.log("hello")}>Hello Qwik</button>;
});
...$()
で常にチャンクが分割されるcomponent$(...)
で別チャンクonClick$(...)
で別チャンク
// app.js
import { componentQrl, qrl } from "@builder.io/qwik";
const App = /*#__PURE__*/ componentQrl(
qrl(
() => import("./app_component_akbu84a8zes.js"),
"App_component_AkbU84a8zes"
)
);
component$(...)
の内容が別チャンク
// app_component_akbu84a8zes.js
import { jsx as _jsx } from "@builder.io/qwik/jsx-runtime";
import { qrl } from "@builder.io/qwik";
export const App_component_AkbU84a8zes = () => {
console.log("render");
return /*#__PURE__*/ _jsx("p", {
onClick$: qrl(
() => import("./app_component_p_onclick_01pegc10cpw"),
"App_component_p_onClick_01pEgC10cpw"
),
children: "Hello Qwik",
});
};
onClick$(...)
が別チャンクに
// app_component_p_onclick_01pegc10cpw.js
export const App_component_p_onClick_01pEgC10cpw =
() => console.log("hello");
- onClick で実際に呼ばれるコード
- Qwik 内 URL 参照オブジェクト
$(...)
で自分で生成することもできる
import {$, component$} from "@builder.io/qwik";
const C = component$(() => {
const V = 1; // Serialize できるので他から参照できる
const f1 = $(() => console.log(V)); // QRL化された関数
const f2 = $(() => f1()); // QRL 同士なので参照できる
// handler として QRL 化された関数を受け取ることができる
return <button onClick$={f2}>btn</button>
})
const C = component$(() => {
const v1 = new (class {})();
let v2 = 1;
const f1 = $(() => {
// NG: class はシリアライズできない
console.log(v1);
// NG: 別チャンクの let を書き換えることができない。signal が必要
v2++;
});
});
// NG: QRL化されてない関数ハンドラは受け取れない
const C = component$(() => {
const f1 = () => { console.log(1) };
return <div onClick$={f1}>btn</button>
});
// OK: 関数リテラルなら静的解析でチャンク化される
const C = component$(() => {
return <div onClick$={() => { console.log(1) }}>btn</button>
});
- スコープ解析により signal と QRL は子チャンクに引き継がれる
const C = hcomponent$(() => {
// --- onMount chunk ---
const singal = useSignal(0);
const handler = $(() => {
// ---- handler chunk ----
singal.value++
});
// --- onRender chunk ---
return <div onClick$={handler}>{signal.value}</button>
});
const Greeter_onMount = (props) => {
const salutation = "Hello";
return qrl(
"./chunk-b.js",
"Greeter_onRender",
// スコープ解析によって参照可能なものを渡す
[salutation, props]
);
};
- チャンク分割でスコープがぶつ切りになるのでシリアライズ/QRL化が必須
- let が引き継げないのはコンテキストが消失するため
- https://qwik.builder.io/docs/advanced/qwikloader/
- 最小ランタイム(1kb)
- 親
q:base
とon:*
からチャンクを解決するハンドラを登録 - (Astro に似ている)
<body q:base="/build/">
<button on:click="./myHandler.js#clickHandler">push me</button>
</body>
- Qwik 版 Next.js (Vite SSR Plugin)
- Adapter: node, deno(-deploy), cloudflare, vercel, cloudrun
// src/routes/with-loader/index.tsx => /with-loader
import { component$ } from "@builder.io/qwik";
import { routeLoader$ } from "@builder.io/qwik-city";
export const useServerData = routeLoader$(async (requestEvent) => {
return { serverTime: Date.now() };
});
export default component$(() => {
const signal = useServerData();
return <p>ServerTime: {signal.value.serverTime}</p>;
});
- ほとんどは React と同じ雰囲気で書ける
- チャンク分割を意識しないとハマる
- The $ dollar sign | Advanced 📚 Qwik Documentation 必読
- React 初見で props 書き換えちゃいけない!ぐらいの不自由感
- 運用上、Qwik City ほぼ必須
- CSS フレームワークはなんでも使える
- Qwik City は強い Astro みたいな感じで使える
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
function Greetings() { return <div>Hello from React</div>; }
export const QGreetings = qwikify$(Greetings, {
eagerness: 'hover' // Hydration するタイミングを指定
});
- https://qwik.builder.io/docs/integrations/react
- Qwik 内で本物の React を SSR + Hydration できる
- Astro のアイランドアーキテクチャと同じ、 Hydration するトリガーをコントロールできる
- ↑ の例だとマウスを hover したとき
- UI ライブラリが足りてない
- Qwik UI ぐらい?
- ほぼフルスクラッチで書く気概が必要
- 複雑な箇所は qwik-react を使ったほうがよさそう
- 自作した
- Qwik 版 Next
- 現状 Qwik をホストする為にほぼ必須(or Astro)
- 流行りの File Based Routing
- SSR/CSR
- DataLoader/ServerAction
- プラットフォーム毎のアダプタ
- node, deno, cloudflare, cloudrun, firebase, StaticSite...
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useMyData = routeLoader$(async (requestEvent) => {
return {...}
});
export default component$(() => {
const signal = useMyData();
return <pre>{JSON.stringify(signal.value, null, 2)}</pre>;
});
- ルーティング時のデータ取得
- async component / getServerSideProps 相当
import type { RequestHandler } from "@builder.io/qwik-city";
export const onGet: RequestHandler = async ({ cacheControl }) => {
cacheControl({
public: true,
maxAge: 5,
sMaxAge: 10,
staleWhileRevalidate: 60 * 60 * 24 * 365,
});
};
- リクエストヘッダを付与
- (ヘッダを尊重するかはデプロイするプラットフォーム次第)
- https://qwik.builder.io/docs/integrations/authjs/
- Next.js と似た感じで認証系を実装できる
import { component$ } from '@builder.io/qwik';
import { useAuthSignin } from '~/routes/plugin@auth';
export default component$(() => {
const signIn = useAuthSignin();
return (<button onClick$={() => signIn.submit(...)}>Sign In</button>);
});
- だいたい Next.js/Remix と同じ機能はある
- サンプルが少ないのでドキュメント読んで実装する基礎力が必要
https://docs.astro.build/ja/install/manual/
// astro.config.mjs
import { defineConfig } from 'astro/config';
import qwikdev from '@qwikdev/astro';
export default defineConfig({ integrations: [qwikdev()] });
- 現状 Qwik でウェブサイトを作るなら一番手頃な選択肢
- Jamstack 的な低頻度の更新なら QwikCity より Astro のが運用が楽
- 例えばブログやドキュメントサイト
- 小さなアセットを高速に配信する必要がある
- CDN ではないサーバーから配信するとRTTで悪化する
- おそらく Cloudflare Pages が最適
- 一応 ServiceWorker による先読みも入っているが...
- Qwik Optimizer を意識したコーディングが必須
- (現状は)コンパイル時エラーではなく、実行時エラーになる
- lint で多少改善 https://qwik.builder.io/docs/advanced/eslint/