如何在Eleventy中使用EJS实现组件组合(含嵌套include)?
Great question! You’re absolutely on the right track leaning into partials for component composition—while EJS doesn’t have native support for React/Vue-style props.children or <slot> functionality, we can simulate this behavior effectively using Eleventy’s global helpers and EJS’s template rendering context.
Why Your Initial Approach Doesn’t Work Out of the Box
EJS’s include syntax doesn’t support passing callback functions as parameters (like the () => { /* markup */ } you tried), since include expects static data or variables, not executable code blocks. But we can work around this by capturing the output of your nested markup and passing that captured string as a children prop to your partials.
Solution 1: Capture Nested Markup with a Custom Helper
This is the closest to the syntax you’re aiming for. We’ll create a global helper function to capture the output of a template block, then pass that captured string to your partials.
Step 1: Register the Capture Helper in .eleventy.js
Add this to your Eleventy config to create a global capture function that taps into EJS’s output buffer:
module.exports = function(eleventyConfig) { eleventyConfig.addGlobal("capture", function(callback) { // Store the original EJS write method const originalWrite = this.ejs.write; let capturedOutput = ""; // Replace the write method to capture output this.ejs.write = function(str) { capturedOutput += str; }; // Execute the callback (your nested markup) callback(); // Restore the original write method this.ejs.write = originalWrite; // Return the captured markup as a string return capturedOutput; }); };
Step 2: Use the Helper in Your Templates
Now you can write components exactly like you envisioned, using capture() to pass nested content as children:
Parent Template (e.g., page.ejs)
<%- include('../grid', { tag: 'ul', // Capture the nested grid-item content and pass as children children: capture(() => { %> <%- include('../grid-item', { size: '50', tag: 'li' }, () => { %> <div>ANY MARKUP YOU WANT</div> <p>Dynamic content, variables, even other includes work here!</p> <% }) %> <%- include('../grid-item', { size: '50', tag: 'li' }, () => { %> <img src="/assets/example.jpg" alt="Custom image content"> <% }) %> <% }) }) %>
grid.ejs Component
<<%= tag %> class="grid-container"> <%= children %> <!-- Render the captured nested content --> </<%= tag %>>
grid-item.ejs Component
<<%= tag %> class="grid-item size-<%= size %>"> <%= children %> <!-- Render the captured content for this item --> </<%= tag %>>
Solution 2: Custom Paired Shortcodes (Alternative)
If you prefer using Eleventy’s shortcode system instead of partials, you can create paired shortcodes that mimic slot behavior. This is similar to Nunjucks/Liquid’s paired shortcodes but tailored for EJS:
Step 1: Register Shortcodes in .eleventy.js
module.exports = function(eleventyConfig) { eleventyConfig.addPairedShortcode("Grid", function(content, options) { const { tag = 'ul' } = options; return `<${tag} class="grid-container">${content}</${tag}>`; }); eleventyConfig.addPairedShortcode("GridItem", function(content, options) { const { size = '100', tag = 'li' } = options; return `<${tag} class="grid-item size-${size}">${content}</${tag}>`; }); };
Step 2: Use Shortcodes in EJS
In your template, call the shortcodes via the this context (Eleventy exposes shortcodes here for EJS):
<%= this.Grid(` ${this.GridItem('<div>ANY MARKUP</div>', { size: '50', tag: 'li' })} ${this.GridItem('<p>More custom content</p>', { size: '50', tag: 'li' })} `, { tag: 'ul' }) %>
This works, but note that the nested content needs to be written as strings (or template literals), which is less intuitive than the first solution’s template-based syntax.
Key Notes
- Keep using EJS’s strengths: Since you’re sticking with EJS, you can still leverage all its "pure JavaScript" features—variables, loops, conditionals, and data objects—right alongside this component pattern.
- Avoid escaping issues: Use
<%- %>instead of<%= %>when including partials or rendering captured content to ensure HTML isn’t escaped. - Test for whitespace: The
capturehelper will include any whitespace from your template, so you can minify output later if needed, or adjust your template formatting to avoid extra spaces.
Your initial intuition about using partials was correct—we just needed to add a small helper to bridge the gap between EJS’s include system and the slot/children pattern you’re used to.
内容的提问来源于stack exchange,提问作者Matt Stow

