@cloudcannon/editable-regions 0.0.18 → 0.0.20-rc.1

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 (45) hide show
  1. package/helpers/checks.ts +0 -22
  2. package/helpers/hydrate-editable-regions.ts +2 -1
  3. package/integrations/astro/react-renderer.mjs +94 -19
  4. package/integrations/astro/svelte-renderer.mjs +72 -15
  5. package/integrations/astro/vue-renderer.mjs +61 -0
  6. package/integrations/eleventy/browser/collect-config.mjs +69 -8
  7. package/integrations/eleventy/browser/inert.mjs +35 -0
  8. package/integrations/eleventy/browser/process-shim.mjs +32 -0
  9. package/integrations/eleventy/browser/stub-mode.mjs +61 -0
  10. package/integrations/eleventy/index.cjs +28 -1
  11. package/integrations/eleventy/index.mjs +82 -30
  12. package/integrations/hugo/browser/entry.js +13 -0
  13. package/integrations/hugo/browser/errors.ts +41 -0
  14. package/integrations/hugo/browser/index.ts +344 -0
  15. package/integrations/hugo/browser/logger.ts +42 -0
  16. package/integrations/hugo/browser/wasm_exec.js +575 -0
  17. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-config-files.html +14 -0
  18. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-dep-template-files.html +66 -0
  19. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-files-with-extension.html +27 -0
  20. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/find-template-files.html +51 -0
  21. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/load-deps.html +5 -0
  22. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/normalize-extensions.html +9 -0
  23. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/resources.html +87 -0
  24. package/integrations/hugo/hugo-module/layouts/partials/editable-regions/wasm-url.html +28 -0
  25. package/integrations/hugo/hugo-module/layouts/partials/editable-regions.html +6 -0
  26. package/integrations/hugo/renderer/build.sh +24 -0
  27. package/integrations/hugo/renderer/go.mod +100 -0
  28. package/integrations/hugo/renderer/go.sum +241 -0
  29. package/integrations/hugo/renderer/main.go +412 -0
  30. package/integrations/liquid/README.md +92 -3
  31. package/integrations/liquid/errors.mjs +3 -1
  32. package/integrations/liquid/fs.mjs +11 -1
  33. package/integrations/liquid/globals.mjs +131 -26
  34. package/integrations/liquid/index.mjs +5 -2
  35. package/integrations/vue.mjs +28 -0
  36. package/nodes/editable-array-item.ts +6 -1
  37. package/nodes/editable-component.ts +2 -3
  38. package/nodes/editable-text.ts +6 -0
  39. package/nodes/editable.ts +6 -1
  40. package/package.json +132 -90
  41. package/types/astro.d.ts +4 -0
  42. package/types/eleventy.d.ts +11 -4
  43. package/types/hugo.d.ts +69 -0
  44. package/types/liquid.d.ts +0 -1
  45. package/types/vue.d.ts +40 -0
@@ -1,6 +1,7 @@
1
1
  // Builders for the `page` and `collections` globals on the shared Liquid
2
- // engine. Both return Promises that LiquidJS awaits at the globals level;
3
- // property access in templates is then synchronous on the resolved objects.
2
+ // engine. Both return Promises that LiquidJS awaits at the globals level.
3
+ // `page` resolves to a plain object; `collections` resolves to an object whose
4
+ // keys are lazy getters, so a template only pays for the collections it reads.
4
5
 
5
6
  import { apiLoadedPromise, CloudCannon } from "../../helpers/cloudcannon.mjs";
6
7
  import { getPageMap, normalizeInputPath } from "./page-map.mjs";
