feat: finish ssr
This commit is contained in:
parent
6dea685fee
commit
3444884905
12 changed files with 163 additions and 40 deletions
|
|
@ -1 +1,2 @@
|
||||||
.git/
|
.git/
|
||||||
|
packages/docsify-server-renderer/build.js
|
||||||
|
|
@ -20,11 +20,11 @@
|
||||||
"themes"
|
"themes"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf lib themes && node build/build.js && mkdir lib/themes && mkdir themes && node build/build-css.js",
|
"build": "rm -rf lib themes && node build/build && mkdir lib/themes && mkdir themes && node build/build-css",
|
||||||
"dev:build": "rm -rf lib themes && mkdir themes && node build/build.js --dev && node build/build-css.js --dev",
|
"dev:build": "rm -rf lib themes && mkdir themes && node build/build --dev && node build/build-css --dev",
|
||||||
"dev": "node app.js & nodemon -w src -e js,css --exec 'npm run dev:build'",
|
"dev": "node app & nodemon -w src -e js,css --exec 'npm run dev:build'",
|
||||||
"build:ssr": "node build/build-ssr",
|
"build:ssr": "node build/build-ssr",
|
||||||
"test": "eslint src --fix"
|
"test": "eslint {src,packages} --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"marked": "^0.3.6",
|
"marked": "^0.3.6",
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ renderer.renderToString({ url })
|
||||||
<link rel="stylesheet" href="//unpkg.com/docsify/themes/buble.css" title="buble" disabled>
|
<link rel="stylesheet" href="//unpkg.com/docsify/themes/buble.css" title="buble" disabled>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<!--inject-app-->
|
||||||
<!--inject-docsify-config-->
|
<!--inject-config-->
|
||||||
<script src="//unpkg.com/docsify/lib/docsify.js"></script>
|
<script src="//unpkg.com/docsify/lib/docsify.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,27 @@
|
||||||
import { Compiler } from '../../src/core/render/compiler'
|
import { Compiler } from '../../src/core/render/compiler'
|
||||||
import { AbstractHistory } from '../../src/core/router/history/abstract'
|
import { AbstractHistory } from '../../src/core/router/history/abstract'
|
||||||
import path from 'path'
|
import { resolve, basename } from 'path'
|
||||||
import fs from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
|
import * as tpl from '../../src/core/render/tpl'
|
||||||
|
|
||||||
|
function cwd (...args) {
|
||||||
|
return resolve(process.cwd(), ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mainTpl (config) {
|
||||||
|
let html = `<nav class="app-nav${config.repo ? '' : 'no-badge'}"><!--navbar--></nav>`
|
||||||
|
|
||||||
|
if (config.repo) {
|
||||||
|
html += tpl.corner(config.repo)
|
||||||
|
}
|
||||||
|
if (config.coverpage) {
|
||||||
|
html += tpl.cover()
|
||||||
|
}
|
||||||
|
|
||||||
|
html += tpl.main(config)
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
export default class Renderer {
|
export default class Renderer {
|
||||||
constructor ({
|
constructor ({
|
||||||
|
|
@ -10,16 +30,80 @@ export default class Renderer {
|
||||||
config,
|
config,
|
||||||
cache
|
cache
|
||||||
}) {
|
}) {
|
||||||
this.template = template
|
this.html = this.template = template
|
||||||
this.path = path
|
this.path = cwd(path)
|
||||||
this.config = config
|
this.config = Object.assign(config, {
|
||||||
|
routerMode: 'history'
|
||||||
|
})
|
||||||
this.cache = cache
|
this.cache = cache
|
||||||
|
|
||||||
this.router = new AbstractHistory()
|
this.router = new AbstractHistory(config)
|
||||||
this.compiler = new Compiler(config, this.router)
|
this.compiler = new Compiler(config, this.router)
|
||||||
|
|
||||||
|
this.router.getCurrentPath = () => this.url
|
||||||
|
this._renderHtml('inject-config', `<script>window.$docsify = ${JSON.stringify(config)}</script>`)
|
||||||
|
this._renderHtml('inject-app', mainTpl(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToString(url) {
|
renderToString (url) {
|
||||||
console.log(url)
|
this.url = url
|
||||||
|
// TODO render cover page
|
||||||
|
const { loadSidebar, loadNavbar } = this.config
|
||||||
|
|
||||||
|
const mainFile = cwd(this.path, `./${this.router.getFile(url)}`)
|
||||||
|
this._renderHtml('main', this._render(mainFile))
|
||||||
|
|
||||||
|
if (loadSidebar) {
|
||||||
|
const name = loadSidebar === true ? '_sidebar.md' : loadSidebar
|
||||||
|
const sidebarFile = cwd(mainFile, '..', name)
|
||||||
|
this._renderHtml('sidebar', this._render(sidebarFile, 'sidebar'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadNavbar) {
|
||||||
|
const name = loadNavbar === true ? '_navbar.md' : loadNavbar
|
||||||
|
const navbarFile = cwd(mainFile, '..', name)
|
||||||
|
this._renderHtml('navbar', this._render(navbarFile, 'navbar'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.html
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderHtml (match, content) {
|
||||||
|
this.html = this.html.replace(new RegExp(`<!--${match}-->`, 'g'), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
_render (path, type) {
|
||||||
|
let html = this._loadFile(path)
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'sidebar':
|
||||||
|
html = this.compiler.sidebar(html)
|
||||||
|
break
|
||||||
|
case 'cover':
|
||||||
|
html = this.compiler.cover(html)
|
||||||
|
break
|
||||||
|
case 'navbar':
|
||||||
|
case 'article':
|
||||||
|
default:
|
||||||
|
html = this.compiler.compile(html)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadFile (filePath) {
|
||||||
|
try {
|
||||||
|
return readFileSync(filePath, 'utf8')
|
||||||
|
} catch (e) {
|
||||||
|
const fileName = basename(filePath)
|
||||||
|
const parentPath = cwd(filePath, '../..')
|
||||||
|
|
||||||
|
if (this.path.length < parentPath.length) {
|
||||||
|
throw Error(`Not found file ${fileName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadFile(cwd(filePath, '../..', fileName))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,5 +70,12 @@ export function fetchMixin (proto) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initFetch (vm) {
|
export function initFetch (vm) {
|
||||||
vm.$fetch(_ => callHook(vm, 'ready'))
|
if (vm.rendered) {
|
||||||
|
vm._fetchCover()
|
||||||
|
vm.$resetEvents()
|
||||||
|
callHook(vm, 'doneEach')
|
||||||
|
callHook(vm, 'ready')
|
||||||
|
} else {
|
||||||
|
vm.$fetch(_ => callHook(vm, 'ready'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export class Compiler {
|
||||||
if (!text) return text
|
if (!text) return text
|
||||||
|
|
||||||
html = compile(text)
|
html = compile(text)
|
||||||
html = emojify(html)
|
html = config.noEmoji ? html : emojify(html)
|
||||||
slugify.clear()
|
slugify.clear()
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
import { inBrowser } from '../util/env'
|
||||||
|
|
||||||
function replace (m, $1) {
|
function replace (m, $1) {
|
||||||
return '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/' + $1 + '.png" alt="' + $1 + '" />'
|
return '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/' + $1 + '.png" alt="' + $1 + '" />'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emojify (text) {
|
export function emojify (text) {
|
||||||
return $docsify.noEmoji ? text : text
|
return text
|
||||||
.replace(/<(pre|template|code)[^>]*?>[\s\S]+?<\/(pre|template|code)>/g, m => m.replace(/:/g, '__colon__'))
|
.replace(/<(pre|template|code)[^>]*?>[\s\S]+?<\/(pre|template|code)>/g, m => m.replace(/:/g, '__colon__'))
|
||||||
.replace(/:(\w+?):/ig, window.emojify || replace)
|
.replace(/:(\w+?):/ig, (inBrowser && window.emojify) || replace)
|
||||||
.replace(/__colon__/g, ':')
|
.replace(/__colon__/g, ':')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,20 +172,20 @@ export function initRender (vm) {
|
||||||
let html = ''
|
let html = ''
|
||||||
let navAppendToTarget = dom.body
|
let navAppendToTarget = dom.body
|
||||||
|
|
||||||
if (!el) {
|
if (el) {
|
||||||
el = dom.create(id)
|
if (config.repo) {
|
||||||
dom.appendTo(dom.body, el)
|
html += tpl.corner(config.repo)
|
||||||
}
|
}
|
||||||
if (config.repo) {
|
if (config.coverpage) {
|
||||||
html += tpl.corner(config.repo)
|
html += tpl.cover()
|
||||||
}
|
}
|
||||||
if (config.coverpage) {
|
|
||||||
html += tpl.cover()
|
|
||||||
}
|
|
||||||
|
|
||||||
html += tpl.main(config)
|
html += tpl.main(config)
|
||||||
// Render main app
|
// Render main app
|
||||||
vm._renderTo(el, html, true)
|
vm._renderTo(el, html, true)
|
||||||
|
} else {
|
||||||
|
vm.rendered = true
|
||||||
|
}
|
||||||
|
|
||||||
if (config.mergeNavbar && isMobile) {
|
if (config.mergeNavbar && isMobile) {
|
||||||
navAppendToTarget = dom.find('.sidebar')
|
navAppendToTarget = dom.find('.sidebar')
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ export function main (config) {
|
||||||
(config.name
|
(config.name
|
||||||
? `<h1><a class="app-name-link" data-nosearch>${config.name}</a></h1>`
|
? `<h1><a class="app-name-link" data-nosearch>${config.name}</a></h1>`
|
||||||
: '') +
|
: '') +
|
||||||
'<div class="sidebar-nav"></div>' +
|
'<div class="sidebar-nav"><!--sidebar--></div>' +
|
||||||
'</aside>')
|
'</aside>')
|
||||||
|
|
||||||
return (isMobile ? `${aside}<main>` : `<main>${aside}`) +
|
return (isMobile ? `${aside}<main>` : `<main>${aside}`) +
|
||||||
'<section class="content">' +
|
'<section class="content">' +
|
||||||
'<article class="markdown-section" id="main"></article>' +
|
'<article class="markdown-section" id="main"><!--main--></article>' +
|
||||||
'</section>' +
|
'</section>' +
|
||||||
'</main>'
|
'</main>'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,35 @@
|
||||||
import { History } from './base'
|
import { History } from './base'
|
||||||
|
import { parseQuery, stringifyQuery, cleanPath } from '../util'
|
||||||
|
import { merge } from '../../util/core'
|
||||||
|
|
||||||
export class AbstractHistory extends History {
|
export class AbstractHistory extends History {
|
||||||
constructor (config) {
|
constructor (config) {
|
||||||
super(config)
|
super(config)
|
||||||
this.mode = 'abstract'
|
this.mode = 'abstract'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse (path) {
|
||||||
|
let query = ''
|
||||||
|
|
||||||
|
const queryIndex = path.indexOf('?')
|
||||||
|
if (queryIndex >= 0) {
|
||||||
|
query = path.slice(queryIndex + 1)
|
||||||
|
path = path.slice(0, queryIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { path, query: parseQuery(query) }
|
||||||
|
}
|
||||||
|
|
||||||
|
toURL (path, params, currentRoute) {
|
||||||
|
const local = currentRoute && path[0] === '#'
|
||||||
|
const route = this.parse(path)
|
||||||
|
|
||||||
|
route.query = merge({}, route.query, params)
|
||||||
|
path = route.path + stringifyQuery(route.query)
|
||||||
|
path = path.replace(/\.md(\?)|\.md$/, '$1')
|
||||||
|
|
||||||
|
if (local) path = currentRoute + path
|
||||||
|
|
||||||
|
return cleanPath('/' + path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { cached } from '../util/core'
|
import { cached } from '../util/core'
|
||||||
|
import { inBrowser } from '../util/env'
|
||||||
|
|
||||||
const decode = decodeURIComponent
|
const decode = decodeURIComponent
|
||||||
const encode = encodeURIComponent
|
const encode = encodeURIComponent
|
||||||
|
|
@ -31,10 +32,13 @@ export function stringifyQuery (obj) {
|
||||||
return qs.length ? `?${qs.join('&')}` : ''
|
return qs.length ? `?${qs.join('&')}` : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBasePath = cached(base => {
|
export const getBasePath = cached((base = '') => {
|
||||||
|
// TODO
|
||||||
|
const path = inBrowser ? window.location.pathname : ''
|
||||||
|
|
||||||
return /^(\/|https?:)/g.test(base)
|
return /^(\/|https?:)/g.test(base)
|
||||||
? base
|
? base
|
||||||
: cleanPath(window.location.pathname + '/' + base)
|
: cleanPath(path + '/' + base)
|
||||||
})
|
})
|
||||||
|
|
||||||
export function getPath (...args) {
|
export function getPath (...args) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
export const UA = window.navigator.userAgent.toLowerCase()
|
export const inBrowser = typeof window !== 'undefined'
|
||||||
|
|
||||||
export const isIE = UA && /msie|trident/.test(UA)
|
export const isMobile = inBrowser && document.body.clientWidth <= 600
|
||||||
|
|
||||||
export const isMobile = document.body.clientWidth <= 600
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://github.com/MoOx/pjax/blob/master/lib/is-supported.js
|
* @see https://github.com/MoOx/pjax/blob/master/lib/is-supported.js
|
||||||
*/
|
*/
|
||||||
export const supportsPushState = (function () {
|
export const supportsPushState = inBrowser && (function () {
|
||||||
// Borrowed wholesale from https://github.com/defunkt/jquery-pjax
|
// Borrowed wholesale from https://github.com/defunkt/jquery-pjax
|
||||||
return window.history &&
|
return window.history &&
|
||||||
window.history.pushState &&
|
window.history.pushState &&
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue