1. 程式人生 > >教你用 100 行 Node.js 程式碼,快速構建一個靜態網站生成器!

教你用 100 行 Node.js 程式碼,快速構建一個靜態網站生成器!

近日,我的一位同事向我尋求建議,她打算為自己構建一個部落格。於是,我對靜態網站生成器和部落格引擎進行了一番研究,發現 Hugo 是一個很不錯的選擇。但是,我的同事還有一些特殊要求,比如,她想要一個自定義的部落格網址和 CSS 主題。儘管這些 Hugo 都可以實現,但我並不打算花時間來學習它。我想自己建立一個簡單的靜態網站生成器,以便我的同事在她已經準備好的  HTML 中編寫部落格文章。

這個靜態網站生成器的程式碼大約 100 行,非常簡潔。它提供了詳細程式碼和示例部落格 。眾所周知,GitLab 提供靜態頁面的免費託管服務,還帶有 CI/CD 功能,它允許你在部署之前編譯頁面。

以下教程將帶你使用 Node.js 設定自己的靜態網站生成器,Node.js 的版本需要 “>= 8.11.x”。

npm init
npm i --save-exact bluebird chokidar fs-extra mustache
mkdir src
mkdir public

首先,設定專案:

開始之前,我們需要弄清楚一個問題:為什麼需要靜態網站生成器?因為某些情況並不需要靜態網站生成器。假如你的部落格訪問量很小,你只需簡單地手工建立 HTML 頁面併發布它們即可。實際上,在伺服器程式設計興起之前,在很長時間內這就是大多數 Web 的釋出方式。但是,一旦頁面和內容增加,對這些頁面中的通用部分(例如頁面底部)進行更改將會變得非常重複和乏味。因此,我們開始尋找一種更加理想的方法,嘗試使用某種簡單的模板引擎來分離常見內容,然後在特定的地方插入所需的內容。

開始研究模板引擎之前,先設定我們的網站。我們需要在專案根目錄下建立 2 個資料夾 :

  1. SRC:我們當前網站所在的位置;

  2. Public:用來存放我們生成的網站。

我們的目標是將 src 目錄的內容複製到 public 目錄中。在專案根目錄下建立 index.js 檔案,其內容如下:

const Promise = require("bluebird");
const fse = require("fs-extra");

Promise.resolve().then(async () => {
  await main();
});

const main = async() => {
  await generateSite();
};

const generateSite = async() => {
  await copyAssets();
};

const copyAssets = async() => {
  await fse.emptyDir("public");
  await fse.copy("src", "public");
};

執行命令 node index.js,即可啟動該指令碼。

 

祝賀你!此刻,你已榮升為一名後端開發人員。

接下來,我們將新增檔案監視器,src 資料夾中的內容一旦發生更改就將重新生成網站。該部落格總共包含 500-1000 個檔案,我們可以在任何變化發生時重新生成整個網站:

const chokidar = require("chokidar");

const main = async() => {
  await generateSite();
  watchFiles();
};

const watchFiles = () => {
  const watcher = chokidar.watch(
    [
      "src"
    ],
    {
      ignored: /(^|[/\])../, // chokidar will watch folders recursively
      ignoreInitial: false,
      persistent: true
    }
  );

  watcher.on("change", async path => {
    console.log("changed " + path + ", recompiling");
    await generateSite();
  });

  // catch ctrl+c event and exit normally
  process.on("SIGINT", function() {
    watcher.close();
  });
};

上面的程式碼清楚地說明了為什麼初始版本有一個名為 generateSite 的函式。現在執行命令 node index.js 啟動我們的靜態網站生成器,如果在 src 目錄中編輯任何檔案,public 都會發生變化。此時,我們還將新增一個環境變數來區分開發和生產模式。在開發模式中,我們將關注更改情況並重新生成網站,而在生產模式中,我們只需重新生成:

const env = process.env.NODE_ENV || "dev";

const main = async () => {
  console.log("Running app in " + env);
  await generateSite();

  if (env === "dev") {
    watchFiles();
  }
};

我們可以執行命令 export NODE_ENV=prod || set NODE_ENV=prod && node index.js 來執行以上程式碼。請注意,觀察源目錄的更改和重新編譯並不是每次都必須的,你可以跳過此步驟,只需在每次進行更改時執行指令碼即可。

至此,差不多完成了!現在來說說模板。我們將使用 Mustache.js 模板,它非常簡單易用,並且我們的需求並不複雜。建立一個資料夾 src/partials,用來存放網站的公共部分。然後稍微修改我們的網站結構,保證所有頁面都存放在 src/pages 目錄中。接下來載入頁面並使用 Mustache 渲染:

const fs = require("fs");

const generateSite = async () => {
  await copyAssets();
  await buildContent();
};

const buildContent = async () => {
  const pages = await compilePages();
  await writePages(pages);
};

const compilePages = async () => {
  const partials = await loadPartials();

  const result = {};
  const pagesDir = path.join("src", "pages");
  const fileNames = await fs.readdirAsync(pagesDir);
  for (const fileName of fileNames) {
    const name = path.parse(fileName).name;
    const fileContent = await fs.readFileAsync(path.join(pagesDir, fileName));
    result[name] = Mustache.render(fileContent.toString(), {}, partials);
  }
  return result;
};

const loadPartials = async () => {
  const result = {};
  const partialsDir = path.join("src", "partials");
  const fileNames = await fs.readdirAsync(partialsDir);
  for (const fileName of fileNames) {
    const name = path.parse(fileName).name;
    const content = await fs.readFileAsync(path.join(partialsDir, fileName));
    result[name] = content.toString();
  }
  return result;
};

const writePages = async pages => {
  for (const page of Object.keys(pages)) {
    await fs.writeFileAsync(path.join("public", page + ".html"), pages[page]);
  }
};

想要了解最終版本,請檢視 Software Dawg 專案(https://gitlab.com/wheresvic/software-dawg)。它與本教程有一些細微差別:

  • 指令碼本身位於 src 目錄下。

  • 程式碼略超過了 100 行,大約 130 行,為了遵循簡潔的程式碼實踐風格,使用常量而不是資料夾路徑的字串。

  • 該指令碼不會複製整個 src 資料夾,而只複製必要的資源,比如 CSS 檔案、圖片等。

  • 該專案使用 node-sass 編譯模板 CSS。然而,這種依賴性不是必需的,因為已編譯的 CSS 檔案也被提交到了 Git。

此外,你還可以安裝 browser-sync 軟體包,然後通過命令 npm run live-reload 執行它,如此一來,只要有任何更改發生瀏覽器就會自動重新整理。請注意,由於任何更改都將重新生成整個網站,因此並不適用於 Windows。

GitLab 提供靜態網站免費託管,只需一個 .gitlab-ci.yml 配置檔案即可。真正令人難以置信之處在於,你可以自定義構建過程,這意味著在該例中,我們可以在部署之前生成網站!有關此功能的詳細資訊,請參見https://about.gitlab.com/features/pages/。