Search code examples
javascriptnode.jsbrowser-syncassemble

Out of memory error: assemble build task & browser sync watch


I'm having an issue where I have a node serve task that watches .hbs files for changes and if a change occurs triggers another node task called 'styleguide'.

This style guide build task is using the node API version of Assemble (v0.23.0).

What's happening is that over time the build task is taking longer and longer to execute until eventually falling over with an error of Out of Memory followed by a JS stacktrace.

Here is the styleguide watch part of the serve task.

const styleguideWatchFiles = [
    './src/templates/layouts/styleguide.hbs',
    './src/templates/styleguide/**/*.hbs',
    './src/components/styleguide/**/*.hbs'
];

//watch STYLEGUIDE
chokidar.watch(styleguideWatchFiles, {
    ignoreInitial: true
})
.on('error', error => log.error(error))
.on('all', (event, path) => {
    log.file('File Changed', path);
    run(styleguide).then(() => {
        browserSync.reload('*.html');
    }).catch(err => {
        log.error(err);
    });
});

Here is the styleguide build task.

/*eslint no-console: 0 */

/**
 * styleguide.js
 *
 * Build script for handling styleguide html templates
 * using category collections in front-matter to categorise parts of styleguide
 * with Assemble and Handlebars.
 *
 * Handlebars: http://handlebarsjs.com/
 * Assemble:   https://github.com/assemble/assemble
 *
 */

import assemble from 'assemble';
import yaml from 'js-yaml';
import plumber from 'gulp-plumber';

import log from './utils/log';
import getPageData from './utils/getPageData';
import renameExt from './utils/renameExtension';
import expand from './utils/expandMatter';

import { styleguidePathConfig as path } from '../config';

export default function styleguide() {

    return new Promise( (resolve, reject) => {
        // Create assemble instance
        let templates = assemble();

        templates.dataLoader('yml', function(str) {
            return yaml.safeLoad(str);
        });

        templates.data(path.data);

        templates.preRender(/\.(hbs|html)$/, expand(templates));

        // Create styleguide pages
        templates.task('preload', (cb) => {

            templates.partials(path.sgPartials);
            templates.layouts(path.layouts);

            // Register helpers
            templates.helpers(path.helpers);

            // Add pages
            templates.pages(path.sgPages);

            // Styleguide page data - used for building dynamic menus
            templates.data({
                coreItems: getPageData('./src/templates/styleguide/core-elements'),
                componentItems: getPageData('./src/templates/styleguide/components'),
                generalItems: getPageData('./src/templates/styleguide/general'),
                sectionItems: getPageData('./src/templates/styleguide/sections')
            });

            cb();

        });

        templates.task('styleguide', ['preload'], () => {

            // Render out the template files to 'dist/styleguide'
            return templates.toStream('pages')
                // Define our own handler for more error information.
                .pipe(plumber({
                    errorHandler: err => {
                        // If we encounter this error on a build task, kill the promise
                        if (process.argv.includes('build')) return reject(err);
                        log.error(`${err.message} in ${err.path}.`);
                    }
                }))
                .pipe(templates.renderFile())
                .pipe(plumber.stop())
                .pipe(renameExt())
                .pipe(templates.dest('dist/styleguide'));

        });

        // Run the Assemble build methods
        templates.build('styleguide', err => {
            if (err) return reject(err);
            return resolve();
        });
    });
}

The getPageData function just loops over the specified folder and builds an array of objects to be used by handlebars template to build out a dynamic menu based on the pages being compiled.

So my question is what's causing the memory leak?

Is is that every time the styleguide.js task is called on change the assemble() instance is not being garbage collected after the resolve is returned?

Do I need to be running the entire thing on watch; calling the 'preload' & styleguide tasks?

Thanks for reading.


Solution

  • Running the styleguide task on it's own (rather than as part of npm start) I saw that the promise wasn't resolving.

    So the issue was a couple of things...

    First of all: In the 'styleguide' task the error log that gulp plumber should have been providing me was in the wrong spot (below the reject). Pulling everything out and rebuilding it piece by piece showed me this (thanks @doowb)

    Second of all: Once I had an error displaying in the console I was able to pin point what was going on. Turns out the reason the task wasn't resolving was because assemble couldn't find a reference to a partial. This led me to my config file where I was setting the partials array and it wasn't including everything I needed.

    I feel so silly now but thanks for steering me on the right track.