Build Dependency Contract
Incremental Buildの無効化はCoreの責務です。PluginはどのContentが影響を受けるかを計算せず、依存stateも自分では持ちません。契約を宣言すると、何を再利用するかはCoreが決めます。
Pluginが関わる契約は、Content DependencyとOutput Dependencyの二つです。前者はprocessedContentCacheで、Coreがどのsource Contentを処理し直すかを決めます。後者はoutputDependenciesとcontext.output.emitで、Coreがどの出力ファイルを書き直すかを決めます。両者は独立した契約です。
Diagram source
flowchart LR
Change["Contentまたはfileの変更"] --> Content["Content Dependency"]
Content --> Reprocess["影響するContentの再処理"]
Reprocess --> Output["Output Dependency"]
Output --> Rewrite["影響するOutputの再書き込み"]
Content Dependency
processedContentCacheは、Pluginの処理済みContentをBuild間でどう再利用してよいかを示す契約です。
dependencyMode |
意味 |
|---|---|
none |
処理結果がsource Content、frontmatter、Pluginのoptions、宣言したversionだけに依存する。 |
tracked |
他のContentやfileをCore経由で読む。Coreがその読み取りを記録し、利用側だけを再処理する。 |
unsafe |
Git、network、時刻、process stateなど、Coreが観測できない入力に依存する。永続的な再利用は行わない。 |
Contentを変化させるPluginが契約を宣言していない場合、そのPluginはunsafeとして扱われます。
versionは契約の名前です。Coreは解決済みのPlugin設定もcache keyに含めます。そのため、options、hooks、versionのいずれかを変えると、cacheされたentryは無効です。
Coreが記録する入力
CoreのContent API経由の読み取りが、安定したidentityを持つdependencyになります。
| 種類 | 記録される場面 | identity |
|---|---|---|
content |
readContent、renderContent、renderNoteEmbed |
Contentのslug |
file |
contentSource.readとreadContentSourceEntryなどのhelper |
file path |
link |
link先がpermalinkに解決されたとき | link id |
contentSource.scan()はentryを列挙するだけでdependencyを作りません。filesystem、network、時計への直接アクセスはCoreから見えないため、それらの入力はunsafeです。
再利用と検証
次のBuildでCoreは、cacheされた処理済みContentを取り出したあと、記録したcontent、file、linkのdependencyをすべて読み直し、fingerprintを比べます。一つでも変わっていれば、そのentryを捨てて処理し直します。
cacheが省略するのはMarkdownとHTMLの処理だけです。post処理のhooks(onPostParsed、onPostProcessed)とmanifestのhook(onManifestCreated)は、cacheの有無にかかわらず常に実行対象です。manifest段階の処理はそこに置いてください。
影響範囲の決定
Coreは各entryのdependency identityをbuild stateに永続化し、そこから逆引きindexを作ります。dependencyが変わると、依存するentryに印を付け、さらにその依存先へと無効化を伝播させます。Contentが追加または削除された場合も、変わったlink先を参照していたentryは無効です。初回Buildや前回のstateが無いときは、すべてのnoteを処理します。
Output Dependency
Output DependencyはContent Dependencyとは別の契約です。種類ごとに、change setの異なる部分に反応します。
| 種類 | 影響を受ける条件 |
|---|---|
content { slug } |
そのContentが変わったとき |
tag { tag } |
そのtagを持つContentが変わったとき |
folder { folder } |
そのfolderのContentが変わったとき |
global |
いずれかのContentが変わったとき |
unknown |
常に。Coreは全Outputの再生成も要求する。 |
宣言する場所は三つです。
| 対象 | 宣言場所 |
|---|---|
| Plugin page | pageTypes[].outputDependencies |
| manifest entry HTMLを更新するPlugin | rootのoutputDependencies。CoreがすべてのContent Outputに加算する。 |
| 生成output | context.output.emit(..., { dependencies })。未宣言ならunknown。 |
対象が特定できる場合はcontent、tag、folderを使ってください。manifest全体に依存する集合変換はglobalです。表現できない入力だけにunknownを使います。
none、tracked、unsafeは処理済みContentの再利用を示す契約であり、Outputの再利用を示すものではありません。たとえばPluginはnoneとglobal output dependencyを安全に組み合わせられます。また、trackedでも生成outputには狭いcontent dependencyを指定できます。誤ったoutput宣言では古いfileが残るため、outputの範囲を完全に表現できない場合はunknownを使ってください。unknownではincremental SSGよりfull output renderを優先します。
Plugin作者向けのルール
- Contentを変化させるPluginはすべて
processedContentCacheを宣言します。 - 他のContentやfileはCoreのAPI経由でのみ読み、dependencyとして記録させます。
- Content mapを持ったり、vaultを再走査したり、Plugin固有のincremental stateを保存したりしません。
- 観測できない入力がある場合は
unsafeを使います。広い再処理は許容できますが、古い結果の再利用は許容できません。 - manifest段階の変更と生成outputにはOutput Dependencyを宣言します。
正確なfieldはPlugin API、Build lifecycleはBuild Systemを参照してください。