eleventy-plugin-edgejs 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -12,6 +12,9 @@ npm install eleventy-plugin-edgejs
12
12
 
13
13
  ## Usage
14
14
 
15
+ > [!TIP]
16
+ > This repository includes a working Eleventy site with more template and syntax examples. Browse the [example source](./example) or see the [Example Site](#example-site) section for instructions on running it locally.
17
+
15
18
  Register the plugin in your Eleventy config file:
16
19
 
17
20
  ```js
@@ -281,6 +284,19 @@ In your layout file (`_includes/layout.edge`):
281
284
  </html>
282
285
  ```
283
286
 
287
+ ## Example Site
288
+
289
+ This repository includes a working Eleventy site with more template and syntax examples. To run it locally:
290
+
291
+ ```sh
292
+ git clone https://github.com/reverentgeek/eleventy-plugin-edgejs.git
293
+ cd eleventy-plugin-edgejs
294
+ npm install
295
+ npm run start:example
296
+ ```
297
+
298
+ This starts a local dev server so you can browse the examples and experiment with Edge.js templates.
299
+
284
300
  ## Further Reading
285
301
 
286
302
  - [Edge.js documentation](https://edgejs.dev/)
package/edgeJsPlugin.js CHANGED
@@ -1,4 +1,103 @@
1
- import { Edge } from "edge.js";
1
+ import { Edge, Template } from "edge.js";
2
+
3
+ // Sentinel prefix for async placeholders (uses null byte to avoid collisions with real content)
4
+ const ASYNC_PREFIX = "\0__EDGE_ASYNC_";
5
+ const ASYNC_SUFFIX = "__\0";
6
+ const ASYNC_PATTERN = /\0__EDGE_ASYNC_(\d+)__\0/g;
7
+
8
+ // Patch Template.prototype.escape to:
9
+ // 1. Render null/undefined as empty string (matches Nunjucks, Handlebars, Liquid, Mustache)
10
+ // 2. Detect unresolved Promises and defer resolution via placeholders
11
+ const _originalEscape = Template.prototype.escape;
12
+
13
+ Template.prototype.escape = function ( input ) {
14
+ // Convert null/undefined to empty string instead of "null"/"undefined"
15
+ if ( input == null ) return "";
16
+
17
+ // Detect unresolved Promises from async filters/shortcodes
18
+ if ( typeof input === "object" && typeof input.then === "function" ) {
19
+ if ( !this.__pendingPromises ) {
20
+ this.__pendingPromises = [];
21
+ }
22
+ const idx = this.__pendingPromises.length;
23
+ this.__pendingPromises.push( input );
24
+ return `${ ASYNC_PREFIX }${ idx }${ ASYNC_SUFFIX }`;
25
+ }
26
+
27
+ return _originalEscape.call( this, input );
28
+ };
29
+
30
+ // Patch Template.prototype.renderRaw to resolve async placeholders after rendering
31
+ const _originalRenderRaw = Template.prototype.renderRaw;
32
+
33
+ Template.prototype.renderRaw = function ( contents, state, templatePath ) {
34
+ this.__pendingPromises = [];
35
+
36
+ const result = _originalRenderRaw.call( this, contents, state, templatePath );
37
+
38
+ // Async mode returns a Promise
39
+ if ( result && typeof result.then === "function" ) {
40
+ return result.then( output => resolveAsyncPlaceholders( this, output ) );
41
+ }
42
+
43
+ return result;
44
+ };
45
+
46
+ // Patch Template.prototype.reThrow to preserve the original error as .cause.
47
+ // Edge.js discards the original error class when wrapping in EdgeError, which breaks
48
+ // Eleventy's two-pass rendering system — it can't detect TemplateContentPrematureUseError
49
+ // and fails instead of deferring the template to a second pass.
50
+ const _originalReThrow = Template.prototype.reThrow;
51
+
52
+ Template.prototype.reThrow = function ( error, filename, lineNumber ) {
53
+ try {
54
+ _originalReThrow.call( this, error, filename, lineNumber );
55
+ } catch ( wrapped ) {
56
+ if ( wrapped !== error ) {
57
+ wrapped.cause = error;
58
+ }
59
+ throw wrapped;
60
+ }
61
+ };
62
+
63
+ // Patch Template.prototype.render for includes/components that may also contain async calls
64
+ const _originalRender = Template.prototype.render;
65
+
66
+ Template.prototype.render = function ( template, state ) {
67
+ if ( !this.__pendingPromises ) {
68
+ this.__pendingPromises = [];
69
+ }
70
+
71
+ const result = _originalRender.call( this, template, state );
72
+
73
+ if ( result && typeof result.then === "function" ) {
74
+ return result.then( output => resolveAsyncPlaceholders( this, output ) );
75
+ }
76
+
77
+ return result;
78
+ };
79
+
80
+ async function resolveAsyncPlaceholders( template, output ) {
81
+ if ( !template.__pendingPromises || template.__pendingPromises.length === 0 ) {
82
+ return output;
83
+ }
84
+
85
+ const resolved = await Promise.all( template.__pendingPromises );
86
+ template.__pendingPromises = [];
87
+
88
+ output = output.replace( ASYNC_PATTERN, ( _, idx ) => {
89
+ const val = resolved[parseInt( idx )];
90
+ if ( val == null ) return "";
91
+ return _originalEscape.call( template, val );
92
+ } );
93
+
94
+ // Check if resolving introduced new placeholders (unlikely but possible with nested async)
95
+ if ( ASYNC_PATTERN.test( output ) ) {
96
+ return resolveAsyncPlaceholders( template, output );
97
+ }
98
+
99
+ return output;
100
+ }
2
101
 
3
102
  export default function edgeJsPlugin( eleventyConfig, options = {} ) {
4
103
  eleventyConfig.versionCheck( ">=3.0.0" );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eleventy-plugin-edgejs",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Eleventy plugin for Edge.js templating",
5
5
  "type": "module",
6
6
  "main": "edgeJsPlugin.js",
@@ -47,4 +47,4 @@
47
47
  "eslint": "^10.0.2",
48
48
  "eslint-config-reverentgeek": "^7.0.3"
49
49
  }
50
- }
50
+ }