@@ -135,7 +136,13 @@ async function materialiseFile(file) {
135
136
  */
136
137
  export async function buildPageData() {
137
138
  await apiLoadedPromise;
138
- const file = CloudCannon?.currentFile?.();
139
+ let file;
140
+ try {
141
+ file = CloudCannon?.currentFile?.();
142
+ } catch {
143
+ // No current file (page with no associated source).
144
+ return {};
145
+ }
139
146
  if (!file) return {};
140
147
  const inputPath = file.path;
141
148
  const data = (await file.data.get()) ?? {};
@@ -150,27 +157,74 @@ export async function buildPageData() {
150
157
  };
151
158
  }
152
159
 
153
- /** @type {Promise<Record<string, Array<any>>> | null} */
160
+ /**
161
+ * Ceiling on concurrent `file.data.get()` calls. One call per file over a
162
+ * collection of thousands fails with `ERR_INSUFFICIENT_RESOURCES` — a net-stack
163
+ * error, so each resolves to a request somewhere behind the editor API.
164
+ */
165
+ const MATERIALISE_CONCURRENCY = 24;
166
+
167
+ /**
168
+ * `Promise.all(items.map(fn))` with at most `limit` calls in flight. Results
169
+ * keep their input order.
170
+ *
171
+ * @template T, R
172
+ * @param {T[]} items
173
+ * @param {(item: T) => Promise<R>} fn
174
+ * @param {number} limit
175
+ * @returns {Promise<R[]>}
176
+ */
177
+ async function mapWithConcurrency(items, fn, limit) {
178
+ /** @type {R[]} */
179
+ const results = new Array(items.length);
180
+ let cursor = 0;
181
+
182
+ const worker = async () => {
183
+ while (cursor < items.length) {
184
+ const index = cursor++;
185
+ results[index] = await fn(items[index]);
186
+ }
187
+ };
188
+
189
+ await Promise.all(
190
+ Array.from({ length: Math.min(limit, items.length) }, worker),
191
+ );
192
+ return results;
193
+ }
194
+
195
+ /** One `CloudCannon.collections()` call, keyed by name. @type {Promise<Map<string, any>> | null} */
196
+ let collectionIndexCache = null;
197
+
198
+ /** Materialised items, per collection name. @type {Map<string, Promise<any[]>>} */
199
+ const collectionItemsCache = new Map();
200
+
201
+ /** @type {Promise<Record<string, any>> | null} */
154
202
  let collectionsCache = null;
155
203
 
156
204
  /** @type {Array<{ target: any, event: "change" | "delete", handler: () => void }>} */
157
205
  let collectionsSubscriptions = [];
158
206
 
159
207
  /**
160
- * Builds (or returns cached) the `collections` object, keyed by collection
161
- * name. Subscribes to `change`/`delete` on each collection and drops the cache
162
- * when either fires, so edits during a session are picked up on the next render.
208
+ * Enumerates the site's collections one API call, cached and subscribes to
209
+ * `change`/`delete` on each so an edit drops the caches. Never calls
210
+ * `collection.items()`: knowing the *names* is what lets the getters be
211
+ * enumerable without fetching behind them.
163
212
  *
164
- * @returns {Promise<Record<string, Array<any>>>}
213
+ * @returns {Promise<Map<string, any>>}
165
214
  */
166
- export function buildCollectionsData() {
167
- if (!collectionsCache) {
168
- collectionsCache = (async () => {
215
+ function loadCollectionIndex() {
216
+ if (!collectionIndexCache) {
217
+ collectionIndexCache = (async () => {
169
218
  await apiLoadedPromise;
170
219
  const allCollections = await CloudCannon?.collections?.();
171
- if (!allCollections?.length) return {};
220
+
221
+ /** @type {Map<string, any>} */
222
+ const index = new Map();
223
+ if (!allCollections?.length) return index;
172
224
 
173
225
  for (const collection of allCollections) {
226
+ index.set(collection.collectionKey, collection);
227
+
174
228
  const handler = () => resetCollectionsCache();
175
229
  collection.addEventListener("change", handler);
176
230
  collection.addEventListener("delete", handler);
@@ -179,31 +233,82 @@ export function buildCollectionsData() {
179
233
  { target: collection, event: "delete", handler },
180
234
  );
181
235
  }
236
+ return index;
237
+ })();
238
+ }
239
+ return collectionIndexCache;
240
+ }
241
+
242
+ /**
243
+ * Materialises one collection's files, memoised per name — the only place that
244
+ * issues per-file requests. An unknown name is `[]`, matching 11ty.
245
+ *
246
+ * @param {string} key
247
+ * @returns {Promise<any[]>}
248
+ */
249
+ function loadCollectionItems(key) {
250
+ let items = collectionItemsCache.get(key);
251
+ if (!items) {
252
+ items = (async () => {
253
+ const collection = (await loadCollectionIndex()).get(key);
254
+ if (!collection) return [];
182
255
 
183
- const entries = await Promise.all(
184
- allCollections.map(async (collection) => {
185
- const key = collection.collectionKey;
186
- let files;
187
- try {
188
- files = await collection.items();
189
- } catch {
190
- return /** @type {[string, any[]]} */ ([key, []]);
191
- }
192
- const items = await Promise.all(files.map(materialiseFile));
193
- return /** @type {[string, any[]]} */ ([key, items]);
194
- }),
256
+ let files;
257
+ try {
258
+ files = await collection.items();
259
+ } catch {
260
+ return [];
261
+ }
262
+ return mapWithConcurrency(
263
+ files,
264
+ materialiseFile,
265
+ MATERIALISE_CONCURRENCY,
195
266
  );
196
- return Object.fromEntries(entries);
267
+ })();
268
+ collectionItemsCache.set(key, items);
269
+ }
270
+ return items;
271
+ }
272
+
273
+ /**
274
+ * Builds (or returns cached) the `collections` object. Every key is a lazy
275
+ * getter returning a `Promise` of its items, which LiquidJS awaits during
276
+ * expression evaluation — so a component that never mentions `collections`
277
+ * issues no per-file requests.
278
+ *
279
+ * Getters not a Proxy: LiquidJS probes `next` and `toLiquid` on every object
280
+ * it resolves, and a blanket-getter Proxy answers those with a Promise, which
281
+ * breaks the lookup entirely.
282
+ *
283
+ * @returns {Promise<Record<string, any>>}
284
+ */
285
+ export function buildCollectionsData() {
286
+ if (!collectionsCache) {
287
+ collectionsCache = (async () => {
288
+ const index = await loadCollectionIndex();
289
+
290
+ /** @type {Record<string, any>} */
291
+ const collections = {};
292
+ for (const key of index.keys()) {
293
+ Object.defineProperty(collections, key, {
294
+ enumerable: true,
295
+ configurable: true,
296
+ get: () => loadCollectionItems(key),
297
+ });
298
+ }
299
+ return collections;
197
300
  })();
198
301
  }
199
302
  return collectionsCache;
200
303
  }
201
304
 
202
- /** Clears the collections cache and tears down its invalidation listeners. */
305
+ /** Clears every collections cache and tears down the invalidation listeners. */
203
306
  export function resetCollectionsCache() {
204
307
  for (const { target, event, handler } of collectionsSubscriptions) {
205
308
  target.removeEventListener(event, handler);
206
309
  }
207
310
  collectionsSubscriptions = [];
311
+ collectionIndexCache = null;
312
+ collectionItemsCache.clear();
208
313
  collectionsCache = null;
209
314
  }
@@ -73,8 +73,11 @@ export function registerLiquidComponent(key, contents) {
73
73
  /**
74
74
  * Wraps `window.cc_components` in a Proxy that resolves any component name on
75
75
  * demand via `{% include %}` — the primary resolution path. Names registered
76
- * via `registerLiquidComponent` take precedence. Call after
77
- * `createSharedLiquidEngine()`.
76
+ * via `registerLiquidComponent` take precedence.
77
+ *
78
+ * Call after `createSharedLiquidEngine()` and last of the `register*` calls:
79
+ * publishing `cc_components` is what tells the editor every helper is in
80
+ * place, and an empty one reads as a missing registration script.
78
81
  *
79
82
  * @returns {void}
80
83
  */
@@ -0,0 +1,28 @@
1
+ import { createApp, h } from "vue";
2
+ import { addEditableComponentRenderer } from "../helpers/cloudcannon.mjs";
3
+
4
+ /**
5
+ * Registers a Vue component with the CloudCannon component system.
6
+ * Creates a wrapper that renders the Vue component to an HTMLElement.
7
+ *
8
+ * @param {string} key - Unique identifier for the component
9
+ * @param {any} component - The Vue component to register
10
+ * @returns {void}
11
+ */
12
+ export const registerVueComponent = (key, component) => {
13
+ /**
14
+ * Wrapper function that renders the Vue component to an HTMLElement.
15
+ *
16
+ * @param {any} props - Props to pass to the Vue component
17
+ * @returns {HTMLElement} The rendered component as an HTMLElement
18
+ */
19
+ const wrappedComponent = (props) => {
20
+ const rootEl = document.createElement("div");
21
+ const app = createApp({ render: () => h(component, props) });
22
+ app.mount(rootEl);
23
+
24
+ return rootEl;
25
+ };
26
+
27
+ addEditableComponentRenderer(key, wrappedComponent);
28
+ };
@@ -1,7 +1,6 @@
1
1
  import "../components/ui/editable-array-item-controls.js";
2
2
  import type EditableArrayItemControls from "../components/ui/editable-array-item-controls.js";
3
3
  import {
4
- hasEditableArrayItem,
5
4
  isEditableArray,
6
5
  isEditableArrayItem,
7
6
  isEditableElement,
@@ -10,6 +9,12 @@ import { CloudCannon, realizeAPIValue } from "../helpers/cloudcannon.mjs";
10
9
  import type EditableArray from "./editable-array.js";
11
10
  import EditableComponent from "./editable-component.js";
12
11
 
12
+ export const hasEditableArrayItem = <T extends object>(
13
+ el: T,
14
+ ): el is T & { editable: EditableArrayItem } => {
15
+ return "editable" in el && el.editable instanceof EditableArrayItem;
16
+ };
17
+
13
18
  export default class EditableArrayItem extends EditableComponent {
14
19
  parent: EditableArray | null = null;
15
20
 
@@ -1,11 +1,10 @@
1
1
  import {
2
2
  areEqualEditables,
3
- hasEditable,
4
- hasEditableText,
5
3
  isEditableElement,
6
4
  isEditableText,
7
5
  } from "../helpers/checks.js";
8
- import Editable from "./editable.js";
6
+ import Editable, { hasEditable } from "./editable.js";
7
+ import { hasEditableText } from "./editable-text.js";
9
8
  import "../components/ui/editable-region-error-card.js";
10
9
  import "../components/ui/editable-component-controls.js";
11
10
  import type EditableComponentControls from "../components/ui/editable-component-controls.js";
@@ -3,6 +3,12 @@ import Editable from "./editable.js";
3
3
 
4
4
  type EditableFocusEvent = CustomEvent<number>;
5
5
 
6
+ export const hasEditableText = <T extends object>(
7
+ el: T,
8
+ ): el is T & { editable: EditableText } => {
9
+ return "editable" in el && el.editable instanceof EditableText;
10
+ };
11
+
6
12
  export default class EditableText extends Editable {
7
13
  editor?: any;
8
14
  focused = false;
package/nodes/editable.ts CHANGED
@@ -3,9 +3,14 @@ import type {
3
3
  CloudCannonVisualEditorAPIV1Dataset,
4
4
  CloudCannonVisualEditorAPIV1File,
5
5
  } from "@cloudcannon/visual-editor-api";
6
- import { hasEditable } from "../helpers/checks";
7
6
  import { apiLoadedPromise, CloudCannon } from "../helpers/cloudcannon.mjs";
8
7
 
8
+ export const hasEditable = <T extends object>(
9
+ el: T,
10
+ ): el is T & { editable: Editable } => {
11
+ return "editable" in el && el.editable instanceof Editable;
12
+ };
13
+
9
14
  declare global {
10
15
  interface HTMLElement {
11
16
  __pendingEditableListeners?: EditableListener[];
package/package.json CHANGED
@@ -1,92 +1,134 @@
1
1
  {
2
- "name": "@cloudcannon/editable-regions",
3
- "version": "0.0.18",
4
- "type": "module",
5
- "description": "Visual Editing for the CloudCannon CMS.",
6
- "keywords": [
7
- "cloudcannon"
8
- ],
9
- "license": "MIT",
10
- "author": "CloudCannon <support@cloudcannon.com>",
11
- "homepage": "https://github.com/CloudCannon/editable-regions#readme",
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/CloudCannon/editable-regions.git"
15
- },
16
- "bugs": {
17
- "url": "https://github.com/CloudCannon/editable-regions/issues",
18
- "email": "support@cloudcannon.com"
19
- },
20
- "scripts": {
21
- "typecheck": "tsc --noEmit",
22
- "typecheck:watch": "tsc --noEmit --watch",
23
- "lint-autofix": "biome check --fix",
24
- "lint": "biome check"
25
- },
26
- "files": [
27
- "components",
28
- "helpers",
29
- "integrations",
30
- "nodes",
31
- "styles",
32
- "types"
33
- ],
34
- "exports": {
35
- "./*": null,
36
- "./astro": {
37
- "types": "./types/astro.d.ts",
38
- "default": "./integrations/astro/index.mjs"
39
- },
40
- "./astro-react-renderer": {
41
- "types": "./types/astro.d.ts",
42
- "default": "./integrations/astro/react-renderer.mjs"
43
- },
44
- "./astro-integration": {
45
- "types": "./types/astro.d.ts",
46
- "default": "./integrations/astro/astro-integration.mjs"
47
- },
48
- "./react": {
49
- "types": "./types/react.d.ts",
50
- "default": "./integrations/react.mjs"
51
- },
52
- "./svelte": {
53
- "types": "./types/svelte.d.ts",
54
- "default": "./integrations/svelte.mjs"
55
- },
56
- "./astro-svelte-renderer": {
57
- "types": "./types/astro.d.ts",
58
- "default": "./integrations/astro/svelte-renderer.mjs"
59
- },
60
- "./liquid": "./integrations/liquid/index.mjs",
61
- "./eleventy": {
62
- "require": {
63
- "types": "./types/eleventy.d.cts",
64
- "default": "./integrations/eleventy/index.cjs"
65
- },
66
- "import": {
67
- "types": "./types/eleventy.d.ts",
68
- "default": "./integrations/eleventy/index.mjs"
69
- }
70
- },
71
- "./eleventy/browser": "./integrations/eleventy/browser/index.mjs",
72
- "./internal/components": "./components/index.js",
73
- "./internal/styles": "./styles/index.js"
74
- },
75
- "devDependencies": {
76
- "@biomejs/biome": "2.5.3",
77
- "@cloudcannon/visual-editor-api": "0.0.19",
78
- "@sindresorhus/slugify": "3.0.0",
79
- "@types/js-beautify": "1.14.3",
80
- "@types/node": "26.1.1",
81
- "@types/react": "19.2.17",
82
- "@types/react-dom": "19.2.3",
83
- "astro": "7.0.7",
84
- "js-beautify": "2.0.3",
85
- "liquidjs": "10.27.2",
86
- "slugify": "1.6.9",
87
- "typescript": "6.0.3"
88
- },
89
- "dependencies": {
90
- "esbuild": "0.28.1"
91
- }
2
+ "name": "@cloudcannon/editable-regions",
3
+ "version": "0.0.20-rc.1",
4
+ "type": "module",
5
+ "description": "Visual Editing for the CloudCannon CMS.",
6
+ "keywords": [
7
+ "cloudcannon"
8
+ ],
9
+ "license": "MIT",
10
+ "author": "CloudCannon <support@cloudcannon.com>",
11
+ "engines": {
12
+ "node": ">=20.19.0"
13
+ },
14
+ "homepage": "https://github.com/CloudCannon/editable-regions#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/CloudCannon/editable-regions.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/CloudCannon/editable-regions/issues",
21
+ "email": "support@cloudcannon.com"
22
+ },
23
+ "scripts": {
24
+ "typecheck": "tsc --noEmit",
25
+ "typecheck:watch": "tsc --noEmit --watch",
26
+ "lint-autofix": "biome check --fix",
27
+ "lint": "biome check",
28
+ "build:hugo": "bash integrations/hugo/renderer/build.sh",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "test:update-snapshots": "vitest run -u",
32
+ "test:build-fixtures": "npm run test:build-astro-fixture && npm run test:build-eleventy-fixture && npm run test:build-eleventy-plugin-config && npm run test:build-hugo-fixture && npm run test:build-hugo-custom-dirs && npm run test:build-hugo-config-options && npm run test:build-hugo-templates-overrides",
33
+ "test:build-hugo-custom-dirs": "npm --prefix test/unit/_fixtures/hugo-custom-dirs run build",
34
+ "test:build-hugo-config-options": "npm --prefix test/unit/_fixtures/hugo-config-options run build",
35
+ "test:build-hugo-templates-overrides": "npm --prefix test/unit/_fixtures/hugo-templates-overrides run build",
36
+ "test:build-hugo-fixtures": "npm run test:build-hugo-fixture && npm run test:build-hugo-custom-dirs && npm run test:build-hugo-config-options && npm run test:build-hugo-templates-overrides",
37
+ "test:build-astro-fixture": "npm --prefix test/unit/_fixtures/astro install --silent && npm --prefix test/unit/_fixtures/astro run build",
38
+ "test:build-eleventy-fixture": "npm --prefix test/unit/_fixtures/eleventy install --silent && npm --prefix test/unit/_fixtures/eleventy run build",
39
+ "test:build-eleventy-plugin-config": "npm --prefix test/unit/_fixtures/eleventy-plugin-config install --silent && npm --prefix test/unit/_fixtures/eleventy-plugin-config run build",
40
+ "test:build-hugo-fixture": "npm --prefix test/unit/_fixtures/hugo run build"
41
+ },
42
+ "files": [
43
+ "components",
44
+ "helpers",
45
+ "integrations",
46
+ "nodes",
47
+ "styles",
48
+ "types",
49
+ "!integrations/hugo/renderer/hugo_renderer.wasm",
50
+ "!integrations/hugo/hugo-module/assets/_cloudcannon"
51
+ ],
52
+ "exports": {
53
+ "./*": null,
54
+ "./astro": {
55
+ "types": "./types/astro.d.ts",
56
+ "default": "./integrations/astro/index.mjs"
57
+ },
58
+ "./astro-react-renderer": {
59
+ "types": "./types/astro.d.ts",
60
+ "default": "./integrations/astro/react-renderer.mjs"
61
+ },
62
+ "./astro-svelte-renderer": {
63
+ "types": "./types/astro.d.ts",
64
+ "default": "./integrations/astro/svelte-renderer.mjs"
65
+ },
66
+ "./astro-vue-renderer": {
67
+ "types": "./types/astro.d.ts",
68
+ "default": "./integrations/astro/vue-renderer.mjs"
69
+ },
70
+ "./astro-integration": {
71
+ "types": "./types/astro.d.ts",
72
+ "default": "./integrations/astro/astro-integration.mjs"
73
+ },
74
+ "./react": {
75
+ "types": "./types/react.d.ts",
76
+ "default": "./integrations/react.mjs"
77
+ },
78
+ "./svelte": {
79
+ "types": "./types/svelte.d.ts",
80
+ "default": "./integrations/svelte.mjs"
81
+ },
82
+ "./vue": {
83
+ "types": "./types/vue.d.ts",
84
+ "default": "./integrations/vue.mjs"
85
+ },
86
+ "./liquid": "./integrations/liquid/index.mjs",
87
+ "./eleventy": {
88
+ "require": {
89
+ "types": "./types/eleventy.d.cts",
90
+ "default": "./integrations/eleventy/index.cjs"
91
+ },
92
+ "import": {
93
+ "types": "./types/eleventy.d.ts",
94
+ "default": "./integrations/eleventy/index.mjs"
95
+ }
96
+ },
97
+ "./eleventy/browser": "./integrations/eleventy/browser/index.mjs",
98
+ "./hugo/browser": {
99
+ "types": "./types/hugo.d.ts",
100
+ "default": "./integrations/hugo/browser/index.ts"
101
+ },
102
+ "./internal/components": "./components/index.js",
103
+ "./internal/styles": "./styles/index.js"
104
+ },
105
+ "devDependencies": {
106
+ "@astrojs/react": "6.0.2",
107
+ "@astrojs/svelte": "9.0.1",
108
+ "@astrojs/vue": "7.0.2",
109
+ "@biomejs/biome": "2.5.8",
110
+ "@cloudcannon/visual-editor-api": "0.0.20",
111
+ "@sindresorhus/slugify": "3.0.0",
112
+ "@sveltejs/vite-plugin-svelte": "7.3.0",
113
+ "@types/js-beautify": "1.14.3",
114
+ "@types/node": "26.2.0",
115
+ "@types/react": "19.2.18",
116
+ "@types/react-dom": "19.2.4",
117
+ "@vitejs/plugin-vue": "6.0.8",
118
+ "astro": "7.2.2",
119
+ "happy-dom": "20.11.2",
120
+ "js-beautify": "2.0.3",
121
+ "liquidjs": "10.29.0",
122
+ "react": "19.2.8",
123
+ "react-dom": "19.2.8",
124
+ "slugify": "1.6.9",
125
+ "svelte": "5.56.9",
126
+ "typescript": "6.0.3",
127
+ "vitest": "4.1.10",
128
+ "vue": "3.5.41",
129
+ "vue-tsc": "3.3.10"
130
+ },
131
+ "dependencies": {
132
+ "esbuild": "0.28.2"
133
+ }
92
134
  }
package/types/astro.d.ts CHANGED
@@ -21,3 +21,7 @@ declare module "@cloudcannon/editable-regions/astro-react-renderer" {
21
21
  declare module "@cloudcannon/editable-regions/astro-svelte-renderer" {
22
22
  // Side-effect only module that registers Svelte renderer
23
23
  }
24
+
25
+ declare module "@cloudcannon/editable-regions/astro-vue-renderer" {
26
+ // Side-effect only module that registers Vue renderer
27
+ }
@@ -17,10 +17,17 @@ export interface LiquidOptions {
17
17
  configPath?: string;
18
18
  /**
19
19
  * Extra bare module specifiers to stub out of the browser bundle, on top of
20
- * the 11ty toolchain and Node built-ins (always stubbed). Use this when the
21
- * config imports a native/Node-only package (e.g. `sharp`) that no
22
- * browser-bound helper actually calls at render time but that would
23
- * otherwise break bundling.
20
+ * the 11ty toolchain and Node built-ins (always stubbed). Two uses:
21
+ *
22
+ * - a native/Node-only package (e.g. `sharp`) that would otherwise break
23
+ * bundling;
24
+ * - a Node-only package the config *calls* at config time, such as a
25
+ * plugin factory in `addPlugin(pluginFoo({ … }))`. The argument is
26
+ * evaluated before `addPlugin` is reached, so stubbing the module is the
27
+ * only way to stop it aborting the auto-mirror replay.
28
+ *
29
+ * A stubbed module called during the replay is skipped with a warning; the
30
+ * same call from a rendered helper still throws.
24
31
  */
25
32
  browserStub?: string[];
26
33
  /**
@@ -0,0 +1,69 @@
1
+ import type {
2
+ CloudCannonVisualEditorAPIRouter,
3
+ CloudCannonVisualEditorAPIV0,
4
+ CloudCannonVisualEditorAPIV1,
5
+ } from "@cloudcannon/visual-editor-api";
6
+
7
+ declare module "@cloudcannon/editable-regions/hugo/browser" {
8
+ /**
9
+ * Boots Hugo live editing from the `window.cc_hugo*` globals emitted by
10
+ * the Hugo module's snapshot prelude. Installs the component proxy
11
+ * immediately; the WASM renderer loads once the CloudCannon Visual Editor
12
+ * API appears.
13
+ */
14
+ export function initHugoLiveEditing(): void;
15
+
16
+ /** Starts (or returns the in-flight start of) the WASM renderer. */
17
+ export function ensureEngine(): Promise<void>;
18
+
19
+ /**
20
+ * Wraps `window.cc_components` in a Proxy manufacturing a renderer for
21
+ * any component name on demand; partial existence is decided by the Hugo
22
+ * renderer at render time. Called by `initHugoLiveEditing`.
23
+ */
24
+ export function initComponentProxy(): void;
25
+ }
26
+
27
+ declare global {
28
+ /** Snapshot metadata emitted onto `window.cc_hugo` by the module's prelude. */
29
+ interface HugoRuntimeMeta {
30
+ generator?: string;
31
+ wasmUrl?: string;
32
+ verbose?: boolean;
33
+ env?: string;
34
+ }
35
+
36
+ /** Result from the editor-site mutation entry points. */
37
+ interface HugoEditorResult {
38
+ error?: string;
39
+ }
40
+
41
+ /** Result from `renderHugoPartials`. */
42
+ interface HugoRenderResult {
43
+ html?: string;
44
+ error?: string;
45
+ }
46
+
47
+ /**
48
+ * The Hugo runtime's `window` doubles as the CloudCannon Visual Editor
49
+ * window and carries the snapshot globals emitted by the module's prelude.
50
+ */
51
+ interface Window {
52
+ /** CloudCannon's versioned API router (present inside the Visual Editor). */
53
+ CloudCannonAPI?: CloudCannonVisualEditorAPIRouter;
54
+ /** The installed v0/v1 CloudCannon API for this page. */
55
+ CloudCannon?: CloudCannonVisualEditorAPIV0 | CloudCannonVisualEditorAPIV1;
56
+ /** Emitter metadata: generator, wasmUrl, verbose, env. */
57
+ cc_hugo?: HugoRuntimeMeta;
58
+ /** Template/config snapshot keyed by physical path. */
59
+ cc_hugo_files?: Record<string, string>;
60
+ }
61
+
62
+ // The Hugo WASM renderer exposes these functions on `globalThis` once it
63
+ // boots; the runtime calls them directly after the engine is ready.
64
+ function writeHugoFiles(json: string): HugoEditorResult | null;
65
+ function removeHugoFiles(json: string): HugoEditorResult | null;
66
+ function readHugoFiles(json: string): Record<string, string>;
67
+ function initHugoEditorSite(): HugoEditorResult | null;
68
+ function renderHugoPartials(json: string): HugoRenderResult | null;
69
+ }
package/types/liquid.d.ts CHANGED
@@ -31,7 +31,6 @@ declare module "@cloudcannon/editable-regions/liquid" {
31
31
  name: string,
32
32
  factory: (liquidEngine: Liquid) => any,
33
33
  ): void;
34
- export function registerProcessEnv(env: Record<string, string>): void;
35
34
  export function registerEleventyData(data: {
36
35
  version: string;
37
36
  generator: string;