0

I'm creating a simple, static site where, in development, I'm using Handlebars.js and making some API calls to fill in the Handlebars templates. But for production, I want to precompile all the templates into static HTML.

I'm trying to automate that process with Gulp, so I've got a task that looks like this:

gulp.task('compileHtml', () => {
  return gulp
    .src('index.html')
    .pipe(someThing())
    .pipe(anotherThing())
    .pipe(compileTemplates())
    .pipe(someOtherThing())
    .pipe(gulp.dest('build'));
});

In my compileTemplates function, I'm using gulp-tap and jsdom to basically run the file with relevant scripts to make the API calls and fill in the Handlebars templates, then remove those scripts and send back the compiled HTML to the next pipe. But I'm having trouble deferring sending back the new DOM until jsdom has had ample time to run all the scripts.

Here's what I have so far:

const compileTemplates = file => {
  return tap(file => {
    const dom = new JSDOM(file.contents,
      {
        runScripts: 'dangerously',
        resources: 'usable',
        beforeParse(window) {
          window.fetch = require('node-fetch');
        },
      },
    );
    const document = dom.window.document;
    const script = document.querySelector('script[src$="handlebars.min.js"]');

    // dom.serialize() still contains my uncompiled templates at this point
    setTimeout(() => {
      script.remove();
      file.contents = Buffer.from(dom.serialize()); // this is what I want to return from this function
    }, 2500);
  });
};

I know I probably need to use a promise to send back file.contents when it's ready, but I'm not great with promises or with Gulp.

I've tried returning a promise that resolves inside the timeout, but I end up with TypeError: dest.on is not a function because the next pipe is ultimately expecting file and not a promise.

How could I refactor to either defer sending back my manipulated file to the next pipe or to send back a promise from this function and then resolve that promise to the new file in my compileHtml task?

I'm using Gulp 4.

cjl750
  • 3,512
  • 3
  • 12
  • 22

1 Answers1

0

After consulting Using setTimeout on Promise Chain, I figured out how to resolve the promise after a timeout.

const compileTemplates = file => {
  return tap(file => {
    const dom = new JSDOM(file.contents,
      {
        runScripts: 'dangerously',
        resources: 'usable',
        beforeParse(window) {
          window.fetch = require('node-fetch');
        },
      },
    );
    const document = dom.window.document;
    const script = document.querySelector('script[src$="handlebars.min.js"]');

    new Promise(resolve => {
      setTimeout(resolve, 2500);
    }).then(() => {
      script.remove();
      file.contents = Buffer.from(dom.serialize());
    });
  });
};

However, that didn't totally solve my problem because I needed to wait for that promise to resolve before sending the new file to the next pipe in my gulp task, but I couldn't find any good documentation on callbacks in gulp-tap.

So I ended up using through2, instead.

const compileHtml = file => {
  return through2.obj(function(file, encoding, callback) {
    const dom = new JSDOM(file.contents,
      {
        runScripts: 'dangerously',
        resources: 'usable',
        beforeParse(window) {
          window.fetch = require('node-fetch');
        },
      },
    );
    const document = dom.window.document;
    const script = document.querySelector('script[src$="handlebars.min.js"]');

    new Promise(resolve => {
      setTimeout(resolve, 2500);
    }).then(() => {
      script.remove();
      file.contents = Buffer.from(dom.serialize());
      this.push(file);
      callback();
    });
  });
}

If anyone knows what this function would look like using gulp-tap, feel free to post an answer!

cjl750
  • 3,512
  • 3
  • 12
  • 22