流浪小猫的博客

设计 pagic.config.ts

· xcatliu

作为一名高级 Markdown 工程师资深博客爱好者,我热衷于写博客折腾各种博客系统,写过多个博客主题。

终于,写主题也无法得到满足,我开始写博客系统了。

或者说是更广义的,静态网站生成器。

如今 Pagic 已经完成了一个雏形,我也邀请了一些朋友试用,经过一些迭代,我决定来重新设计一下 pagic.config.ts,毕竟作为一个静态网站生成器,大部分用户只需要配置一下 pagic.config.ts 就可以构建网站了,所以配置文件的设计是至关重要的。

设计原则§

  1. 约定优于配置
  2. 尽可能语义话,一目了然
  3. 类型统一,不要有选项既能传字符串又能传函数
  4. 参考:Deno, tsconfig.json, webpack, Hexo, VuePress, Hugo

命名约定§

文件名、目录名§

使用下划线命名法 foo_bar/baz_v2.md

依据:Deno 的建议

变量名、参数名§

使用驼峰命名法 fooBar

依据:Deno 的实际做法

配置文件名称§

pagic.config.tsxpagic.config.ts

命名依据:

文件相关的配置§

文件相关的配置大多参考的 tsconfig.json,因为其最贴合需求——支持指定文件(files)构建。

配置项 类型 默认值 描述
srcDir string '.' 构建的源目录。其中的页面都会以相同的目录结构被构建到 outDir 中。默认为当前目录,此时当前目录下的 README.md 会被构建到 ${outDir}/index.html
outDir string 'dist' 构建的目标目录
include glob[] ['**/*'] 在构建的源目录下,所有匹配上 include(并且没有匹配上 exclude)的文件都会被构建。glob 语法见此
exclude glob[] 较长,后续详细介绍 构建的根目录下,需要排除的文件
files string[] [] 指定文件构建。当传入的是空数组时,会以 includeexclude 来匹配;当传入的数组长度大于 0 时,会忽略 includeexclude,精确的按照 files 列表来构建
root string '/' 部署站点的基础路径,如果网站部署在一个子路径下,比如 https://xcatliu.github.io/pagic/,那么 root 应该被设置为 '/pagic/'

exclude§

exclude 的默认值较长:

[
  // Dot files
  '**/.*',
  // Node common files
  '**/package.json',
  '**/package-lock.json',
  '**/node_modules',
  // pagic.config.ts and pagic.config.tsx
  'pagic.config.{ts,tsx}',
  // https://docs.npmjs.com/using-npm/developers.html#keeping-files-out-of-your-package
  '**/config.gypi',
  '**/CVS',
  '**/npm-debug.log'

  // ${config.outDir} will be added later
]

注意这里的每一项都可以匹配到文件或目录,以 **/node_modules 为例,它:

