Docsify 深入源码

本文作者:ivweb 高磊

背景

当前互联网时代,技术门槛越来越低,人人都可以建立并生成各式各样,多元化、多样化的站点。

文档站点一般作为各行各业领域内的知识技术介绍及使用的资料站点,可提高资料的使用效率,保证资料的质量。

目前市面上有大致这么几款主流的文档生成站点,分别为docsifygitbookPhenomic等,可帮助用户快速搭建文档站点。

今天我们详细介绍下docsify的使用文档及实现原理。

介绍

docsify是由现饿了么前端团队@elemeFE的cinwell.li编写的一套文档站点生成框架,github上已有3k+ star,这款框架和其他框架如gitbook等相比,最大的区别就在于docsify不是静态生成html,而通过动态请求markdown编译生成html。

docsify还具有轻应用、全文搜索功能、支持多个主题、兼容IE10+、支持SSR等特性。

docsify-cli快速入手

docsify提供了一个名为docsify-cli脚手架工具,通过这个脚手架工具,可以快速生成docsify站点并部署到本地或远程服务器。

安装

 // 安装docsify-cli
npm install docsify-cli -g

初始化

/* @desc: 初始化docsify,将模板文件拷贝至path下
 * @param:local[boolean],local默认为false,若设置为true,则将js,css等资源文件拷贝到本地path下的vendor目录下.
 * @param:theme[string], theme默认为'vue',主题可选'buble','dark','pure','vue'.
 */
 -----------------------------------------------------
|        docsify init <path> [--local] [--theme]      |
|---or---                                             |
|        docsify i <path> [-l] [-t]                   |
 -----------------------------------------------------

部署服务

 /* @desc: 初始化docsify后,才能部署docsify,否则会报出下方的errorMsg
 * @errorMsg: No docs found, please run docsify init first.
 * @param: open[boolean],默认为false,若为true,则部署后自动打开默认浏览器该站点地址.
 * @param: port[number],默认为3000,可自定义其他未被占用的端口.
 */
 -----------------------------------------------------
|        docsify serve <path> [--open] [--port]       |
|---or---                                             |
|        docsify s <path> [-o] [-p]                   |
 -----------------------------------------------------

经过这三步,就可以顺利的部署一个属于自己的文档站点了,不过要想一键部署一个站点,也未尝不可,下面让你快速掌握装逼技巧:

更快速——一键生成

原理及特点:

  • 生成初始模板页面结构配置,若通过config读取到配置文件,那么合并模板页面结构配置,若未读取到,则读取path下是否有package.json,并读取package.json中的docsify配置,若配置存在,则合并,否则用初始的模板页面结构配置。
  • 文档通过服务端渲染输出
  • 没有serve模式下的热加载,所谓热加载,即监控文件更改并重新加载浏览器(对于部署在远程服务器上来说,最好别用热加载)/* @desc: 一键生成文档站点. * @param: config[string],默认为false, 需要加载的配置文件,可自定义docsify配置. * @param: port[number],默认为4000,可自定义其他未被占用的端口. */ ----------------------------------------------------- | docsify start <path> [--config] [--port] | |---or--- | | docsify start <path> [-c] [-p] | -----------------------------------------------------

Docsify源码解读 @ v4.3.1

初始化工作

docsify在初始化的流程如下:

  • 首先通过initMixinrouterMixinrenderMixinfetchMixineventMixin等方法向Docsify对象原型注入方法和属性
  • 然后初始化全局对象Docsify、DocsifyCompiler、marked、Prism,即挂载到window对象之上。
  • dom加载完成触发回调事件 this._init()

接下来我们看下在docsify每个模块具体都干了些什么事情:

initMixin

源码如下:

 export function initMixin (proto) {      // proto为Docsify原型对象
  proto._init = function () {
    const vm = this
    vm.config = config || {}

    initLifecycle(vm) // Init hooks
    initPlugin(vm) // Install plugins
    callHook(vm, 'init')
    initRouter(vm) // Add router
    initRender(vm) // Render base DOM
    initEvent(vm) // Bind events
    initFetch(vm) // Fetch data
    callHook(vm, 'mounted')
  }
}

这里this._init方法依次由上至下调用了上图所示的方法,完成初始化工作。

