mirror of https://github.com/lukechilds/docs.git
5 changed files with 201 additions and 4 deletions
@ -1,7 +1,8 @@ |
|||||
const memoize = require('micro-memoize'); |
const memoize = require('micro-memoize'); |
||||
const { rehypeVscode } = require('unified-vscode'); |
// const { rehypeVscode } = require('unified-vscode');
|
||||
|
const { rehypeShikiTwoslash } = require('./rehype-shiki-twoslash'); |
||||
const rehypeImgs = require('./rehype-image-size'); |
const rehypeImgs = require('./rehype-image-size'); |
||||
|
|
||||
const rehypePlugins = [memoize(rehypeVscode), rehypeImgs]; |
const rehypePlugins = [rehypeShikiTwoslash, rehypeImgs]; |
||||
|
|
||||
module.exports = { rehypePlugins }; |
module.exports = { rehypePlugins }; |
||||
|
@ -0,0 +1,146 @@ |
|||||
|
const createShikiHighlighter = require('shiki-twoslash'); |
||||
|
const memoize = require('micro-memoize'); |
||||
|
const visit = require('unist-util-visit'); |
||||
|
const pAll = require('p-all'); |
||||
|
const hastToString = require('hast-util-to-string'); |
||||
|
|
||||
|
const DEFAULT_THEME = 'dark-plus'; |
||||
|
|
||||
|
function getTokenClassNames(token) { |
||||
|
let classNames; |
||||
|
let classNameString = ''; |
||||
|
token.explanation.forEach(expl => { |
||||
|
const thing = expl.scopes.find(scope => scope.themeMatches.length > 0); |
||||
|
const themeMatches = thing && thing.themeMatches && thing.themeMatches[0]; |
||||
|
const name = themeMatches && themeMatches.name; |
||||
|
const formatted = |
||||
|
name && |
||||
|
name |
||||
|
.toString() |
||||
|
.split(', ') |
||||
|
.map(entry => entry.toLowerCase()); |
||||
|
if (formatted) { |
||||
|
classNames = formatted; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
classNames && |
||||
|
classNames.length && |
||||
|
classNames.forEach((className, index) => { |
||||
|
classNameString += `${slugify(className)}${index !== classNames.length - 1 ? ' ' : ''}`; |
||||
|
}); |
||||
|
|
||||
|
return classNameString === '' ? 'plain' : classNameString; |
||||
|
} |
||||
|
|
||||
|
function removeEmptyLine(index, tree) { |
||||
|
const [item] = tree.slice(index * -1); |
||||
|
if (item && item.properties && item.properties.className.includes('empty')) { |
||||
|
tree.pop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function codeLanguage(node) { |
||||
|
const className = node.properties.className || []; |
||||
|
let value; |
||||
|
|
||||
|
for (const element of className) { |
||||
|
value = element; |
||||
|
|
||||
|
if (value.slice(0, 9) === 'language-') { |
||||
|
return value.slice(9); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 'bash'; |
||||
|
} |
||||
|
|
||||
|
const rehypeShikiTwoslash = options => { |
||||
|
let theme = options.theme || DEFAULT_THEME; |
||||
|
const generateHighlighter = memoize(async () => createShikiHighlighter({ theme, cache }), { |
||||
|
isPromise: true, |
||||
|
}); |
||||
|
|
||||
|
const highlight = async (node, lang) => { |
||||
|
const hash = generateHash(node); |
||||
|
return cache.wrap(hash, async () => { |
||||
|
const highlighter = await generateHighlighter(); |
||||
|
return highlighter.codeToThemedTokens(node, lang); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
function tokensToHast(lines) { |
||||
|
const tokenLineClassName = (options && options.tokenLineClassName) || 'token-line'; |
||||
|
const tokenLineElement = (options && options.tokenLineElement) || 'span'; |
||||
|
const tree = []; |
||||
|
|
||||
|
for (const line of lines) { |
||||
|
if (line.length === 0) { |
||||
|
tree.push( |
||||
|
u('element', { |
||||
|
tagName: tokenLineElement, |
||||
|
properties: { className: `${tokenLineClassName} ${tokenLineClassName}__empty` }, |
||||
|
}) |
||||
|
); |
||||
|
} else { |
||||
|
const lineChildren = []; |
||||
|
|
||||
|
for (const token of line) { |
||||
|
const className = getTokenClassNames(token); |
||||
|
lineChildren.push( |
||||
|
u( |
||||
|
'element', |
||||
|
{ |
||||
|
tagName: 'span', |
||||
|
properties: { style: `color: ${token.color}`, className: `token ${className}` }, |
||||
|
}, |
||||
|
[u('text', token.content)] |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
tree.push( |
||||
|
u( |
||||
|
'element', |
||||
|
{ tagName: tokenLineElement, properties: { className: tokenLineClassName } }, |
||||
|
lineChildren |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
removeEmptyLine(2, tree); |
||||
|
removeEmptyLine(1, tree); |
||||
|
|
||||
|
return tree; |
||||
|
} |
||||
|
|
||||
|
async function transformer(tree) { |
||||
|
const nodes = []; |
||||
|
|
||||
|
visit(tree, 'element', (node, index, parent) => { |
||||
|
if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') { |
||||
|
return; |
||||
|
} else { |
||||
|
nodes.push(node); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
await pAll( |
||||
|
nodes.map(node => () => visitor(node)), |
||||
|
{ concurrency: 25 } |
||||
|
); |
||||
|
return tree; |
||||
|
} |
||||
|
|
||||
|
async function visitor(node) { |
||||
|
const lang = codeLanguage(node); |
||||
|
const tokens = await highlight(hastToString(node), lang); |
||||
|
const tree = tokensToHast(tokens); |
||||
|
node.properties.lines = tokens.length - 1; |
||||
|
node.properties.lang = lang; |
||||
|
node.children = tree; |
||||
|
} |
||||
|
|
||||
|
return transformer; |
||||
|
}; |
||||
|
|
||||
|
module.exports = rehypeShikiTwoslash; |
Loading…
Reference in new issue