Skip to content

metalsmith/slots

Repository files navigation

@metalsmith/slots

A Metalsmith plugin to divide file contents into slots, associate metadata with them and process them separately

metalsmith: core plugin npm: version ci: build code coverage license: MIT

Installation

NPM:

npm install @metalsmith/slots

Yarn:

yarn add @metalsmith/slots

Usage

Pass @metalsmith/slots to metalsmith.use:

import slots from '@metalsmith/slots'

metalsmith.use(slots()) // defaults
metalsmith.use(
  slots({
    // explicit defaults
    pattern: '**/*.{md,html}'
  })
)

Now you can divide your file in parameterized logical content sections or slots, with their own front-matter blocks. Just define the slot field for each. You can associate any metadata with the slots just like file front-matter.

---
layout: default.njk
title: This becomes file.title
---
This becomes file.contents
--- # first slot (becomes file.slots.author)
slot: author
name: John Doe
topics: [sports, finance]
---
This becomes file.slots.author.contents
--- # second slot (becomes file.slots.ads)
slot: ads
url: https://someadprovider.com/?id=abcde1234
---
<!-- end of file -->

@metalsmith/slots then parses the file, removing the slots content from the main file.contents field and adding them to file.slots:

const file = {
  layout: 'default.njk',
  title: 'This becomes file.title',
  contents: Buffer.from('This becomes file.contents'),
  slots: {
    author: {
      slot: 'author',
      name: 'John Doe',
      contents: 'This becomes file.slots.author.contents',
      topics: ['sports', 'finance']
    },
    ads: {
      slot: 'ads',
      contents: '<!-- end of file -->',
      url: 'https://someadprovider.com/?id=abcde1234'
    }
  }
}

If the file already has an existing slots property holding an object, the slots will be shallowly merged in with Object.assign. If the file already has an existing property with another type, it will be overwritten and log a debug warning.

There is one limitation: you cannot *interrupt* the main content with a slot and then continue it. Because front-matter is parsed without an explicit "end" boundary, slots must always be defined at the end of the file.

Defining default slots

You can define a slots property in metalsmith.metadata():

metalsmith.metadata({
  slots: {
    author: {
      slot: 'author',
      name: 'Anonymous',
      contents: 'This author preferred we not publish their identity'
    }
  }
})

This property can then be used by plugins like @metalsmith/layouts that merge file metadata into global metadata as rendering context.

If you rather really set the defaults to the files so other plugins can access it, you can use @metalsmith/default-values

Rendering slots in a layout

With the previous examples, @metalsmith/layouts can render slots in a layout, using slots defined inline in a file, or fall back to metalsmith.metadata:

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>{{ title }}</title>
  </head>
  <body>
    <article>
      <aside>
        <iframe src="{{ slots.ads.url }}"></iframe>
      </aside>
      {{ contents | safe }}
      <footer>
        <h3>By {{ slots.author.name }}</h3>
        Writes about {{ slots.author.topics | join(', ') }}
        <p>{{ slots.author.contents }}</p>
      </footer>
    </article>
  </body>
</html>

Note that you can also use {{ slots.slotname }} as an alias for {{ slots.slotname.contents }} in templating languages that toString() the values they output.

It is not (yet) possible to render slots into their own layouts by defining a slot layout field.

Rendering markdown in a slot

It is easy to render markdown in slots with @metalsmith/markdown's keys and wildcard options to target slot contents of all files:

metalsmith.use(
  markdown({
    wildcard: true,
    keys: ['slots.*.contents']
  })
)

Rendering slots in file.contents

@metalsmith/in-place can be used to render slots inside the file.contents.

index.md

---
layout: default.njk
title: This becomes file.title
---
<h1>{{ title }}</h1>
This becomes file.contents

By {{ slots.author.name }}.
Writes mostly about {{ slots.author.topics | join(', ') }}
<hr>{{ slots.author.contents | safe }}
---
slot: author
name: John Doe
topics: [sports, finance]
---
This becomes file.slots.author.contents.

Combining plugins

An example of using all of @metalsmith layouts, in-place, markdown, default-values and slots in a common order in a metalsmith build:

metalsmith
  // default slots for all files processed with @metalsmith/layouts or in-place
  .metadata({
    slots: {
      author: {
        slot: 'author',
        name: 'Anonymous',
        contents: 'This author preferred we not publish their identity'
      }
    }
  })
  // default slots by file pattern, eg no author for homepage
  .use(
    defaultValues([
      {
        pattern: 'home.md',
        defaults: { slots: (file) => ({ ...(file.slots || {}), author: false }) }
      }
    ])
  )
  .use(slots({ pattern: '**/*.md' }))
  // render markdown inside slots
  .use(markdown({ wildcard: true, keys: ['slots.*.contents'] }))
  // render slots inside file.contents
  .use(inPlace({ pattern: '**/*.html', transform: 'nunjucks' }))
  // render slots inside a file layout
  .use(layouts({ pattern: '**/*.html' }))

Debug

To enable debug logs, set the DEBUG environment variable to @metalsmith/slots*:

metalsmith.env('DEBUG', '@metalsmith/slots*')

Alternatively you can set DEBUG to @metalsmith/* to debug all Metalsmith core plugins.

CLI usage

To use this plugin with the Metalsmith CLI, add @metalsmith/slots to the plugins key in your metalsmith.json file:

{
  "plugins": [
    {
      "@metalsmith/slots": {}
    }
  ]
}

License

MIT

About

A Metalsmith plugin to divide file contents in slots, associate metadata with them and process them separately

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published