nested includes, slots
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.
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.

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.
awesome, thanks for sharing!
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!