Skip to content

Instantly share code, notes, and snippets.

@mizchi
Created December 15, 2023 10:18
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mizchi/7abb8d88dd344d38ef355a8b424505a8 to your computer and use it in GitHub Desktop.
Save mizchi/7abb8d88dd344d38ef355a8b424505a8 to your computer and use it in GitHub Desktop.

Qwik それはフロントエンドの見た夢

@mizchi | Workers Tech Talk


Qwik とは

  • SSR 前提 で最適化を行うUIライブラリ
    • クライアントの処理が最小限
    • すべてが非同期チャンク
  • すべてがチャンク化された遅延ハイドレーション
    • 必要なコードのみ動的ロード
    • 静的解析で関数単位でチャンク化
  • (使いこなせれば)理論上最強

Qwik Optimizer が強すぎる

Qwik コンポーネントの例

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"
  )
);

// 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",
  });
};

// app_component_p_onclick_01pegc10cpw.js
export const App_component_p_onClick_01pEgC10cpw = () => console.log("hello");

Counter: 多段展開の例

export const Counter = component$(
  /*chunk: onMount*/ () => {
    const count = useSignal(0);
    return (
      /* chunk: onRender */
      <button
        type="button"
        onClick$={
          /* chunk: onClick */
          () => count.value++
        }
      >
        {count.value}
      </button>
    );
  }
);
  • onMount: 関数の body 部分
  • onRender: return <>...</>
  • 実体は複数チャンクで構成されるツリー構造
  • クロージャにみえる参照は QRL 経由で親子間で解決

QRL: $(...)

  • 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>
})

Qwik で動かないコード: 1

const C = component$(() => {
  const v1 = new (class {})();
  let v2 = 1;
  const f1 = $(() => {
    // NG: class はシリアライズできない
    console.log(v1);
    // NG: 別チャンクの let を書き換えることができない。signal が必要
    v2++;
  });
});

Qwik で動かないコード: 2

// 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

  • スコープ解析により 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>
});

onMount 最適化で察する Qwik Optimizer

const Greeter_onMount = (props) => {
  const salutation = "Hello";
  return qrl(
    "./chunk-b.js",
    "Greeter_onRender",
    // スコープ解析によって参照可能なものを渡す
    [salutation, props]
  );
};
  • チャンク分割でスコープがぶつ切りになるのでシリアライズ/QRL化が必須
  • let が引き継げないのはコンテキストが消失するため

Qwickloader

<body q:base="/build/">
  <button on:click="./myHandler.js#clickHandler">push me</button>
</body>

QwikCity

  • 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-qwik: qwikify$

  • Qwik 内で React コンポーネントが使える
  • Qwik で遅延ハイドレーション
/** @jsxImportSource react */
import { qwikify$ } from "@builder.io/qwik-react";

// An existing React component
function Greetings() {
  return <div>Hello from React</div>;
}

// Qwik component wrapping the React component
export const QGreetings = qwikify$(Greetings);

Qwik 書き味


Qwik エコシステム

  • UI ライブラリが足りてない
    • Qwik UI ぐらい?
    • ほぼフルスクラッチで書く気概が必要
    • 複雑な箇所は qwik-react を使ったほうがよさそう
  • QwikCity
    • qwik-auth で auth.js が使える
    • プラットフォームへの adapter が充実
      • node, deno, cloudflare, cloudrun, firebase, StaticSite...

個人Web開発の銀の弾丸?

  • QwikCity で超高速フロントエンド
  • CloudflarePages で低コスト運用
    • Qwik の低遅延要求に応える
    • QwikCity のビルドサイズも 200k ぐらい
  • あとは永続層どうするか問題
    • d1 + prisma いつ動くんですかねェ...

参考


おわり

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment