Skip to content

Latest commit

 

History

History
2133 lines (1652 loc) · 60.1 KB

hugo.adoc

File metadata and controls

2133 lines (1652 loc) · 60.1 KB

Hugo Bookshop Reference Guide

Concepts

🌟
Migrating from Bookshop 2.0 to 3.0? Read the Migration Guide first.

Bookshop defines conventions for writing static components for Hugo. Using these conventions, Bookshop provides an ergonomic way to build pages out of your components, build and browse these components locally, and generate rich live editing experiences in CloudCannon.

For an example of what this looks like in a real-world example, see our Editing MegaKit with Bookshop video.

Prerequisites

  • Bookshop requires Node >= 16 installed on your machine.

  • This guide expects that you already have Hugo installed on your machine.

  • Since Hugo Bookshop utilizes the Hugo module system, the Go Programming Language needs to be installed on your machine.

Starting Point

💡
Short on time? You can use our Hugo Bookshop Starter Template and jump straight into editing it in CloudCannon. Come back here when you want to build it out further, or create your own from scratch.

This guide will walk you through getting Bookshop connected to an existing Hugo site. If you don’t have a site already, running hugo new site mysite is a good start. Alternatively, grab one of CloudCannon’s preconfigured Hugo templates.

Creating your Bookshop

The first step is to create the directory structure for your Bookshop. To create this structure, you can run the following command in the root of your repository:

npx @bookshop/init --new component-library --framework hugo

This command should provide you with the following directory structure:

component-library/
├─ config.toml
├─ bookshop/
│  └─ bookshop.config.cjs
├─ components/
│  └─ sample/
│     ├─ sample.bookshop.yml
│     ├─ sample.scss
│     └─ sample.hugo.html
└─ shared/
   ├─ hugo
   │  └─ page.hugo.html
   └─ styles/
      └─ global.scss

Here’s a quick run-through of what has been generated:

config.toml

This file registers your component library as a Hugo Module. This will allow us to use it as a dependency for your site.

bookshop/bookshop.config.cjs

This houses the configuration for your Bookshop project, in this case instructing Bookshop to use the @bookshop/hugo-engine package for any live component rendering.

components/

This is where you will write your component files, a sample component has been added for you.

shared/hugo/

Any non-component files that you want to be able to use when live editing can be placed here. A page helper has been created, which helps render arrays of components.

shared/styles/

Any SCSS files in this directory will be imported alphabetically before component SCSS files. These are used on both the site, and the component browser.


Creating these files yourself?

Bookshop File Reference
component-library/config.toml
[module]
hugoVersion.extended = true
hugoVersion.min = "0.86.1"

[[module.mounts]]
source = "."
target = "layouts/partials/bookshop"
includeFiles = ["**/*.hugo.html"]

[[module.mounts]]
source = "."
target = "assets/bookshop"
component-library/bookshop/bookshop.config.cjs
module.exports = {
    engines: {
        "@bookshop/hugo-engine": {}
    }
}

We’ll cover creating components and shared files in Authoring New Components.

Connecting Your Bookshop to Hugo

Bookshop is distributed as a Hugo Module, so the first step is to make sure that your Hugo site is set up for the Hugo Module system. If there isn’t a go.mod file in your site root, run hugo mod init site.local to initialize this.

With your site ready, we need to pull in the primary dependency of bookshop/hugo, as well as the component library we just created.

The following should be placed in your Hugo site config (usually config.toml in your root folder).

site/config.toml
[module]
replacements = "components.local -> ../component-library"

[[module.imports]]
path = 'components.local'

[[module.imports]]
path = 'github.com/cloudcannon/bookshop/hugo/v3'

Adjust the ../component-library path in replacements if you created it with a different name, or in a different place.

💡
This path is relative to the Hugo themes directory (whether or not it exists), hence ../component-library actually points to a component library in your Hugo source directory.

With that configuration in place, running hugo serve should download the required module and host your site. Nothing will appear different yet, but we now have access to use components.

Lastly, we’ll need to install a few npm packages for Bookshop. These aren’t used as part of your production build, but they provide the developer tooling that enables structured data and live editing.

These packages should be installed at the root of the repository that contains your site. If this folder doesn’t have a package.json file yet, run npm init -y to create one.

To get setup, run the following command to install the needed Bookshop packages:

# npm
npm i --save-exact @bookshop/generate @bookshop/browser @bookshop/hugo-engine

# or yarn
yarn add --exact @bookshop/generate @bookshop/browser @bookshop/hugo-engine
🌟
Bookshop uses a fixed versioning scheme, where all packages are released for every version. It is recommended that you keep the npm packages and your plugins at the same version. To help with this, you can run npx @bookshop/up@latest from your repository root to update all Bookshop packages in sync.

Using Components in Hugo

If you ran the @bookshop/init command earlier, you should see that you now have a file at components/sample/sample.hugo.html. Let’s have a go using that component somewhere on our site.

💡
Bookshop supports multiple SSG targets, which is why we denote this as .hugo.html.
💡
We’ll cover creating these components soon — if you want to add a new component now, you can run npx @bookshop/init --component <name> in your Bookshop directory to scaffold it out automatically.

Bookshop provides a range of partials which we will cover. The most important of these is the default bookshop partial that we will use to access our components.

To start, add the following snippet to one of your layouts:

index.html
...

{{ partial "bookshop" (slice "sample" (dict "text" "Hello from the sample component")) }}

...

This partial expects a slice where the first element is the Bookshop name of a component, and the second element contains the arguments to that component.

If you now load your Hugo site in a browser, you should see the sample component rendered on the page. There won’t be any styles yet, we’ll cover that soon. First though, there are a few neater ways you can use the bookshop partial:

Alternate syntax

Writing a Hugo dict by hand can be cumbersome, and these will often point to front matter objects. If you have the front matter:

index.md
sample:
  text: Hello World

Then you can replace the partial we just wrote with the following:

index.html
...

{{ partial "bookshop" (slice "sample" .Params.sample) }}

...

To go one step further, you can add the key _bookshop_name to this object:

index.md
sample:
  _bookshop_name: sample
  text: Hello World

Which lets us pass the object directly to the bookshop partial:

index.html
...

{{ partial "bookshop" .Params.sample }}

...
💡
The Bookshop name of a component is the path to its directory.
So the name for components/sample/sample.hugo.html is sample,
and the name for components/generic/button/button.hugo.html would be generic/button.
💡
The structures generated by Bookshop for CloudCannon include a _bookshop_name field for you, which can be used to render components dynamically. We’ll cover this a bit later on in Connecting Bookshop to CloudCannon.

Using Shared Bookshop Helpers in Hugo

Shared Bookshop helpers can be placed in the shared/hugo directory. i.e:

component-library/
├─ components/
└─ shared/
  └─ hugo/
    └─ helper.hugo.html

This can then be included using the bookshop_partial partial:

{{ partial "bookshop_partial" (slice "helper" (dict "lorem" "ipsum")) }}

The arguments are the same as the bookshop partial. This is otherwise a standard Hugo partial, with the extra feature that it can be used anywhere within your Hugo site or your components.

ℹ️
While developing locally, components will have access to the rest of your site. This isn’t true when live editing, which is why these helper files exist.

You will notice that @bookshop/init created a page.hugo.html file for you. Given the following front matter:

content_blocks:
  - _bookshop_name: hero
    hero_text: Hello World
    image: /image.png
  - _bookshop_name: cta
    heading: Join our newsletter
    location: /signup

You can render the array of components using the page helper like so:

{{ partial "bookshop_partial" (slice "page" .Params.content_blocks) }}

This will loop through the given array, and render each component according to its _bookshop_name key.

Give this a try now — replace the sample component you added with the page helper, and add the following to your front matter:

content_blocks:
  - _bookshop_name: sample
    text: A sample example
  - _bookshop_name: sample
    text: A second sample example
🌟
It is essential to render arrays of components using the page helper. Live editing only works within Bookshop components and helpers, so using this method means that rearranging and adding new components will work in the Visual Editor.

Importing Bookshop Styles

Bookshop provides some helpers for including the component and global styles that you defined in your component library.

ℹ️
Locating styles inside your Bookshop is optional — you can always define them with the rest of your site — but authoring your styles in your component library will provide a better experience when we cover using the local component browser.

@bookshop/init created a sample.scss file for you at components/sample/sample.scss. This file will currently be empty, but you should add a style here to test that the Bookshop SCSS integration is working for you. Something like:

.c-sample {
  background-color: blanchedalmond;
}

To include all of your Bookshop styles in Hugo, you can use the bookshop_scss partial in your baseof.html layout. This partial returns a slice of all SCSS files, which can then be included into your existing Hugo resource pipeline:

baseof.html
{{ $bookshop_scss_files := partial "bookshop_scss" . }}
{{ $scss := $bookshop_scss_files | resources.Concat "css/bookshop.css" | resources.ToCSS | resources.Minify |
    resources.Fingerprint }}
<link rel="stylesheet" href="{{ $scss.Permalink }}">
🌟
Bookshop SCSS files are loaded in order of all shared files, followed by all component files, alphabetically.

Authoring New Components

💡
To create new components, you can simply run npx @bookshop/init --component <name> in an existing Bookshop

Components live within the components/ directory, each inside a folder bearing their name. A component is defined with a <name>.bookshop.<format> file. This file serves as the schema for the component, defining which properties it may be supplied.

Components may also be nested within folders, which are then referenced as part of the component name. For example, the following structure would define the components hero, button/large and button/small:

components/
├─ hero/
|  |  hero.bookshop.yml
|  └─ hero.hugo.html
└─ button/
   ├─ large/
   |  |  large.bookshop.yml
   │  └─ large.hugo.html
   └─ small/
      |  small.bookshop.yml
      └─ small.hugo.html

Authoring Component Template Files

Beyond the naming convention, Bookshop template files are what you would expect when working with Hugo. A basic button component might look like the following:

components/button/button.hugo.html
<a class="c-button" href="{{ .link_url }}">{{ .link_text }}</a>

Components can, of course, reference other components:

components/hero/hero.hugo.html
<h1>{{ .hero_text }}</h1>
{{ partial "bookshop" (slice "button" (dict "link_url" .url "link_text" "Click me")) }}

Authoring Component Styles

A <component>.scss file can be written alongside your other component files. Beyond the location and the automatic import, there is nothing special about the contents of this file.

Authoring Component Bookshop Files

The Bookshop file for each component is the most important piece of the Bookshop ecosystem. This file drives the Structured Data in CloudCannon, the local component browser, and Bookshop’s live editing.
The sample.bookshop.yml file that our init command generated contains the following:

sample.bookshop.yml
# Metadata about this component, to be used in the CMS
spec:
  structures:
    - content_blocks
  label: Sample
  description:
  icon:
  tags:

# Defines the structure of this component, as well as the default values
blueprint:
  text: "Hello World!"

# Overrides any fields in the blueprint when viewing this component in the component browser
preview:

# Any extra CloudCannon inputs configuration to apply to the blueprint
_inputs: {}

Let’s walk through an example file section by section to understand what’s going on.

Component Spec

spec:
  structures:
    - content_blocks
  label: Example
  description: An example Bookshop component
  icon: book
  tags:
    - example

This section is used when creating the Structure for your component. The structures array defines which structure keys to register this component with. In other words, with the above snippet, this component will be one of the options within an array named content_blocks, or another input configured to use _structures.content_blocks.

The other keys are used when the component is displayed in CloudCannon or in the Bookshop Component Browser. icon should be the name of a suitable material icon to use as the thumbnail for your component.

Component Blueprint

blueprint:
  text: Hello World!

The blueprint is the primary section defining your component. This will be used as the intitial state for your component when it is added to a page, and should thus include all properties used in your template.

Component Preview

preview:
  text: Vestibulum id ligula porta felis euismod semper.

Your blueprint represents the initial state of your component, but in the component browser you might want to see a preview of your component filled out with example data.

The preview object will be merged with your blueprint before a component is rendered in the component browser. This is a deep merge, so given the following specification:

