禅与静态个人站开发技术

06/11/2019 02:56:45 @ New York

本来的标题是“分享一下用 Nuxt.js 快速重写了积灰多年的个人网站的经历顺便回顾下最近的生活”。现在的标题好像有点误导人:《禅与摩托车维修艺术》没有讲摩托车维修,这篇文章却真的打算讲静态站开发……

成品在 https://mondaychen.github.io/。代码在 https://github.com/mondaychen/mondaychen.github.io/tree/v2


之前看到一个推特黑工程师写博客之前要用一大堆最新最火爆的前端库和工具链先做一个个人网站,最后网站倒是写的不错,三年却只写了一篇博客……嘛,不能免俗的我也忍不住把自己 2016 年写的老个人站翻出来重写了一遍。原先的网站是一套非常原始的 require.js + jQuery + backbone 的结构,用 gulp 管理依赖。这几样当年很火的东西,最近两年入行的前端工程师可能只听过一个 jQuery 吧。

这次重写没有动样式,把 UI 库换成了 vue.js,原先的 hash SPA 模式换成了生成静态页面。博客数据仍然是用 Markdown + JSON 维护。

create-nuxt-app 介绍

重写网站用的是 Vue.js 的快速开发框架 Nuxt.js(与 React.js 社区的 Next.js 核心功能基本一致)。它的命令行工具 create-nuxt-app 可以帮我们快速配置一个如下技术栈的项目:

  • Vue.js
  • Vue Router (with advanced layout system and Asynchronous Data fetching)
  • Webpack (with hot reload)
  • Pre-processor: (Vue file, ES2015, scss/less/stylus)
  • 一个可选的 backend server,如 express,对我们来说因为要生成静态站点,所以不重要,默认就好
  • 可选的 UI framework,我在这里没有选,而是通过 plugin 引入了 vue-material,下文会提到

此外它还提供了一些别的组件,比如可选的测试框架和代码样式工具。对于我们这种小型项目来说测试不必要,但是统一代码样式还是个好习惯,推荐勾上 ESLint + Prettier。

create-nuxt-app 的具体文档在 这里

路由

Nuxt 路由的文档在 这里。它提供的路由配置简单好懂,且基本上覆盖了大多数需求,只要在 pages 目录建符合约定的文件即可。作为参考,我的站点的路由有这几种:

  • / (主页)
  • /blog/en/ (英文博客列表页)
  • /blog/en/[title] (英文博客详情页)
  • /blog/cn/ (中文博客列表页)
  • /blog/cn/[title] (中文博客详情页)

我的 pages 目录如下:

.
├── blog
│   └── _lang
│       ├── _article.vue
│       └── index.vue
└── index.vue

其中 blog/_lang/index.vue 里面用 validate 保证它只能是 cn 或者 en。

Markdown

Webpack loader

我的博客文章都是用 markdown 格式的文件存储的。Nuxt 本身并没有支持 md 格式,所以需要为 webpack 加入对应的支持。

我自己用的是一个叫 frontmatter-markdown-loader 的 webpack 插件。这个插件不但可以把 markdown 转换为 html,还能帮你提取里面的元数据 (attributes),这样一来你就可以把一些自定义的数据也写在 markdown 里,比如发布日期等。

在 Nuxt 中启用新的 webpack loader 是通过在 nuxt.config.js 文件里面的 extend 里扩展 webpack config 完成的。

extend(config, ctx) {
  // add markdown loader
  config.module.rules.push({
    test: /\.md$/,
    loader:  'frontmatter-markdown-loader'
  })
}

使用 asyncData 在页面中动态读取 Markdown

准备好了 webpack,我们还需要在页面中读取这些文件,并把 html 渲染到页面里。Nuxt 在页面组件层面提供的 asyncData 方法是很合适的入口,因为 import 是一个异步操作。对应的文档在 https://zh.nuxtjs.org/guide/async-data/。

asyncData 支持 async/await,也支持 Promise 作为返回值。在 文章页面 和 列表页面 中,你能分别看到两种用法的例子。

出于管理上简单的考虑,我用一个 JSON 文件 来列出所有的博客文章的文件和在 URL 中对应的地址,而不是扫描目录。

代码高亮

我是使用 Prismjs 来做代码高亮的。这里用到了 Nuxt 的 plugin 功能来做 inject。

新增的 Plugin 文件需要在 nuxt.config.js 里面的 plugins 加上它的路径。参考文档。

我的 Prism.js plugin 在这个文件。使用 Vue.prototype.$Prism = Prism 就可以在 Vue instance 上面注册这个变量了。

最后在 _article.vue 里面加入如下语句就可以为我们页面里的代码加入自动高亮了。

mounted()  {
  this.$Prism.highlightAll()
}

提取 CSS 文件和代码切分

如果你像我一样喜欢用 JS 里的 import './xxx.css' 而不是页面 head 里的 来引入第三方 css 文件,或者你在 vue 文件里写较多的样式,你可能需要考虑在 nuxt.config.js 文件里把 extractCSS 设为 true,让 Nuxt 帮你把这些 css 抽取到文件中,而不是写进 html 里面。

如果你打开这个选项的话,我建议把 splitChunks 里面的 pages 设为 false,来避免由于过多拆分 css 文件导致的样式渲染延迟问题。

生成静态页

做完本地开发之后,终于可以上线啦。我的网站在 github pages 上,当然静态站部署到哪里都很方便。

配置目标路由

生成静态页的命令是 Nuxt 自带的,只需要在项目根目录执行 npm run generate 即可。但是在此之前我们需要告诉 Nuxt 有哪些目标路由需要生成。我的 nuxt.config.js 中的配置如下:

generate: {
  routes: [
    '/blog/cn',
    '/blog/en',
    ...blogs.cn.map((blog) => `/blog/cn/${blog.url}`),
    ...blogs.en.map((blog) => `/blog/en/${blog.url}`),
  ]
}

其中 blogs 是我之前提到的 JSON 格式的博客列表 。

部署到 github pages 官方文档对此有非常详细的说明,我就不赘述了。 https://v2.nuxt.com/deployments/github-pages#deploy-nuxt-on-github-pages

碎碎念时间

我在元旦的时候立了个 flag 说要多写文章,结果快过去半年了,这是动笔写的第一篇文章(而且还是入门向的)。但工作生活上还算是有些进展:家里添了一个可爱的小宝宝,换了令人满意的新工作,尝试了一些新技术,读了一些好书,听了一些好播客,在股市里赚了一些钱又赔了一些。工作和技术那部分打算之后写文章展开讲讲,希望这次不要拖那么久了。

loading...