手写一个简化版 vuepress 需要知道什么?

2023-11-10

自实现 vuepress 效果图如下:

在这里插入图片描述

首先我们来看看 vuepress 是怎么工作的:

1. 全局安装 vuepress

npm install -g vuepress

2. 运行编写好的 docs 文件,编译后的浏览器显示文档网页

vuepress dev docs

3. 将编写好的 docs md文档文件 build 打包成静态 html 网页

vuepress build docs

接下来教你如何自实现 uabpress ,并实现以上功能!

1.实现npm全局安装 uabpress 库并且 命令行 可执行

1. 首先我们来实现自己编写一个库,发布到npm后 可通过npm i 自己库名安装

//创建一个项目名文件夹,在文件夹内初始化这个项目
npm init -y // 项目初始化
npm adduser //添加你的 npm 账号 目的:指定发布库的账号
npm publish //将当前库发布到 npm 内 (开发完成这个库后通过这个命名发布)

发布后过一会儿即可去 npm 内查看, 这是我发布后的库: uabpress
如果 npm 你的账号的 packages 内以生成该库即可通过npm install -g 你的库名 安装了

2. 全局安装库后,直接通过库名在cmd中运行你的库

这里我们需要用到一个命令行插件 commander, 下面是commander的使用方法
commander 的功能很简单配置如下:

const { program } = require('commander') //生成一个获取命令行输入内容的全局对象
program.version('1.0.0')                 //设置你的库当前版本
//下面就是通过 获取命令行输入内容 做一系列操作 运行对应执行文件等

这是 uabpress 内获取命令行输入内容进行的操作: github.com/uabjs/uabpress/blob/main/bin/index.js
那为什么 uabpress dev docs 会首先运行 bin/index.js 文件呢? 见 package.json