blueprint:
  hero_text: "Hello World"
  cta:
    button_text: ""
    button_url: "#"

preview:
  cta:
    button_text: "Click me"

Your component preview data will be:

hero_text: "Hello World"
cta:
  button_text: "Click me"
  button_url: "#"
ℹ️
In a future Bookshop release, component thumbnails will be automatically generated. This will also use the preview object.

Inputs Configuration

_inputs:
  text:
    type: "html"
    comment: "This comment will appear in the CMS"

The _inputs section of your Bookshop file can be used to configure the keys in your blueprint. This object is passed through unaltered to CloudCannon, so see the CloudCannon Inputs Documentation to read more.

This configuration is scoped to the individual component, so you can configure the same key differently across components — even if the components are nested within one another.

Blueprint Arrays

Arrays of objects in your blueprint will be transformed into CloudCannon Structures automatically, and initialized as empty arrays. Using the following Blueprint:

blueprint:
  text: Sample Text
  items:
    - item_content: Hello World

A new component added to the page will take the form:

text: Sample Text
items: []

Editors will then be able to add and remove objects to the items array.

Nesting Components

Your blueprint can reference other components and structures to create rich page builder experiences:

blueprint:
  hero_text: Hello World
  button: bookshop:button

In this example, the button key will become an Object Structure containing the values specified in your button component blueprint. If you desired an array of buttons, you could use the following:

blueprint:
  hero_text: Hello World
  buttons: [bookshop:button]  # equivalent
  buttons:
    - bookshop:button         # equivalent

If you’re creating a layout component, you likely want to support a set of components. For this, you can reference the keys we defined in spec.structures as such:

blueprint:
  section_label: My Section

  # Make header a single component that can be selected from the content_blocks set
  header: bookshop:structure:content_blocks

  # Make inner_components an array that can contain components marked content_blocks
  inner_components: [bookshop:structure:content_blocks]

To give a concrete example, say we have the following hero.bookshop.yml file:

spec:
  structures: [content_blocks]

blueprint:
  hero_text: Hello World
  cta_button: bookshop:button
  column_components: [bookshop:structure:content_blocks]

Then our hero.hugo.html file to render this might look like the following:

<div class="hero">
  <h1>{{ .hero_text }}</h1>
  {{ with .cta_button }}
    {{ partial "bookshop" . }}
  {{ end }}
  <div class="column">
    {{ range .column_components }}
      {{ partial "bookshop" . }}
    {{ end }}
  </div>
</div>
🌟
Object Structures in CloudCannon may be empty, so testing for the existence of this component in your template is recommended.

Initializing Nested Components

By default, nested components using the bookshop: shorthand will be initialized empty. For example, the blueprint:

blueprint:
  hero_text: Hello World
  button: bookshop:button

Will be initialized in CloudCannon as:

hero_text: Hello World
button:

Where button will provide an editor with the option to add a button component. To instead have the button component exist on creation, you can use the syntax bookshop:button!:

blueprint:
  hero_text: Hello World
  button: bookshop:button!

The same setting can be applied to a structure shorthand by specifying the component that should be initialized. Taking the following example:

blueprint:
  hero_text: Hello World
  column_components:
    - bookshop:structure:content_blocks!(hero)
    - bookshop:structure:content_blocks!(button)

This will be initialized in CloudCannon as:

hero_text: Hello World
column_components:
  - _bookshop_name: hero
    # hero fields
  - _bookshop_name: button
    # button fields

Where column_components can be then further added to/removed from by an editor, as per the tagged structure.

Supported File Formats

💡
When you run npx @bookshop/init --component <name> you will be prompted to pick which configuration format you want to create the component with.

In the examples above, we have been writing the Bookshop configuration files using YAML. This is the recommended format, but you can also choose another if you prefer. Here is a real-world example of a component written in each supported format:

hero.bookshop.yml
# Metadata about this component, to be used in the CMS
spec:
  structures:
    - content_blocks
    - page_sections
  label: Hero
  description: A large hero component suitable for opening a landing page
  icon: crop_landscape
  tags:
    - Above the Fold
    - Multimedia

