Skip to content

Commit

Permalink
refactor(embed): async fetch embed files, fixed #387
Browse files Browse the repository at this point in the history
  • Loading branch information
QingWei-Li committed Feb 12, 2018
1 parent 471e407 commit 67d7d6b
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 85 deletions.
6 changes: 2 additions & 4 deletions src/core/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,10 @@ export function fetchMixin (proto) {
// Load main content
last.then(
(text, opt) => {
this._renderMain(text, opt)
loadSideAndNav()
this._renderMain(text, opt, loadSideAndNav)
},
_ => {
this._renderMain(null)
loadSideAndNav()
this._renderMain(null, {}, loadSideAndNav)
}
)

Expand Down
135 changes: 62 additions & 73 deletions src/core/render/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import { genTree } from './gen-tree'
import { slugify } from './slugify'
import { emojify } from './emojify'
import { isAbsolutePath, getPath } from '../router/util'
import { isFn, merge, cached } from '../util/core'
import { isFn, merge, cached, isPrimitive } from '../util/core'
import { get } from '../fetch/ajax'

const cachedLinks = {}
let uid = 0

function getAndRemoveConfig (str = '') {
export function getAndRemoveConfig (str = '') {
const config = {}

if (str) {
Expand All @@ -25,62 +24,37 @@ function getAndRemoveConfig (str = '') {

return { str, config }
}

const compileMedia = {
markdown (url) {
const id = `docsify-get-${uid++}`

if (!process.env.SSR) {
get(url, false).then(text => {
document.getElementById(id).innerHTML = this.compile(text)
})

return `<div data-origin="${url}" id=${id}></div>`
} else {
return `<div data-origin="${url}" id=${uid}></div>
<script>
var compile = window.__current_docsify_compiler__
Docsify.get('${url}', false).then(function(text) {
document.getElementById('${uid}').innerHTML = compile(text)
})
</script>`
return {
url
}
},
iframe (url, title) {
return `<iframe src="${url}" ${title || 'width=100% height=400'}></iframe>`
return {
code: `<iframe src="${url}" ${title || 'width=100% height=400'}></iframe>`
}
},
video (url, title) {
return `<video src="${url}" ${title || 'controls'}>Not Support</video>`
return {
code: `<video src="${url}" ${title || 'controls'}>Not Support</video>`
}
},
audio (url, title) {
return `<audio src="${url}" ${title || 'controls'}>Not Support</audio>`
return {
code: `<audio src="${url}" ${title || 'controls'}>Not Support</audio>`
}
},
code (url, title) {
const id = `docsify-get-${uid++}`
let ext = url.match(/\.(\w+)$/)
let lang = url.match(/\.(\w+)$/)

ext = title || (ext && ext[1])
if (ext === 'md') ext = 'markdown'

if (!process.env.SSR) {
get(url, false).then(text => {
document.getElementById(id).innerHTML = this.compile(
'```' + ext + '\n' + text.replace(/`/g, '@qm@') + '\n```\n'
).replace(/@qm@/g, '`')
})
lang = title || (lang && lang[1])
if (lang === 'md') lang = 'markdown'

return `<div data-origin="${url}" id=${id}></div>`
} else {
return `<div data-origin="${url}" id=${id}></div>
<script>
setTimeout(() => {
var compiler = window.__current_docsify_compiler__
Docsify.get('${url}', false).then(function(text) {
document.getElementById('${id}').innerHTML = compiler
.compile('\`\`\`${ext}\\n' + text.replace(/\`/g, '@qm@') + '\\n\`\`\`\\n')
.replace(/@qm@/g, '\`')
})
})
</script>`
return {
url,
lang
}
}
}
Expand Down Expand Up @@ -109,20 +83,59 @@ export class Compiler {
compile = marked
}

this._marked = compile
this.compile = cached(text => {
let html = ''

if (!text) return text

html = compile(text)
if (isPrimitive(text)) {
html = compile(text)
} else {
html = compile.parser(text)
}

html = config.noEmoji ? html : emojify(html)
slugify.clear()

return html
})
}

matchNotCompileLink (link) {
compileEmbed (href, title) {
const { str, config } = getAndRemoveConfig(title)
let embed
title = str

if (config.include) {
if (!isAbsolutePath(href)) {
href = getPath(this.contentBase, href)
}

let media
if (config.type && (media = compileMedia[config.type])) {
embed = media.call(this, href, title)
embed.type = config.type
} else {
let type = 'code'
if (/\.(md|markdown)/.test(href)) {
type = 'markdown'
} else if (/\.html?/.test(href)) {
type = 'iframe'
} else if (/\.(mp4|ogg)/.test(href)) {
type = 'video'
} else if (/\.mp3/.test(href)) {
type = 'audio'
}
embed = compileMedia[type].call(this, href, title)
embed.type = type
}

return embed
}
}

_matchNotCompileLink (link) {
const links = this.config.noCompileLinks || []

for (var i = 0; i < links.length; i++) {
Expand Down Expand Up @@ -182,33 +195,9 @@ export class Compiler {
const { str, config } = getAndRemoveConfig(title)
title = str

if (config.include) {
if (!isAbsolutePath(href)) {
href = getPath(contentBase, href)
}

let media
if (config.type && (media = compileMedia[config.type])) {
return media.call(_self, href, title)
}

let type = 'code'
if (/\.(md|markdown)/.test(href)) {
type = 'markdown'
} else if (/\.html?/.test(href)) {
type = 'iframe'
} else if (/\.(mp4|ogg)/.test(href)) {
type = 'video'
} else if (/\.mp3/.test(href)) {
type = 'audio'
}

return compileMedia[type].call(_self, href, title)
}

if (
!/:|(\/{2})/.test(href) &&
!_self.matchNotCompileLink(href) &&
!_self._matchNotCompileLink(href) &&
!config.ignore
) {
if (href === _self.config.homepage) href = 'README'
Expand Down
84 changes: 84 additions & 0 deletions src/core/render/embed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { get } from '../fetch/ajax'
import { merge } from '../util/core'

const cached = {}

function walkFetchEmbed ({ step = 0, embedTokens, compile }, cb) {
const token = embedTokens[step]

if (!token) {
return cb({})
}

get(token.embed.url).then(text => {
let embedToken

if (token.embed.type === 'markdown') {
embedToken = compile.lexer(text)
} else if (token.embed.type === 'code') {
embedToken = compile.lexer(
'```' +
token.embed.lang +
'\n' +
text.replace(/`/g, '@DOCSIFY_QM@') +
'\n```\n'
)
}
cb({ token, embedToken })
walkFetchEmbed({ step: ++step, compile, embedTokens }, cb)
})
}

export function prerenderEmbed ({ compiler, raw }, done) {
let hit
if ((hit = cached[raw])) {
return done(hit)
}

const compile = compiler._marked
let tokens = compile.lexer(raw)
const embedTokens = []
const linkRE = compile.InlineLexer.rules.link
const links = tokens.links

tokens.forEach((token, index) => {
if (token.type === 'paragraph') {
token.text = token.text.replace(
new RegExp(linkRE, 'g'),
(src, filename, href, title) => {
const embed = compiler.compileEmbed(href, title)

if (embed) {
if (embed.type === 'markdown' || embed.type === 'code') {
embedTokens.push({
index,
embed
})
}
return embed.code
}

return src
}
)
}
})

let moveIndex = 0
walkFetchEmbed({ compile, embedTokens }, ({ embedToken, token }) => {
if (token) {
const index = token.index + moveIndex

merge(links, embedToken.links)

tokens = tokens
.slice(0, index)
.concat(embedToken, tokens.slice(index + 1))
moveIndex += embedToken.length - 1
} else {
cached[raw] = tokens.concat()
tokens.links = cached[raw].links = links
done(tokens)
}
})
}
32 changes: 26 additions & 6 deletions src/core/render/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getPath, isAbsolutePath } from '../router/util'
import { isMobile, inBrowser } from '../util/env'
import { isPrimitive } from '../util/core'
import { scrollActiveSidebar, scroll2Top } from '../event/scroll'
import { prerenderEmbed } from './embed'

function executeScript () {
const script = dom
Expand Down Expand Up @@ -119,18 +120,37 @@ export function renderMixin (proto) {
getAndActive(this.router, 'nav')
}

proto._renderMain = function (text, opt = {}) {
proto._renderMain = function (text, opt = {}, next) {
if (!text) {
return renderMain.call(this, text)
}

callHook(this, 'beforeEach', text, result => {
let html = this.isHTML ? result : this.compiler.compile(result)
if (opt.updatedAt) {
html = formatUpdated(html, opt.updatedAt, this.config.formatUpdated)
}
let html
const callback = () => {
if (opt.updatedAt) {
html = formatUpdated(html, opt.updatedAt, this.config.formatUpdated)
}

callHook(this, 'afterEach', html, text => renderMain.call(this, text))
callHook(this, 'afterEach', html, text => renderMain.call(this, text))
}
if (this.isHTML) {
html = this.result
callback()
next()
} else {
prerenderEmbed(
{
compiler: this.compiler,
raw: text
},
tokens => {
html = this.compiler.compile(tokens)
callback()
next()
}
)
}
})
}

Expand Down
5 changes: 3 additions & 2 deletions src/core/util/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
export function cached (fn) {
const cache = Object.create(null)
return function cachedFn (str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
const key = isPrimitive(str) ? str : JSON.stringify(str)
const hit = cache[key]
return hit || (cache[key] = fn(str))
}
}

Expand Down

0 comments on commit 67d7d6b

Please sign in to comment.