IT教程 ·

40行代码手撸一个静态文档生成器[译]

Redis(2)——跳跃表

 

为何要造这个轮子

当我设计竖立个人网站时,我的需求很简朴,做一个只要几个页面的网站,安排一些关于自身的信息,我的妙技和项目就够了。

毫无疑问,它应当是纯静态的(不须要后端效劳,可托管在任何地方)。

我曾运用过Jekyll, HugoHexo这些着名的静态文档生成器,但我认为它们有太多的功用,我不想为我的网站增添这么多的庞杂性。

所以我认为,针对我的需求,一个简朴的静态文档生成器就能够满足。

嗯,手动构建一个简朴的生成器,应当不会那末难。

正文

需求剖析

这个生成器必需满足以下前提:

  • EJS模板生成HTML文件。
  • 具有规划文件,一切页面都应当具有雷同的页眉,页脚,导航等。
  • 允允许重用规划组件。
  • 站点的大抵信息封装到一个设置文件中。
  • 从JSON文件中读取数据。

    比方:项目列表,如许我能够轻松地迭代和构建项目页面。

为何运用 EJS 模板?

因为 EJS 很简朴,它只是嵌入在 HTML 中的 JavaScript 罢了。

项目构造

public/  
 src/  
   assets/  
   data/  
   pages/  
   partials/  
   layout.ejs  
 site.config.js
  • public: 生成站点的位置。
  • src: 源文件。
  • src/assets: 包含 CSS, JS, 图片 等
  • src/data: 包含 JSON 数据。
  • src/pages: 依据个中的 EJS 生成 HTML 页面的模板文件夹。
  • src/layout.ejs: 重要的原页面模板,包含特别<%-body%>占位符,将插进去详细的页面内容。
  • site.config.js: 模板中全局设置文件。

生成器

生成器代码位于scripts/build.js文件中,每次想重修站点时,实行npm run build敕令即可。

完成要领是将以下剧本增加到package.jsonscripts块中:

"build": "node ./scripts/build"

下面是完全的生成器代码:

const fse = require('fs-extra')
const path = require('path')
const { promisify } = require('util')
const ejsRenderFile = promisify(require('ejs').renderFile)
const globP = promisify(require('glob'))
const config = require('../site.config')

const srcPath = './src'
const distPath = './public'

// clear destination folder
fse.emptyDirSync(distPath)

// copy assets folder
fse.copy(`${srcPath}/assets`, `${distPath}/assets`)

// read page templates
globP('**/*.ejs', { cwd: `${srcPath}/pages` })
  .then((files) => {
    files.forEach((file) => {
      const fileData = path.parse(file)
      const destPath = path.join(distPath, fileData.dir)

      // create destination directory
      fse.mkdirs(destPath)
        .then(() => {
          // render page
          return ejsRenderFile(`${srcPath}/pages/${file}`, Object.assign({}, config))
        })
        .then((pageContents) => {
          // render layout with page contents
          return ejsRenderFile(`${srcPath}/layout.ejs`, Object.assign({}, config, { body: pageContents }))
        })
        .then((layoutContent) => {
          // save the html file
          fse.writeFile(`${destPath}/${fileData.name}.html`, layoutContent)
        })
        .catch((err) => { console.error(err) })
    })
  })
  .catch((err) => { console.error(err) })

接下来,我将诠释代码中的详细组成部分。

依靠

我们只须要三个依靠项:

  • 把我们的模板编译成HTML
  • Node 文件模块的衍生版,具有更多的功用,并增添了Promise的支撑。
  • 递归读取目次,返回包含与指定形式婚配的一切文件,范例是数组。

Promisify

我们运用Node供应的util.promisify将一切回调函数转换为基于Promise的函数。

它使我们的代码更短,更清楚,更易于浏览。

const { promisify } = require('util')  
const ejsRenderFile = promisify(require('ejs').renderFile)  
const globP = promisify(require('glob'))

加载设置

在顶部,我们加载站点设置文件,以稍后将其注入模板衬着中。

const config = require('../site.config')

站点设置文件自身会加载其他JSON数据,比方:

const projects = require('./src/data/projects')

module.exports = {  
  site: {  
    title: 'NanoGen',  
    description: 'Micro Static Site Generator in Node.js',  
    projects  
  }  
}

清空站点文件夹

我们运用fs-extra供应的emptyDirSync函数清空 生成后的站点文件夹。

fse.emptyDirSync(distPath)

拷贝静态资本

我们运用fs-extra供应的copy函数,该函数以递归体式格局复制静态资本 到站点文件夹。

fse.copy(`${srcPath}/assets`, `${distPath}/assets`)

编译页面模板

起首我们运用glob(已被 promisify)递归读取src/pages文件夹以查找.ejs文件。

它将返回一个婚配给定形式的一切文件数组。

globP('**/*.ejs', { cwd: `${srcPath}/pages` })  
  .then((files) => {

关于找到的每一个模板文件,我们运用Nodepath.parse函数来分开文件途径的各个组成部分(比方目次,称号和扩大名)。

然后,我们在站点目次中运用fs-extra供应的mkdirs函数建立与之对应的文件夹。

files.forEach((file) => {  
  const fileData = path.parse(file)  
  const destPath = path.join(distPath, fileData.dir)

 // create destination directory  
  fse.mkdirs(destPath)

然后,我们运用EJS编译文件,并将设置数据作为数据参数。

因为我们运用的是已 promisify 的ejs.renderFile函数,因而我们能够返回挪用效果,并鄙人一个promise链中处理效果。

.then(() => {  
  // render page  
  return ejsRenderFile(`${srcPath}/pages/${file}`, Object.assign({}, config))  
})

鄙人一个then块中,我们得到了已编译好的页面内容。

如今,我们编译规划文件,将页面内容作为body属性通报进去。

.then((pageContents) => {  
  // render layout with page contents  
  return ejsRenderFile(`${srcPath}/layout.ejs`, Object.assign({}, config, { body: pageContents }))  
})

末了,我们得到了生成好的编译效果(规划+页面内容的 HTML),然后将其保存到对应的HTML文件中。

.then((layoutContent) => {  
  // save the html file  
  fse.writeFile(`${destPath}/${fileData.name}.html`, layoutContent)  
})

调试效劳器

为了使检察效果更轻易,我们在package.jsonscripts中增加一个简朴的静态效劳器。

"serve": "serve ./public"

运转 npm run serve 敕令,翻开就看到效果了。

进一步探究

Markdown

大多数静态文档生成器都支撑以Markdown花样编写内容。

而且,它们还支撑以YAML花样在顶部增加一些元数据,以下所示:

---  
title: Hello World  
date: 2013/7/13 20:46:25  
---

只须要一些修正,我们就能够支撑雷同的功用了。

起首,我们必需增添两个依靠:

  • markdown编译为HTML
  • markdown中提取元数据(front matter)。

然后,我们将glob的婚配形式更新为包含.md文件,并保存.ejs,以支撑衬着庞杂页面。

假如想要布置一些纯 HTML 页面,还需包含.html

globP('**/*.@(md|ejs|html)', { cwd: `${srcPath}/pages` })

关于每一个文件,我们都必需加载文件内容,以便能够在顶部提取到元数据。

.then(() => {  
  // read page file  
  return fse.readFile(`${srcPath}/pages/${file}`, 'utf-8')  
})

我们将加载后的内容通报给front-matter

它将返回一个对象,个中attribute属性就是提取后的元数据。

然后,我们运用此数据扩大站点设置。

.then((data) => {  
  // extract front matter  
  const pageData = frontMatter(data)  
  const templateConfig = Object.assign({}, config, { page: pageData.attributes })

如今,我们依据文件扩大名将页面内容编译为 HTML。

假如是.md,则应用marked函数编译;

假如是.ejs,我们继承运用EJS编译;

假如是.html,便无需编译。

let pageContent  

switch (fileData.ext) {  
  case '.md':  
    pageContent = marked(pageData.body)  
    break  
  case '.ejs':  
    pageContent = ejs.render(pageData.body, templateConfig)  
    break  
  default:  
    pageContent = pageData.body  
}

末了,我们像之前一样衬着规划。

增添元数据,最显著的一个意义是,我们能够为每一个页面设置零丁的标题,以下所示:

---  
title: Another Page  
---

并让规划动态地衬着这些数据:

<title><%= page.title ? `${page.title} | ` : '' %><%= site.title %></title>

如此一来,每一个页面将具有唯一的<title>标签。

多种规划的支撑

另一个风趣的探究是,在特定的页面中运用差别的规划。

比方特地为站点首页设置一个举世无双的规划:

---  
layout: minimal  
---

我们须要有零丁的规划文件,我将它们放在src/layouts文件夹中:

src/layouts/  
   default.ejs  
   mininal.ejs

假如front matter涌现了规划属性,我们将应用layouts文件夹中同名模板文件举行衬着; 假如未设置,则应用默许模板衬着。

const layout = pageData.attributes.layout || 'default'

return ejsRenderFile(`${srcPath}/layouts/${layout}.ejs`, 
  Object.assign({}, templateConfig, { body: pageContent })
)

纵然增加了这些新特征,构建剧本也才只要60行。

下一步

假如你想更进一步,能够增加一些不难的附加功用:

  • 可热重载的调试效劳器

    你能够运用像 (内置自动从新加载) 或 (视察文件修正以自动触发构建剧本)如许的模块去完成。

  • 自动布置

    增加剧本以将站点布置到GitHub Pages等罕见的托管效劳,或仅经由过程SSH(运用scprsync等敕令)将文件上传到你自身的效劳器上。

  • 支撑 CSS/JS 预处理器

    在静态文件被复制到站点文件前,增添一些预处理器(SASS 编译为 CSS,ES6 编译为 ES5 等)。

  • 更好的日记打印

    增加一些 console.log 日记输出 来更好地剖析发生了什么。

    你能够运用chalk包来完美这件事。

反应? 有什么发起吗? 请随时宣布批评或与我联络!

结束语

一段时间后,我决定将项目转换为CLI模块,以使其更易于运用,它位于上面链接的master分支中。

译者:

本日本想写一篇(一个高性能的goroutine池)源码剖析,怎样环境太吵,静不下心,遂罢。

这是一篇我前些日子无意间看到的文章,虽然是17年的文章,在读完以后仍对我产生了一些思索。

愿望这篇文章对你有所协助。

转载本站文章请说明作者和出处 ,请勿用于任何商业用途。

bp(net core)+easyui+efcore实现仓储管理系统——入库管理之二(三十八)

参与评论