浅入浅出VuePress

303 阅读4分钟

前言

VuePress是一个Vue2驱动的静态网站生成器,专注于快速搭建文档类站点与博客。

设计思路

MD to HTML

md 文件是工程师们🦍常用的文本格式文件,在使用VuePress构建的项目中约定用此文件类型写作。VuePress 编译打包工具用的Webpack,为了实现md文件的解析与模块转化,Vuepress实现了一个 Webpack Loader —— markdown-loader,它能够通过正则匹配字符串解析md文件内容,并把一个个md文件解析转换为Vue SFC,因此可以直接在md文件中使用template/script/style标签,再配合vue-loader就可以把一个个md文件转换为HTML页面

路由

VuePress 的页面路由约定由目录结构关系生成。通常我们的网站内容文件集中在一个名为 docs的文件夹中,位于 docs/hello.md 的文件所对应的路由就是 /hellodocs/zh/hello.md所对应的就是 /zh/hello

主题与插件

VuePress 维护了一个 APP 实例对象,并作为 context传递给了themeAPIpluginAPI,主题与插件的工作原理就是通过约定的配置格式修改context状态

SSG

SSG(静态网站生成)SSR(服务端渲染) 的一个变种。服务端渲染指的就是将HTML页面的生成下放到服务端,以支持更好的SEO以及更快的内容到达时间,而SSG就是在SSR的基础上每个页面只生成一次HTML(因为咱搭建的是文档类站点,默认所有IP访问此站点所呈现的内容是一摸一样的,不存在HTML内容的动态变化)

约定式配置

VuePress 专注于快速搭建文档类网站,因此在业务需求上不需要过于灵活的配置支持,故而可将部分配置抽象提取到VuePress本身实现,让用户专心于文档内容的编写,如上文所说的路由,比较典型的还有文档目录结构主题目录结构

实现思路

cli

Vuepress 选用cac来支持命令行指令的解析与执行,详情可见GitHub,核心源码部分如下:

module.exports = function (cli, options) {
  // 注册 dev cli 
  cli
    .command(`dev [targetDir]`, 'start development server')
    .option('-p, --port <port>', 'use specified port (default: 8080)')
    .option('-t, --temp <temp>', 'set the directory of the temporary file')
    .option('-c, --cache [cache]', 'set the directory of cache')
    .option('--host <host>', 'use specified host (default: 0.0.0.0)')
    .option('--no-cache', 'clean the cache before build')
    .option('--no-clear-screen', 'do not clear screen when dev server is ready')
    .option('--debug', 'start development server in debug mode')
    .option('--silent', 'start development server in silent mode')
    .option('--open', 'open browser when ready')
    .action((sourceDir = '.', commandOptions) => {
      const { debug, silent } = commandOptions

      logger.setOptions({ logLevel: silent ? 1 : debug ? 4 : 3 })
      logger.debug('global_options', options)
      logger.debug('dev_options', commandOptions)
      env.setOptions({ isDebug: debug, isTest: process.env.NODE_ENV === 'test' })

      wrapCommand(dev)({
        sourceDir: path.resolve(sourceDir),
        ...options,
        ...commandOptions
      })
    })
}

编译打包

在接收到cli命令如 yarn dev:docs 后,VuePress 首先会执行固定的逻辑初始化站点配置,在初始化配置结束之后根据参数dev/build执行不同的Webpack编译流程

创建实例 APP

GitHub

创建APP实例,重心在于确定编译打包环境以及TargetDir

  • isProd 编译打包环境
  • options 站点上下文配置对象
  • sourceDir 文档站点内容目录,默认为 docs
  • vuepressDir 文档站点自定义配置目录,默认为 docs/.vuepress
  • libDir 项目根目录,默认为 docs 上一级目录
module.exports = class App {
  constructor (options = {}) {
    // 运行环境
    this.isProd = process.env.NODE_ENV === 'production'
    // 初始化参数
    this.options = options
    // 文档目录,常为 docs
    this.sourceDir = this.options.sourceDir || path.join(__dirname, 'docs.fallback')
    logger.debug('sourceDir', this.sourceDir)
    if (!fs.existsSync(this.sourceDir)) {
      logger.warn(`Source directory doesn't exist: ${chalk.yellow(this.sourceDir)}`)
    }
    // 文档自定义设置目录 常为 docs/.vuepress
    this.vuepressDir = path.resolve(this.sourceDir, '.vuepress')
    // VuePress 核心模块路径
    this.libDir = path.join(__dirname, '../')
  }
}

初始化配置

在此阶段会初始化 themeApi 以及 pluginApi,并配置好渲染模板、全局布局组件、解析匹配docs文件夹下md文件

