@bookshop/hugo-engine 3.5.0 → 3.6.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/.nyc_output/processinfo/{0abf820b-2028-4355-a51f-2ae7ceb14fc6.json → 1431bee8-ccd0-4960-baf6-ada559b83a2a.json} +1 -1
  2. package/.nyc_output/processinfo/{0c4785c3-d657-4de0-97e2-a5c8bed13440.json → 29274b6a-50dc-45fe-b64c-01e87d36afd7.json} +1 -1
  3. package/.nyc_output/processinfo/{5ab837f9-47fd-4613-be9e-8cad7f893cb6.json → afbfa0bc-7bfc-4af9-804e-4279bb321fd7.json} +1 -1
  4. package/.nyc_output/processinfo/{6671a456-fd5a-43f9-8289-01bfd323ba6a.json → d79aa8b6-f561-45d4-adf3-f3418730227c.json} +1 -1
  5. package/.nyc_output/processinfo/e59a4cba-f1f7-4145-8de9-d7ee7cfa5830.json +1 -0
  6. package/.nyc_output/processinfo/index.json +1 -1
  7. package/bookshop-hugo-templates/helpers/component.html +11 -2
  8. package/bookshop-hugo-templates/helpers/flat_component_key.html +9 -0
  9. package/full-hugo-renderer/hugo_renderer.wasm +0 -0
  10. package/full-hugo-renderer/hugo_renderer.wasm.gz +0 -0
  11. package/lib/engine.js +56 -18
  12. package/lib/engine.test.js +79 -2
  13. package/lib/engine__test__.js +452 -0
  14. package/package.json +4 -3
  15. package/prep__test__.js +5 -0
  16. package/.nyc_output/processinfo/5217b0dd-26fb-4b8d-af21-def9fd98ee74.json +0 -1
  17. /package/.nyc_output/{0abf820b-2028-4355-a51f-2ae7ceb14fc6.json → 1431bee8-ccd0-4960-baf6-ada559b83a2a.json} +0 -0
  18. /package/.nyc_output/{0c4785c3-d657-4de0-97e2-a5c8bed13440.json → 29274b6a-50dc-45fe-b64c-01e87d36afd7.json} +0 -0
  19. /package/.nyc_output/{5217b0dd-26fb-4b8d-af21-def9fd98ee74.json → afbfa0bc-7bfc-4af9-804e-4279bb321fd7.json} +0 -0
  20. /package/.nyc_output/{5ab837f9-47fd-4613-be9e-8cad7f893cb6.json → d79aa8b6-f561-45d4-adf3-f3418730227c.json} +0 -0
  21. /package/.nyc_output/{6671a456-fd5a-43f9-8289-01bfd323ba6a.json → e59a4cba-f1f7-4145-8de9-d7ee7cfa5830.json} +0 -0