# Defines the structure of this component, as well as the default values
blueprint:
  hero_text: ""
  hero_level: h1
  hero_image: ""
  hero_image_alt: ""
  subcomponents: [bookshop:structure:content_blocks]

# Overrides any fields in the blueprint when viewing this component in the component browser
preview:
  hero_text: Bookshop Makes Component Driven Development Easy
  hero_image: https://placekitten.com/600/400

# Any extra CloudCannon inputs configuration to apply to the blueprint
_inputs:
  hero_level:
    type: select
    options:
      values:
        - h1
        - h2
        - h3
        - h4
hero.bookshop.toml
# Metadata about this component, to be used in the CMS
[spec]
structures = [ "content_blocks", "page_sections" ]
label = "Hero"
description = "A large hero component suitable for opening a landing page"
icon = "crop_landscape"
tags = [ "Above the Fold", "Multimedia" ]

# Defines the structure of this component, as well as the default values
[blueprint]
hero_text = ""
hero_level = "h1"
hero_image = ""
hero_image_alt = ""
subcomponents = [ "bookshop:structure:content_blocks" ]

# Overrides any fields in the blueprint when viewing this component in the component browser
[preview]
hero_text = "Bookshop Makes Component Driven Development Easy"
hero_image = "https://placekitten.com/600/400"

# Any extra CloudCannon inputs configuration to apply to the blueprint
[_inputs]
hero_level.type = "select"
hero_level.options.values = [ "h1", "h2", "h3", "h4" ]
hero.bookshop.js
module.exports = () => {
  const spec = {
    structures: [
      "content_blocks",
      "page_sections",
    ],
    label: "Hero",
    description: "A large hero component suitable for opening a landing page",
    icon: "crop_landscape",
    tags: [
      "Above the Fold",
      "Multimedia",
    ]
  };

  const blueprint = {
    hero_text: "",
    hero_level: "h1",
    hero_image: "",
    hero_image_alt: "",
    subcomponents: [ "bookshop:structure:content_blocks" ],
  };

  const preview = {
    hero_text: "Bookshop Makes Component Driven Development Easy",
    hero_image: "https://placekitten.com/600/400",
  };

  const _inputs = {
    hero_level: {
      type: "select",
      options: {
        values: [
          "h1",
          "h2",
          "h3",
          "h4",
        ]
      }
    }
  };

  return {
    spec,
    blueprint,
    preview,
    _inputs,
  }
}
hero.bookshop.json
{
  "spec": {
    "structures": [
      "content_blocks",
      "page_sections"
    ],
    "label": "Hero",
    "description": "A large hero component suitable for opening a landing page",
    "icon": "crop_landscape",
    "tags": [
      "Above the Fold",
      "Multimedia"
    ]
  },
  "blueprint": {
    "hero_text": "",
    "hero_level": "h1",
    "hero_image": "",
    "hero_image_alt": "",
    "subcomponents": [ "bookshop:structure:content_blocks" ]
  },
  "preview": {
    "hero_text": "Bookshop Makes Component Driven Development Easy",
    "hero_image": "https://placekitten.com/600/400"
  },
  "_inputs": {
    "hero_level": {
      "type": "select",
      "options": {
        "values": [
          "h1",
          "h2",
          "h3",
          "h4"
        ]
      }
    }
  }
}
💡
Can’t decide? You can always run npx @bookshop/up --format <format> to automatically convert all of your files if you change your mind.

Providing Custom Component Thumbnails

When an editor is selecting a component in CloudCannon, the icon from the component spec will be used as the thumbnail. You can provide a custom image to use instead by placing a <component>.preview.<format> in your component directory. To provide a custom icon, which will be shown when viewing an array of components, you can also provide a <component>.icon.<format> file.

components/
└─ hero/
   |  hero.bookshop.yml
   ├─ hero.preview.png
   ├─ hero.icon.svg
   └─ hero.hugo.html

See the CloudCannon Structures Reference for extra keys that you can set in your component spec to control the display of these images.

