javascriptnode.jsjekyll

Problems running Jekyll and tasks via ESM


I'm encountering strange behavior running a Jekyll build. I kick off the build using npm start from the package.json

package.json

  "scripts": {
    "start": "node index.mjs",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

That executes the necessary tasks to build the Jekyll site via the index.mjs file.

index.mjs:

import { exec } from "child_process";
import browserSync from "browser-sync";
import fs from "fs";
import postcss from "postcss";
import autoprefixer from "autoprefixer";
import { minify } from "terser";

const mainJsFile = "main.min.js";

function deleteSiteDirectory() {
  const siteDir = "./_site";
  const sassCacheDir = "./.sass-cache";

  // Delete _site directory
  fs.rm(siteDir, { recursive: true, force: true }, (err) => {
    if (err) {
      console.error(`Error deleting directory ${siteDir}: ${err}`);
    } else {
      console.log(`Directory ${siteDir} has been deleted`);
    }
  });

  // Delete .sass-cache directory
  fs.rm(sassCacheDir, { recursive: true, force: true }, (err) => {
    if (err) {
      console.error(`Error deleting directory ${sassCacheDir}: ${err}`);
    } else {
      console.log(`Directory ${sassCacheDir} has been deleted`);
    }
  });

  // Delete main.min.js file
  fs.rm(mainJsFile, { force: true }, (err) => {
    if (err) {
      console.error(`Error deleting file ${mainJsFile}: ${err}`);
    } else {
      console.log(`File ${mainJsFile} has been deleted`);
    }
  });
}

async function concatenateAndMinifyJsFiles() {
  const jsDir = "./js/utils/";

  try {
    // Delete main.min.js file
    await fs.promises.rm(mainJsFile, { force: true });
    console.log(`File ${mainJsFile} has been deleted`);

    // Concatenate and minify .js files
    const files = await fs.promises.readdir(jsDir, { withFileTypes: true });
    const jsFiles = files.filter((file) => file.isFile() && file.name.endsWith(".js"));
    if (jsFiles.length === 0) {
      console.log(`No .js files found in ${jsDir}`);
      return;
    }

    const concatenatedJs = jsFiles.map((file) => fs.readFileSync(`${jsDir}${file.name}`, "utf8")).join("\n");
    const result = await minify(concatenatedJs);
    const minifiedJs = result.code;

    const outputDir = "./js/";
    const outputFile = `${outputDir}${mainJsFile}`;

    await fs.promises.writeFile(outputFile, minifiedJs);
    console.log(`File ${mainJsFile} has been created`);
  } catch (err) {
    console.error(`Error: ${err}`);
  }
}

function runJekyllCommand() {
  exec("jekyll serve --watch", (error, stdout, stderr) => {
    if (error) {
      console.error(`exec error: ${error}`);
      return;
    }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);

    // Start BrowserSync
    browserSync.init({
      server: {
        baseDir: "_site/",
      },
    });

    // Watch for changes in the source files and reload the browser
    browserSync.watch("./_site/*.*").on("change", browserSync.reload);
  });

  // Initialize Browsersync
  browserSync.init({
    server: {
      baseDir: "./_site/",
    },
  });

  // Use Browsersync reload function as a listener to Jekyll build events
  browserSync.watch("**/*.*").on("change", browserSync.reload);
}

function autoprefixCssFiles() {
  const cssDir = "./_site/css/";
  fs.readdir(cssDir, (err, files) => {
    if (err) {
      console.error(`Error reading directory ${cssDir}: ${err}`);
      return;
    }

    files.forEach((file) => {
      if (file.endsWith(".css")) {
        const filePath = `${cssDir}${file}`;
        fs.readFile(filePath, (err, css) => {
          if (err) {
            console.error(`Error reading file ${filePath}: ${err}`);
            return;
          }

          postcss([autoprefixer({ overrideBrowserslist: [">1%"] })])
            .process(css, { from: filePath, to: filePath })
            .then((result) => {
              fs.writeFile(filePath, result.css, (err) => {
                if (err) console.error(`Error writing file ${filePath}: ${err}`);
              });
            });
        });
      }
    });
  });
}

if (process.env.NODE_ENV !== "production") {
  deleteSiteDirectory();
  concatenateAndMinifyJsFiles();
  runJekyllCommand();
  autoprefixCssFiles();

  console.log("Running in development mode. Development tasks have been executed");
}

When the browser opens, I get a Cannot GET / message on the screen and nothing else. Here's a screenshot. If I refresh the browser the site is there. I can't determine why this is happening.

enter image description here


Solution

  • The issue appears to be BrowserSync attempting to run before Jekyll is ready. First I removed the duplicate browserSync.watch() method that was part of the Jekyll exec command:

    browserSync.watch("./_site/*.*").on("change", browserSync.reload);
    

    Then I placed the BrowserSync init and watch commands in a setTimeout() method to delay them running until Jekyll had run:

    setTimeout(() => {
      // Initialize Browsersync
      browserSync.init({
        server: {
          baseDir: "_site/",
        },
      });
    
      // Use Browsersync reload function as a listener to Jekyll build events
      browserSync.watch("**/*.*").on("change", browserSync.reload);
    }, 1000);
    

    This resolved the Cannot GET / error.