v3 への移行
zfb 2.x のプロジェクトを独自のスタイルエンジンとコンポーネントエンジンへ移行します。
はじめる前に
このガイドは、Tailwind CSS、Preact または React のコンポーネント、島、MDX、旧設定を使うプロジェクト向けです。v3 では旧エンジンとの連携が削除されます。互換モードや自動変換はありません。まず動作する 2.x の状態をコミットしてください。設定をプリセットパッケージから受け取る場合は、アプリより先にプリセットを更新します。プリセットに削除済みのキーが残ると、アプリ側で削除しても設定の読み込みが失敗します。
以下の確認には、v3 の zfb バイナリと対応する @takazudo/zfb パッケージを使ってください。2.x のドキュメントホストに入ったバイナリや型は、依然として 2.x の動作を表します。
設定
framework: "preact" や framework: "react" を含め、framework を削除します。残っていると次の設定エラーになります。
the framework key was removed in zfb 3; delete the key. zfb now uses zudo-react.tailwind を wind に置き換えます。生成ユーティリティを使わず、手書き CSS と CSS Modules だけを使う場合は、tailwind: { enabled: false } の代わりに wind: false を指定します。旧キーが残ると次のエラーになります。
the tailwind key was removed in zfb 3; utilities are compiled by the built-in zudo-wind engine. Replace tailwind: { enabled: false } with wind: false, or delete the key. See the v3 migration guidewind を省略すると、リセットが none でトークンのない空の v1 設定として生成が有効になります。旧テーマは復元されません。wind の内部オブジェクトは未知のフィールドを拒否します。各フィールドとマージ規則は wind の設定を参照してください。
スタイルシート
エントリーとそのインポート先から、@import "tailwindcss"、preflight と utilities のインポート、および @theme、@source、@apply、@utility、@plugin などの Tailwind ディレクティブを削除します。wind: false でも、残ったディレクティブは診断 ZW009 で失敗します。1 回の実行で、残ったディレクティブをすべて file:line:column 付きで列挙します。独自のセレクターと宣言は手書き CSS に、ユーティリティの値は wind.tokens に移します。
手書き CSS の var() が参照する Tailwind のデフォルトテーマ変数も定義されなくなります。デフォルトテーマ変数を参照してください。
v3 はパレット、間隔の単位、名前付きスケール、ブレークポイントを暗黙には提供しません。wind.reset に none、minimal-v1、owned-v1 から選んでください。既定は none です。新しい basic-blog の雛形は owned-v1 を選び、パレットと spacingUnit を宣言し、記事のリスト表示を手書き CSS で戻しています。リセットを選ぶ際は見出し、リスト、境界線、フォームに加え、html と code/pre のフォントスタック、sub/sup、::placeholder、::file-selector-button、[hidden] を確認してください。これらを含まないページのピクセル差分では差を検出できません。Tailwind preflight との違いも参照してください。
import { defineConfig } from "zfb/config";
export default defineConfig({
wind: {
spec: 1,
reset: "owned-v1",
tokens: {
spacingUnit: "0.25rem",
colors: { accent: "var(--color-accent)" },
},
breakpoints: { sm: { minWidthPx: 640 } },
dark: { attribute: "data-theme", value: "dark" },
},
});スタイルシートに --color-accent を宣言し、使うユーティリティに必要なトークンを追加します。site-header のようにクラス位置の名前がどのユーティリティ基部にも一致しなければ、通常のクラステキストとして残ります。カタログの基部から始まる名前は最長一致する基部で検証されるため、色またはフォントサイズのトークンがなければ text-link は ZW006 になります。CSS が候補全体を所有する場合は wind.authoredClasses: { "text-link": true } に予約します。完全一致するとユーティリティの検証、診断、CSS 生成が抑止されます。それ以外の場合はユーティリティの綴りを直すか不足トークンを宣言します。ユーティリティ言語とリセットは Tailwind と異なります。ディレクティブ、バリアント、トークン、未対応の構文は Tailwind からの移行表を参照してください。
BEM の card__title や snake_case の card_title のようにアンダースコアを使う手書きの名前は、card-title と同じく通常のクラステキストです。line-clamp-2、contents、container など zudo-wind が実装しない Tailwind ユーティリティは ZW014 の警告になり、代わりに CSS で書くべき宣言を示します。これでビルドを失敗させるには wind.strict: true を設定します。const や条件分岐に置いたクラス文字列も、class 属性に届けば直接書いたクラスと同じく検査されるため、そこにある未対応のユーティリティもビルドを失敗させます。診断とツールを参照してください。
コンポーネントと島
zfb build と zfb dev はページ、コンポーネント、アイランドの JSX を、自動変換と jsxImportSource: で常にコンパイルします。プロジェクトの tsconfig.json は不要で、その jsx と jsxImportSource の設定も無視されます。ただし、ファイル単位の @jsxImportSource pragma はそのファイルの build 変換を上書きできます。zfb check、エディター、TypeScript によるテストファイルの型検査で同じランタイムの JSX 型を使うには、tsconfig.json で compilerOptions.jsx を react-jsx、compilerOptions.jsxImportSource を @takazudo/ にします。テストランナーの JSX 変換は、ランナーがこの設定を tsconfig から読むよう構成されていない限り、別途設定してください。使用中の @takazudo/zfb と @takazudo/zfb-runtime は残します。旧 zfb エンジンのためだけに追加していた preact、react、react-dom、@types/react、@types/react-dom は削除します。ランタイムは独立パッケージではなく、@takazudo/ から提供されます。
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@takazudo/zfb/zudo-react"
}
}/ または / というファイル単位の pragma が残っていないか、たとえば grep -rln "@jsxImportSource" src pages components で探し、zudo-react が描画するコンポーネントから削除します。ファイル単位の pragma は build で使う import source への唯一の上書きですが、React や Preact の指定によってそれらのレンダラーがサポートされるわけではありません。Preact が未インストールなら、Preact の pragma によってビルドが Could not resolve "preact/jsx-runtime" で失敗します。Preact がウィジェットの peer dependency などとして残っている場合は、静的描画が ZR_CHILD 診断で失敗します。どちらの失敗の前にも、zfb build と zfb dev は、ページやページが import するモジュールにあるこうした pragma ごとに、プロジェクトのファイル、行、列を示す zfb warn: 行を表示します。この警告は表示するだけで、終了ステータスは変わりません。依存パッケージ内のファイルは対象外なので、サードパーティウィジェットの埋め込みのように DOM ホストの背後へ遅延マウントする Preact のウィジェットパッケージは、警告なしでビルドできます。zfb check はページ以外の authored .tsx / .jsx も調べ、--skip-tsc でも ZB005 を警告します。依存パッケージ、生成出力、スクラッチ領域は対象外で、警告だけでは check は失敗しません。
hook を使うコンポーネントは、セットアップ時に一度実行される zudo-react コンポーネントへ書き換えます。書き込み可能な状態とフォームのモデルには signal()、読み取り専用の派生値には computed()、ライフサイクルには getScope() と scope.effect()、条件付き領域とキー付き領域には Show と For を使います。読み取り専用または computed の signal は書き込み可能なモデルにできません。JSX では class、for、style={{ "background-color": "red" }} のような HTML/CSS の表記と、ネイティブの on:click リスナーを使います。React 式の props とブランドのない要素オブジェクトは、zudo-react の JSX または h() に置き換えます。rawHtml は信頼できる内容に限り、children と併用できません。<script> と <style> のテキストの子は拒否されるため、信頼できる静的文字列を <style rawHtml={CSS} /> または <script rawHtml={JS} /> に渡すか、子を持たない外部 <script src= を使います。この raw-text の文字列はそのまま出力され、エスケープもサニタイズもされません。この規則は <title> と <textarea> には当てはまらず、通常のテキストの子を受け取ります。変換例は Preact hooks からの移行表を参照してください。
標準的な HTML/SVG マークアップは、TypeScript、サーバーレンダリング、ハイドレーション、新規マウントで共有する有限の語彙に従います。<meta property="og:title"> の Open Graph メタデータ、<link rel="preload" as="font"> の preload リンク、外部の <script defer>、SVG の use/href は通常の JSX で直接記述できます。これらの対応属性に rawHtml の回避策は不要です。未知の属性名と React 式の表記は引き続きエラーになり、カスタム要素の属性は文字列のみです。値の分類と SVG 名前空間の詳細はコンポーネントと JSXを参照してください。
移行時に .mdx と .md ファイルの手書き raw HTML テーブルを確認してください。raw HTML の <table><tr>… は ZR_PARSER_CONTEXT: table > tr で失敗するため、各行を明示した <tbody> で囲みます。この規則は両方の形式の raw HTML に適用されます。GFM のパイプテーブルは別の形式で、Markdown コンパイラーがすでに <thead> と <tbody> を出力するため変更は不要です。構造規則の詳細はテーブルと select の子要素を参照してください。
ビルドや dev のレンダリングが React 形式の綴りを拒否すると、代わりに使う綴りを示し、JSX であれば要素を記述したファイル、行、列も示します。事前に zfb check を実行する必要はありません。この位置と、構造上の文脈や生成されたバンドルのスタックとの違いは、サーバーレンダリングの「レンダリング失敗の読み方」を参照してください。
zudo-react の JSX と ESLint
React 向け ESLint ルールは、zudo-react が必要とする表記を拒否することがあります。eslint-plugin-react の recommended config には react/no-unknown-property が含まれ、class、for、crossorigin を React 向けの属性名に一致しないとして報告し、className、htmlFor、crossOrigin へ自動修正する場合があります。また on:click も未知のプロパティとして報告します。ignore オプションは属性名の文字列配列なので、on:<event>[:capture] のような任意のリスナー表記全体は除外できません。no-unknown-property ルールを参照してください。
このルールは、zfb が zudo-react でコンパイルする JSX ファイルだけで無効にします。自動変換は React の識別子を使わないため、同じファイルでは react/jsx-uses-react と react/react-in-jsx-scope も無効にします。React hooks または Fast Refresh のルールも適用している場合は、それらのルールやプリセットを React で描画するファイルだけに移すか、zudo-react のファイルに限って現在有効な該当ルールを無効にします。対象となるルールは react-hooks/rules-of-hooks、react-hooks/exhaustive-deps、react-refresh/only-export-components です。zudo-react コンポーネントは React hooks の代わりに signal と scope を使い、React Refresh ルールは zfb が提供しない Fast Refresh 連携を検査します。React を引き続き使うファイルでは、これらのプラグインとルールを維持してください。現在の React Hooks プリセットには他の React 固有ルールも含まれるため、この一覧は既知の移行時の競合だけを示し、それ以外のルールが zudo-react と互換であるとは保証しません。React Hooks の lint ルールとReact Refresh ルールを参照してください。
Flat config を使う混在プロジェクトでは、React プリセットより後に次の override を追加します。サンプルの glob は zudo-react で描画する実ファイルに置き換えてください。hooks と refresh のルール ID は、それぞれのプラグインをすでに設定している場合だけ追加し、各プリセット自体は React で描画するファイルに限定します。ESLint のflat config ガイドも参照してください。
{
files: ["components/zudo-react/**/*.{js,jsx,ts,tsx}"], // zudo-react の実ソースに置き換える
rules: {
"react/no-unknown-property": "off",
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
},
}zudo-react の契約は TypeScript と renderer で確認します。上記のとおり tsconfig.json の JSX 設定を zudo-react に向けて zfb check を実行し、zfb build または zfb dev で renderer が示す表記エラーとソース位置を確認してください。ESLint の提案や自動修正が renderer の契約を変えることはありません。
<Island> は引き続きコンポーネントを包むビルド時の要素です。ただし受け渡しは厳密になります。子コンポーネントは 1 つ、props は JSON、識別情報はソーススキャナーと一致する必要があります。手書きのラッパーにも完全で有効なメタデータが必要です。ハイドレーションは一致するサーバーのマークアップを採用し、不一致の島は個別に失敗します。SSR を省略するルートは data-when で起動を調整し、クライアントで正常にマウントした後にフォールバックを置き換えます。フォームは明示的な書き込み可能モデルを使い、select は単一選択のみで、ハイドレーション時は DOM の値が優先されるため、動作を確認してください。島のガイドも参照してください。
任意の島 prop
任意の島 prop では、自身が持つ列挙可能な文字列キーのデータプロパティで、値が undefined のものは SSR と JSON transport の前に再帰的に省略されます。配列内のレコードも同じです。サーバーコンポーネントとクライアントには同じキーで正規化した props が渡るため、Object.hasOwn の結果は一致します。明示的な null はプロパティとして保持されます。
レコードプロパティの省略は v2 と同じ動作です。配列は厳密で、undefined の要素や穴は値に自動変換せずエラーになります。配列要素を存在させたままにする場合は null を使ってください。
import { serializeProps } from "@takazudo/zfb/zudo-react/server";
serializeProps({
currentSlug: undefined,
nodes: [{ slug: "a", description: undefined }, { slug: "b", description: null }],
});
// {"nodes":[{"slug":"a"},{"slug":"b","description":null}]}詳細はサーバーレンダリングを参照してください。
zudo-react のクライアントランタイムについて、Preact と同じバンドルサイズになる保証はなく、固定のバイト数予算もまだ決まっていません。別のフォローアップで、本番用アイランドの出力を測定し、安全に測定できる最適化を評価したうえで、その結果から回帰予算を決めます。この作業は v3 への移行を妨げません。このガイドではプロジェクト固有のサイズを保証しません。
パッケージ内で重複する島の定義
v3.2.0 では、v2.7.1 で島マーカーの衝突に追加された例外(#2441)が削除されました。v3.2.0 のリリースノートは当初、この破壊的変更を告知していませんでした。3.2.0 より前の zfb は、同名の島の定義が二つあっても、両方のファイルが同じ node_modules パッケージ内にある場合や、一方がそのパッケージの配布ファイルをバイト単位で同一にステージングしたコピーである場合、黙って一方だけを残していました。現在の島の識別子は、定義元のモジュールパスと binding です。パッケージ名やバイト列の一致、コンパイル済みの対応ファイルがあることは、二つの定義が同じ関数である証明になりません。
典型例は、コンパイル済みの dist/ 出力とソースの両方を配布するパッケージです。あるページはコンパイル済みの export を import し、生成されたルートは node_modules の外にステージングされたパッケージソースのコピーを import します。二つの境界は同じマーカーを持つ別々の定義に解決され、ビルドが失敗します。
ambiguous owned island marker "Widget": <first definition> (Widget) used at <first page> and <second definition> (Widget); rename one defining functionすべての境界が一つの定義を import するようにしてください。
両方の場所でパッケージのコンパイル済み export を import する。
または、ステージングしたコピーでソースを複製せず、
export { Widget } from "@scope/のようにパッケージからコンポーネントを re-export する。pkg/ routes/ index";
定義元の関数名を変更するのは、二つの定義が本当に別のコンポーネントである場合だけにしてください。ステージングしたコピーとパッケージソースが同等であることを宣言する、明示的な信頼済み provenance マッピングが将来の手段として設計されていますが、まだ利用できません。
zfb ビルドの外でレンダリングする島
v3.0.0 で Island は子を検証するようになりましたが、v3.0.0 のリリースノートは当初、この変更を告知していませんでした。v2 の Island はマーカー名を displayName ?? name から取り、検証なしでラッパーを出力していたので、displayName を持つ minify 済みの関数もレンダリングできました。v3 では、zfb のサーバーバンドルが設定するビルド識別子と登録済みの島の名前を読み取り、子の関数名と displayName を比較します。zfb がレンダリングするページには影響しません。別のバンドラーや素の Node で Island をレンダリングするパッケージやスクリプトは、次のような ZR_ISLAND_IDENTITY で失敗するようになりました。
ZR_ISLAND_IDENTITY: ThemeToggle has no build identity
ZR_ISLAND_IDENTITY: dc conflicts with displayName SidebarToggle同期のレンダリングを @takazudo/ の withIslandTestContext で包み、利用側のバンドラーの minify 出力で関数名を保持してください。詳しくはzfb ビルドの外で Island をレンダリングするを参照してください。
MDX と md-wasm
@takazudo/zfb-md-wasm の compile() と renderHtml() のオプションは jsxRuntime を受け付けなくなりました。react や preact を選ぶ代わりに、このフィールドを削除してください。型付き API は拒否し、JSON の検証では jsxRuntime を示す未知フィールドのエラーになります。コンパイル済み MDX は Fragment を @takazudo/ からインポートします。出力をバンドルする側でこのサブパスを解決し、旧エンジンへエイリアスしないようにします。
CLI と環境変数
スクリプトと CI から ZFB_TAILWIND_BIN と ZFB_TAILWIND_OXIDE_WARMUP を削除します。Tailwind の同梱バイナリとピンは削除されました。zfb css は単独の CSS コマンドとして残り、zudo-wind でコンパイルします。明示的な --source 入力も引き続き使えます。node_modules や dist 内のパッケージファイルも、プロジェクトの設定済み出力ディレクトリの外なら指定できます。ただし、設定済みの outDir、--output のパス、zfb のスクラッチルート内の一致ファイルは除外されます。すべて除外されると zfb css は ZW010 エラーで失敗し、一部だけ除外されると ZW010 警告を出して残りを走査します。CLI のソース規則を参照してください。zfb wind explain <candidate> は一つの候補を説明し、zfb wind audit はプロジェクト内の候補を調べます。build と dev は .tsx、.ts、.jsx、.js、.mdx、.md をスキャンし、単独コマンドの明示的なソースには .html と .mjs も指定できます。
zfb --version はリリースバージョンと embedded esbuild のバージョンを表示し、Tailwind の行は表示しません。移行中のエラーの調査には wind の診断ガイドを使ってください。
クラスやコンポーネントを配布するパッケージ
以前、配布した safelist に依存していたパッケージは、完全な候補をバージョン付き JSON マニフェストとして配布してください。利用側は producer のキーを付けてファイルを宣言します。異なる producer はプリセットとアプリのマージ後も保持されます。すべてのエントリーが厳密に検証され、利用側で必要なトークンを設定する必要があります。マニフェストはトークンを定義できず、自動発見もされません。手作業で管理せず、zfb wind manifest --producer <id> --output <file> --config <利用側の config> で生成してください。zfb wind manifest を参照してください。
{
"schemaVersion": 1,
"specVersion": 1,
"producer": "widgets",
"candidates": ["group", "p-0", "hover:bg-accent"]
}import { defineConfig } from "zfb/config";
export default defineConfig({
wind: {
manifests: { widgets: { path: "widgets/wind.json" } },
tokens: { colors: { accent: "#2563eb" } },
},
});widgets パッケージは . を export する必要があります。bare specifier はパッケージの exports から解決され、. で始まるパスは宣言した config のディレクトリ(プリセット由来ならそのプリセットパッケージ)を基準に解決されます。マニフェストパスの解決を参照してください。
配布するコンポーネントは @takazudo/ からインポートし、同じ JSX import source を使ってください。zudo-react が描画するコンポーネントには Preact や React の JSX runtime import を含めないでください。独自フレームワークを持つ不透明な自己マウント型ウィジェットは、島から遅延読み込みできます。サードパーティウィジェットの埋め込みを参照してください。マニフェストの検証と探索ルートは ソースと候補を参照してください。
チェックリスト
設定プリセットを先に更新し、対応する v3 パッケージとバイナリを導入します。
zfb --versionとpackage.jsonを確認します。frameworkを削除し、tailwindをwindまたはwind: falseに置き換えます。zfb checkで削除済みキーと無効な内部データを確認します。Tailwind のインポートとディレクティブを削除し、トークンとリセットを設定したうえで、代表的なページの
htmlとcode/preのフォントスタック、sub/sup、::placeholder、::file-selector-button、[hidden]を確認します。プロジェクトの入出力パスを指定してzfb css --input <entry.css> --output <output.css>を実行します。zfb check、エディター、テストファイルの型検査用に tsconfig の JSX 設定を合わせ、必要に応じてテストランナーの JSX 変換を別途設定します。ファイル単位の@jsxImportSourcepragma を削除し、依存関係、コンポーネント、島、フォームモデルを更新します。React の表記・hooks・refresh ルールは React を使うファイルに限定し、zudo-react 用の ESLint overrideは zudo-react のファイルだけに適用します。その後zfb checkを実行し、型エラーを修正します。md-wasm 呼び出しから
jsxRuntimeを削除し、コンパイル済み出力の利用側を更新します。パッケージの TypeScript チェックを実行します。動的に組み立てる有限のクラスを完全なリテラルかマニフェストに置き換えます。CI または pre-push のチェックとして
zfb wind audit --fail-on errorを使い、代表的なクラスに対してzfb wind explain <candidate>を実行します。多数の候補を調べるときは、zfb wind audit --json --severity warningとzfb wind explain --stdin --jsonで、機械可読な結果を 1 回のプロセスで得られます。ビルドスクリプトから Tailwind の環境変数を削除します。
zfb buildを実行し、主要ページ、島、フォーム、スタイルをブラウザーで比較します。