{
  "name": "uabpress",
  "version": "1.0.0",
  "description": "基于 Vue3.0 SSR 的一个快速高效的 Markdown 网站制作工具",
  "main": "index.js",
  "bin": {  //npm全局安装库会将package.json内的bin内容自动设置成电脑全局变量
    "uabpress": "./bin/index.js"   //这一行的目的就是向你的电脑全局一个uabpress的环境变量
  },
  ....

这样下来我们全局安装uabpress:npm i -g uabpress后, 在命令行中就可以通过uabpress -V命令来查看版本号,或者uabpress dev docs运行对应文档文件
在这里插入图片描述

2.实现vuepress dev docs功能

在这里插入图片描述
可见当执行vuepress dev docs后就自动打开了一个浏览器窗口,运行出了通过docs文件夹编译成的文档网页,这里是项目中最核心的环节,详情如下:

1. 开启一个 koa 服务默认开启端口为3000端口

对应项目文件 45行

app.start(options.port, () => {
   console.log('编译的docs目录: ' + path.resolve(options.sourceDir))
})

2. 静态文件加载 如:image、css、js

对应项目文件 15行

app.use(async (ctx, next) => {
  if (ctx.url.startsWith('/assets')) {
    try {
      const buffer = fs.readFileSync(path.resolve(__dirname, './' + ctx.url))
      ctx.type = path.extname(ctx.url).slice(1)
      ctx.body = buffer
    } catch(e) {
      ctx.body = ''
    }
  } else {
    await next()
  }
})

3. 通过docs文件夹路径 获取该文件夹所有文件路径

对应项目文件 12行

const glob = require('glob')
//sourceDir 是docs文件夹路径
function getFolder(sourceDir) {
  return glob.sync(path.join(sourceDir, '/**/*.md'), { absolute: false })
        .filter(v => v.indexOf('node_modules') === -1 )
        .map(v => path.relative(sourceDir, v))
}
function createMiddleware (options) {
  return async (ctx, next) => {
    ctx.menu = getFolder(options.sourceDir) //获取文件名菜单 返回路径数组
    await next()
  }
}

4. 通过路径判断是否 新增、更新、删除 该fileNode(文件节点)

通过ctx.menu路径判断是否 新增、更新、删除 该fileNode节点,从而生成一棵树在nodes数组上的文件结构树
对应项目文件 27行

async patch(filePaths) {
  const treeFlags = this.treekey()
  const newFiles = {}
  filePaths.forEach(filePath => {
    newFiles[this.formatFilePath(filePath)] = null
  })
  Object.keys(this.nodes)
    .filter(filePaths => filePaths.indexOf(treeFlags) === -1)
    .forEach(async (filePath) => {
      if (filePath in newFiles) {
        await this.updateFile(filePath)
        delete newFiles[filePath]
      } else {
        await this.removeFile(filePath)
      }
    })

  //剩下的 newFiles 则是新增的文件
  Object.keys(newFiles).forEach(async (filePath) => {
    await this.addFile(filePath)
  })
}

5. 以docs文件路径 编译文档结构树的具体实现如下

对应项目文件 3行

const Provider = require('./Provider')

module.exports = function () {
  const provider = new Provider()
  Array.from([
    require('./middleware/title'),       // 解析标题
    require('./middleware/prefix'),      // 标题层级
    require('./middleware/breadcrumb'),  // 计算面包屑
    require('./middleware/autoNumber'),  // 自动生成序号
    require('./middleware/marked'),      // markdown转html
    require('./middleware/themes')       // 添加样式
  ]).forEach(middleware => {
    provider.useMiddleware(middleware)
  })
  return provider
}

/**
 * root: TreeNode { path:'', children:[], parent: [TreeNode] },
 * nodes 内有两种形式: 标题集, 文件集
 * nodes: {
 *  '标题集/?': {
 *    path: 'level1B/levelB',
      children: [ [FileNode], ... ],
      parent: TreeNode { path: 'level1B', children: [Array], parent: [TreeNode] }
 *  }
 *  '标题集/文件集.md': FileNode {
      resolvePath: [Function],
      path: '文件集.md',
      isFileNode: true,
      parent: [TreeNode],
      body: '11111111111',
      lastModified: 1606828666908,
      title: '文件集.md',
      prefix: '',
      breadcrumb: [Object],
      catalogs: [],
      html: '<p>11111111111</p>\n'+ ...<h1~3>,
      themes: [Array],
      getTheme: [Function]
    },
    ...
   },
   middlewares: [],
   resolvePath: fn
 */

6. 获取浏览器url 查找文档结构树中对应页面 ssr渲染

koa获取浏览器请求时的url,通过 url我们可以找到nodes节点树上对应该文件的fileNode文件节点, 获取该文件节点里面编译好的html内容,在通过vue3, ssr 生成网页html,传入ctx.body内返回给前端

对应项目文件 14行

const Vue = require('vue')   //这是vue3
const compilerSsr = require('@vue/compiler-ssr') //将vue文件template模板编译成 render 方法
const compilerSfc = require('@vue/compiler-sfc') // 解析vue文件的内容
const serverRenderer = require('@vue/server-renderer') //将createApp的实例转换成html字符串

const createRender = sfcPath => {
  sfcPath = sfcPath ? sfcPath : path.resolve(__dirname, '../template/App.vue')
  const { descriptor } = compilerSfc.parse(fs.readFileSync(sfcPath, 'utf-8'))
  const render = compilerSsr.compile(descriptor.template.content).code
  return async (data) => {
    const app = Vue.createApp({
      ssrRender: new Function('require', render)(require),
      data: () => data
    })
    return serverRenderer.renderToString(app)  //将编译好的html字符串传入ctx.body相应出去
  }
}

const renderMarkdown = async ({ reqFile, template, provider, options }) => {
  const skin = options.theme || '默认皮肤'
  // 获取 menu 菜单数据及其页面数据
  const data = {
    menu: provider.toArray(fileNode => ({
      path: fileNode.path,
      name: fileNode.title,
      prefix: fileNode.prefix || ''
    })),
    skinPath: '',
    breadcrumb: null,
    catalogs: [],
    markdown: ''
  }

  //解析该路径页面的 data 数据
  await provider.getItem(reqFile, fileNode => {
    if (!fileNode) {
      data.markdown = `<h3>${reqFile} 不存在<h3>`
    } else {
      // console.log("fileNode----", fileNode);
      data.breadcrumb = fileNode.breadcrumb.html
      data.catalogs = fileNode.catalogs
      data.skinPath = fileNode.getTheme(skin).path
      data.markdown = [
        fileNode.html
      ].join('')
    }
  })
  return await createRender(template)(data)
}

7. 通过watch监听文件变化利用socket.io实现热加载

对应项目文件 13行

const watch = require('watch')
const io = require('socket.io')

//options.sourceDir就是docs文件夹路径,目的:监听该路径内的文件修改时间如果改变则触发页面刷新
watch.watchTree(options.sourceDir, changePath => {
  progress.init() //刷新的进度条 初始化
  progress.step() //刷新的进度条 加载动画
  socket.emit('reload', changePath) //docs 改变重新加载
})

app.use(async (ctx, next) => {
  await next() //当所有中间件都执行完成后给最后生成的body添加socket监听
  if (ctx.type === 'text/html') {
    ctx.body = `
    <!DOCTYPE html>
    <html>
      <script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
      <script>
        var socket = io()
        socket.on("reload", function(changePath) {
            window.location.reload()
        })
      </script>
      ${ctx.body}
    </html>
    `
  }
})

最后一步就是通过 open 打开浏览器以及对应地址
对应项目文件 36行

const open = require("open")
open(`http://localhost:${port}`)

到此全部工序就完成了!

3.实现vuepress build docs打包功能

命令执行在:
对应项目文件 43行

// 2. 解析到指令为 build 则执行下面操作
program
  .command('build')
  .description('编译页面文件(生成html)')
  .option('-t, --theme [theme]', 'Markdown样式,可选 default、techo')
  .option('-o, --output [output]', '输出目录')
  .action(async (options) => {
    console.log('')
    // 打包生成静态html页面
    await build({
      theme: options.theme || 'default', //打包后的html样式
      root: path.resolve(options.args.length > 0 ? options.args[0] : '.'), //打包路径
      output: path.resolve(options.output || 'dist') //默认输出到dist文件夹
    })
    process.exit()
  })

// 1. 将命令传递给 program 解析
program.parse(process.argv) // process.argv是命令行输入的命令

核心打包实现:
对应项目文件 66行

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手写一个简化版 vuepress 需要知道什么? 的相关文章

  • TCP flag注释

    http blog csdn net wisage article details 6049733 三次握手Three way Handshake 一个虚拟连接的建立是通过三次握手来实现的 1 B gt SYN gt A 假如服务器A和客户
  • 【华为OD机试】寻找相同子串(C++ Python Java)2023 B卷

    时间限制 C C 1秒 其他语言 2秒 空间限制 C C 262144K 其他语言524288K 64bit IO Format lld 语言限定 C clang11 C clang 11 Pascal fpc 3 0 2 Java jav
  • 若依系统(Ruoyi-Vue)去除redis数据库

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 目的 一 去除redis 配置 二 去除ruoyi framework下RedisConfig的配置 三 在ruoyi common的core redis下新建My

随机推荐

  • 领域驱动设计-Domain-Driven-Design概念

    2021了 你应该要了解DDD了 不然领导和你吹牛你都听不懂 或者你都没法和别人吹牛了 一 Evans DDD 是什么 1 1 背景 2002年 敏捷宣言诞生 时代处于 CS 到 BS 的转换时期 2003年 Eric Evans 发表 l
  • angular?!小白修仙之路……

    一 简介 Angular是一个功能非常完备的前端框架 最早由 Misko Hevery 等人创建 2009 年被Google 公式收购 用于其多款产品 Angular基于TypeScript 通过增强HTML的方式提供一种便捷开发Web应用
  • 【100%通过率 】【华为OD机试 c++ 】不含 101 的数【2023 Q1

    华为OD机试 题目列表 2023Q1 点这里 2023华为OD机试 刷题指南 点这里 题目描述 小明在学习二进制时 发现了一类不含 101的数 也就是 将数字用二进制表示 不能出现 101 现在给定一个整数区间 l r 请问这个区间包含了多
  • 【python 多线程存数据lock(锁)】

    多线程存数据不会数据丢失 案例一 这里只是简单的线程池 import os from concurrent futures import ThreadPoolExecutor from time import perf counter im
  • PTA-计算工资

    计算工资 某公司员工的工资计算方法如下 一周内工作时间不超过40小时 按正常工作时间计酬 超出40小时的工作时间部分 按正常工作时间报酬的1 5倍计酬 员工按进公司时间分为新职工和老职工 进公司不少于5年的员工为老职工 5年以下的为新职工
  • TypeScript 之类型判断

    在使用 Angular 做项目的时候 对 TypeScript 的类型判断不太熟练 为了方便查找 特意对 TypeScript 的类型判断做了简单梳理 文章只是 TS 官网的内容摘要 没有高深的知识 想要深入学习 TS 还要看官网文档 基础
  • 18M 超轻量系统开源

    图像识别作为深度学习算法的主流实践应用方向 早已在生活的各个领域发挥作用 如安全检查和身份核验时的人脸识别 无人货架和智能零售柜中的商品识别 这些任务背后的关键技术都在于此 图1 PP ShiTu应用于商品识别效果示意 开发者应用展示 然而
  • JMeter压测原则之独立部署监控

    无论是用哪种压测工具 我们都会比较关心压测工具所在机器的的系统资源占用情况 毕竟很多人压着压着 压力机出现性能瓶颈了还不知道 并且还错误的评估成是被测系统的性能问题 很多初学者好像都犯过这种尴尬的错误 文章分成三个部分说明 为什么说Perf
  • a &a &a[0]之间的区别和联系

    数组中 a为数组的首地址 a 0 为数组第一个元素的地址 所以 a a 0 但是 a又是什么东西呢 我们来做下面的代码测试 include
  • Binary operator ‘==‘ cannot be applied to operands of type ‘Int‘ and ‘[Int]‘

    等号两边数据类型不一致进行比较报错 Binary operator cannot be applied to operands of type Int and Int 这个提示也挺明确 二元运算符 两边不能使用 Int 和 Int 写代码时
  • c++ socket、 listen、accept、recv 、send、 connect函数记录

    文章目录 socket bind 和connect 函数 listen 和accept 函数 send recv read 和write 函数 TCP客户端 Tcp服务端 socket int socket int domain int t
  • 如何进行代码审查?

    如何review开发人员的代码 前置的一些概念 review级别 参与人身份和方式不同划分 相关开发自己看代码 非正式会议 开发人员组内 相关开发 直接上级 相关开发 直接上级 总监 1 团队review制度 团队内根据实际情况规定流程 在
  • 与机器学习相关的数学家,你认识几个?

    机器学习 需要一定的数学基础 也需要一定的代码能力 我们发布了一篇 机器学习的数学基础 里面有很多数学公式是数学家的名字命名的 然而 好多人不知道那些数学家长什么样子 于是 我们搜集了十位数学家的资料 排名不分先后 看看大家能从图片中叫出几
  • Vue_test

    文章目录 vue test笔记 1 脚手架文件结构 2 关于不同版本的Vue 3 vue config js配置文件 4 ref属性 5 配置项props 6 mixin 混入 7 插件 8 scoped样式 9 总结TodoList案例
  • Neo4js安装报错:未能加载指定的模块“\Neo4j-Management.psd1”

    情形如下 解决方法 更改bin neo4j ps1文件里Import Module PSScriptRoot Neo4j Management psd1 为绝对路径
  • Mybatis-generator代码自动生成(包含swagger注解,bean中文注释,service接口,serviceImpl实现类)

    Mybatis generator代码自动生成 包含swagger注解 bean中文注释 service接口 serviceImpl实现类 Dao接口是继承tkmybatis 简介 项目地址 代码生成步骤 代码生成图示 简介 由于平时老是需
  • 公众号上传临时素材获取media_id

    公众号上传临时素材获取media id java语言 更新于2018 01 15 public class Util private static final String UPLOAD URL https api weixin qq co
  • Linux驱动系列-PWM驱动

    转自 嵌入式系统研发 1 概述 本文主要讲述了Linux的PWM驱动框架 实现方法 驱动添加方法和调试方法 示例Linux内核版本 6 2 8 2 原理 PWM是Pulse Width Modulation的简称 中文译作脉冲宽度调制 作为
  • Vue实现底部对话框

    效果 手机上的效果 电脑上的效果 代码 App vue
  • 手写一个简化版 vuepress 需要知道什么?

    自实现 vuepress 效果图如下 首先我们来看看 vuepress 是怎么工作的 1 全局安装 vuepress npm install g vuepress 2 运行编写好的 docs 文件 编译后的浏览器显示文档网页 vuepres