Skip to content

Commit

Permalink
feat(live): opt-out of live rendering with the live_render flag in props
Browse files Browse the repository at this point in the history
  • Loading branch information
bglw committed Feb 23, 2022
1 parent ba132d9 commit 939be4c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 6 deletions.
38 changes: 38 additions & 0 deletions guides/live-editing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,41 @@ As a result, it is recommended to render groups of Bookshop components using a `
== Editor Links

When live editing, Bookshop will automatically add CloudCannon Editor Links to your components. See the link:editor-links.adoc[Editor Links Guide] for more.

== Customization

Bookshop enables live editing for any component found on the page. For most use cases this will work out-of-the-box, but for complex components that use site functions or data, you might want to disable the live render, so that the component doesn't break in the Visual Editor.

Bookshop can disable live rendering based a flag in the component's props. Bookshop will look for any of the following attributes:

* live_render
* liveRender
* _live_render
* _liveRender

So for example, in Eleventy:

.*index.html*
```
<!-- This component will re-render in the visual editor -->
{% bookshop 'item' props: props %}

<!-- This component will **not** re-render in the visual editor -->
{% bookshop 'page' live_render: false props: props %}
```

If you have a specific component that you never want to live render, you can set `_live_render` in the component props.

.*component.bookshop.toml*
```
[component]
...

[props]
_live_render = false
...
```

This will be hidden from editors due to the leading underscore, and will disable live rendering for that component anywhere it is used.

NOTE: This flag will only work correctly 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)
4 changes: 2 additions & 2 deletions guides/visual-data-bindings.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ So for example, in Eleventy:
.*index.html*
```
<!-- This component will **not** get a binding -->
{% bookshop 'item' data_binding: false, props: props %}
{% bookshop 'item' data_binding: false props: props %}

<!-- This include will get a binding -->
{% bookshop_include 'page' data_binding: true, props: props %}
{% bookshop_include 'page' data_binding: true props: props %}
```

NOTE: 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.
Expand Down
2 changes: 1 addition & 1 deletion javascript-modules/engines/eleventy-engine/lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class Engine {
}
return index ? result?.[index] : result;
} catch (e) {
console.error(`Error evaluating \`${str}\` in the Eleventy engine`, e);
console.error(`Error evaluating \`${str}\` in the Eleventy engine`, e.toString());
return '';
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,22 @@ Feature: Eleventy Bookshop CloudCannon Live Editing Granular Steps
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector h1 should contain "Rerendered"

@web
Scenario: Bookshop doesn't live render flagged components
Given a site/index.html file containing:
"""
---
[front_matter]
---
{% bookshop "single" live_render: false bind: block %}
"""
Given 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
"""
block:
title: "Rerendered"
"""
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector h1 should contain "Hello There"
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,24 @@ Feature: Hugo Bookshop CloudCannon Live Editing Granular Steps
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector h1 should contain "Rerendered"

@web
Scenario: Bookshop doesn't live render flagged components
Given a site/layouts/index.html file containing:
"""
<html>
<body>
{{ partial "bookshop_bindings" `(dict "title" .Params.block.title "_live_render" false)` }}
{{ partial "bookshop" (slice "single" (dict "title" .Params.block.title "_live_render" false)) }}
</body>
</html>
"""
Given 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
"""
block:
title: "Rerendered"
"""
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector h1 should contain "Hello There"
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,22 @@ Feature: Jekyll Bookshop CloudCannon Live Editing Granular Steps
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector h1 should contain "Rerendered"

@web
Scenario: Bookshop doesn't live render flagged components
Given a site/index.html file containing:
"""
---
[front_matter]
---
{% bookshop single bind=page.block liveRender=false %}
"""
Given 🌐 I have loaded my site in CloudCannon
When 🌐 CloudCannon pushes new yaml:
"""
block:
title: "Rerendered"
"""
Then 🌐 There should be no errors
* 🌐 There should be no logs
* 🌐 The selector h1 should contain "Hello There"
12 changes: 9 additions & 3 deletions javascript-modules/integration-tests/main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ test("(unit test) test parity between SSGs", async t => {
const feats = await getFeatures();
const ssgFeats = feats.filter(f => ssgs.test(f));
const scenarios = {};
let errs = [];
ssgFeats.forEach(f => {
const ssg = f.split('/')[0];
scenarios[ssg] = scenarios[ssg] || [];

const doc = parser.convertFeatureFileToJSON(path.join(__dirname, 'features', f));
doc.feature.children.forEach(scenario => {
if (scenario.steps.map(t => t.text).filter(t => /DEBUG/.test(t)).length) {
errs.push(`[!DEBUGS] Scenario "${scenario.name}" contains a DEBUG step.`)
}
});

if (doc.feature.tags.map(d => d.name).includes("@bespoke")) return;
scenarios[ssg] = [
...scenarios[ssg],
Expand All @@ -31,15 +38,14 @@ test("(unit test) test parity between SSGs", async t => {
.map(c => c.name)
];
});
let errs = [];
Object.entries(scenarios).forEach(([ssg, tests]) => {
Object.entries(scenarios).forEach(([otherSsg, otherTests]) => {
tests.forEach(scenario => {
if (!otherTests.includes(scenario)) {
errs.push(`${ssg} has the scenario "${scenario}". ${otherSsg} should also have this test.`);
errs.push(`[!PARITY] ${otherSsg} is missing the scenario "${scenario}" (scenario exists for ${ssg} and is not @bespoke)`);
}
});
});
});
t.deepEqual(errs, []);
t.deepEqual(errs.sort(), []);
});
7 changes: 7 additions & 0 deletions javascript-modules/live/lib/app/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ export const renderComponentUpdates = async (liveInstance, documentNode) => {
// We only need to render the outermost component
if (depth) return;

const liveRenderFlag = scope?.live_render
?? scope?.liveRender
?? scope?._live_render
?? scope?._liveRender
?? true;
if (!liveRenderFlag) return;

const output = vDom.createElement('div');
await liveInstance.renderElement(
name,
Expand Down

0 comments on commit 939be4c

Please sign in to comment.