Skip to content

Commit

Permalink
Merge pull request #162 from CloudCannon/feat/hugo-extra-files
Browse files Browse the repository at this point in the history
Add ability to register extra files with Hugo Bookshop for live editing
  • Loading branch information
bglw authored Sep 27, 2023
2 parents 906f726 + f352417 commit ad8ea76
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

## Unreleased

* Added support for extraFiles that can be passed to Bookshop's Hugo engine, allowing custom shortcodes and partials to be used
* See the [Hugo extra files guide](https://github.com/CloudCannon/bookshop/blob/main/guides/hugo-extra-files.md) for interim documentation

## v3.7.0 (September 21, 2023)

* Added support for Sass and CSS imports in Astro components
Expand Down
67 changes: 67 additions & 0 deletions guides/hugo-extra-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Using extra files with Hugo Bookshop

Bookshop's Hugo engine supports defining custom files that can be used in the Visual Editor. This feature allows you to use custom partials and shortcodes without having to move those files into Bookshop's component directory.

Note that the templating within these files is constrained by Bookshop's [Hugo Live Editing Support](https://github.com/CloudCannon/bookshop/blob/main/guides/hugo.adoc#hugo-live-editing-support) in the same way as your core components, so this _doesn't_ provide a way to utilize site functions such as `resources.Get`.

To provide extra files to Hugo Bookshop, specify the path and content of each in your Bookshop configuration file.

E.g. for a component library at `component-lib`, the file `component-lib/bookshop/bookshop.config.js` file should contain:

```js
module.exports = {
engines: {
"@bookshop/hugo-engine": {
extraFiles: {
"layouts/shortcodes/name.html": `My name is {{ .Get "name" }}`
"layouts/partials/test.html": `<!-- raw partial text -->`
}
}
}
}
```

Bookshop uses a simulated Hugo file structure when live editing, so the path you specify should be relative to the root of a Hugo site — most of the time the files you're writing should be at `layouts/shortcodes/*` and `layouts/partials/*`.

This configuration file is evaluated at build-time, so you can use NodeJS APIs to load files rather than specifying them inline. For example:

```js
const fs = require("fs");

module.exports = {
engines: {
"@bookshop/hugo-engine": {
extraFiles: {
"layouts/shortcodes/name.html": fs.readFileSync("site/layouts/shortcodes/name.html", { encoding: "utf8" }),
"layouts/partials/test.html": fs.readFileSync("site/layouts/partials/test.html", { encoding: "utf8" })
}
}
}
}
```

The working directory will be the root of your repository, unless you have changed directory in your CloudCannon postbuild script before running `npx @bookshop/generate`.

If you need to be more explicit about the location, you can use `__dirname` to reference files relative to the Bookshop configuration file itself:

```js
const fs = require("fs");
const path = require("path");

module.exports = {
engines: {
"@bookshop/hugo-engine": {
extraFiles: {
"layouts/shortcodes/name.html": fs.readFileSync(
path.join(__dirname, "../../site/layouts/shortcodes/name.html"),
{ encoding: "utf8" }
),
"layouts/partials/test.html": fs.readFileSync(
path.join(__dirname, "../../site/layouts/partials/test.html"),
{ encoding: "utf8" }
)
}
}
}
}
```
2 changes: 2 additions & 0 deletions javascript-modules/engines/hugo-engine/lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class Engine {
this.key = 'hugo';
this.name = options.name;
this.files = options.files;
this.extraFiles = options.extraFiles;
this.origin = (typeof document === 'undefined' ? '' : document.currentScript?.src) || `/bookshop.js`;
this.synthetic = options.synthetic ?? false;

Expand Down Expand Up @@ -82,6 +83,7 @@ export class Engine {

const componentSuccess = window["writeHugoFiles"](JSON.stringify(mappedFiles));
const templateSuccess = window["writeHugoFiles"](JSON.stringify(templates));
const extraSuccess = window["writeHugoFiles"](JSON.stringify(this.extraFiles));

// BIG OL' TODO: Writing these files ahead of render() seems to be load-bearing,
// which doesn't yet make sense to me.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
@hugo @bespoke
Feature: Hugo Bookshop CloudCannon Live Editing With Extra Files

Background:
Given the file tree:
"""
package.json from starters/generate/package.json # <-- this .json line hurts my syntax highlighting
component-lib/
go.mod from starters/hugo/components.go.mod
config.toml from starters/hugo/components.config.toml
bookshop/
bookshop.config.js from starters/hugo/bookshop.config.js
site/
go.mod from starters/hugo/site.go.mod
config.toml from starters/hugo/site.config.toml
"""
* a site/layouts/index.html file containing:
"""
<html>
<body>
{{ partial "bookshop_bindings" `.Params.component_data` }}
{{ partial "bookshop" (slice "test_component" .Params.component_data) }}
</body>
</html>
"""

@web
Scenario: Bookshop can live edit with a custom shortcode
Given [front_matter]:
"""
component_data:
label: "Statement:"
body_markdown: >-
# Hello World
"""
Given a site/content/_index.md file containing:
"""
---
[front_matter]
---
"""
Given a component-lib/components/test_component/test_component.hugo.html file containing:
"""
<p class="label">{{ .label }}</p>
<div class="content">
{{ .body_markdown | markdownify }}
</div>
"""
Given a component-lib/bookshop/bookshop.config.js file containing:
"""
module.exports = {
engines: {
"@bookshop/hugo-engine": {
extraFiles: {
"layouts/shortcodes/name.html": "Alan"
}
}
}
}
"""
Given 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
"""
component_data:
label: "Statement:"
body_markdown: >-
# Hello {{% name %}}!
"""
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector .label should contain "Statement:"
* 🌐 The selector .content>h1 should contain "Hello Alan!"

@web
Scenario: Bookshop can live edit with a custom partial
Given [front_matter]:
"""
component_data:
text: "Hello World"
"""
Given a site/content/_index.md file containing:
"""
---
[front_matter]
---
"""
Given a site/layouts/partials/text.html file containing:
"""
<p class="text">Text: [{{ .text }}]</p>
"""
Given a component-lib/components/test_component/test_component.hugo.html file containing:
"""
<p class="component">Inside the Bookshop component!</p>
{{ partial "text.html" . }}
"""
Given a component-lib/bookshop/bookshop.config.js file containing:
"""
const fs = require("fs");
const path = require("path");
module.exports = {
engines: {
"@bookshop/hugo-engine": {
extraFiles: {
"layouts/partials/text.html": fs.readFileSync(
path.join(__dirname, "../../site/layouts/partials/text.html"),
{ encoding: "utf8" }
)
}
}
}
}
"""
Given 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
"""
component_data:
text: "Hello CloudCannon!"
"""
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector .component should contain "Inside the Bookshop component!"
* 🌐 The selector .text should contain "Text: [Hello CloudCannon!]"

0 comments on commit ad8ea76

Please sign in to comment.