1 | # Metalsmith
|
2 |
|
3 | [![npm: version][npm-badge]][npm-url]
|
4 | [![ci: build][ci-badge]][ci-url]
|
5 | [![code coverage][codecov-badge]][codecov-url]
|
6 | [![license: MIT][license-badge]][license-url]
|
7 | [![Gitter chat][gitter-badge]][gitter-url]
|
8 |
|
9 | > An extremely simple, _pluggable_ static site generator for NodeJS.
|
10 |
|
11 | In Metalsmith, all of the logic is handled by plugins. You simply chain them together.
|
12 |
|
13 | Here's what the simplest blog looks like:
|
14 |
|
15 | ```js
|
16 | import { fileURLToPath } from 'node:url'
|
17 | import { dirname } from 'path'
|
18 | import Metalsmith from 'metalsmith'
|
19 | import layouts from '@metalsmith/layouts'
|
20 | import markdown from '@metalsmith/markdown'
|
21 |
|
22 | const __dirname = dirname(fileURLToPath(import.meta.url))
|
23 |
|
24 | Metalsmith(__dirname)
|
25 | .use(markdown())
|
26 | .use(
|
27 | layouts({
|
28 | pattern: '**/*.html'
|
29 | })
|
30 | )
|
31 | .build(function (err) {
|
32 | if (err) throw err
|
33 | console.log('Build finished!')
|
34 | })
|
35 | ```
|
36 |
|
37 | ## Installation
|
38 |
|
39 | NPM:
|
40 |
|
41 | ```
|
42 | npm install metalsmith
|
43 | ```
|
44 |
|
45 | Yarn:
|
46 |
|
47 | ```
|
48 | yarn add metalsmith
|
49 | ```
|
50 |
|
51 | ## Quickstart
|
52 |
|
53 | What if you want to get fancier by hiding unfinished drafts, grouping posts in collections, and using custom permalinks? Just add plugins...
|
54 |
|
55 | ```js
|
56 | import { fileURLToPath } from 'node:url'
|
57 | import { dirname } from 'node:path'
|
58 | import Metalsmith from 'metalsmith'
|
59 | import collections from '@metalsmith/collections'
|
60 | import layouts from '@metalsmith/layouts'
|
61 | import markdown from '@metalsmith/markdown'
|
62 | import permalinks from '@metalsmith/permalinks'
|
63 | import drafts from '@metalsmith/drafts'
|
64 |
|
65 | const __dirname = dirname(fileURLToPath(import.meta.url))
|
66 | const t1 = performance.now()
|
67 | const devMode = process.env.NODE_ENV === 'development'
|
68 |
|
69 | Metalsmith(__dirname) // parent directory of this file
|
70 | .source('./src') // source directory
|
71 | .destination('./build') // destination directory
|
72 | .clean(true) // clean destination before
|
73 | .env({
|
74 | // pass NODE_ENV & other environment variables
|
75 | DEBUG: process.env.DEBUG,
|
76 | NODE_ENV: process.env.NODE_ENV
|
77 | })
|
78 | .metadata({
|
79 | // add any variable you want & use them in layout-files
|
80 | sitename: 'My Static Site & Blog',
|
81 | siteurl: 'https://example.com/',
|
82 | description: "It's about saying »Hello« to the world.",
|
83 | generatorname: 'Metalsmith',
|
84 | generatorurl: 'https://metalsmith.io/'
|
85 | })
|
86 | .use(drafts(devMode)) // only include drafts when NODE_ENV === 'development'
|
87 | .use(
|
88 | collections({
|
89 | // group all blog posts by adding key
|
90 | posts: 'posts/*.md' // collections:'posts' to metalsmith.metadata()
|
91 | })
|
92 | ) // use `collections.posts` in layouts
|
93 | .use(
|
94 | markdown({
|
95 | // transpile all md file contents into html
|
96 | keys: ['description'], // and also file.description
|
97 | globalRefs: {
|
98 | // define links available to all markdown files
|
99 | home: 'https://example.com'
|
100 | }
|
101 | })
|
102 | )
|
103 | .use(permalinks()) // change URLs to permalink URLs
|
104 | .use(
|
105 | layouts({
|
106 | // wrap layouts around html
|
107 | pattern: '**/*.html'
|
108 | })
|
109 | )
|
110 | .build((err) => {
|
111 | // build process
|
112 | if (err) throw err // error handling is required
|
113 | console.log(`Build success in ${((performance.now() - t1) / 1000).toFixed(1)}s`)
|
114 | })
|
115 | ```
|
116 |
|
117 | ## How does it work?
|
118 |
|
119 | Metalsmith works in three simple steps:
|
120 |
|
121 | 1. Read all the files in a source directory.
|
122 | 2. Invoke a series of plugins that manipulate the files.
|
123 | 3. Write the results to a destination directory!
|
124 |
|
125 | Each plugin is invoked with the contents of the source directory, and each file can contain YAML front-matter that will be attached as metadata, so a simple file like...
|
126 |
|
127 | ```yml
|
128 | ---
|
129 | title: A Catchy Title
|
130 | date: 2024-01-01
|
131 | ---
|
132 | An informative article.
|
133 | ```
|
134 |
|
135 | ...would be parsed into...
|
136 |
|
137 | ```js
|
138 | {
|
139 | 'path/to/my-file.md': {
|
140 | title: 'A Catchy Title',
|
141 | date: new Date(2024, 1, 1),
|
142 | contents: Buffer.from('An informative article'),
|
143 | stats: fs.Stats
|
144 | }
|
145 | }
|
146 | ```
|
147 |
|
148 | ...which any of the plugins can then manipulate however they want. Writing plugins is incredibly simple, just take a look at the [example drafts plugin](examples/drafts-plugin/index.js).
|
149 |
|
150 | Of course they can get a lot more complicated too. That's what makes Metalsmith powerful; the plugins can do anything you want!
|
151 |
|
152 | ## Plugins
|
153 |
|
154 | A [Metalsmith plugin](https://metalsmith.io/api/#Plugin) is a function that is passed the file list, the metalsmith instance, and a done callback.
|
155 | It is often wrapped in a plugin initializer that accepts configuration options.
|
156 |
|
157 | Check out the official plugin registry at: https://metalsmith.io/plugins.
|
158 | Find all the core plugins at: https://github.com/search?q=org%3Ametalsmith+metalsmith-plugin
|
159 | See [the draft plugin](examples/drafts-plugin) for a simple plugin example.
|
160 |
|
161 | ## API
|
162 |
|
163 | Check out the full API reference at: https://metalsmith.io/api.
|
164 |
|
165 | ## CLI
|
166 |
|
167 | In addition to a simple [Javascript API](#api), the Metalsmith CLI can read configuration from a `metalsmith.json` file, so that you can build static-site generators similar to [Jekyll](https://jekyllrb.com) or [Hexo](https://hexo.io) easily. The example blog above would be configured like this:
|
168 |
|
169 | `metalsmith.json`
|
170 |
|
171 | ```json
|
172 | {
|
173 | "source": "src",
|
174 | "destination": "build",
|
175 | "clean": true,
|
176 | "metadata": {
|
177 | "sitename": "My Static Site & Blog",
|
178 | "siteurl": "https://example.com/",
|
179 | "description": "It's about saying »Hello« to the world.",
|
180 | "generatorname": "Metalsmith",
|
181 | "generatorurl": "https://metalsmith.io/"
|
182 | },
|
183 | "plugins": [
|
184 | { "@metalsmith/drafts": true },
|
185 | { "@metalsmith/collections": { "posts": "posts/*.md" } },
|
186 | { "@metalsmith/markdown": true },
|
187 | { "@metalsmith/permalinks": "posts/:title" },
|
188 | { "@metalsmith/layouts": true }
|
189 | ]
|
190 | }
|
191 | ```
|
192 |
|
193 | Then run:
|
194 |
|
195 | ```bash
|
196 | metalsmith
|
197 |
|
198 | # Metalsmith · reading configuration from: /path/to/metalsmith.json
|
199 | # Metalsmith · successfully built to: /path/to/build
|
200 | ```
|
201 |
|
202 | Options recognised by `metalsmith.json` are `source`, `destination`, `concurrency`, `metadata`, `clean` and `frontmatter`.
|
203 | Checkout the [static site](examples/static-site), [Jekyll](examples/jekyll) examples to see the CLI in action.
|
204 |
|
205 | ### Local plugins
|
206 |
|
207 | If you want to use a custom plugin, but feel like it's too domain-specific to be published to the world, you can include plugins as local npm modules: (simply use a relative path from your root directory)
|
208 |
|
209 | ```json
|
210 | {
|
211 | "plugins": [{ "./lib/metalsmith/plugin.js": true }]
|
212 | }
|
213 | ```
|
214 |
|
215 | ## The secret...
|
216 |
|
217 | We often refer to Metalsmith as a "static site generator", but it's a lot more than that. Since everything is a plugin, the core library is just an abstraction for manipulating a directory of files.
|
218 |
|
219 | Which means you could just as easily use it to make...
|
220 |
|
221 | - [A project scaffolder.](examples/project-scaffolder)
|
222 | - [A build tool for Sass files.](examples/build-tool)
|
223 | - [A simple static site generator.](examples/static-site)
|
224 | - [A Jekyll-like static site generator.](examples/jekyll)
|
225 |
|
226 | ## Resources
|
227 |
|
228 | - [Gitter Matrix community chat](https://app.gitter.im/#/room/#metalsmith_community:gitter.im) for chat, questions
|
229 | - [X (formerly Twitter) announcements](https://x.com/@metalsmithio) and the [metalsmith.io news page](https://metalsmith.io/news) for updates
|
230 | - [Awesome Metalsmith](https://github.com/metalsmith/awesome-metalsmith) - great collection of resources, examples, and tutorials
|
231 | - [emmer.dev on metalsmith](https://emmer.dev/blog/tag/metalsmith/) - A good collection of various how to's for metalsmith
|
232 | - [glinka.co on metalsmith](https://www.glinka.co/blog/) - Another great collection of advanced approaches for developing metalsmith
|
233 | - [Getting to Know Metalsmith](http://robinthrift.com/post/getting-to-know-metalsmith/) - a great series about how to use Metalsmith for your static site.
|
234 |
|
235 | ## Troubleshooting
|
236 |
|
237 | Set `metalsmith.env('DEBUG', '*metalsmith*')` to debug your build. This will log debug logs for all plugins using the built-in `metalsmith.debug` debugger.
|
238 | For older plugins using [debug](https://github.com/debug-js/debug/) directly, run your build with `export DEBUG=metalsmith-*,@metalsmith/*` (Linux) or `set DEBUG=metalsmith-*,@metalsmith/*` for Windows.
|
239 |
|
240 | ### Node Version Requirements
|
241 |
|
242 | Future Metalsmith releases will at least support the oldest supported Node LTS versions.
|
243 |
|
244 | Metalsmith 2.6.x supports NodeJS versions 14.18.0 and higher.
|
245 | Metalsmith 2.5.x supports NodeJS versions 12 and higher.
|
246 | Metalsmith 2.4.x supports NodeJS versions 8 and higher.
|
247 | Metalsmith 2.3.0 and below support NodeJS versions all the way back to 0.12.
|
248 |
|
249 | ### Compatibility & support policy
|
250 |
|
251 | Metalsmith is supported on all common operating systems (Windows, Linux, Mac).
|
252 | Metalsmith releases adhere to [semver (semantic versioning)](https://semver.org/) with 2 minor gray-area exceptions for what could be considered breaking changes:
|
253 |
|
254 | - Major Node version support for EOL (End of Life) versions can be dropped in minor releases
|
255 | - If a change represents a major improvement that is backwards-compatible with 99% of use cases (not considering outdated plugins), they will be considered eligible for inclusion in minor version updates.
|
256 |
|
257 | ## Credits
|
258 |
|
259 | Special thanks to [Ian Storm Taylor](https://github.com/ianstormtaylor), [Andrew Meyer](https://github.com/Ajedi32), [Dominic Barnes](https://github.com/dominicbarnes), [Andrew Goodricke](https://github.com/woodyrew), [Ismay Wolff](https://github.com/ismay), [Kevin Van Lierde](https://github.com/webketje) and [others](https://github.com/segmentio/metalsmith/graphs/contributors) for their contributions!
|
260 |
|
261 | ## [License](LICENSE)
|
262 |
|
263 | [npm-badge]: https://img.shields.io/npm/v/metalsmith.svg
|
264 | [npm-url]: https://www.npmjs.com/package/metalsmith
|
265 | [ci-badge]: https://github.com/metalsmith/metalsmith/actions/workflows/test.yml/badge.svg
|
266 | [ci-url]: https://github.com/metalsmith/metalsmith/actions/workflows/test.yml
|
267 | [codecov-badge]: https://coveralls.io/repos/github/metalsmith/metalsmith/badge.svg?branch=master
|
268 | [codecov-url]: https://coveralls.io/github/metalsmith/metalsmith?branch=master
|
269 | [license-badge]: https://img.shields.io/github/license/metalsmith/metalsmith
|
270 | [license-url]: LICENSE
|
271 |
|
272 | [gitter-badge]: https://img.shields.io/badge/[gitter:matrix]-join-blue.svg
|
273 | [gitter-url]: https://app.gitter.im/#/room/#metalsmith_community:gitter.im
|