@@ -1 +1 @@
1
- {"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","pid":5428,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683759482562,"ppid":5417,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/0abf820b-2028-4355-a51f-2ae7ceb14fc6.json","externalId":"","uuid":"0abf820b-2028-4355-a51f-2ae7ceb14fc6","files":[]}
1
+ {"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","pid":5712,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1684198717201,"ppid":5701,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/1431bee8-ccd0-4960-baf6-ada559b83a2a.json","externalId":"","uuid":"1431bee8-ccd0-4960-baf6-ada559b83a2a","files":[]}
@@ -1 +1 @@
1
- {"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","pid":5434,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683759482573,"ppid":5417,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/0c4785c3-d657-4de0-97e2-a5c8bed13440.json","externalId":"","uuid":"0c4785c3-d657-4de0-97e2-a5c8bed13440","files":[]}
1
+ {"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","pid":5740,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1684198717601,"ppid":5701,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/29274b6a-50dc-45fe-b64c-01e87d36afd7.json","externalId":"","uuid":"29274b6a-50dc-45fe-b64c-01e87d36afd7","files":[]}
@@ -1 +1 @@
1
- {"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","pid":5451,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683759483129,"ppid":5417,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/5ab837f9-47fd-4613-be9e-8cad7f893cb6.json","externalId":"","uuid":"5ab837f9-47fd-4613-be9e-8cad7f893cb6","files":[]}
1
+ {"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","pid":5718,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1684198717210,"ppid":5701,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/afbfa0bc-7bfc-4af9-804e-4279bb321fd7.json","externalId":"","uuid":"afbfa0bc-7bfc-4af9-804e-4279bb321fd7","files":[]}
@@ -1 +1 @@
1
- {"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","pid":5450,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683759483075,"ppid":5417,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/6671a456-fd5a-43f9-8289-01bfd323ba6a.json","externalId":"","uuid":"6671a456-fd5a-43f9-8289-01bfd323ba6a","files":[]}
1
+ {"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","pid":5734,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/lib/worker/subprocess.js"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1684198717580,"ppid":5701,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/d79aa8b6-f561-45d4-adf3-f3418730227c.json","externalId":"","uuid":"d79aa8b6-f561-45d4-adf3-f3418730227c","files":[]}
@@ -0,0 +1 @@
1
+ {"parent":null,"pid":5701,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/cli.js","-v"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1684198716760,"ppid":5690,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/e59a4cba-f1f7-4145-8de9-d7ee7cfa5830.json","externalId":"","uuid":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","files":[]}
@@ -1 +1 @@
1
- {"processes":{"0abf820b-2028-4355-a51f-2ae7ceb14fc6":{"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","children":[]},"0c4785c3-d657-4de0-97e2-a5c8bed13440":{"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","children":[]},"5217b0dd-26fb-4b8d-af21-def9fd98ee74":{"parent":null,"children":["0abf820b-2028-4355-a51f-2ae7ceb14fc6","0c4785c3-d657-4de0-97e2-a5c8bed13440","5ab837f9-47fd-4613-be9e-8cad7f893cb6","6671a456-fd5a-43f9-8289-01bfd323ba6a"]},"5ab837f9-47fd-4613-be9e-8cad7f893cb6":{"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","children":[]},"6671a456-fd5a-43f9-8289-01bfd323ba6a":{"parent":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","children":[]}},"files":{},"externalIds":{}}
1
+ {"processes":{"1431bee8-ccd0-4960-baf6-ada559b83a2a":{"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","children":[]},"29274b6a-50dc-45fe-b64c-01e87d36afd7":{"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","children":[]},"afbfa0bc-7bfc-4af9-804e-4279bb321fd7":{"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","children":[]},"d79aa8b6-f561-45d4-adf3-f3418730227c":{"parent":"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830","children":[]},"e59a4cba-f1f7-4145-8de9-d7ee7cfa5830":{"parent":null,"children":["1431bee8-ccd0-4960-baf6-ada559b83a2a","29274b6a-50dc-45fe-b64c-01e87d36afd7","afbfa0bc-7bfc-4af9-804e-4279bb321fd7","d79aa8b6-f561-45d4-adf3-f3418730227c"]}},"files":{},"externalIds":{}}
@@ -12,14 +12,23 @@
12
12
  {{- $component_name := index . 0 -}}
13
13
  {{- $component_props := index . 1 -}}
14
14
  {{- $component_path := partial "_bookshop/helpers/component_key" $component_name -}}
15
+ {{- $flat_component_path := partial "_bookshop/helpers/flat_component_key" $component_name -}}
15
16
 
17
+ {{- $resolved_component := false -}}
16
18
  {{- if templates.Exists ( printf "partials/%s" $component_path ) -}}
19
+ {{- $resolved_component = $component_path -}}
20
+ {{- else if templates.Exists ( printf "partials/%s" $flat_component_path ) -}}
21
+ {{- $resolved_component = $flat_component_path -}}
22
+ {{- end -}}
23
+
24
+ {{- if $resolved_component -}}
17
25
 
18
26
  {{/* Suppress comments when live editing {{ (printf "<!--bookshop-live name(%s)-->" $component_name) | safeHTML }} */}}
19
- {{- partial $component_path $component_props -}}
27
+ {{- partial $resolved_component $component_props -}}
20
28
  {{/* Suppress comments when live editing {{ "<!--bookshop-live end-->" | safeHTML }} */}}
21
29
 
22
30
  {{- else -}}
23
31
  {{- $file_loc := slicestr $component_path 9 -}}
24
- {{- partial "_bookshop/errors/err" (printf "Component \"%s\" does not exist.\n Create this component by placing a file in your bookshop at %s" $component_name $file_loc) -}}
32
+ {{- $flat_file_loc := slicestr $flat_component_path 9 -}}
33
+ {{- partial "_bookshop/errors/err" (printf "Component \"%s\" does not exist.\n Create this component by placing a file in your bookshop at %s or %s" $component_name $file_loc $flat_file_loc) -}}
25
34
  {{- end -}}
@@ -0,0 +1,9 @@
1
+ {{/*
2
+ Converts a bare Bookshop component key to a flat Bookshop path
3
+ i.e. "a/b" --> "bookshop/components/a/b.hugo.html"
4
+
5
+ Expects a String.
6
+ */}}
7
+
8
+ {{ $component_path := (printf "bookshop/components/%s.hugo.html" .) }}
9
+ {{ return $component_path }}
package/lib/engine.js CHANGED
@@ -2,7 +2,6 @@ import compressedHugoWasm from "../full-hugo-renderer/hugo_renderer.wasm.gz";
2
2
  import { gunzipSync } from 'fflate';
3
3
  import translateTextTemplate from './translateTextTemplate.js';
4
4
  import { IdentifierParser } from './hugoIdentifierParser.js';
5
- import { version } from '../package.json';
6
5
 
7
6
  const sleep = (ms = 0) => {
8
7
  return new Promise(r => setTimeout(r, ms));
@@ -42,9 +41,12 @@ export class Engine {
42
41
  this.key = 'hugo';
43
42
  this.name = options.name;
44
43
  this.files = options.files;
45
- this.origin = document.currentScript?.src || `/bookshop.js`;
44
+ this.origin = (typeof document === 'undefined' ? '' : document.currentScript?.src) || `/bookshop.js`;
45
+ this.synthetic = options.synthetic ?? false;
46
46
 
47
- this.initializeHugo();
47
+ if (!this.synthetic) {
48
+ this.initializeHugo();
49
+ }
48
50
  }
49
51
 
50
52
  async initializeHugo() {
@@ -56,6 +58,7 @@ export class Engine {
56
58
  "layouts/partials/bookshop_bindings.html": (await import("../bookshop-hugo-templates/bookshop_bindings.html")).default,
57
59
  "layouts/partials/_bookshop/helpers/component.html": (await import("../bookshop-hugo-templates/helpers/component.html")).default,
58
60
  "layouts/partials/_bookshop/helpers/component_key.html": (await import("../bookshop-hugo-templates/helpers/component_key.html")).default,
61
+ "layouts/partials/_bookshop/helpers/flat_component_key.html": (await import("../bookshop-hugo-templates/helpers/flat_component_key.html")).default,
59
62
  "layouts/partials/_bookshop/helpers/partial.html": (await import("../bookshop-hugo-templates/helpers/partial.html")).default,
60
63
  "layouts/partials/_bookshop/helpers/partial_key.html": (await import("../bookshop-hugo-templates/helpers/partial_key.html")).default,
61
64
  "layouts/partials/_bookshop/errors/bad_bookshop_tag.html": (await import("../bookshop-hugo-templates/errors/bad_bookshop_tag.html")).default,
@@ -79,6 +82,20 @@ export class Engine {
79
82
 
80
83
  const componentSuccess = window["writeHugoFiles"](JSON.stringify(mappedFiles));
81
84
  const templateSuccess = window["writeHugoFiles"](JSON.stringify(templates));
85
+
86
+ // BIG OL' TODO: Writing these files ahead of render() seems to be load-bearing,
87
+ // which doesn't yet make sense to me.
88
+ // If these files are created for the first time in render(), the site doesn't
89
+ // appear to build (need to extract some logs from hugo to determine why).
90
+ // Perplexingly, writing then building in eval() appears to have been working
91
+ // fine in the same case.
92
+ // My suspicion would be something around our changeEvents() handling
93
+ // in the Hugo builder, but I don't know why that wouldn't affect eval()... 🤷‍♂️
94
+ // For now, the load-bearing stubs will live in this Hugo initialization.
95
+ window.writeHugoFiles(JSON.stringify({
96
+ "layouts/index.html": "Uninitialized Layout",
97
+ "content/_index.md": "{ \”initialized\": false }\n"
98
+ }));
82
99
  }
83
100
 
84
101
  async initializeLocalCompressedHugo() {
@@ -140,14 +157,20 @@ export class Engine {
140
157
  return `components/${name}/${base}.hugo.html`;
141
158
  }
142
159
 
160
+ getFlatComponentKey(name) {
161
+ return `components/${name}.hugo.html`;
162
+ }
163
+
143
164
  getComponent(name) {
144
165
  const key = this.getComponentKey(name);
145
- return this.files?.[key];
166
+ const flatKey = this.getFlatComponentKey(name);
167
+ return this.files?.[key] ?? this.files?.[flatKey];
146
168
  }
147
169
 
148
170
  hasComponent(name) {
149
171
  const key = this.getComponentKey(name);
150
- return !!this.files?.[key];
172
+ const flatKey = this.getFlatComponentKey(name);
173
+ return !!(this.files?.[key] ?? this.files?.[flatKey]);
151
174
  }
152
175
 
153
176
  hasShared(name) {
@@ -210,9 +233,9 @@ export class Engine {
210
233
  const component_regex = /execute of template failed: template: ([^:]+):\d/ig;
211
234
  let file_stack = [...error_string.matchAll(component_regex)].map(([, file]) => `layouts/${file}`);
212
235
  if (file_stack.length) {
213
- const deepest_errored_component = file_stack[file_stack.length-1];
236
+ const deepest_errored_component = file_stack[file_stack.length - 1];
214
237
  const error_chunks = error_string.split("execute of template failed:");
215
- const error_msg = error_chunks[error_chunks.length-1] ?? "template error";
238
+ const error_msg = error_chunks[error_chunks.length - 1] ?? "template error";
216
239
  window.writeHugoFiles(JSON.stringify({
217
240
  [deepest_errored_component]: [
218
241
  `<div style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
@@ -232,7 +255,7 @@ export class Engine {
232
255
  return `layouts/partials/bookshop/components/${file}/${filename}.hugo.html`;
233
256
  });
234
257
  if (file_stack.length) {
235
- const deepest_errored_component = file_stack[file_stack.length-1];
258
+ const deepest_errored_component = file_stack[file_stack.length - 1];
236
259
  window.writeHugoFiles(JSON.stringify({
237
260
  [deepest_errored_component]: [
238
261
  `<div class="bookshop_error" style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
@@ -308,9 +331,25 @@ export class Engine {
308
331
  }
309
332
 
310
333
  async eval(str, props = [{}], logger) {
311
- while (!window.buildHugo) await sleep(10);
334
+ if (!this.synthetic) {
335
+ while (!window.buildHugo) await sleep(10);
336
+ }
312
337
  let props_obj = props.reduce((a, b) => { return { ...a, ...b } });
313
338
  let full_props = props_obj;
339
+ str = str.trim();
340
+
341
+ // We can return early if we're evaluating a literal string or digit
342
+ if (/^".*"$|^`.*`$|^\d+(\.\d+)?$|^true$|^false$/.test(str)) {
343
+ logger?.log?.(`Unwrapping the string ${str}`);
344
+ try {
345
+ if (/^`.*`$/.test(str)) {
346
+ return JSON.parse(str.replace(/^`|`$/g, '"'));
347
+ }
348
+ return JSON.parse(str);
349
+ } catch (e) {
350
+ logger?.log?.(`Was not valid JSON for some reason. Moving on...`);
351
+ }
352
+ }
314
353
 
315
354
  // We're capable of looking up a simple variable
316
355
  // (and it's hard to pass to wasm since we store variables on the context)
@@ -336,17 +375,11 @@ export class Engine {
336
375
  const process = async (obj) => {
337
376
  for (const [k, v] of Object.entries(obj)) {
338
377
  if (typeof v === "string") {
339
- if (/^".*"$/.test(v)) {
340
- // Normalized strings look like "\"string\""
341
- logger?.log?.(`Unwrapping the string ${k}: ${v}`);
342
- obj[k] = JSON.parse(v);
343
- } else {
344
- // No inner quotes means it needs to be evaled again
345
- logger?.log?.(`Evaluating the inner ${k}: ${v}`);
346
- obj[k] = await this.eval(v, [props_obj], logger.nested());
347
- }
378
+ logger?.log?.(`Evaluating the inner ${k}: ${v}`);
379
+ obj[k] = await this.eval(v, [props_obj], logger?.nested?.());
348
380
  } else if (typeof v === "object") {
349
381
  logger?.log?.(`Processing the inner object ${k}`);
382
+ process(v);
350
383
  }
351
384
  }
352
385
  }
@@ -380,6 +413,11 @@ export class Engine {
380
413
  }
381
414
  }).join('');
382
415
  const eval_str = `${variable_decl}{{ with .Params.props }}${assignments}{{ jsonify (${str}) }}{{ end }}`;
416
+
417
+ if (this.synthetic) {
418
+ return null;
419
+ }
420
+
383
421
  window.writeHugoFiles(JSON.stringify({
384
422
  "layouts/index.html": eval_str,
385
423
  "content/_index.md": JSON.stringify({ props: props_obj, full_props: full_props }, null, 2)
@@ -1,5 +1,82 @@
1
1
  import test from 'ava';
2
+ import { Engine } from './engine__test__.js';
2
3
 
3
- test("stub", async t => {
4
- t.pass()
4
+ let engine = null, logger = null;
5
+ test.before(async t => {
6
+ engine = new Engine({ synthetic: true });
7
+ logger = {
8
+ log: (str) => console.log(str),
9
+ nested: () => logger,
10
+ }
11
+ });
12
+
13
+ test("eval value", async t => {
14
+ const val = await engine.eval(`.test`, [{ test: 'bookshop' }]);
15
+ t.is(val, 'bookshop');
16
+ });
17
+
18
+ test("eval variable", async t => {
19
+ const val = await engine.eval(`$var`, [{ "$var": 'string' }, { test: 'bookshop' }]);
20
+ t.is(val, 'string');
21
+ });
22
+
23
+ test("eval literals", async t => {
24
+ let val = await engine.eval(`"test"`, [{}]);
25
+ t.is(val, 'test');
26
+
27
+ val = await engine.eval("`test`", [{}]);
28
+ t.is(val, 'test');
29
+
30
+ val = await engine.eval("4", [{}]);
31
+ t.is(val, 4);
32
+
33
+ val = await engine.eval("99.9", [{}]);
34
+ t.is(val, 99.9);
35
+
36
+ val = await engine.eval("true", [{}]);
37
+ t.is(val, true);
38
+
39
+ val = await engine.eval("false", [{}]);
40
+ t.is(val, false);
41
+ });
42
+
43
+ test("eval through dot scope", async t => {
44
+ const val_later = await engine.eval(`.test`, [{ test: 'bookshop' }, { ".": { test: 'inner_bookshop' } }]);
45
+ t.is(val_later, 'inner_bookshop');
46
+
47
+ const val_earlier = await engine.eval(`.test`, [{ ".": { test: 'inner_bookshop' } }, { test: 'bookshop' }]);
48
+ t.is(val_earlier, 'inner_bookshop');
49
+ });
50
+
51
+ test("access dot scope", async t => {
52
+ const dot = await engine.eval(`.`, [{ test: 'bookshop' }]);
53
+ t.deepEqual(dot, { test: 'bookshop' });
54
+
55
+ const dot_dot = await engine.eval(`.`, [{ test: 'bookshop' }, { ".": { test: 'inner_bookshop' } }]);
56
+ t.deepEqual(dot_dot, { test: 'inner_bookshop' });
57
+ });
58
+
59
+ test("eval literal dict", async t => {
60
+ const dot = await engine.eval(`(dict "zanzibar" "gin")`, [{}]);
61
+ t.deepEqual(dot, { zanzibar: 'gin' });
62
+ });
63
+
64
+ test("eval dict with scope access", async t => {
65
+ const dot = await engine.eval(`(dict "zanzibar" .live)`, [{ live: "editing" }]);
66
+ t.deepEqual(dot, { zanzibar: 'editing' });
67
+ });
68
+
69
+ test("eval dict with variable access", async t => {
70
+ const dot = await engine.eval(`(dict "zanzibar" $live)`, [{ "$live": "editing" }]);
71
+ t.deepEqual(dot, { zanzibar: 'editing' });
72
+ });
73
+
74
+ test("eval complex dict", async t => {
75
+ const dot = await engine.eval(`(dict "zanzibar" (slice .edit .live) "and" "a_literal")`, [{ live: "editing" }, { edit: "live" }]);
76
+ t.deepEqual(dot, { zanzibar: ['live', 'editing'], and: 'a_literal' });
77
+ });
78
+
79
+ test("eval array index", async t => {
80
+ const dot = await engine.eval(`(index .live 1)`, [{ live: ["editing", "lively"] }]);
81
+ t.deepEqual(dot, "lively");
5
82
  });
@@ -0,0 +1,452 @@
1
+ // import compressedHugoWasm from "../full-hugo-renderer/hugo_renderer.wasm.gz";
2
+ import { gunzipSync } from 'fflate';
3
+ import translateTextTemplate from './translateTextTemplate.js';
4
+ import { IdentifierParser } from './hugoIdentifierParser.js';
5
+
6
+ const sleep = (ms = 0) => {
7
+ return new Promise(r => setTimeout(r, ms));
8
+ }
9
+
10
+ const dig = (obj, path) => {
11
+ if (typeof path === 'string') path = path.replace(/\[(\d+)]/g, '.$1').split('.');
12
+ obj = obj[path.shift()];
13
+ if (obj && path.length) return dig(obj, path);
14
+ return obj;
15
+ }
16
+
17
+ let mapHugoVariablesToParams = (obj, path) => {
18
+ let resolved = {};
19
+ for (let [k, v] of Object.entries(obj)) {
20
+ if (k.startsWith('$')) {
21
+ const remapped = `__bksh_${k.substring(1)}`;
22
+ obj[remapped] = v;
23
+ resolved[k] = `${path}.${remapped}`;
24
+ } else {
25
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
26
+ resolved = { ...resolved, ...mapHugoVariablesToParams(v, `${path}.${k}`) }
27
+ }
28
+ }
29
+ }
30
+ return resolved
31
+ }
32
+
33
+ export class Engine {
34
+ constructor(options) {
35
+ options = {
36
+ name: "Hugo",
37
+ files: {},
38
+ ...options,
39
+ };
40
+
41
+ this.key = 'hugo';
42
+ this.name = options.name;
43
+ this.files = options.files;
44
+ this.origin = (typeof document === 'undefined' ? '' : document.currentScript?.src) || `/bookshop.js`;
45
+ this.synthetic = options.synthetic ?? false;
46
+
47
+ if (!this.synthetic) {
48
+ this.initializeHugo();
49
+ }
50
+ }
51
+
52
+ async initializeHugo() {
53
+ const templates = {
54
+ "layouts/partials/bookshop.html": (await import("../bookshop-hugo-templates/bookshop.html")).default,
55
+ "layouts/partials/bookshop_scss.html": (await import("../bookshop-hugo-templates/bookshop_scss.html")).default,
56
+ "layouts/partials/bookshop_partial.html": (await import("../bookshop-hugo-templates/bookshop_partial.html")).default,
57
+ "layouts/partials/bookshop_component_browser.html": (await import("../bookshop-hugo-templates/bookshop_component_browser.html")).default,
58
+ "layouts/partials/bookshop_bindings.html": (await import("../bookshop-hugo-templates/bookshop_bindings.html")).default,
59
+ "layouts/partials/_bookshop/helpers/component.html": (await import("../bookshop-hugo-templates/helpers/component.html")).default,
60
+ "layouts/partials/_bookshop/helpers/component_key.html": (await import("../bookshop-hugo-templates/helpers/component_key.html")).default,
61
+ "layouts/partials/_bookshop/helpers/flat_component_key.html": (await import("../bookshop-hugo-templates/helpers/flat_component_key.html")).default,
62
+ "layouts/partials/_bookshop/helpers/partial.html": (await import("../bookshop-hugo-templates/helpers/partial.html")).default,
63
+ "layouts/partials/_bookshop/helpers/partial_key.html": (await import("../bookshop-hugo-templates/helpers/partial_key.html")).default,
64
+ "layouts/partials/_bookshop/errors/bad_bookshop_tag.html": (await import("../bookshop-hugo-templates/errors/bad_bookshop_tag.html")).default,
65
+ "layouts/partials/_bookshop/errors/err.html": (await import("../bookshop-hugo-templates/errors/err.html")).default,
66
+ };
67
+
68
+ templates["config.toml"] = "params.env_bookshop_live = true";
69
+
70
+ // When this script is run locally, the hugo wasm is loaded as binary rather than output as a file.
71
+ if (compressedHugoWasm?.constructor === Uint8Array) {
72
+ await this.initializeInlineHugo();
73
+ } else {
74
+ await this.initializeLocalCompressedHugo();
75
+ }
76
+
77
+ // TODO: Tidy
78
+ const mappedFiles = {};
79
+ for (const file of Object.entries(this.files)) {
80
+ mappedFiles[`layouts/partials/bookshop/${file[0]}`] = translateTextTemplate(file[1], {});
81
+ }
82
+
83
+ const componentSuccess = window["writeHugoFiles"](JSON.stringify(mappedFiles));
84
+ const templateSuccess = window["writeHugoFiles"](JSON.stringify(templates));
85
+
86
+ // BIG OL' TODO: Writing these files ahead of render() seems to be load-bearing,
87
+ // which doesn't yet make sense to me.
88
+ // If these files are created for the first time in render(), the site doesn't
89
+ // appear to build (need to extract some logs from hugo to determine why).
90
+ // Perplexingly, writing then building in eval() appears to have been working
91
+ // fine in the same case.
92
+ // My suspicion would be something around our changeEvents() handling
93
+ // in the Hugo builder, but I don't know why that wouldn't affect eval()... 🤷‍♂️
94
+ // For now, the load-bearing stubs will live in this Hugo initialization.
95
+ window.writeHugoFiles(JSON.stringify({
96
+ "layouts/index.html": "Uninitialized Layout",
97
+ "content/_index.md": "{ \”initialized\": false }\n"
98
+ }));
99
+ }
100
+
101
+ async initializeLocalCompressedHugo() {
102
+ try {
103
+
104
+ let prefetched = {};
105
+ if (typeof window !== "undefined" && window?.CloudCannon && window?.CloudCannon?.prefetchedFiles) {
106
+ prefetched = await window.CloudCannon.prefetchedFiles?.();
107
+ this.availablePrefetchKeys = Object.keys(prefetched);
108
+ }
109
+
110
+ const go = new Go();
111
+ const compressedWasmOrigin = this.origin.replace(/\/[^\.\/]+\.(min\.)?js/, compressedHugoWasm.replace(/^\./, ''));
112
+ const compressedWasmPath = (new URL(compressedWasmOrigin)).pathname;
113
+
114
+ let compressedBuffer;
115
+ if (prefetched[compressedWasmPath]) {
116
+ compressedBuffer = await prefetched[compressedWasmPath]?.arrayBuffer();
117
+ this.loadedFrom = "prefetch";
118
+ } else {
119
+ const compressedResponse = await fetch(compressedWasmOrigin);
120
+ compressedBuffer = await compressedResponse.arrayBuffer();
121
+ this.loadedFrom = "network";
122
+ }
123
+
124
+ const renderer = gunzipSync(new Uint8Array(compressedBuffer));
125
+
126
+ // Check the magic word at the start of the buffer as a way to verify the CDN download worked.
127
+ const isWasm = ([...renderer.slice(0, 4)]).map(g => g.toString(16).padStart(2, '0')).join('') === "0061736d";
128
+ if (!isWasm) throw "Not WASM";
129
+
130
+ const compressedResult = await WebAssembly.instantiate(renderer, go.importObject);
131
+ go.run(compressedResult.instance);
132
+ } catch (e) {
133
+ console.error("Couldn't load the local compressed Hugo WASM");
134
+ console.error(e);
135
+ }
136
+ }
137
+
138
+ async initializeInlineHugo() {
139
+ const go = new Go();
140
+ const buffer = compressedHugoWasm.buffer;
141
+ const renderer = gunzipSync(new Uint8Array(buffer));
142
+ const result = await WebAssembly.instantiate(renderer, go.importObject);
143
+ go.run(result.instance);
144
+ }
145
+
146
+ getSharedKey(name) {
147
+ return `shared/hugo/${name}.hugo.html`;
148
+ }
149
+
150
+ getShared(name) {
151
+ const key = this.getSharedKey(name);
152
+ return this.files?.[key];
153
+ }
154
+
155
+ getComponentKey(name) {
156
+ const base = name.split("/").reverse()[0];
157
+ return `components/${name}/${base}.hugo.html`;
158
+ }
159
+
160
+ getFlatComponentKey(name) {
161
+ return `components/${name}.hugo.html`;
162
+ }
163
+
164
+ getComponent(name) {
165
+ const key = this.getComponentKey(name);
166
+ const flatKey = this.getFlatComponentKey(name);
167
+ return this.files?.[key] ?? this.files?.[flatKey];
168
+ }
169
+
170
+ hasComponent(name) {
171
+ const key = this.getComponentKey(name);
172
+ const flatKey = this.getFlatComponentKey(name);
173
+ return !!(this.files?.[key] ?? this.files?.[flatKey]);
174
+ }
175
+
176
+ hasShared(name) {
177
+ const key = this.getSharedKey(name);
178
+ return !!this.files?.[key];
179
+ }
180
+
181
+ resolveComponentType(name) {
182
+ if (this.hasComponent(name)) return 'component';
183
+ if (this.hasShared(name)) return 'shared';
184
+ return false;
185
+ }
186
+
187
+ transformData(data) {
188
+ return {
189
+ Params: data
190
+ };
191
+ }
192
+
193
+ async storeMeta(meta = {}) {
194
+ while (!window.writeHugoFiles) {
195
+ await sleep(100);
196
+ };
197
+ window.writeHugoFiles(JSON.stringify({
198
+ "config.toml": [
199
+ meta.baseurl ? `baseURL = ${meta.baseurl}` : "",
200
+ meta.copyright ? `copyright = ${meta.copyright}` : "",
201
+ meta.title ? `title = ${meta.title}` : "",
202
+ "params.env_bookshop_live = true"
203
+ ].join('\n')
204
+ }));
205
+ const err = window.initHugoConfig();
206
+ if (err) {
207
+ console.error(err);
208
+ }
209
+
210
+ // window.loadHugoBookshopMeta(JSON.stringify(meta));
211
+ }
212
+
213
+ async storeInfo(info = {}) {
214
+ while (!window.writeHugoFiles) {
215
+ await sleep(100);
216
+ };
217
+
218
+ const data = info?.data;
219
+ if (!data || typeof data !== "object") return;
220
+ const files = {};
221
+ for (const [file, contents] of Object.entries(data)) {
222
+ files[`data/${file}.json`] = JSON.stringify(contents);
223
+ }
224
+ window.writeHugoFiles(JSON.stringify(files));
225
+ }
226
+
227
+ /**
228
+ * Tries to parse an error message and patch up the components
229
+ * in a rudimentary way before a rebuild.
230
+ */
231
+ async componentQuack(error_string = "", log_messages = []) {
232
+ try {
233
+ const component_regex = /execute of template failed: template: ([^:]+):\d/ig;
234
+ let file_stack = [...error_string.matchAll(component_regex)].map(([, file]) => `layouts/${file}`);
235
+ if (file_stack.length) {
236
+ const deepest_errored_component = file_stack[file_stack.length - 1];
237
+ const error_chunks = error_string.split("execute of template failed:");
238
+ const error_msg = error_chunks[error_chunks.length - 1] ?? "template error";
239
+ window.writeHugoFiles(JSON.stringify({
240
+ [deepest_errored_component]: [
241
+ `<div style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
242
+ `Failed to render ${deepest_errored_component}. <br/>`,
243
+ `<pre style="margin-top: 10px; background-color: lightcoral; border: solid 1px black; white-space: pre-line;">`,
244
+ `<code style="font-family: monospace; color: black;">${error_msg.replace(/</, '&lt;')}</code></pre>`,
245
+ `</div>`
246
+ ].join('\n')
247
+ }));
248
+ return deepest_errored_component;
249
+ }
250
+
251
+ const error_logs = log_messages.filter(log => log.startsWith("ERROR")).join("\n");
252
+ const missing_regex = /Component "([^"]+)" does not exist/ig;
253
+ file_stack = [...error_logs.matchAll(missing_regex)].map(([, file]) => {
254
+ let filename = file.split('/').pop();
255
+ return `layouts/partials/bookshop/components/${file}/${filename}.hugo.html`;
256
+ });
257
+ if (file_stack.length) {
258
+ const deepest_errored_component = file_stack[file_stack.length - 1];
259
+ window.writeHugoFiles(JSON.stringify({
260
+ [deepest_errored_component]: [
261
+ `<div class="bookshop_error" style="padding: 10px; background-color: lightcoral; color: black; font-weight: bold;">`,
262
+ `Failed to find component ${deepest_errored_component}`,
263
+ `</div>`
264
+ ].join('\n')
265
+ }));
266
+ return deepest_errored_component;
267
+ }
268
+ } catch (e) {
269
+ console.error(`ComponentQuack failed to patch things up: ${e}`);
270
+ return null;
271
+ }
272
+ }
273
+
274
+ async render(target, name, props, globals, logger) {
275
+ while (!window.buildHugo) {
276
+ logger?.log?.(`Waiting for the Hugo WASM to be available...`);
277
+ await sleep(100);
278
+ };
279
+
280
+ let writeFiles = {};
281
+
282
+ if (this.hasComponent(name)) {
283
+ writeFiles["layouts/index.html"] = `{{ partial "bookshop" (slice "${name}" .Params.component) }}`
284
+ } else if (this.hasShared(name)) {
285
+ writeFiles["layouts/index.html"] = `{{ partial "bookshop_partial" (slice "${name}" .Params.component) }}`
286
+ } else {
287
+ console.warn(`[hugo-engine] No component found for ${name}`);
288
+ return "";
289
+ }
290
+ logger?.log?.(`Going to render ${name}, with layout:`);
291
+ logger?.log?.(writeFiles["layouts/index.html"]);
292
+
293
+ if (!globals || typeof globals !== "object") globals = {};
294
+ props = {
295
+ ...globals, ...props,
296
+ env_bookshop_live: true
297
+ };
298
+
299
+ // If we have assigned a root scope we need to pass that in as the context
300
+ if (props["."]) props = props["."];
301
+ writeFiles["content/_index.md"] = JSON.stringify({
302
+ component: props
303
+ }, null, 2) + "\n";
304
+ window.writeHugoFiles(JSON.stringify(writeFiles));
305
+
306
+ window.hugo_wasm_logging = [];
307
+ let render_attempts = 1;
308
+ let buildError = window.buildHugo();
309
+ while (buildError && render_attempts < 5) {
310
+ if (this.componentQuack(buildError, window.hugo_wasm_logging) === null) {
311
+ // Can't find a template to overwrite and re-render
312
+ break;
313
+ }
314
+ // Try render again with the problem template stubbed out
315
+ window.hugo_wasm_logging = [];
316
+ buildError = window.buildHugo();
317
+ render_attempts += 1;
318
+ }
319
+
320
+ if (buildError) {
321
+ console.error(buildError);
322
+ return;
323
+ }
324
+
325
+ const output = window.readHugoFiles(JSON.stringify([
326
+ "public/index.html"
327
+ ]));
328
+
329
+ target.innerHTML = output["public/index.html"];
330
+ return;
331
+ }
332
+
333
+ async eval(str, props = [{}], logger) {
334
+ if (!this.synthetic) {
335
+ while (!window.buildHugo) await sleep(10);
336
+ }
337
+ let props_obj = props.reduce((a, b) => { return { ...a, ...b } });
338
+ let full_props = props_obj;
339
+ str = str.trim();
340
+
341
+ // We can return early if we're evaluating a literal string or digit
342
+ if (/^".*"$|^`.*`$|^\d+(\.\d+)?$|^true$|^false$/.test(str)) {
343
+ logger?.log?.(`Unwrapping the string ${str}`);
344
+ try {
345
+ if (/^`.*`$/.test(str)) {
346
+ return JSON.parse(str.replace(/^`|`$/g, '"'));
347
+ }
348
+ return JSON.parse(str);
349
+ } catch (e) {
350
+ logger?.log?.(`Was not valid JSON for some reason. Moving on...`);
351
+ }
352
+ }
353
+
354
+ // We're capable of looking up a simple variable
355
+ // (and it's hard to pass to wasm since we store variables on the context)
356
+ if (/^\$/.test(str)) {
357
+ logger?.log?.(`Trying to short circuit to return a variable`);
358
+ if (props_obj[str]) return props_obj[str];
359
+ }
360
+
361
+ // If we have assigned a root scope we need to pass that in as the context
362
+ if (props_obj["."]) {
363
+ logger?.log?.(`Nesting the props object into its dot scope`);
364
+ props_obj = props_obj["."];
365
+ }
366
+
367
+ if (str === ".") {
368
+ logger?.log?.(`Short circuiting dot notation to return the scope`);
369
+ return props_obj;
370
+ }
371
+
372
+ let normalized = this.normalize(str);
373
+ if (typeof normalized === 'object') {
374
+ logger?.log?.(`Digging into the object ${JSON.stringify(normalized)}`);
375
+ const process = async (obj) => {
376
+ for (const [k, v] of Object.entries(obj)) {
377
+ if (typeof v === "string") {
378
+ logger?.log?.(`Evaluating the inner ${k}: ${v}`);
379
+ obj[k] = await this.eval(v, [props_obj], logger?.nested?.());
380
+ } else if (typeof v === "object") {
381
+ logger?.log?.(`Processing the inner object ${k}`);
382
+ process(v);
383
+ }
384
+ }
385
+ }
386
+ await process(normalized);
387
+ return normalized;
388
+ }
389
+ if (/^\w+(\.\w+)*$/.test(normalized)) {
390
+ logger?.log?.(`Trying to short circuit to return the dot notation ${normalized}`);
391
+ // We're capable of looking up a simple dot notation access
392
+ const result = dig(props_obj, normalized);
393
+ if (result !== undefined) return result
394
+ }
395
+
396
+ // Rewrite array.0 into index array 0
397
+ str = str.replace(/(.*)\.(\d+)$/, (_, obj, index) => {
398
+ return `index (${obj}) ${index}`;
399
+ })
400
+
401
+ const variable_pairs = mapHugoVariablesToParams(full_props, ".Params.full_props");
402
+ const variable_decl = Object.entries(variable_pairs).map(([k, v]) => {
403
+ return `{{ ${k} := ${v} }}`
404
+ }).join('');
405
+
406
+ const assignments = Object.entries(props_obj).filter(([key]) => key.startsWith('$')).map(([key, value]) => {
407
+ if (Array.isArray(value)) {
408
+ return `{{ ${key} := index ( \`{"a": ${JSON.stringify(value)}}\` | transform.Unmarshal ) "a" }}`
409
+ } else if (typeof value === 'object') {
410
+ return `{{ ${key} := \`${JSON.stringify(value)}\` | transform.Unmarshal }}`
411
+ } else {
412
+ return `{{ ${key} := ${JSON.stringify(value)} }}`
413
+ }
414
+ }).join('');
415
+ const eval_str = `${variable_decl}{{ with .Params.props }}${assignments}{{ jsonify (${str}) }}{{ end }}`;
416
+
417
+ if (this.synthetic) {
418
+ return null;
419
+ }
420
+
421
+ window.writeHugoFiles(JSON.stringify({
422
+ "layouts/index.html": eval_str,
423
+ "content/_index.md": JSON.stringify({ props: props_obj, full_props: full_props }, null, 2)
424
+ }));
425
+
426
+ const buildError = window.buildHugo();
427
+ if (buildError) {
428
+ console.warn(buildError);
429
+ return;
430
+ }
431
+
432
+ const output = window.readHugoFiles(JSON.stringify([
433
+ "public/index.html"
434
+ ]))["public/index.html"];
435
+
436
+ try {
437
+ return JSON.parse(output);
438
+ } catch (e) {
439
+ logger?.log?.(`Error evaluating \`${str}\` in the Hugo engine`);
440
+ logger?.log?.(output);
441
+ return null;
442
+ }
443
+ }
444
+
445
+ normalize(str) {
446
+ return (new IdentifierParser(str)).build();
447
+ }
448
+
449
+ loader() {
450
+ // esbuild loader if required
451
+ }
452
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bookshop/hugo-engine",
3
3
  "packageManager": "yarn@3.0.0",
4
- "version": "3.5.0",
4
+ "version": "3.6.0-beta.0",
5
5
  "description": "Bookshop frontend Hugo renderer",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -14,11 +14,12 @@
14
14
  "*.js",
15
15
  "lib/*.js",
16
16
  "!**/*.test.js",
17
+ "!**/*__test__.js",
17
18
  "full-hugo-renderer/hugo_renderer.wasm.gz",
18
19
  "bookshop-hugo-templates/**/*"
19
20
  ],
20
21
  "scripts": {
21
- "test": "nyc ava -v"
22
+ "test": "node prep__test__.js && nyc ava -v"
22
23
  },
23
24
  "author": "@bglw",
24
25
  "license": "MIT",
@@ -30,7 +31,7 @@
30
31
  "nyc": "^15.1.0"
31
32
  },
32
33
  "dependencies": {
33
- "@bookshop/helpers": "3.5.0",
34
+ "@bookshop/helpers": "3.6.0-beta.0",
34
35
  "esbuild": "^0.13.10",
35
36
  "fflate": "^0.7.3",
36
37
  "liquidjs": "9.28.0"
@@ -0,0 +1,5 @@
1
+ import fs from "fs";
2
+
3
+ let contents = fs.readFileSync("lib/engine.js", { encoding: "utf-8" });
4
+ contents = contents.replace("import compressedHugoWasm", "// import compressedHugoWasm");
5
+ fs.writeFileSync("lib/engine__test__.js", contents);
@@ -1 +0,0 @@
1
- {"parent":null,"pid":5417,"argv":["/opt/hostedtoolcache/node/16.20.0/x64/bin/node","/home/runner/work/bookshop/bookshop/javascript-modules/node_modules/ava/cli.js","-v"],"execArgv":[],"cwd":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine","time":1683759481967,"ppid":5406,"coverageFilename":"/home/runner/work/bookshop/bookshop/javascript-modules/engines/hugo-engine/.nyc_output/5217b0dd-26fb-4b8d-af21-def9fd98ee74.json","externalId":"","uuid":"5217b0dd-26fb-4b8d-af21-def9fd98ee74","files":[]}