100行搭建一个node.js简单的静态站点生成器

最近我的一个同事想开一个博客,问我有没有什么建议。在研究了一些静态站点生成器和博客引擎后,我觉得 Hugo 是个很不错的选择。然而,我同事还有一些其它需求,比如想让博客都有自定义 URL 以及自定义 CSS 主题。虽然用 Hugo 也能实现这些要求,但是我还是决定跳过学习使用 Hugo 这部分,看看如果我同事已经有随时可用的 HTML 而且在 HTML 中写博客没有问题,能否创建一个很简单的静态站点生成器。

	接下来就教你使用 node.js >= 8.11.x 创建自己的静态站点生成器。我们首先设置项目:

npm init
npm i --save-exact bluebird chokidar fs-extra mustache
mkdir src
mkdir public
首先我们问一个问题——为什么需要静态站点生成器?答案是实际上你并不是很需要它。如果你的博客流量很小,只需要手工编写 HTML 页面然后发布就行了。实际上,在服务器端编程兴起之前,大部分 web 发布都是通过这种方式完成的。然而,如果你已经有了一些页面和内容,修改所有页面的共有部分(比如页脚)会很麻烦。因此,如果我们能有某种简单的模板引擎,可以分离出共有内容并将其插入需要的位置,那就再好不过了。

	在讲解模板引擎前,首先设置我们的网站。我们会在项目根 src(当前网站所在的位置)和 public(包含我们生成的网站)下创建 2 个文件夹。将 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 运行该脚本,然后坐等胜利的喜悦就完事儿了。

图片描述(最多50字)
图:恭喜,你现在是一名后端开发了!

	在第二步,我们添加一个文件监视器,这样 src 文件夹内出现任何变动都会重新生成该网站。由于这会是个总共有 500-1000 个文件的博客(假设有 100 个博客条目),我们可以在任何变化时重新生成整个网站:



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              前端交流;582735936

    }
  );
  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/patials,它会保存我们的共有部分。然后我们略微修改网站结构,这样全部页面现在就出现在 src/pages中了。剩下的工作就是加载所有的 partials 文件、加载页面并用 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;        前端交流;582735936
};
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]);
  }
};
	最终的代码版本参见 GitLab 上的 Software Dawg 项目( https:// gitlab.com/wheresvic/so ftware-dawg ),和教程中的内容有几处细微的不同:

脚本本身在 src 文件夹中。

略微超过了 100 行,主要是由于干净的代码实践,即常量而不是文件夹路径的字符串等。

没有复制整个 src 文件夹,脚本只复制了必需的文件,比如 CSS,图像等。

项目还用了 node-sass 来编译模板 CSS,不过该环境依赖不是必需的。

	另外,你还可以全局安装 browser-sync 包,通过提供的命令行 npm run live-reload 运行,这样你的浏览器会自动刷新所有页面。注意,由于我们是根据更改重新生成整个站点,所以在 Windows 上效果不太好。

	通过本文的方法,几乎完美地满足了我同事的需求,不仅非常灵活,而且还能让她根据自己的喜好进行自定义。

猜你喜欢

转载自blog.csdn.net/li420520/article/details/83514128