实际上,**/node_modules 会被转化为 **/node_modules{,/**},这样就能匹配到以上所有情况了。

需要注意的是:

通过这些配置,Pagic 可以灵活的运用于各种场景:

场景一:纯网站,独立的目录存放源文件§

如果仅仅是想搭建一个网站,那么最方便的方式是使用这样的目录结构,将源文件与构建好的文件分开:

website/
├── dist/
|   └── index.html
├── src/
|   ├── _layout.tsx
|   └── README.md
└── pagic.config.ts

此时 pagic.config.ts 的配置很简单:

export default {
  srcDir: 'src',
  outDir: 'dist'
}

场景二:纯网站,根目录存放源文件§

项目的根目录下一般都有个 README.md,有时我们希望这个文件也被构建为一个页面,此时我们可以将 srcDir 设置为 '.',比如参考 GitBook 的目录结构组织的话,就是这样的:

book/
├── dist/
|   ├── basics/
|   |   └── index.html
|   ├── advenced/
|   |   └── index.html
|   └── index.html
├── basics/
|   └── README.md
├── advenced/
|   └── README.md
├── _layout.tsx
├── README.md
└── pagic.config.ts

此时 pagic.config.ts 的配置也很简单:

export default {
  srcDir: '.',
  outDir: 'dist'
}

注意如果有需要排除的文件,可以使用 exclude 排除掉,比如:

export default {
  srcDir: '.',
  outDir: 'dist',
  exclude: ['examples']
}

场景三:项目 + 网站§

如果是在一个项目中要搭建网站,又不想重新建一个仓库,那么前两种方式都可以满足需求:

  1. 网站源文件放到 docs 目录下,srcDir 配置为 'docs' 即可。优点是配置简单,不用配置 includeexclude
  2. 直接在根目录下构建网站,srcDir 配置为 '.',再配置 include 包含网站的存放目录即可。优点是包含了根目录下的 README.md

场景四:仅展示 README.md§

有的项目很简单,只需要一个 README.md 即可,不需要其他页面了,此时可以配置 files 仅包含 README.md

export default {
  srcDir: '.',
  outDir: 'dist',
  files: ['README.md']
}

插件§

插件是 Pagic 中最核心的功能,Pagic 甚至将最基本的构建流程也拆分为了插件——内置插件。插件分为三种:

  1. 内置插件:最基本的构建流程,一定会运行
  2. 官方插件:由 Pagic 实现的插件,可选,如:sidebarga
  3. 第三方插件:由第三方实现的插件,可选。遵循 Deno 的设计,入口为一个 url,如:https://github.com/xcatliu/pagic_plugin_example/blob/master/mod.ts
配置项 类型 默认值 描述
plugins string[] ['init', 'md', 'tsx', 'script', 'layout', 'write'] 插件列表,内置插件和官方插件的取值均为插件名,第三方插件的取值为其入口 url

插件执行的顺序按照其配置顺序,除非该插件在实现时配置了一个 insert 属性:

export default {
  name: 'customPlugin',
  insert: 'before:script',
  // fn 的设计参考了 Deno https://deno.land/manual/testing#writing-tests
  fn: (pagic) => {
    // balabala
  }
}

name 是插件的唯一标识,会被用于 insert 中的插件排序。

insert 的语法是 before:${pluginName}after:${pluginName}。这种方式比其他静态网站生成器中需要注册各种各样的生命周期钩子更方便也更灵活。

fn 是一个函数,仅接受一个参数,pagic 实例,它可以访问到 Pagic 在运行中的所有配置、上下文。如果插件需要一些额外的配置,可以约定在 pagic.config.ts 中新增一条配置项 customPlugin,然后在 fn 中可以通过 pagic.config.customPlugin 获取到配置。

需要注意的是:

主题§

主题是 Pagic 中的核心功能之一,有官方主题和第三方主题两种

配置项 类型 默认值 描述
theme string default 官方主题的取值为主题名,第三方插件的取值为其入口 url

主题的运行机制很容易理解——当运行 pagic build 时,会将主题中的所有文件都“复制”到 srcDir 下,然后按照正常的模式运行 build

当然,这个“复制”并不会真的复制文件。而且遇到冲突的文件时,也是以用户的文件为准。

为什么主题需要一个入口文件呢?

因为 Deno 的设计中,模块调用是以 url 为基础的,想象一个网址表示一个主题,我们没有办法像 node 一样用 fs.readdir 来找到此主题目录下的所有文件,所以必须有一个入口文件来表示此主题包含了哪些文件:

export default {
  extends: 'default',
  files: [
    'assets/index.css',
    'assets/reset.css',
    'assets/variables.css',
    '_layout.tsx',
    'favicon.ico'
  ]
}

其中 files 表示需要被“复制”到 srcDir 下的文件。

另外还支持使用 extends 来继承一个主题,官方主题的取值为主题名,第三方插件的取值为其入口 url。它常用于想基于某个主题自定义样式,创建一个新主题。比如官方的 docs 主题以及很完善了,那么可以继承后覆盖掉其 assets/variables.css 文件,创建一个新样式的主题。

网站配置§

这里列出一些约定好的配置,它们通常由插件或主题来实现。

配置项 类型 默认值 描述
title string '' 网站标题,通常会放到页面的标题后面,如:函数的类型 · TypeScript 入门教程,若该页面不存在页面标题,则只会展示网站标题 TypeScript 入门教程
description string '' 网站描述,通常会放到 <head><meta name="description"> 中展示,也有可能展示在页面中
head React.ReactNode undefined 额外被注入到 <head> 中的内容,可以写 jsx。注意此时需要将配置文件改为 tsx 后缀 pagic.config.tsx
sidebar undefined 侧边栏
nav undefined 顶部导航
github string undefined 网站的 GitHub 地址,通常会展示在网页的右上角
ga undefined Google Analytics 配置
gitalk undefined Gitalk 配置
tocAd React.ReactNode undefined 展示在 toc 上方的广告
tools undefined 一些额外的功能

开发环境配置§

配置项 类型 默认值 描述
watch boolean false 是否观察 srcDir 目录,有变化后重新构建
serve boolean false 是否启动一个静态服务
port number 8000 静态服务端口号

本文会持续更新,直到 Pagic 1.0 发布。