Write a binding package
Publish the selector document that turns a page into your extract.
A binding package configures one engine for one site. For dom@1 it is a manifest plus a selector document. It is data only: no package code runs during extraction.
The manifest
bindings/<name>/fruitful-package.json:
{
"kind": "binding",
"name": "com.example.blog.binding.archive",
"version": "1.0.0",
"extractor": "dom@1",
"files": ["binding.json"],
"emits": "com.example.blog.post@^1.0.0#extract",
"document": "binding.json"
}emits names the def the output conforms to. When it is the same def the consuming package's transform expects, nothing else is needed. When it differs, that package's transform exports normalize; see Write the normalize transform.
The document
binding.json is an entry selector and the fields read relative to each match:
{
"$schema": "https://fruitful.app/schemas/fruitful-binding.schema.json",
"entry": "article.post",
"fields": [
{ "name": "title", "selector": "h2 a", "attribute": "textContent" },
{ "name": "href", "selector": "h2 a", "attribute": "href" },
{ "name": "date", "selector": "time", "attribute": "datetime" },
{ "name": "author", "selector": ".byline a", "attribute": "textContent" },
{ "name": "tags", "selector": ".tags a", "attribute": "textContent", "many": true },
{
"name": "image",
"selector": "figure",
"fields": [
{ "name": "src", "selector": "img", "attribute": "src" },
{ "name": "alt", "selector": "img", "attribute": "alt" }
]
}
]
}attributereadstextContent,innerHTML,exists(a boolean), or any attribute such ashrefordata-id.manycollects every match as an array.- Nested
fieldsread an object relative to the matched element. Useattributeorfields, not both. :scopeselects the entry element itself.:scope + trreaches a sibling row, which Hacker News needs because each submission spans two table rows.
Every field must be declared by the emitted extract def, and the def's required list must be covered. Validation reports both.
Check it against a fixture
yarn fruitful plugin coverage feed-packages/<name> --jsonCoverage reports, per fixture, how many entries the entry selector matched, how many extracted, which required fields were missing on which entries, and any field never observed. Iterate until required fields are present on every entry and nothing is never observed, then explain any remaining gap in the package notes.
Reference: Binding package.