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

feat(core): expressive content replacement #139

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

stephancill
Copy link
Contributor

@stephancill stephancill commented Dec 23, 2023

Change Summary

This PR proposes a standard syntax for string manipulation inside Mod elements by adopting mustache.js. This library is lightweight at 6.5kb minified or 2.7kb minified+gzipped (bundlephobia) and uses a familiar {{}} syntax for replacement as well as path.to.value syntax for accessing nested context values, making it backwards compatible.

Arbitrary functions can be exposed to the mustache syntax in the replaceInlineContext function by passing anonymous function builders along with the context to the rendering function (see the Lambdas section in the manual and spec).

An example adjustment to the replaceInlineContext function which introduces a decimals operator which displays a number to a fixed number of decimals would look like this:

function replaceInlineContext(target: string, context: any): string {
  const output = Mustache.render(target, {
    ...context,
    decimals: function () {
      return function (text: string, render: (text: string) => string) {
        var parts = text.split(" ");
        var value = parseFloat(render(parts[0]));
        var decimalPlaces = parseInt(render(parts[1]), 10);
        return value.toFixed(decimalPlaces);
      };
    },
  });
  return output;
}

And would be used like this:

console.log(
  replaceInlineContext(
    "Output: {{#decimals}}{{refs.sample.value}} 3{{/decimals}}",
    { refs: { sample: { value: 0.12345678 } } }
  )
);

Output: 0.123

Merge Checklist

  • PR has a changeset
  • PR includes documentation if necessary
  • PR updates the rich-embed examples if necessary
  • includes a parallel PR for Mod-starter and the gateway if necessary

Copy link

changeset-bot bot commented Dec 23, 2023

🦋 Changeset detected

Latest commit: aee0091

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
@mod-protocol/core Minor
api Patch
web Patch
@mods/chatgpt-shorten Patch
@mods/chatgpt Patch
@mods/dall-e Patch
@mods/giphy-picker Patch
@mods/image-render Patch
@mods/imgur-upload Patch
@mods/infura-ipfs-upload Patch
@mods/livepeer-video Patch
@mods/nft-minter Patch
@mods/url-render Patch
@mods/video-render Patch
@mods/zora-nft-minter Patch
@mod-protocol/react-ui-shadcn Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

vercel bot commented Dec 23, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
api ✅ Ready (Inspect) Visit Preview Dec 23, 2023 3:18pm
docs ✅ Ready (Inspect) Visit Preview Dec 23, 2023 3:18pm
example-nextjs-shadcn ✅ Ready (Inspect) Visit Preview Dec 23, 2023 3:18pm

@stephancill stephancill added the enhancement New feature or request label Dec 23, 2023
Copy link
Contributor

@davidfurlong davidfurlong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like using existing standards for this.

however I'm a bit hesitant of this exact implementation, as

  • mustache hasn't had a commit in a year
  • I'm concerned about how many functions will be needed to support all the edge cases

however I think it'll be reasonably easy to change later so I'm ok with going with it

import Mustache from "mustache";

// Prevent default HTML escaping behaviour
Mustache.escape = function (value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overriding globals is probably not a good idea given that this code is executed in the same context as other apps

return target.replace(/{{([^{{}}]+)}}/g, (_, key) => get(context, key, ``));
const output = Mustache.render(target, {
...context,
decimals: function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with this pattern, this list of functions is going to grow very long and it'll be bundled into every app.

@stephancill
Copy link
Contributor Author

I agree with your evaluation. Another gripe I have with mustache is that the syntax is quite verbose. The ideal library for our use case is lightweight and has a comprehensive library of helper built-in helper functions.

handlebars is an alternative based on mustache which is actively maintained but is a bit heavier (73kb) and contains a lot of logic that we won't be using and still lacks the comprehensive standard library.

A possible course of action could be to go ahead with mustache, take inspiration from existing other long-standing analogs like the python's jinja and implement their helper standard functions to start off with, bundle them all together for now and figure out a more sophisticated bundling solution in the long run.

I think this is a necessary feature if we want to make rich client-only logic possible - but not strictly necessary if we work under the assumption that every mod will have a backend which can handle this kind of transformation logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants