eta icon indicating copy to clipboard operation
eta copied to clipboard

nested includes, slots

Open tycobbb opened this issue 4 years ago • 2 comments

Is your feature request related to a problem? Please describe.

i'm using eta in a little static-site build tool that i'm writing. it'd be nice to be able to use nested includes or ideally, slots. maybe you already can? does this even seem feasible?

it would help in situations where you want to define a shared layout for elements within a page. even it eta's current state, i expected to be able to do something like:

<!-- this seems to produce invalid template afaict -->
<%~ 
  include("partial", {
    body: include("content")
  })
%>

Describe the solution you'd like

something syntactically similar to svelte slots, but not as wild. if even the above worked, that would be nice, but aspirationally:

<%~ include("partial", { normal: "var" }, () => ` %>
  <div slot="name">
    this div is inserted into `it.name` in `partial`
  </div>
  
  <div slot="body">
    this div is inserted into `it.body` in `partial`
  </div>
  
  <!-- maybe nicer if include also has some way of binding to a slot -->
  <div slot="other">
    <%~ include("other") %>
  </div>
<% `) %>

a third argument to the template that is a string or a function producing a string that eta can parse and insert into the correct slots in the variables dictionary.

Describe alternatives you've considered

the approach outlined in the first section, nested includes and variables. i would go so far as to say that if that worked, it would probably be enough. and i could write my own preprocessing to produce that from something like:

<w:include path="wrapper" name="thing" top=5>
  <w:include path="content" w:slot="body" />

  <div w:slot="footer">
    footer
  </div>
</w:include>

Additional context

not sure, but happy to provide any. long live html.

tycobbb avatar Feb 16 '22 21:02 tycobbb

import type { EtaConfig } from "eta/dist/types/config.js";
import type { AstObject, TemplateObject } from "eta/dist/types/parse.js";
import type { TemplateHelperOpts } from "../template.js";

const contentRegex = /^content\s*\(\s*"([^]*)"\s*\)\s*\${$/;
const slotRegex = /^slot\s*\(\s*"([^]*)"\s*\)(\s*\${)?$/;
const plugin = {
    processFnString: (fnStr: string) => {
      return `it[Symbol.for("slotsPlugin")]=it[Symbol.for("slotsPlugin")]||{};${fnStr}`;
    },
    processAST: (ast: Array<AstObject>, _env: EtaConfig) => {
      for (const token of ast) {
        //Make sure it's a template object
        if (typeof token === "string") continue;
        //Make sure it's an evaluate tag
        if (token.t !== "e") continue;

        const val = token.val.trim();
        //Its a @content "name"
        if (contentRegex.test(val)) {
          const slotName = "_" + contentRegex.exec(val)![1].replaceAll(/\W/g, "");
          token.val = `
          it[Symbol.for("slotsPlugin")]["${slotName}"] = tR;
          tR = "";
          ___slot${slotName}();
          [tR, it[Symbol.for("slotsPlugin")]["${slotName}"]] = [it[Symbol.for("slotsPlugin")]["${slotName}"], tR.trim()];
          function~___slot${slotName} () {
        `
            .replace(/\s/g, "")
            .replace("~", " ");
        } else if (slotRegex.test(val)) {
          const match = slotRegex.exec(val)!;
          const slotName = "_" + match[1].replaceAll(/\W/g, "");
          const hasDefault = Boolean(match[2]);
          token.val = `
          if(it[Symbol.for("slotsPlugin")]["${slotName}"]) {
            tR += it[Symbol.for("slotsPlugin")]["${slotName}"];
          } ${
            hasDefault
              ? `
              else {
                ___slotdefault${slotName}() ?? "";
              }
          
              function~___slotdefault${slotName} () {`
              : ""
          }
          `
            .replace(/\s/g, "")
            .replace("~", " ");
        }
      }

      return ast;
    }
};

export default plugin;

EDIT: Fixed regex for slot name

Needed slots in my app, made this, enjoy.

image

Don't really care to make this a plugin myself as I don't want to deal with making sure its compatible on all browsers since I'm using it on nodejs. If anyone wants to make it into a plugin, just plop a link to this comment somewhere in the readme.

alexkar598 avatar May 09 '22 00:05 alexkar598

awesome, thanks for sharing!

tycobbb avatar May 09 '22 01:05 tycobbb

For now, I think this is outside the scope of the project, so I'm going to convert this to a discussion.

But I think it would be awesome if someone were to implement it as a plugin!

bgub avatar Apr 28 '23 00:04 bgub