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 条评论
登录 后参与评论

相关文章

来自专栏ImportSource

教你看懂redis配置系列

摘要:最近工作中需要开发redis的一个云管理平台。所以要求要对这些参数了如指掌,特结合网络资料和自己翻译小撸此文。感谢junjie 【 简介】 我们可以在启动...

4379
来自专栏蛋未明的专栏

myweb0.2版本(更新)

1303
来自专栏友弟技术工作室

MongoDB简易教程mongo简介及应用场景安装和使用mongodbPHP中操作mongo数据库python中操作mongo数据库

传统数据库中,我们要操作数据库数据都要书写大量的sql语句,而且在进行无规则数据的存储时,传统关系型数据库建表时对不同字段的处理也显得有些乏力,mongo应运而...

3456
来自专栏大内老A

模拟在WCF中的应用

在《模拟(Impersonation)与委托(Delegation)》一文中,我们对模拟和委托这两个概念以及相关编程实现进行了详细说明。如果将模拟使用在WCF上...

1869
来自专栏Greenplum

Greenplum 列存表(AO表)的膨胀和垃圾检查与空间收缩

Greenplum支持行储存(HEAP储存)与列(append-only)储存,对于AO存储,虽然是appendonly,但实际上GP是支持DELETE和UPD...

930
来自专栏编程之路

Java代码生成器:1分钟提供增删改查api,由世界上最好的语言pyhton编写

用世界上最好的语言python编写的Java代码生成器,私人订制,模版任意设置,使用此代码生成器1分钟可以迅速完成增删改查全部功能,并提供api接口。

1383
来自专栏PHP在线

CI一些优秀实践

最近准备接手改进一个别人用Codeigniter写的项目,虽然之前也有用过CI,但是是完全按着自己的意思写的,没按CI的一些套路。用在公众的项目,最好还是按框架...

3255
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第二十天 Redis学习【悟空教程】

rpm -e --nodeps java-1.6.0-openjdk-1.6.0.0-1.66.1.13.0.el6.i686

1025
来自专栏Google Dart

AngularDart Material Design 是/否 按钮 顶

可以使用MaterialSaveCancelButtonsDirective等指令来提供基本文本自定义,该指令用保存/取消替换是/否。

1025
来自专栏个人分享

Hadoop 2.6 MapReduce运行原理详解

  市面上的hadoop权威指南一类的都是老版本的书籍了,索性学习并翻译了下最新版的Hadoop:The Definitive Guide, 4th Editi...

641

扫码关注云+社区