diff --git a/CHANGELOG.md b/CHANGELOG.md index 7588d587..705b0385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/guides/hugo-extra-files.md b/guides/hugo-extra-files.md new file mode 100644 index 00000000..edc8d444 --- /dev/null +++ b/guides/hugo-extra-files.md @@ -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": `` + } + } + } +} +``` + +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" } + ) + } + } + } +} +``` diff --git a/javascript-modules/engines/hugo-engine/lib/engine.js b/javascript-modules/engines/hugo-engine/lib/engine.js index 4062a445..47c1d31d 100644 --- a/javascript-modules/engines/hugo-engine/lib/engine.js +++ b/javascript-modules/engines/hugo-engine/lib/engine.js @@ -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; @@ -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. diff --git a/javascript-modules/integration-tests/features/hugo/live_editing/hugo_bookshop_live_extra_data.feature b/javascript-modules/integration-tests/features/hugo/live_editing/hugo_bookshop_live_extra_data.feature new file mode 100644 index 00000000..9d2d3973 --- /dev/null +++ b/javascript-modules/integration-tests/features/hugo/live_editing/hugo_bookshop_live_extra_data.feature @@ -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: + """ + +
+ {{ partial "bookshop_bindings" `.Params.component_data` }} + {{ partial "bookshop" (slice "test_component" .Params.component_data) }} + + + """ + + @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: + """ +{{ .label }}
+Text: [{{ .text }}]
+ """ + Given a component-lib/components/test_component/test_component.hugo.html file containing: + """ +Inside the Bookshop component!
+ {{ 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!]"