@200stack/static-forms-eleventy
v0.2.4
Published
Eleventy Static Forms adapter for 200stack build-time materialization.
Readme
Static Forms Eleventy adapter
checkboxes(複数選択)は 0.2.3 以上(core は 0.2.2 以上)・build protocol v2 が必要です。
電話番号 tel・URL url・日付 date・数値 number は 0.2.4 以上(core も 0.2.4 以上)・build protocol v2 が必要です。
v2 と開発表示は slotId だけで描画できます。slotId は、200stack のフォームの画面の「サイトに貼るコード」に入っている名前(コードに書く名前)です。page の url・filesystem の outputPath は不要です。
{% staticForm "slot.contact" %}v2 と開発表示の言語はフォームの定義の lang (ja・en) に従います。言語ごとに別のフォームを作り、それぞれのページに置いてください。通常、プラグインの lang 指定は不要です。定義に lang がないフォームだけ、プラグインの lang: 'en' などで既定の文言を選べます。どちらも省略なら日本語です。定義と異なる lang を指定すると、本番ビルド・開発表示ともエラーになります。同じ言語の明示指定は省略と同じ結果です。lang は shortcode ごとの指定ではなく、プラグインの設定です。日英のフォームを同じサイトに置く場合は、プラグインの lang を省略して各フォームの定義に従わせてください。
送信・戻る・確認の送信・noscript・状態・項目エラーと開発の案内は、解決した言語の辞書を使います。保存された項目名や submitLabel・backLabel などの文言は自動翻訳しません。backLabel は確認の mode: 'configured' で省略可能です。本番・開発のフォームルートには HTML の lang と既存の data-200stack-static-form-lang を出します。ページ全体の <html lang> はサイト側で指定してください。データ取得に失敗して定義が分からない開発診断は、プラグインの lang、省略なら日本語を使います。v1 の出力は従来の英語固定で、定義・adapter の lang を適用しません。StaticFormLang 型を adapter から import できます。
0.1.0 は 0.2.0 のビルド入力を読めません。新しい定義・入力を使うサイトは adapter と core・SDK を対応する版へ更新してください。
言語を足す手順
- core の
src/core/messages.tsにフォーム文言・状態・検証エラー・開発診断をすべて持つ辞書を足し、staticFormLanguageNamesに表示名を足します。StaticFormLangと wire schema の許可値は辞書のキーから導かれます。実行時の登録はしません。 - 管理画面の自動返信の固定文(件名・本文・HTML の
langとフォント)と、作成時の既定定義・言語選択を足します。 - core の dist と controller bundle、Worker の同梱 controllers、adapter の controller contract、agent の生成物を作り直し、browser import 境界・bundle 予算・core と adapters のテストを確認します。言語追加には controller の再配備も必要です。
- DB(管理画面) → Worker(新しい core と controller) → build-runtime → 管理画面 → npm の順で配備し、サイトを再ビルドします。
import staticForms from '@200stack/static-forms-eleventy'
export default function (eleventyConfig) {
eleventyConfig.addPlugin(staticForms) // 各フォームの定義の lang に従う
}同じフォーム(コードに書く名前が同じもの)を1ページに複数置く場合は、2つ目以降に shortcode の第2引数で異なる index を渡してください(例: {% staticForm "slot.contact", { index: 1 } %})。既定は 0 です。同じ instanceId の2つ目以降は診断を表示し、送信の controller を起動しません。生成される ID は固定の CSS セレクタとして扱わないでください。
開発表示の重複診断には CSS の :has() を使い、JS は実行しません。:has() と埋め込みの <style> が使えるブラウザ・CSP が必要です。
route・outputPath は v1 入力でのみ必須です(page の値で補えます)。v2 では使いませんが、shortcode に明示的に渡された値は検証します。
v2 の staticForm shortcode は、固定入力の publicApiOrigin にある /v1/controller/static-dom-controller.<sha256>.js を module script として参照します。controller はサイトの出力へコピーしません。hash は同梱 bundle の SHA-256 全64桁です。
CSP を使うサイトは script-src に Static Forms の配信元ドメインを追加してください。
ボット対策は、送信の前にブラウザの中で行う計算の確認(ALTCHA と互換の proof-of-work)です。controller が自分で解くので、外部のスクリプトや iframe を読まず、CSP に追加の配信元は要りません。
リリース時に contracts/build-controller.mjs が bundle・controller contract・contracts/controller-manifest.v2.json を生成します。manifest は { name, hash, file } の配列で、file はこのパッケージのルートからの相対パスです。配信側は各版の bundle を対応する URL へ配置し、公開済みの版を保持してください。
生成コマンドはファイルへ直接出力します。stdout を bundle ファイルへリダイレクトする必要はありません。
見た目を指定する(v2)
const style = {
section: { title: 'text-xl font-bold' },
field: { root: 'mb-5', label: 'font-medium', error: 'text-rose-600' },
email: { control: 'rounded border aria-[invalid=true]:border-rose-600' },
radio: { group: 'grid gap-2', option: 'flex gap-2', input: 'size-4' },
byName: { email: { label: 'text-indigo-800' } },
}
eleventyConfig.addPlugin(staticForms, { style })値はクラス名の文字列だけです。項目は field → 種類 (text / email / textarea / select / radio / checkbox) → byName[name] の順に連結します。この順は CSS の優先順位を保証しません。未知の byName、または種類に合わない部品は警告し、後者は使いません。shortcode の style はプラグイン既定を丸ごと置き換えます。
style と lang はサーバーで HTML に反映し、client props には含めません。HTML の semanticDomDigest が変わると remount して入力・フォーカス・送信状態をリセットします。エラーの入力は aria-invalid="true"、項目外枠は data-200stack-static-form-invalid、送信中の root は aria-busy="true" です。checkbox は consentText があれば fieldset > legend + label > input、select は先頭に空の選択肢を出し、確認画面は configured・generated とも入力内容を表示します。固定 CSS は nonce を受け取らないため CSP hash を使います。
| キー | DOM (part) |
| --- | --- |
| root / form | ルート section / form |
| section.root / .title / .description | section / h2 / p |
| field.root、*.label / .description / .error | 項目 div、label または legend、説明 p、エラー p |
| text 系の .control | input / textarea / select |
| radio・checkbox の .group / .option / .input | fieldset / 選択肢 label / input |
| formErrors / status / submit | 全体エラー / 状態 / 主送信 button |
| privacy.* / confirmation.* / completion.* | 案内、確認、完了の各 p / a / div / 見出し / 一覧 / buttons |
| style のキー | part の正確な値 | 本番の DOM | 開発の表示 |
| --- | --- | --- | --- |
| root / form | root / form | section / form | 外側の div[role=form] (root form) |
| section.* | section / section-title / section-description | section / h2 / p | 同じ |
| field.root / *.label / .description / .error | field / label / description / error | 項目 div / label または legend / p / p | 同じ |
| text 系 .control | control | input / textarea / select | 同じ |
| choice .group / .option / .input | group / option / input | fieldset / label / input | 同じ |
| formErrors / status / submit | form-errors / status / submit | div / div / button | 出ない / 出ない / 無効の button |
| privacy.* | privacy / privacy-link | p / a | 同じ |
| confirmation.* | confirmation、confirmation-title、confirmation-description、confirmation-list、confirmation-item、confirmation-label、confirmation-value、confirmation-back、confirmation-send | div / h2 / p / dl / div / dt / dd / buttons | 出ない |
| completion.* | completion / completion-title / completion-body | div / h2 / p | 出ない |
条件で出ない説明・確認・完了は指定しても効果がありません。consentText が無い checkbox には group と label がありません。theme と公開目印は v2 と開発表示だけで、v1 の出力には効きません。制御文字、JSON 全体 32 KiB、合成済み class 属性 2,000 バイトを超えるとビルドエラーです。開発表示も同じ検査・クラスを使います。Tailwind は完全なクラス名をソースに書き、v3 の content または v4 の @source に含めます。動的なクラスは検出されず、エラー用には aria-[invalid=true]:border-rose-600 のように書きます。
CSS の目印と theme.css
v2 本番と開発表示では各部品に data-200stack-static-form-part が付きます。複数の役割は空白区切りなので [data-200stack-static-form-part~="form"] のように ~= を使います。公開する目印は、部品名の data-200stack-static-form-part、項目外枠の data-200stack-static-form-type・-name・-required・-invalid です。name には .・: が入り得るため値を引用符で囲みます。name を変更すると name を狙った CSS と byName は黙って外れます。controller 実行時の要素と開発表示の説明文は公開契約外です。HTML は項目ごとに 100〜200 バイト、選択肢ごとに約 70 バイト増えます。
eleventyConfig.addPlugin(staticForms, { theme: true }){% staticFormsTheme %}theme: true は static-forms/theme.css を出力し、shortcode は Eleventy の pathPrefix を反映した <link> を出します。色・角丸は --200stack-sf-accent、--200stack-sf-accent-hover、--200stack-sf-text、--200stack-sf-muted、--200stack-sf-border、--200stack-sf-frame-border、--200stack-sf-error、--200stack-sf-error-text、--200stack-sf-radius、--200stack-sf-surface、--200stack-sf-error-surface、--200stack-sf-on-accent、--200stack-sf-completion-surface、--200stack-sf-completion-text を上書きします。
@layer static-forms;
[data-200stack-static-form-part~="root"] { --200stack-sf-accent: #0f766e; --200stack-sf-accent-hover: #115e59; --200stack-sf-text: #ecfdf5; --200stack-sf-muted: #a7f3d0; --200stack-sf-border: #2d6a5c; --200stack-sf-radius: 14px; --200stack-sf-surface: #06251f; --200stack-sf-error: #fb7185; --200stack-sf-error-surface: #4c0519; }一般のサイトでは CSS の先頭に @layer static-forms; を置きます。レイヤー外の CSS と、後ろのレイヤーの CSS が勝ちます。Tailwind v4 では @layer theme, base, static-forms, components, utilities; を @import "tailwindcss"; より前に置き、@import '@200stack/static-forms-eleventy/theme.css'; と layer() なしで読み込みます。
theme.css だけの本番は外部 CSS なので style-src 'self'(または配信元)で足りますが、v2 は固定の <style> を常に出します。本番は 'sha256-LE3mSYpIJxrT67iqOfLXzAcqron3YCrRA+rogsSVPYA='、開発表示は 'sha256-PooA9aoFhckRsEL2CK2Uxp0tODRwW2JydE6jQ+WgQ+M=' を style-src(指定時は style-src-elem にも)へ追加してください。開発表示の重複診断 style は出力ごとに hash が必要です。クラス指定を使う本番でも固定 style の hash は必要です。
0.1 からの移行
| 0.1 | 0.2 |
| --- | --- |
| 平たい label / description / error | field.label / .description / .error |
| input / textarea / select | 種類ごとの .control |
| fields.* / types.* の input | text 系は .control、radio・checkbox は .input |
| 文字列の field / section / confirmation / completion | それぞれの .root |
| choiceGroup / choiceLabel / radio / checkbox | radio / checkbox の .group / .option / .input |
| sectionTitle / sectionDescription | section.title / .description |
| confirmationList など | confirmation.list など |
| types / fields | 種類キー / byName |
| first / last | section 内の表示位置は CSS、特定項目は byName |
StaticFormElementClasses・StaticFormFieldClasses は削除済みで、項目の型は StaticFormFieldStyle です。旧キーは読み替えず、Static Forms style uses 0.1 key at fields.email; see README: Migrating from 0.1 のようにエラーになります。
送信の完了と計測
完了時はメッセージを表示するか、サイトのルートからのパス(例 /contact/thanks?from=contact)へ移動します。移動先は 2,048 字以内で、/ から始まる必要があります。//、絶対 URL、バックスラッシュ、空白・制御文字(0x00〜0x20、0x7f〜0x9f、U+2028・U+2029)、#、パスの .. 区間、エンコードされた .・/・バックスラッシュ(%2e・%2f・%5c、大文字も含む)は拒否します。クエリは使えます。移動先はページの URL を基準に解決し、同じ origin であることを確認します。
サンクスページはサイト側で用意してください。末尾のスラッシュはサイトのルーティングに合わせ、検索に載せたくない場合は noindex を設定してください。そのページの計測タグは、ページを直接開いた場合にも動きます。移動中は「移動しています…」(en: Redirecting…)を表示し、送信ボタンは無効のままです。移動の呼び出しが失敗した場合は、送信失敗とは区別した案内と手動リンクを表示します。location.assign を使うため、戻るボタンで入力済みの完了状態に戻れます(bfcache で保持された場合)。
受付が確認できた時点で、ルートから 200stack-static-form:accepted を通知します。bubbles: true・cancelable: false の CustomEvent です。message では完了要素を表示した後、redirect では移動を呼ぶ前に通知します。直接受付とポーリングの両方が対象です。確認できずにポーリングが時間切れになった場合や dispose 後には通知しません。
detail は通知ごとに作る凍結した { slotId?: string, publicFormId: string, instanceId: string } です。入力値・受付 ID・receipt・session は含みません。v2 の HTML には data-200stack-static-form-slot があり、SDK が渡した slotId を通知できます。属性がない旧い HTML では slotId のキーを省略します。v1 と開発表示にはこの属性を出しません。回数の保証は「同じ controller の同じ送信で最大 1 回」です。再読み込みや remount をまたぐ 1 回だけの保証はありません。イベントは偽造できるので、課金・権限付与などの受付証明には使えません。
STATIC_FORM_ACCEPTED_EVENT と StaticFormAcceptedEventDetail は core と adapter から export します。ブラウザで定数を使う場合は @200stack/static-forms-core/browser を使い、adapter の入口からは型だけを import してください。リスナーの Promise は待たず、通常の DOM リスナーの例外はブラウザが報告します。通知をキャンセルして完了処理を止めることはできません。
GTM と GA4 の例は どちらか一方 を使ってください。リスナーはサイト全体で 1 個とし、必要なら slotId で対象を絞ります。redirect で同じ成果をイベントとサンクスページの両方で数えないでください。移動前の通知は計測先への配送完了を保証しません。
計測は、サイトの Cookie の同意の管理(GTM・GA4 の同意モードなど)に従って送ってください。EU などでは計測に同意が要るのが一般的です。例は入力値を送りません。入力値を計測に足さないでください。
共有の外部 JS をレイアウトで一度読み込みます。サイトのルートに static-form-measurement.js を置き、Eleventy の設定に eleventyConfig.addPassthroughCopy('static-form-measurement.js') を足して /static-form-measurement.js を配信してください。次は GTM の例です。
<script defer src="/static-form-measurement.js"></script>// static-form-measurement.js
document.addEventListener('200stack-static-form:accepted', (event) => {
const { slotId } = event.detail
if (slotId !== 'slot.contact') return
window.dataLayer = window.dataLayer || []
window.dataLayer.push({ event: 'static_form_accepted', form_slot: slotId })
})GTM ではカスタムイベント static_form_accepted をトリガーにし、GA4 イベントタグの名前を generate_lead、パラメーター form_slot を同名のデータレイヤー変数に設定します。GA4 を直接呼ぶ場合は、上の dataLayer の 2 行を次に置き換えてください(リスナーは追加しません)。
if (typeof window.gtag === 'function') {
window.gtag('event', 'generate_lead', { form_slot: slotId })
}CSP では共有 JS の配信元を script-src で許可します。インラインにする場合は hash または CSP と一致する nonce が必要です。GTM・GA 自体の配信元の script-src と送信先の connect-src なども別途許可してください。
開発中の表示
アプリの設定の「開発用トークン」でトークンを発行してください。発行・利用できるのは admin と developer です。Eleventy を実行するサーバー側の環境に、次を設定します。値は、開発用トークンの画面に出る環境変数の例に置き換えてください。STATIC_FORMS_DEV_ENVIRONMENT_ID には、アプリのデフォルトブランチの環境の ID が自動で入っているので、そのまま使ってください。表示されるのは、そのアプリに接続したフォームです(環境・ブランチごとには分かれません)。ローカルの画面に「環境・branch に対応する設置がありません。」と出たら、開発用トークンの画面で今の環境 ID を確かめ、STATIC_FORMS_DEV_ENVIRONMENT_ID を置き換えてください。
export STATIC_FORMS_DEV_ENABLED=1
export STATIC_FORMS_DEV_API_URL=https://dashboard.example.com
export STATIC_FORMS_DEV_TOKEN=sfdev_REPLACE_WITH_ISSUED_TOKEN
export STATIC_FORMS_DEV_APP_ID=app-id
export STATIC_FORMS_DEV_ENVIRONMENT_ID=environment-id
export STATIC_FORMS_DEV_BRANCH=feature/contactAPI URL は HTTPS の origin を指定します。STATIC_FORMS_DEV_BRANCH はフォームの選び分けには使いません。作業中のブランチ名を入れておくと、保存データが別のブランチで取ったものでないかを確かめられます。branch を付けずに取った開発用のデータ(scope の branch が *)を使うときは * を入れてください。git からは推測しません。トークンを含む環境変数が出力されないよう、global data に process.env を丸ごと載せないでください。
eleventy --serve と eleventy --watch では、公開版の入力を開発用プレビューとして表示します。送信はできず、送信用の controller script も出しません。incremental には対応しません。
データは1回の生成の開始時に取得し、その生成の間は固定します。更新はローカルの再生成で反映します。取得に失敗した場合は フォームの位置に診断を表示し、次の生成で取得し直します。
通常のビルドや実行種別が不明の場合は開発 API を呼びません。ビルド用の入力・pin・STATIC_FORMS_BUILD_ONLY などがあれば、serve・watch でも本番の入力を使います。入力が無ければエラーになります。
v1 の本番入力で serve・watch を使う場合、proof は最初の生成で検証して確立します。2回目以降は本番の HTML を再描画し、proof の追記・再確立は行いません。
API のデータを保存して使う場合は、プロジェクトのルートで次を実行します。
npx static-forms pull
export STATIC_FORMS_DEV_SNAPSHOT_PATH=.static-forms/development.json既定の保存先は配信対象外の .static-forms/development.json です。更新には npx static-forms pull --force を使い、その後サイトを再生成してください。保存データにトークンは含まれません。
保存した snapshot は API より優先し、読み込みに失敗しても API に切り替えません。保存データを読む場合も、開発の有効化とアプリ・環境・STATIC_FORMS_DEV_BRANCH の設定は必要です。