⚠️
Make sure that the config.toml file in your Bookshop has includeFiles = ["*/.hugo.html"] alongside the layouts mount.
If this isn’t present, run npx @bookshop/up@latest in your component library to migrate this file.
Without this configuration, your Hugo build will error when it enounters an image file in this directory.

Using the Bookshop Component Browser

The Bookshop component browser allows you to browse and experiment with your components. When running in development the component browser also provides hot reloading of component templating and styles. An example browser showing the components in our Eleventy starter template can be seen here: https://winged-cat.cloudvent.net/components/

In your local development environment, run:
npx @bookshop/browser

By default, this will discover any Bookshop directories in or under the current working directory, and will host a component library on port 30775.

After running this command, a component browser will be viewable on http://localhost:30775

💡
Run npx @bookshop/browser --help to see the available options.

Integrating the Component Browser With Your Site

Coming Soon — Bookshop Hugo provides a helper for embedding the Bookshop browser in your website. This references the component browser started in the previous command, and embeds a live copy into your website.

This allows you to:

  • Rely on your site layouts and styles in your component

  • Host a component browser on a page of your built site

To install the component browser on a page of your site, use the bookshop_component_browser partial in that page’s layout.

components.html
{{ partial "bookshop_component_browser" }}

If you’re running Hugo locally, open another terminal and run npx @bookshop/browser in your Bookshop, or a parent directory. You should now be able to visit the page that you installed the component browser on, and see your components in a playground environment.

💡
If you’re running the browser command on a custom port, you can pass that port as an argument with {{ partial "bookshop_component_browser" 1234 }}

Hosting a Component Library

Any page that contains the bookshop_component_browser snippet will get picked up by npx @bookshop/generate (See Connecting Bookshop to CloudCannon) and turned into a hosted component browser, no extra configuration is needed.

Connecting Bookshop to CloudCannon

ℹ️
This guide assumes that your site is already set up with CloudCannon. If this isn’t the case, hop over to the CloudCannon Documentation and get setup with a successful build first.

Now that you understand how everything works locally, we can integrate Bookshop with CloudCannon. Bookshop does most of the heavy lifting for you, so we’ll get to see the benefits pretty quickly.
The main thing you need to do is create a postbuild script that runs Bookshop’s generate script. This should be placed inside a folder named .cloudcannon at the root of your repository.

.cloudcannon/postbuild
npm i
npx @bookshop/generate

This command will automatically discover your component library as well as the output site from your build, and will then generate CloudCannon Structures for your components. This step will also connect live editing to any pages on your site that contain Bookshop components.

With Hugo, there is one extra step to get live editing working. For any components or helpers in your Hugo layouts, you will need to use the bookshop_bindings partial to connect it to the page’s front matter.

For most setups, your site layouts will only contain the page helper, so this snippet will be all you need:

<layout>.html
{{ partial "bookshop_bindings" `.Params.content_blocks` }}
{{ partial "bookshop_partial" (slice "page" .Params.content_blocks) }}

If you’re using other components in your layouts, add the bookshop_bindings partial to them. This partial needs to be passed a string representation of the data being passed to the component that follows it. For example:

<layout>.html
{{ partial "bookshop_bindings" `(dict hero_text .Params.hero_text)` }}
{{ partial "bookshop" (slice "hero" (dict hero_text .Params.hero_text)) }}
⚠️
Hugo has a built-in option to minify output files, which is enabled by adding the --minify flag to the hugo command. Be sure to keep the HTML comments though (keepComments = true), or else Bookshop will not be able to generate the correct page bindings. Refer to the Hugo documentation for more information.

With that in place, live editing should work in CloudCannon. If you have the following front matter on a page:

---
content_blocks:
---

And the page helper listed above in your layout, then in the CloudCannon sidebar you should be able to add our sample component and see it render live on the page.

💡
If something isn’t working, browse through our Hugo Bookshop Starter Template to see how everything is configured.

Data Bindings

Once you have components rendered on the page, Bookshop will create Visual Data Bindings automatically.

If a component is passed data from the page front matter, you will be able to interact with that component directly on the page.

