Integrating Eleventy with gulp, upstream JS
Eleventy notes that it, "works great with data — use both front matter and external data files" but the static site generator stops short of working well with upstream in-memory data objects for local development.
This post is prompted by a project for a highly-modular design system as part of my work for EMBL — a leading laboratory for the life sciences. We're using Eleventy with gulp (for task running and building) and Fractal (for design components).
We wanted to:
- Have gulp trigger builds of Eleventy
- Utilise Eleventy’s watch/refresh commands for local development
- Send variables to Eleventy’s JavaScript data files from gulp (or other Node JS tasks)
There are methods to achieve part 1 but not with part 2 and 3. But we made a solution.
tl;dr
- We forked 11ty’s cmd.js to better integrate with gulp and other Node JS.
- I made a demo at khawkins98/gulp-eleventy-example.
- I hope Eleventy’s maintainers can take some inspiration from this.
Still here? Great. Read on.
Scenario
Here's some really useful data, please use it
Fractal creates a really useful JavaScript object that lists component file structure, the contents of those files and compiles nested templates.
An illustrative example of the file structure:
fractalComponents = {
component: “myComponent”,
files: {
“myComponent.css”: {
“Contents of the CSS”
},
“myComponent.njk”: {
“Contents of the NJK template”
},
“CHANGELOG.md”: {
“Contents of the changelog”
},
“README.md”: {
“Contents of the readme”
}
… and so on
Also, Fractal enables component Nunjucks templates can be invoked like:
<!-- Could not render component '@myNavComponent' - component not found. -->
Which is great as it allows us to npm install
components and not have to move/symlink the Nunjucks templates into Eleventy src/
directory — and it keeps syntax consistent across environments.
In principle we could get Eleventy to do the same tasks, scanning the file system and rendering our templates — but we didn’t want to duplicate our build process — especially considering that both Eleventy and Fractal were already running in Node JS.
So, that means we've got some in-memory data we'd really like for Eleventy to receive, and receive updates to if a component is added or edited.
This is important for local development
In our use case we're editing files locally and want a Task A (Fractal) to be able to see changes, updates its data and feed it to Task B (Eleventy).
Eleventy's current design is a non-issue for our production builds. For production, Fractal generates the data and just hands it off to Eleventy. (That said: I could envision a scenario where part of the build process the Eleventy process might want to feed data to some Process C for dynamic rendering.)
So what are you asking for?
Designing our solution
As previously mentioned, Eleventy has a very nice feature supporting JavaScript data files. And as with most watch
command's Eleventy's watch observes only file-system changes — that means we need our upstream task (gulp) to be able to trigger an Eleventy rebuild for local development.
So a conceptual example for our desired scenario looks like the below.
- Have some upstream data that we want to pass to Eleventy.
// Generate a sample list of all files in a scope outside of Eleventy
gulp.task('file-list', function () {
global.fileList = []; // we could pass by not using a `global`, but this is the simplest for an example
return gulp.src(['./somePath/**/*.{njk,html,js,md}'])
.pipe(through.obj(function (file, enc, cb) {
global.fileList.push(file.path);
cb(null);
}));
});
- Have an Eleventy data file pull in a variable.
// ./src/site/_data/fileList.js
// Capture the sample list of all files from gulp
// for demonstration Gulp integration with Eleventy
module.exports = {
files: global.fileList
};
- On a targeted change event, have Gulp invoke ask Eleventy to rebuilt.
// Watch something for changes
gulp.task('watch', function() {
gulp.watch(['./src/**/*.{njk,html,js,md'], gulp.series('file-list', 'eleventy:reload'));
});
// Or another scenario with an `.on` event triggering a refresh
let fractal = require(fractalConfig).initialize();
fractal.components.on('updated', function() {
elev.restart();
elev.write();
}
// Refresh eleventy
gulp.task('eleventy:reload', function(done) {
elev.restart()
elev.write()
});
Recap: we want to do some local development, let a parent process update a variable and and then ask Eleventy to trigger a rebuild, pulling in the new data by the Eleventy JS data file.
Eleventy, can you hear me?
Making it happen
Eleventy’s entry is cmd.js
and we need access to elev
— but that unfortunately is inside a try
statement.
So I forked 11ty’s cmd.js in the local project. Those changes better integrate with external JS with a few minor changes but it's all about a key change: module.exports = elev;
In this way Gulp, or any other Node process, can now use Eleventy as a child task.
How? Like this
- Set up Eleventy using our forked local command file.
// Prepare eleventy
process.argv.push('--config=eleventy.js'); // Eleventy config
const elev = require('./eleventy-cmd.js');
- We aks eleventy to do its initial build.
gulp.task('eleventy:build', function(done) {
elev.write().then(function() {
console.log('Done building 11ty');
done();
});
});
- Do a deep rebuild of Eleventy when a file outside of Eleventy’s scope changes or an event trigger is received.
gulp.task('eleventy:reload', function(done) {
elev.restart()
elev.write()
});
This change works well for us but we'll of course need to make sure our local cmd.js
incorporates any upstream changes (we forked it from Eleventy 0.9.0)
Enough talking
Here's some code to try
I made a demo repository using this approach that you can:
git clone https://github.com/khawkins98/gulp-eleventy-example.git
andnpm install
, or just:- Browse 🔎 the demo at github.com/khawkins98/gulp-eleventy-example
- If you have feedback 💬, I'd love to hear it. Either as an issue or on Twitter @khawkins98
What's next
- I'll likely make an issue on Eleventy about supporting this
- If that doesn't get support (or I feel inspired) I may also make an npm
gulp-eleventy-example
package