Skip to content

Commit 40e83d9

Browse files
znckphanan
andauthored
feat: syntax highlight inline template (#59)
* feat: syntax highlight inline template * fix: include new lines in interpolation * Update src/.vuepress/markdown/highlight.js Co-Authored-By: Phan An <me@phanan.net> * Update src/.vuepress/config.js Co-Authored-By: Phan An <me@phanan.net> * Update src/.vuepress/markdown/highlight.js Co-Authored-By: Phan An <me@phanan.net> Co-authored-by: Phan An <me@phanan.net>
1 parent f3f5830 commit 40e83d9

File tree

3 files changed

+183
-114
lines changed

3 files changed

+183
-114
lines changed

src/.vuepress/config.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ module.exports = {
4545
title: 'Vue.js',
4646
description: 'Vue.js - The Progressive JavaScript Framework',
4747
head: [
48-
['link', {
49-
href: 'https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
50-
rel: 'stylesheet'
51-
}]
48+
[
49+
'link',
50+
{
51+
href:
52+
'https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
53+
rel: 'stylesheet'
54+
}
55+
]
5256
],
5357
themeConfig: {
5458
nav: [
@@ -97,5 +101,13 @@ module.exports = {
97101
}
98102
}
99103
}
104+
},
105+
markdown: {
106+
/** @param {import('markdown-it')} md */
107+
extendMarkdown: md => {
108+
md.options.highlight = require('./markdown/highlight')(
109+
md.options.highlight
110+
)
111+
}
100112
}
101113
}

src/.vuepress/markdown/highlight.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
const { parse } = require('@babel/parser')
2+
const { default: traverse } = require('@babel/traverse')
3+
const prism = require('prismjs')
4+
const loadLanguages = require('prismjs/components/index')
5+
const MARKER = '__INLINE_TEMPLATE_STRING__'
6+
7+
/**
8+
* @param {(code: string, lang?: string) => string} fancyHighlight
9+
*/
10+
module.exports = fancyHighlight => {
11+
prism.languages['vue-html'] = prism.languages.extend('markup', {})
12+
13+
prism.languages.insertBefore('vue-html', 'tag', {
14+
interpolation: {
15+
pattern: /{{((?!}})(.|\n))*}}/,
16+
inside: {
17+
punctuation: /^{{|}}$/,
18+
'inline-js language-js': {
19+
pattern: /.*/,
20+
inside: prism.languages.javascript
21+
}
22+
}
23+
}
24+
})
25+
26+
prism.languages['vue-html'].tag.inside = prism.languages.insertBefore(
27+
'inside',
28+
'attr-value',
29+
{
30+
directive: {
31+
pattern: /(?<=\s)((v-[a-z0-9-]+)|:|@|#)(((?<=[:@#])|:)([a-z0-9-]+|(\[[^\]]+\])))?(\.[a-z0-9]+)*="[^"]+"/i,
32+
inside: {
33+
'punctuation directive-shorthand': /^[:@#]/,
34+
'identifier directive-name': /^v-[a-z0-9-]+/i,
35+
'directive-argument': [
36+
{
37+
pattern: /^((?<=[:@#])|:)[a-z0-9-]+/i,
38+
inside: {
39+
punctuation: /^:/,
40+
identifier: /[a-z0-9-]+/i
41+
}
42+
},
43+
{
44+
pattern: /^((?<=[:@#])|:)(\[[^\]]+\])/,
45+
inside: {
46+
punctuation: /^\[|\]$/,
47+
'inline-js language-js': {
48+
pattern: /.*/,
49+
inside: prism.languages.javascript
50+
}
51+
}
52+
}
53+
],
54+
'directive-modifier': {
55+
pattern: /^.[a-z0-9-]+/i,
56+
inside: {
57+
punctuation: /^./,
58+
identifier: /[a-z0-9-]+/i
59+
}
60+
},
61+
'directive-expression': {
62+
pattern: /^=(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,
63+
inside: {
64+
puntuation: [/^=/, /^["']|["']$/],
65+
'inline-js language-js': {
66+
pattern: /.*/,
67+
inside: prism.languages.javascript
68+
}
69+
}
70+
}
71+
}
72+
}
73+
},
74+
prism.languages['vue-html'].tag
75+
)
76+
77+
/**
78+
* Highlight template strings in JavaScript.
79+
*
80+
* @param {string} code
81+
* @param {string|undefined} lang?
82+
*/
83+
return function highlight(code, lang) {
84+
if (lang !== 'js') {
85+
return fancyHighlight(code, lang)
86+
}
87+
88+
const source = extractTemplate(code)
89+
if (!source) return fancyHighlight(code, lang)
90+
91+
try {
92+
const copy = code.replace(source, MARKER)
93+
const output = fancyHighlight(copy, lang)
94+
const template = `<span class="language-html">${prism.highlight(
95+
source,
96+
prism.languages['vue-html'],
97+
'html'
98+
)}</span>`
99+
100+
return output.replace(MARKER, template)
101+
} catch (error) {
102+
console.log(error)
103+
104+
return fancyHighlight(code, lang)
105+
}
106+
}
107+
}
108+
109+
/**
110+
* @param {string} code
111+
*/
112+
function extractTemplate(code) {
113+
try {
114+
const AST = parse(code, { sourceType: 'module' })
115+
116+
let source = ''
117+
118+
traverse(AST, {
119+
/**
120+
* @param {import("@babel/traverse").NodePath<import('@babel/types').ObjectExpression>} node$
121+
*/
122+
ObjectExpression(node$) {
123+
/**
124+
* @type {import("@babel/traverse").NodePath<import("@babel/types").ObjectProperty>|null}
125+
*/
126+
const property$ = node$.get('properties').find(node$ => {
127+
if (node$.isObjectProperty()) {
128+
const key$ = node$.get('key')
129+
130+
return key$.isIdentifier({ name: 'template' })
131+
}
132+
})
133+
134+
if (!property$) return
135+
136+
const template$ = property$.get('value')
137+
138+
if (template$.isTemplateLiteral()) {
139+
if (template$.get('quasis').length === 1) {
140+
const item$ = template$.get('quasis')[0]
141+
142+
if (item$.isTemplateElement()) {
143+
source = item$.node.value.raw
144+
}
145+
}
146+
} else if (template$.isStringLiteral()) {
147+
source = template$.node.value
148+
}
149+
}
150+
})
151+
152+
return source
153+
} catch (error) {
154+
return ''
155+
}
156+
}
157+
158+
module.exports()

0 commit comments

Comments
 (0)