GitHub

async process () {
    // 读取加载 config 文件配置
    await this.resolveConfigAndInitialize()
    // 依据 base 调整 head 标签的 url
    this.normalizeHeadTagUrls()
    // 加载主题配置
    this.themeAPI = loadTheme(this)
    // 初始化配置 dev 及 build 渲染模板
    this.resolveTemplates()
    // 加载全局基础布局组件
    this.resolveGlobalLayout()
    // 初始化内联插件
    this.applyInternalPlugins()
    // 同步设置用户自定义插件
    this.applyUserPlugins()
    // 同步插件配置到相应的钩子函数
    this.pluginAPI.initialize()
    // 初始化 markdown-it 实例
    this.markdown = createMarkdown(this)
    // 解析 docs 目录下 md 文件结构
    await this.resolvePages()
    // 调用 additionalPages 钩子函数
    await this.pluginAPI.applyAsyncOption('additionalPages', this)
    await Promise.all(
      this.pluginAPI.getOption('additionalPages').appliedValues.map(async (options) => {
        // 添加页面
        await this.addPage(options)
      })
    )
    // 调用 ready 钩子函数
    await this.pluginAPI.applyAsyncOption('ready')
    await Promise.all([
      // 调用 clientDynamicModules 钩子函数
      this.pluginAPI.applyAsyncOption('clientDynamicModules', this),
      // 调用 enhanceAppFiles 钩子函数
      this.pluginAPI.applyAsyncOption('enhanceAppFiles', this),
      // 调用 globalUIComponents 钩子函数
      this.pluginAPI.applyAsyncOption('globalUIComponents', this)
    ])
  }

Dev

调用 this.devProcess.process() 设置好HMR并初始化Webpack配置,然后选用 WebpackDevServer启动项目,DevProcess详情请看GitHub

GitHub

async dev () {
    // 创建 DevProcess 实例,初始化配置
    this.devProcess = new DevProcess(this)
    // 初始化 webpack 配置等以准备 launch dev server
    await this.devProcess.process()
    const error = await new Promise(resolve => {
      try {
        // HMR
        this.devProcess
            .on('fileChanged', ({ type, target }) => {
              console.log(`Reload due to ${chalk.red(type)} ${chalk.cyan(path.relative(this.sourceDir, target))}`)
              this.process()
            })
            // 创建 dev server
            .createServer()
            // 启动服务
            .listen(resolve)
      } catch (err) {
        resolve(err)
      }
    })
    if (error) {
      throw error
    }
    return this
}

Build

检查并初始化Webpack配置(包括 clientConfig & serverConfig,因为采用SSG),BuildProcess 详情请看GtiHub

GitHub

async build () {
    // 创建 BuildProcess 实例
    this.buildProcess = new BuildProcess(this)
    // 初始化 webpack 客服端以及服务端配置等以准备 SSR
    await this.buildProcess.process()
    // SSR
    await this.buildProcess.render()
    return this
}

App 实例属性(build)

  • base: string 部署站点基础路径
  • buildProcess: Build Build 实例
  • cacheDirectory: string 缓存文件夹
  • cacheIdentifer:string 缓存标识符
  • ClientConputedMixinConstructor: ClientComputedMixin ClientComputedMixin 类
  • cwd: string 本地项目路径
  • devTemplate: string dev 环境渲染模板路径
  • globalLayout: string 全局基础布局组件路径
  • isProd: boolean 运行环境标识
  • libDir: string VuePress核心模块路径
  • markdown: MarkdownIt MarkdownIt实例
  • options: object 命令行执行结果对象
  • outDir:string 打包文件输出目录
  • pages: array 站点所有页面数据集合
  • pluginAPI: PluginAPI PluginAPI实例
  • siteConfig: object 站点基础配置参数
  • sourceDir: string 站点文档路径
  • ssrTemplate: string SSR渲染模板
  • tempPath: string 临时数据文件路径
  • themeAPI: ThemeAPI: ThemeAPI实例
  • themeConfig: object 主题配置参数
  • vuepressDir: string 站点自定义配置路径
  • writeTemp: function 输出临时文件内容的函数

相关技术

  • Vue2 流行的渐进式JavaScript框架,封装了视图与数据状态的相互制约与影响逻辑,采用 SFC(单文件组件) 以简单的声明式开发有效降低初学者学习成本
  • SSR(服务端渲染) 不同于SPA的一种前端解决方案,相较而言有更快的内容到达时间,并对SEO友好,但也加大了服务端的负载
  • markdown-it 一个实现解析MDHTML的工具
  • Webpack 前端不能不懂的项目编译构建打包工具