initLifycycle

这里定义了六个钩子函数,即initbeforeEachafterEachdoneEachreadymounted,钩子函数初始化为空数组。下面介绍下钩子的生命周期:

  • init: 仅在第一次初始化页面时调用。
  • beforeEach: 开始解析 Markdown 内容时前调用。
  • afterEach: markdown 解析成 html 后调用。beforeEach 和 afterEach 支持异步处理,通过回调返回结果。
  • doneEach: 路由切换后数据全部加载完成后调用
  • ready: 首次初始化页面且加载完数据后调用。
  • mounted: 渲染完成后调用

initPlugin

引入用户自定义插件,即其设置的钩子函数,来替换默认钩子函数。

callHook(vm,'init')

调用"init"钩子函数,vm为当前实例。

initRouter

设置路由模式,默认为'hash',用户可设置为'history'或'hash'。

  • hash: 类似vue-router中的hash模式,使用 URL 的 hash 来模拟一个完整的 URL,当 URL 改变时,页面不会重新加载,支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。
  • history: 通过history完成 URL 跳转而无须重新加载页面,依赖 HTML5 History API。

initRender

这里主要为编译层,初始化markdown编译器,重写了marked的一些标签编译方法,例如heading、code、link、paragraph、image等,最终生成app的DOM实例,并添加到DOM中(注意:此时Content、navbar和sidebar仅仅是模板实例,内容数据还未获取)。

initEvent

为切换sidebar的按钮绑定事件。

initFetch

通过xhr请求获取sidebar、navbar、content、coverpage的text数据,然后填充进DOM实例中。

callHook(vm,'mounted')

调用"mounted"钩子函数,vm为当前实例。

routerMixin

源码如下:

export function routerMixin (proto) {    // proto为Docsify原型对象
  proto.route = {}
}

我们看到,router模块在运行周期中初始化仅仅定义了一个名为route的Docsify原型对象的对象属性。

renderMixin

源码如下:

 export function renderMixin (proto) {    // proto为Docsify原型对象,下面代码细节就不讲了,感兴趣的话可以down一份研究下
    proto._renderTo = function (el, content, replace) {
        // 如何插入content,replace = ['outerHTML'|']
        ...
    }
    proto._renderSidebar = function (text) {
        // 读取并解析sidebar配置,渲染sidebar
        ...
    }
    proto._bindEventOnRendered = function (activeEl) {
        // 滚动条滑动模块,以及对配置项autoHeader、auto2top的处理
        ...
    }
    proto._renderNav = function (text) {
        // 渲染navbar内容,处理nav上活动标签样式
        ...
    }
    proto._renderMain = function (text, opt = {}) {
        // 渲染content内容,首先调用beforeEach钩子,然后将markdown编译成html,最后调用afterEach处理后返回html,最后渲染content
        // 然后读取excuteScript配置,若为true,则执行script内嵌脚本。若想执行外链脚本,需要引入external-script插件
        ...
    }
    proto._renderCover = function (text) {
        // 渲染封面页
        ...
    }
    proto._updateRender = function () {
        // 渲染文档站点标题
        ...
    }
}

fetchMixin

源码如下:

export function fetchMixin (proto) {        // 同renderMixin的proto所述
    let last                                // 记录上一次请求信息
    proto._fetch = function (cb = noop) {
        ...                                 // 强制结束上一次请求,加载content、nav以及sidebar数据
    }
    proto._fetchCover = function () {
        ...                                 // 请求并渲染封面数据
    }
    proto.$fetch = function (cb = noop) {
        ...                                 // 封装了一个方法用作路由切换的数据请求
    }
}

eventMixin

源码如下:

 export function eventMixin (proto) {        // 同renderMixin的proto所述
  proto.$resetEvents = function () {        // 滚动到当前路由中id='query.id'的dom块,并使sidebar的活动标签实时响应,获取nav活动标签
    scrollIntoView(this.route.query.id)
    sidebar.getAndActive(this.router, 'nav')
  }
}

initGlobalAPI

源码如下:

export default function () {
  window.Docsify = { util, dom, get, slugify }
  window.DocsifyCompiler = Compiler
  window.marked = marked
  window.Prism = prism
}

这里导出一个方法,里面主要暴露了四个全局对象,方便提供使用。

  • window.Docsify 对象包含了工具类方法->utildom操作方法->dom请求markdown内容方法->get缓存处理方法->slugify
  • window.DocsifyCompiler 为构造函数生成的对象,是对开源项目marked做了一些扩展性,自定义了一些如前文所述的标签编译方法。
  • window.marked 即如上所述的marked对象
  • window.Prism 即开源项目prism,一个轻量级,强大,优雅的语法高亮库。

最后

Docsify好用之处就在于其提供了丰富的可扩展性,用户可以做更多想做的事情。除此之外,Docsify还提供了一些实用的功能(也可查阅Docsify官方文档):

  • 可将文档站点部署到Github Pages或远程服务器,若要配置到远程服务器的话且在serve模式下,可在serve手动关掉livereload,因为它会单独开启一个端口服务来监控文件更改。
  • vue开发者的福利——可在markdown写vue代码,来演示vue的Demo
  • PWA离线功能,虽然还没试过此功能,但感觉会瞬间让站点高大上起来。
  • 服务端渲染(SSR),主要依赖Docsify的docsify-server-renderer模块,官方示例Node直出-Docsify。

若本文内容有不足或需要改正的地方,欢迎在评论区拍砖!

原文出处:IVWEB社区

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android小菜鸡

用H5页面打开APP

  业务场景,一个分享出去的h5界面通过页面内某个事件的触发,启动目标app并执行相关逻辑处理或做其他页面跳转(如:跳应用市场下载应用等)。下面是我在企业开发过...

671
来自专栏日常学python

Ajax网页爬取案例详解

首先列举出一些python中爬虫常用的库,用之前需要先下载好,本文假设你已经安装好相应的库。

621
来自专栏农夫安全

浅说驱动程序的加载过程

在开始之前,首先简要介绍一下本文的主题,这篇文章是关于将内核模块加载到操作系统内核的方法的介绍。所谓“内核模块”,指的便是通常所说的驱动程序。不过因为加载到内核...

2649
来自专栏小特工作室

Navi.Soft31.WebMVC框架(含示例地址)

1概述 1.1应用场景 互联网高速发展,互联网软件也随之越来越多,Web程序越来越被广泛使用.它部署简单,维护方便,深得众多软件公司使用 Bootstrap前端...

2037
来自专栏Jerry的SAP技术分享

使用ABAP编程实现对微软Office Word文档的操作

SAP ABAP里提供了一个标准的类CL_DOCX_DOCUMENT,提供了本地以".docx"结尾的微软Office word文档的读和写操作。

1122
来自专栏Django中文社区

让 Django 完成翻译:迁移数据库

我们已经编写了博客数据库模型的代码,但那还只是 Python 代码而已,Django 还没有把它翻译成数据库语言,因此实际上这些数据库表还没有真正的在数据库中创...

3169
来自专栏liulun

基于QT的webkit与ExtJs开发CB/S结构的企业应用管理系统

一:源起 1.何为CB/S的应用程序     C/S结构的应用程序,是客户端/服务端形式的应用程序,这种应用程序要在客户电脑上安装一个程序,客户使用这个程序与...

2008
来自专栏大数据架构师专家

Linux 进程管理之四大名捕

四大名捕,最初出现于温瑞安创作的武侠小说,是朝廷中正义力量诸葛小花的四大徒弟,四人各怀绝技,分别是轻功暗器高手“无情”、内功卓越的高手“铁手”、腿功惊人的“追命...

282
来自专栏更流畅、简洁的软件开发方式

【自然框架】QuickPager分页控件的单独的源码 V2.0.4.2。

  QuickPager的源码分离出来之后由两个项目组成,一个是QuickPager、另一个是QuickPagerSQL。分页控件的演示也独立了出来。 如图...

23210
来自专栏杨建荣的学习笔记

MySQL中的binlog和redo浅析(r12笔记第5天)

有一个小问题可能很多人都想起过,那就是MySQL中既然已经有了binlog,为什么还需要redo,这个问题看起来好像很简单,但是细细品来,还是有不少值得注...

34110

扫码关注云+社区