Using Prism with marked for static syntax highlighting
2017-09-27
This content is built from markdown files to static html. Syntax highlighting for code snippets is also generated during this step. It is very easy to add syntax-highlighting only in the browser, but I wanted to remove the necessary JavaScript dependencies from the HTML code and the additional rendering effort for the browser.
Example
let ab = { cde: 1, fg: 'h' }
console.log(`Variable ab = ${ab}`)
// comment
ab.i = 17
For the Redesign I decided to switch from highlightjs to Prism. This post lists the steps to achieve build-time syntax highlighting for many languages using Prism and node.js.
Add a highlight function that uses Prism
For this I needed a custom highlight function, as described in this techy issue.
const marked = require('marked');
const Prism = require('prismjs');
console.log('Available Prism languages', JSON.stringify(Object.keys(Prism.languages)))
marked.setOptions({
highlight: (code, lang) => {
if (!Prism.languages[lang]) {
const fallback = 'markup'
console.error(`Language '${lang}' is not available in Prism.js, will use '${fallback}' instead.`)
lang = fallback
}
return Prism.highlight(code, Prism.languages[lang])
}
})
const html = marked('# Sample\n```javascript\nalert('hello');\n```')
This seems to work, but unfortunately Prism expects slightly different markup.
Marked will wrap the highlighted code in <pre><code class="lang-javascript">[...]</code></pre>
But Prism.js needs this markup: <pre class="language-javascript"><code class="language-javascript">[...]</code></pre>
Add a custom marked renderer for code blocks
For that I wrote a custom renderer adapting code written by Glen Cheney:
const marked = require('marked')
const Prism = require('prismjs');
const renderer = new marked.Renderer()
// wrap code block the way Prism.js expects it
renderer.code = function(code, lang, escaped) {
code = this.options.highlight(code, lang);
if (!lang) {
return '<pre><code>' + code + '</code></pre>';
}
// e.g. "language-js"
var langClass = 'language-' + lang;
return '<pre class="' + langClass + '"><code class="' + langClass + '">' +
code +
'</code></pre>';
};
marked.setOptions({
renderer,
highlight: (code, lang) => {
if (!Prism.languages[lang]) {
const fallback = 'markup'
console.error(`Language '${lang}' is not available in Prism.js, will use '${fallback}' instead.`)
lang = fallback
}
return Prism.highlight(code, Prism.languages[lang])
}
})
const html = marked('# Sample\n```javascript\nalert('hello');\n```')
Add more languages to Prism
Currently Prism only knows the following languages _"markup", "xml", "html", "mathml", "svg", "css", "clike", "javascript", "js"_, but I wanted more.
For this, I added
const Prism = require('prismjs')
// for more, check http://prismjs.com/index.html#languages-list
require('prismjs/components/prism-batch')
require('prismjs/components/prism-typescript')
require('prismjs/components/prism-php')
require('prismjs/components/prism-python')
/* ... */
Now prism will support the following languages: "markup", "xml", "html", "mathml", "svg", "css", "clike", "javascript", "js", "batch", "typescript", "ts", "python"
Add other Plugins to Prism
Prism comes with a variety of plugins, but currently I am not using any.
There is an issue on git on how to integrate plugins that need DOM, maybe there will be an explanation in the future.
Full script
I created a separate node module for my custom marked render.
Usage
const marked = require('./custom-marked')
config = {
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: true,
smartypants: false,
}
marked.setOptions(config)
const html = marked('# Sample\n```javascript\nalert('hello');\n```')
File custom-marked.js:
const marked = require('marked')
const Prism = require('prismjs')
// add languages, for more see http://prismjs.com/index.html#languages-list
require('prismjs/components/prism-batch')
require('prismjs/components/prism-typescript')
require('prismjs/components/prism-php')
require('prismjs/components/prism-python')
console.log('Available Prism languages', JSON.stringify(Object.keys(Prism.languages)))
// custom renderer and highlight function
const custom = new (function Custom() {
this.renderer = new marked.Renderer()
this.renderer.code = function (code, lang, escaped) {
code = this.options.highlight(code, lang)
if (!lang) lang = 'unknown'
var langClass = `language-${lang}`
return `<pre class="${langClass}"><code class="${langClass}">${code}</code></pre>`
}
this.highlight = function (code, lang) {
if (!Prism.languages[lang]) {
const fallback = 'javascript'
console.error(`prismjs does not know language '${lang}', using '${fallback}' instead.`)
lang = fallback
}
return Prism.highlight(code, Prism.languages[lang])
}
return this
})()
// export marked with a changed setOptions method
module.exports = marked
marked._setOptions = marked.setOptions
marked.setOptions = options => {
// use custom renderer and highlight functions if not set from the caller
if (!options.hasOwnProperty('renderer')) options.renderer = custom.renderer
if (typeof options.highlight !== 'function') options.highlight = custom.highlight
marked._setOptions(options)
}