Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 11ty.js templates support returning a buffer (sync render) #3629

Open
monochromer opened this issue Jan 22, 2025 · 10 comments
Open
Labels
breaking-change This will have to be included with a major version as it breaks backwards compatibility. bug

Comments

@monochromer
Copy link
Contributor

I have issue with buffers returned from js template:

import { createCanvas, ImageData } from '@napi-rs/canvas';

export default {
  data: {
    permalink: '/assets/noise.png',
    tileSize: 64
  },

  render(data) {
    const tileSize = data.tileSize ?? 64;
    const black = new Uint8ClampedArray([0, 0, 0, 255]);
    const white = new Uint8ClampedArray([255, 255, 255, 255]);
    const result = new Uint8ClampedArray(tileSize * tileSize * 4);

    for (let row = 0; row < tileSize; row++) {
      for (let column = 0; column < tileSize; column++) {
        result.set(Math.random() > 0.5 ? white : black, (row * tileSize + column) * 4);
      }
    }

    const canvas = createCanvas(tileSize, tileSize);
    const context = canvas.getContext('2d');
    context.putImageData(new ImageData(result, tileSize, tileSize), 0, 0);

    return canvas.toBuffer('image/png');
  }
}

When i use async render function, it works fine. But with sync render i got broken image.

Code - https://stackblitz.com/edit/stackblitz-starters-jojfy7jh

@Ryuno-Ki
Copy link
Contributor

I can reproduce (although not on Stackblitz because of how they build (using musl)).

mod in _getInstance of

} else if ("data" in mod || "render" in mod) {

is the default module you return.

Therefore

return this.normalize(inst.render.call(inst, data));

is called which produces a binary file (I have logged the output). The binary appears fine although my image viewer reports it as broken.

So I digged a little deeper:

xxd _site/noise.png > noise.hex  # Using async render
xxd _site/noise_broken.png > noise_broken.hex # Using sync render
diff --suppress-common-lines -y noise.hex noise_broken.hex
stat -c %s _site/noise.png
stat -c %s _site/noise_broken.png

The diff showed a longer file on the right side (the broken version)
The stat compares 1628 vs. 2956 bytes. Therefore the sync call is writing more data into the Buffer.

Logging buffer.length shows a value around 1600. I have no clue where the additional bytes might come from.

@monochromer
Copy link
Contributor Author

Problem in this code:

normalize(result) {
if (Buffer.isBuffer(result)) {
return result.toString();
}
return result;
}

let rendered = await fn(data);

When function returns a promise, condition if (Buffer.isBuffer(result)) doesn't work. Further let rendered = await fn(data); get correct buffer.

With sync render inside normalize we get result.toString() with incorrect buffer.

@Ryuno-Ki
Copy link
Contributor

I assume, it's designed for raw Buffer values.

Therefore I think the first step would be to add tests to https://github.com/11ty/eleventy/tree/b6622297b72e5b1d7d7d01f2c3008aacd39ec517/test/Util so we catch the current behaviour (and a failing test for what you intend to do).

A „quick” solution could be extending the signature of the normalize function to consider data as well.
If the target isn't a text format (such as HTML), it shouldn't try to render a Buffer to a string. I have no clear picture in my mind on how this would play along with the other template engines.

@monochromer
Copy link
Contributor Author

I see similar discussion here - #2352.

I think buffer should not be special handled. If user use buffer, he knows how to work with one.

@Ryuno-Ki
Copy link
Contributor

Hm, introduced back in v0.7.0: 9072cbc

That was the release that introduced this Template Engine: https://github.com/11ty/eleventy/releases/tag/v0.7.0

I agree with you:

I think buffer should not be special handled. If user use buffer, he knows how to work with one.

@zachleat zachleat added the bug label Jan 28, 2025
@zachleat zachleat added this to the Eleventy 3.0.1 milestone Jan 28, 2025
@zachleat
Copy link
Member

Hm! I’m okay adding this but if it’s not a 3.0 regression it will need to be moved to 4.0 as a breaking change

@zachleat zachleat added the breaking-change This will have to be included with a major version as it breaks backwards compatibility. label Jan 28, 2025
@zachleat
Copy link
Member

In the mean time I’d welcome a PR that adds a configuration API opt-in to unlock this behavior in 3.0

@zachleat zachleat changed the title Js template returns broken buffer JavaScript 11ty.js templates support returning a buffer (sync render) Jan 28, 2025
@Ryuno-Ki
Copy link
Contributor

Hi Zach,

yeah, it pretty much sounded like a breaking change to me. But then, we have one thing to look forward to in Eleventy v4 now 😇

@Ryuno-Ki
Copy link
Contributor

@monochromer Do you have thoughts on how that API might look like?
I'd imagine something like isBlob = false as function parameter (that could even lead to a non-breaking API!).

@monochromer
Copy link
Contributor Author

monochromer commented Jan 29, 2025

Do you have thoughts on how that API might look like?

Maybe something like that:

// template.11ty.js
export default {
  // think about naming
  eleventyJavaScriptTemplateOptions: {
    blob: 'bypass' /* or `string` or function to transform buffer */

    /* or */
    postProcessRenderedResult: (result) => {}
  },

  data: {},

  render(data) {
    const buffer = getBufferSomehow();
    return buffer;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change This will have to be included with a major version as it breaks backwards compatibility. bug
Projects
None yet
Development

No branches or pull requests

3 participants