By default, Bookshop will add bindings for any components on the page, but will not add bindings for shared helper files. This prevents Bookshop rendering data bindings around our shared page helper, so that the components within are immediately accessible.

This behavior can be customised by including a flag in the component’s data. Bookshop will look for any of the following keys:

  • data_binding

  • dataBinding

  • _data_binding

  • _dataBinding

For example:

<!-- This component will **not** get a binding -->
{{ partial "bookshop" (slice "item" (dict "data_binding" false "props" props)) }}

<!-- This partial **will** get a binding -->
{{ partial "bookshop_partial" (slice "page" (dict "data_binding" true "props" props)) }}
ℹ️
This flag only applies to the component directly and doesn’t cascade down. Any subcomponents will follow the standard rules, or can also specify their own Data Binding flag.

Live Editing Site Data and Collections

The npx @bookshop/generate command connects Bookshop to a subset of your site’s data when live editing.

Data will not be available through .Site.Data, but will be available through site.Data. Accessing site pages when live editing is not yet supported in Hugo.

🌟
For data to be accessible, you will need to set data_config: true in your CloudCannon Global Configuration file.

Passing Data to Bookshop Components

In order to live edit Bookshop components, Bookshop needs a clear path between a component and the data it draws from. In general, you should avoid adding logic around your Bookshop components in your site layouts, and instead move that logic into a Bookshop component or helper.

An example:

index.md
---
hero_text: "Hello World"
---
index.html
<!--
  This component can make the connection between "text" and ".Params.hero_text",
  and will work as expected in the visual editor.
-->
{{ partial "bookshop_bindings" `(dict "text" .Params.hero_text)` }}
{{ partial "bookshop" (slice "hero" (dict "text" .Params.hero_text)) }}

{{ my_title := .Params.hero_text }}

<!--
  This component does not have the context to map text back to its origin,
  and will error in the visual editor.
  (Assignments _inside_ Bookshop components will work correctly)
-->
{{ partial "bookshop_bindings" `(dict text $my_title)` }}
{{ partial "bookshop" (slice "hero" (dict "text" $my_title)) }}

The same is true for site data. Access this directly from inside your component, rather than passing it to the component from your layout.

Hugo Live Editing Support

Bookshop’s Hugo editing is based upon a subset of Hugo running in the browser. As such, not all Hugo features will be available within Bookshop components.

As a general rule: Functions that access files directly (such as resources.Get), or access other parts of the site (such as site.GetPage), will not return anything useful in the visual editor.

If you’re encountering an unsupported function, such as finding that resources.Get returns nil, see Rendering Different Content When Live Editing

Rendering Different Content When Live Editing

You can render special content in the live editing environment by checking the Bookshop Live Editor flag. This can be useful to show extra information to your editors, or to use a feature that isn’t supported while live editing.

{{ if site.Params.env_bookshop_live }}
  <p>I am being edited live!</p>
  <h1>Fallback {{ .my_page }} title</h1>
{{ else }}
  <p>Standard build output</p>
  <h1>{{ with site.GetPage .my_page }}{{ .Title }}{{ end }}</h1>
{{ end }}

Disabling Live Editing

Some components won’t be compatible with live editing, in this case you can disable live editing with on a flag in the component’s data. This is intended for components such as navigation and footer blocks that aren’t connected to live editing. In most scenarios, you should use the templating flags in Rendering Different Content When Live Editing.

🌟
This setting will only apply if the component is rendered directly from a site layout. If this component is within another component, it will still update live (as the parent re-rendering will encapsulate it).

Bookshop will look for any of the following keys on a top-level component:

  • live_render

  • liveRender

  • _live_render

  • _liveRender

For example:

<!-- This component will re-render in the visual editor -->
{{ partial "bookshop" (slice "navigation" (dict "props" props)) }}

<!-- This component will **not** re-render in the visual editor -->
{{ partial "bookshop" (slice "navigation" (dict "live_render" false "props" props)) }}

If you have a specific component that you never want to live edit, you can set _live_render in the component’s blueprint.

ℹ️
Since the blueprint only affects the creation of new components, you will need to add the _live_render flag to any existing component data in your front matter.