ドル記号 $

Qwikは、アプリケーションをシンボルと呼ばれる小さな部品に分割します。コンポーネントは多くのシンボルに分割できるため、シンボルはコンポーネントよりも小さくなります。この分割は、Qwikオプティマイザーによって実行されます。

$サフィックスは、この変換が行われるときにオプティマイザーと開発者の両方に通知するために使用されます。開発者として、$が表示される場合は常に特別なルールが適用されることを理解する必要があります(すべての有効なJavaScriptが、有効なQwikオプティマイザー変換であるとは限りません)。

コンパイル時の影響

オプティマイザーは、バンドル中にViteプラグインとして実行されます。オプティマイザーの目的は、アプリケーションを小さな遅延ロード可能なチャンクに分割することです。オプティマイザーは、式(通常は関数)を新しいファイルに移動し、式が移動された場所を指す参照を残します。

$は、オプティマイザーにどの関数を別のファイルに抽出するか、どの関数をそのままにするかを伝えます。オプティマイザーは、マジック関数の内部リストを保持するのではなく、変換する関数を知るために、$サフィックスのみに依存します。システムは拡張可能であり、開発者は独自の$関数(myCustomFunction$()など)を作成できます。

import { component$ } from '@builder.io/qwik';
 
export default component$(() => {
  console.log('render');
  return <button onClick$={() => console.log('hello')}>Hello Qwik</button>;
});

上記のコンポーネントは、$構文のおかげで複数のチャンクに分割されます

app.js
import { componentQrl, qrl } from '@builder.io/qwik';
 
const App = /*#__PURE__*/ componentQrl(
  qrl(() => import('./app_component_akbu84a8zes.js'), 'App_component_AkbU84a8zes')
);
 
export { App };
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('button', {
    onClick$: qrl(
      () => import('./app_component_button_onclick_01pegc10cpw'),
      'App_component_button_onClick_01pEgC10cpw'
    ),
    children: 'Hello Qwik',
  });
};
app_component_button_onclick_01pegc10cpw.js
export const App_component_button_onClick_01pEgC10cpw = () => console.log('hello');

ルール

オプティマイザーは、コードを抽出するためのシグナルとして$を使用します。開発者は、抽出には制約が伴うため、$が存在する場合は常に特別なルールが適用されることを理解する必要があります。(すべての有効なJavaScriptコードがオプティマイザーの有効なコードであるとは限りません。)

最悪の種類のコードマジックは、開発者に見えない種類のものです。

許可される式

$で終わる任意の関数の最初の引数には、特定の制限があります

ローカル識別子のないリテラル

const bar = 'bar';
const foo = 'foo';
 
// Invalid expressions
foo$({ value: bar }); // it contains a local identifier "bar"
foo$(`Hello, ${bar}`); // it contains a local identifier "bar"
foo$(count + 1); // it contains a local identifier "count"
foo$(foo); // foo is not exported, so it's not importable
 
// Valid expressions
foo$(`Hello, bar`); // string literal without local identifiers
foo$({ value: 'stuff' }); // object literal without local identifiers
foo$(1 + 3); // expression without local identifiers

インポート可能な識別子

// Invalid
const foo = 'foo';
foo$(foo); // foo is not exported, so it's not importable
 
// Valid
export const bar = 'bar';
foo$(bar);
 
// Valid
import { bar } from './bar';
foo$(bar);

クロージャ

クロージャの場合、ルールは少し緩和されており、ローカル識別子を参照してキャプチャできます。

ルール: 関数が字句的に変数(またはパラメーター)をキャプチャする場合、その変数は次のようである必要があります

  1. constであること
  2. 値がシリアライズ可能であること。
キャプチャされた変数はconstとして宣言する必要があります。

無効

component$(() => {
  let foo = 'value'; // variable is not a const
  return <div onClick$={() => console.log(foo)}/>
});

有効

component$(() => {
  const foo = 'value';
  return <div onClick$={() => console.log(foo)}/>
});
ローカルでキャプチャされた変数はシリアライズ可能である必要があります
// Invalid
component$(() => {
  const foo = new MyCustomClass(12); // MyCustomClass is not serializable
  return <div onClick$={() => console.log(foo)}/>
});
 
// Valid
component$(() => {
  const foo = { data: 12 };
  return <div onClick$={() => console.log(foo)}/>
});
モジュールで宣言された変数はインポート可能である必要があります

オプティマイザーによって抽出されている関数がトップレベルのシンボルを参照している場合、そのシンボルはインポートまたはエクスポートされている必要があります。

// Invalid
const foo = new MyCustomClass(12);
component$(() => {
  // Foo is declared at the module level, but it's not exported
  console.log(foo);
});
 
// Valid
export const foo = new MyCustomClass(12);
component$(() => {
  console.log(foo);
});
 
// Valid
import { foo } from './foo';
component$(() => {
  console.log(foo);
});

詳細

スクロールイベントを処理する仮想的な問題を見てみましょう。次のようなコードを書きたくなるかもしれません

function onScroll(fn: () => void) {
  document.addEventListener('scroll', fn);
}
onScroll(() => alert('scroll'));

このアプローチの問題点は、スクロールイベントが発生しなくても、イベントハンドラーがすぐにロードされてしまうことです。必要なのは、遅延ロード可能な方法でコードを参照する方法です。

開発者は次のように書くことができます。

export scrollHandler = () => alert('scroll');
 
onScroll(() => (await import('./some-chunk')).scrollHandler());

