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 Studio 使用小技巧

当初从eclipse转到Android Studio,为了快速适应Android Studio,我把它的快捷键设置了eclipse了,如何设置,见:Andro...

3049
来自专栏西安-晁州

vuejs、eggjs、mqtt全栈式开发设备管理系统

vuejs、eggjs、mqtt全栈式开发简单设备管理系统 业余时间用eggjs、vuejs开发了一个设备管理系统,通过mqtt协议上传设备数据至web端实时展...

1.9K7
来自专栏lgp20151222

tar包和jar包和war包的区别?

tar:tar是*nix下的打包工具,生成的包通常也用tar作为扩展名,其实tar只是负责打包,不一定有压缩,事实上可以压缩,也可以不压缩,通常你看到xxxx....

722
来自专栏前端知识铺

前端路由简介以及vue-router实现原理

简单来说路由就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。

2926
来自专栏mySoul

中止请求和超时 跨域的HTTP请求 认证方式 JSONP

作为同源策略的一部分,XMLHttpRequest对象可以发起HTTP请求,由于同源的影响,导致必须是同源的,

952
来自专栏有趣的Python

6-Flask构建弹幕微电影网站-博客小项目学完flask基础

已上线演示地址: http://movie.mtianyan.cn 项目源码地址:https://github.com/mtianyan/movie_proj...

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

【自然框架】QuickPager分页控件的总体介绍和在线演示

QuickPager分页控件的特点  两种运行方式:自动运行、手动运行。前者便捷,后者灵活。  多种分页方式:Postback、Postback伪URL、URL...

2258
来自专栏小程序容器

OpenApplus小程序容器

OpenApp+ (https://www.openapplus.com)一个小程序容器,配置简单、功能完善、界面流畅、开箱即用!使用OpenApp+可以快速扩...

4529
来自专栏服务端技术杂谈

node.js实现BigPipe详解

BigPipe 是 Facebook 开发的优化网页加载速度的技术。网上几乎没有用 node.js 实现的文章,实际上,不止于 node.js,BigPipe ...

3516
来自专栏程序员宝库

IntelliJ IDEA 教程设置讲解

IntelliJ在业界被公认为最好的java开发工具之一,尤其在智能代码助手、代码自动提示、重构、J2EE支持、Ant、JUnit、CVS整合、代码审查、 创新...

964

扫码关注云+社区