これは機能しますが、多くの手間がかかります。開発者は、コードを別のファイルに配置し、チャンク名をハードコーディングする必要があります。代わりに、オプティマイザーを使って、自動的にこの作業を実行します。しかし、オプティマイザーにこのようなリファクタリングを実行したいことを伝える方法が必要です。この目的のために、$()をマーカー関数として使用します。

function onScroll(fnQrl: QRL<() => void>) {
  document.addEventListener('scroll', async () => {
    const fn = await fnQrl.resolve();
    fn();
  });
}
 
onScroll($(() => alert('scroll')));

オプティマイザーは次のように生成します。

onScroll(qrl('./chunk-a.js', 'onScroll_1'));
chunk-a.js
export const onScroll_1 = () => alert('scroll');
  1. 開発者がする必要があったのは、関数を$()でラップして、その関数を新しいファイルに移動し、したがって遅延ロードされるべきであることをオプティマイザーに知らせることだけでした。
  2. onScroll関数は、関数を使用する前にその関数のQRLをロードする必要があるという事実を考慮する必要があるため、わずかに異なる実装が必要でした。実際には、QRL.resolve()の使用は、QwikフレームワークがQRL.resolve()を直接扱うことを開発者に期待することがほとんどない高レベルのAPIを提供しているため、Qwikアプリケーションではまれです。

ただし、コードを$()でラップするのは少し不便です。このため、implicit$FirstArg()を使用して、QRLを受け取る関数のラッピングと型マッチングを自動的に実行できます。implicit$FirstArg()に渡される関数はQrlのサフィックスを持ち、その関数の結果は$のサフィックスを持つ値に設定する必要があります。

const onScroll$ = implicit$FirstArg(onScrollQrl);
 
onScroll$(() => alert('scroll'));

これで、開発者は特定の関数を遅延ロードする必要があることを表現するための簡単な構文を手に入れました。

シンボルの抽出

次のコードがあるとします。

export const MyComp = component$(() => {
  /* my component definition */
});

オプティマイザーはコードを2つのファイルに分割します。

元のファイル

const MyComp = component(qrl('./chunk-a.js', 'MyComp_onMount'));

およびチャンク

chunk-a.js
export const MyComp_onMount = () => {
  /* my component definition */
};

オプティマイザーの結果は、MyComponMountメソッドが新しいファイルに抽出されたことです。これを行うことにはいくつかの利点があります。

  • 親コンポーネントは、MyCompの実装の詳細を取り込むことなく、MyCompを参照できます。
  • アプリケーションにはより多くのエントリーポイントが生まれ、バンドラーがコードベースをチャンクアップするためのより多くの方法を提供します。

レキシカルスコープのキャプチャ

オプティマイザーは式(通常は関数)を新しいファイルに抽出し、遅延ロードされた場所を指すQRLを残します。

簡単な例を見てみましょう。

export const Greeter = component$(() => {
  return <div>Hello World!</div>;
});

これは次のような結果になります。

const Greeter = component(qrl('./chunk-a.js', 'Greeter_onMount'));
chunk-a.js
const Greeter_onMount = () => {
  return qrl('./chunk-b.js', 'Greeter_onRender');
};
chunk-b.js
const Greeter_onRender = () => <span>Hello World!</span>;

上記は、抽出された関数のクロージャが変数をキャプチャしない単純なケースです。抽出された関数のクロージャがレキシカルに変数をキャプチャする、より複雑なケースを見てみましょう。

export const Greeter = component$((props: { name: string }) => {
  const salutation = 'Hello';
 
  return (
    <div>
      {salutation} {props.name}!
    </div>
  );
});

関数を抽出する単純な方法では機能しません。

const Greeter = component(qrl('./chunk-a.js', 'Greeter_onMount'));
chunk-a.js
const Greeter_onMount = (props) => {
  const salutation = 'Hello';
  return qrl('./chunk-b.js', 'Greeter_onRender');
};
chunk-b.js
const Greeter_onRender = () => (
  <div>
    {salutation} {props.name}!
  </div>
);

この問題は、chunk-b.jsで見られます。抽出された関数は、もはや関数のレキシカルスコープに存在しないsalutationpropsを参照しています。このため、生成されるコードは少し異なる必要があります。

chunk-a.js
const Greeter_onMount = (props) => {
  const salutation = 'Hello';
  return qrl('./chunk-b.js', 'Greeter_onRender', [salutation, props]);
};
chunk-b.js
const Greeter_onRender = () => {
  const [salutation, props] = useLexicalScope();
 
  return (
    <div>
      {salutation} {props.name}!
    </div>
  );
};

2つの変更点に注目してください。

  1. Greeter_onMountQRLは、salutationpropsを格納するようになりました。これは、クロージャ内の定数をキャプチャする役割を果たします。
  2. 生成されたクロージャGreeter_onRenderには、salutationpropsを復元するプリアンブルがあります(const [salutation, props] = useLexicalScope())。

オプティマイザー(およびQwikランタイム)がレキシカルにスコープされた定数をキャプチャできる機能は、遅延ロード可能なリソースに抽出できる関数を大幅に改善します。これは、複雑なアプリケーションをより小さな遅延ロード可能なチャンクに分割するための強力なツールです。

貢献者

このドキュメントの改善にご協力いただいたすべての貢献者に感謝します。

  • the-r3aper7
  • manucorporat
  • adamdbradley
  • saikatdas0790
  • anthonycaron
  • ubmit
  • literalpie
  • forresst
  • mhevery
  • AnthonyPAlicea
  • zanettin
  • mrhoodz
  • thejackshelton
  • hamatoyogi