@lwrjs/markdown-view-provider 0.7.2 → 0.8.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,42 +26,128 @@ __markAsModule(exports);
26
26
  __export(exports, {
27
27
  getHtmlHighlighter: () => getHtmlHighlighter
28
28
  });
29
- var import_unified = __toModule(require("unified"));
30
- var import_unist_util_modify_children = __toModule(require("unist-util-modify-children"));
31
29
  var import_hast_util_to_string = __toModule(require("hast-util-to-string"));
30
+ var import_hast_util_is_element = __toModule(require("hast-util-is-element"));
31
+ var import_unist_util_visit = __toModule(require("unist-util-visit"));
32
32
  var Shiki = __toModule(require("shiki"));
33
- var import_rehype_parse = __toModule(require("rehype-parse"));
34
- var hastParser = (0, import_unified.default)().use(import_rehype_parse.default, {fragment: true});
35
- function isPreNode(node) {
36
- return node.tagName === "pre";
37
- }
38
- function isParentNode(node) {
39
- return Array.isArray(node.children);
40
- }
41
- function isCodeNode(node) {
42
- return node.tagName === "code";
43
- }
44
- async function getHtmlHighlighter(options) {
45
- const highlighter = await Shiki.getHighlighter({theme: options.theme});
33
+ async function getHtmlHighlighter({theme}) {
34
+ const highlighter = await Shiki.getHighlighter({
35
+ theme,
36
+ langs: ["javascript"]
37
+ });
38
+ const languageRegistry = {
39
+ loaded: new Set(highlighter.getLoadedLanguages()),
40
+ unknown: new Set()
41
+ };
42
+ async function loadMissingLanguages(langs) {
43
+ const promises = [];
44
+ for (const lang of langs) {
45
+ if (!languageRegistry.loaded.has(lang) && !languageRegistry.unknown.has(lang)) {
46
+ promises.push(highlighter.loadLanguage(lang).then(() => languageRegistry.loaded.add(lang), () => languageRegistry.unknown.add(lang)));
47
+ }
48
+ }
49
+ await Promise.all(promises);
50
+ }
46
51
  return function htmlHighlighter() {
47
- return function(tree) {
48
- (0, import_unist_util_modify_children.default)((node, index, parent) => {
49
- if (isPreNode(node) && isParentNode(node) && node.children.length === 1 && isCodeNode(node.children[0]) && typeof node.children[0].properties === "object" && Array.isArray(node.children[0].properties.className) && typeof node.children[0].properties.className[0] === "string" && node.children[0].properties.className[0].startsWith("language-")) {
50
- const code = (0, import_hast_util_to_string.default)(node);
51
- const language = node.children[0].properties.className[0].slice("language-".length);
52
- let highlightedCodeHTML;
53
- try {
54
- highlightedCodeHTML = highlighter.codeToHtml(code, language);
55
- } catch (error) {
56
- if (error.message != "No language registration for term")
57
- throw error;
58
- else
59
- return;
60
- }
61
- const highlightedElm = hastParser.parse(highlightedCodeHTML);
62
- parent.children[index] = highlightedElm;
52
+ return async function(tree) {
53
+ const highlightCandidates = [];
54
+ (0, import_unist_util_visit.default)(tree, "element", (node, index, parent) => {
55
+ if (!(0, import_hast_util_is_element.default)(node) || node.tagName !== "code" || !(0, import_hast_util_is_element.default)(parent) || parent.tagName !== "pre") {
56
+ return;
57
+ }
58
+ const lang = getLanguage(node);
59
+ if (lang) {
60
+ highlightCandidates.push({parent, node, lang});
63
61
  }
64
- })(tree);
62
+ });
63
+ const langs = new Set(highlightCandidates.map(({lang}) => lang));
64
+ await loadMissingLanguages(langs);
65
+ for (const {parent, node, lang} of highlightCandidates) {
66
+ if (languageRegistry.loaded.has(lang)) {
67
+ const tokens = highlighter.codeToThemedTokens((0, import_hast_util_to_string.default)(node), lang, theme);
68
+ addClassToHastElement(parent, "shiki");
69
+ const backgroundColor = highlighter.getBackgroundColor(theme);
70
+ addStyleToHastElement(parent, `background-color: ${backgroundColor}`);
71
+ node.children = tokensToHast(tokens);
72
+ }
73
+ }
65
74
  };
66
75
  };
67
76
  }
77
+ function getLanguage(node) {
78
+ const className = node.properties?.className;
79
+ if (className) {
80
+ for (const part of className) {
81
+ const match = part.match(/language-(\w+)/);
82
+ if (match) {
83
+ return match[1];
84
+ }
85
+ }
86
+ }
87
+ return void 0;
88
+ }
89
+ function addClassToHastElement(elm, klass) {
90
+ const properties = elm.properties || {};
91
+ const className = properties.className || [];
92
+ className.push(klass);
93
+ properties.className = className;
94
+ elm.properties = properties;
95
+ }
96
+ function addStyleToHastElement(elm, style) {
97
+ const properties = elm.properties || {};
98
+ const styles = properties.style || [];
99
+ styles.push(style);
100
+ properties.style = styles;
101
+ elm.properties = properties;
102
+ }
103
+ function tokensToHast(lines) {
104
+ const tree = [];
105
+ for (const line of lines) {
106
+ if (line.length === 0) {
107
+ tree.push({
108
+ type: "text",
109
+ value: "\n"
110
+ });
111
+ } else {
112
+ for (const token of line) {
113
+ tree.push({
114
+ type: "element",
115
+ tagName: "span",
116
+ properties: {
117
+ style: tokenToStyle(token)
118
+ },
119
+ children: [
120
+ {
121
+ type: "text",
122
+ value: token.content
123
+ }
124
+ ]
125
+ });
126
+ }
127
+ tree.push({
128
+ type: "text",
129
+ value: "\n"
130
+ });
131
+ }
132
+ }
133
+ tree.pop();
134
+ return tree;
135
+ }
136
+ function tokenToStyle(token) {
137
+ const styles = [];
138
+ if (token.color) {
139
+ styles.push(`color: ${token.color}`);
140
+ }
141
+ if (token.fontStyle) {
142
+ if (token.fontStyle & Shiki.FontStyle.Bold) {
143
+ styles.push("font-weight: bold");
144
+ }
145
+ if (token.fontStyle & Shiki.FontStyle.Italic) {
146
+ styles.push("font-style: italic");
147
+ }
148
+ if (token.fontStyle & Shiki.FontStyle.Underline) {
149
+ styles.push("text-decoration: underline");
150
+ }
151
+ }
152
+ return styles;
153
+ }
@@ -7,6 +7,6 @@ interface HtmlHighlighterOptions {
7
7
  * @param options Allow the configuration of the HTML Highlighter (e.g. the `theme` used).
8
8
  * @returns An unified Plugin with the HTML Transformer.
9
9
  */
10
- export declare function getHtmlHighlighter(options: HtmlHighlighterOptions): Promise<unified.Plugin>;
10
+ export declare function getHtmlHighlighter({ theme }: HtmlHighlighterOptions): Promise<unified.Plugin>;
11
11
  export {};
12
12
  //# sourceMappingURL=highlighter.d.ts.map
@@ -1,54 +1,145 @@
1
- import unified from 'unified';
2
- import unistUtilModifyChildren from 'unist-util-modify-children';
3
1
  import hastUtilToString from 'hast-util-to-string';
2
+ import isElement from 'hast-util-is-element';
3
+ import visit from 'unist-util-visit';
4
4
  import * as Shiki from 'shiki';
5
- import rehypeParse from 'rehype-parse';
6
- const hastParser = unified().use(rehypeParse, { fragment: true });
7
- function isPreNode(node) {
8
- return node.tagName === 'pre';
9
- }
10
- function isParentNode(node) {
11
- return Array.isArray(node.children);
12
- }
13
- function isCodeNode(node) {
14
- return node.tagName === 'code';
15
- }
16
5
  /**
17
6
  * Generates an configured unified.Plugin for highlighting `code` snippets.
18
7
  * @param options Allow the configuration of the HTML Highlighter (e.g. the `theme` used).
19
8
  * @returns An unified Plugin with the HTML Transformer.
20
9
  */
21
- export async function getHtmlHighlighter(options) {
22
- const highlighter = await Shiki.getHighlighter({ theme: options.theme });
10
+ export async function getHtmlHighlighter({ theme }) {
11
+ // By default the Shiki highlighter loads all the languages upfront, which can take up to 500 ms.
12
+ //
13
+ // It's currently impossible to create a `Highlighter` without preloading only language
14
+ // (https://github.com/shikijs/shiki/issues/326). In the mean time, we forcing the highlighter
15
+ // to only load JavaScript.
16
+ const highlighter = await Shiki.getHighlighter({
17
+ theme,
18
+ langs: ['javascript'],
19
+ });
20
+ const languageRegistry = {
21
+ loaded: new Set(highlighter.getLoadedLanguages()),
22
+ unknown: new Set(),
23
+ };
24
+ async function loadMissingLanguages(langs) {
25
+ const promises = [];
26
+ for (const lang of langs) {
27
+ if (!languageRegistry.loaded.has(lang) && !languageRegistry.unknown.has(lang)) {
28
+ promises.push(highlighter.loadLanguage(lang).then(() => languageRegistry.loaded.add(lang), () => languageRegistry.unknown.add(lang)));
29
+ }
30
+ }
31
+ await Promise.all(promises);
32
+ }
23
33
  return function htmlHighlighter() {
24
- return function (tree) {
25
- unistUtilModifyChildren((node, index, parent) => {
26
- if (isPreNode(node) &&
27
- isParentNode(node) &&
28
- node.children.length === 1 &&
29
- isCodeNode(node.children[0]) &&
30
- typeof node.children[0].properties === 'object' &&
31
- Array.isArray(node.children[0].properties.className) &&
32
- typeof node.children[0].properties.className[0] === 'string' &&
33
- node.children[0].properties.className[0].startsWith('language-')) {
34
- const code = hastUtilToString(node);
35
- const language = node.children[0].properties.className[0].slice('language-'.length);
36
- let highlightedCodeHTML;
37
- try {
38
- highlightedCodeHTML = highlighter.codeToHtml(code, language);
39
- }
40
- catch (error) {
41
- // Do not though errors for unknown terms, just return and leave the code block as is
42
- if (error.message != 'No language registration for term')
43
- throw error;
44
- else
45
- return;
46
- }
47
- const highlightedElm = hastParser.parse(highlightedCodeHTML);
48
- parent.children[index] = highlightedElm;
34
+ return async function (tree) {
35
+ const highlightCandidates = [];
36
+ visit(tree, 'element', (node, index, parent) => {
37
+ // Ignore non-code elements: pre > code.
38
+ if (!isElement(node) ||
39
+ node.tagName !== 'code' ||
40
+ !isElement(parent) ||
41
+ parent.tagName !== 'pre') {
42
+ return;
43
+ }
44
+ // Ignore node if no language is specified.
45
+ const lang = getLanguage(node);
46
+ if (lang) {
47
+ highlightCandidates.push({ parent, node, lang });
48
+ }
49
+ });
50
+ const langs = new Set(highlightCandidates.map(({ lang }) => lang));
51
+ await loadMissingLanguages(langs);
52
+ for (const { parent, node, lang } of highlightCandidates) {
53
+ if (languageRegistry.loaded.has(lang)) {
54
+ const tokens = highlighter.codeToThemedTokens(hastUtilToString(node), lang, theme);
55
+ // Add background color and class name to the <pre> element.
56
+ addClassToHastElement(parent, 'shiki');
57
+ const backgroundColor = highlighter.getBackgroundColor(theme);
58
+ addStyleToHastElement(parent, `background-color: ${backgroundColor}`);
59
+ // Replace the <code> element with highlighted nodes.
60
+ node.children = tokensToHast(tokens);
49
61
  }
50
- })(tree);
62
+ }
51
63
  };
52
64
  };
53
65
  }
66
+ function getLanguage(node) {
67
+ const className = node.properties?.className;
68
+ if (className) {
69
+ for (const part of className) {
70
+ const match = part.match(/language-(\w+)/);
71
+ if (match) {
72
+ return match[1];
73
+ }
74
+ }
75
+ }
76
+ return undefined;
77
+ }
78
+ function addClassToHastElement(elm, klass) {
79
+ const properties = elm.properties || {};
80
+ const className = properties.className || [];
81
+ className.push(klass);
82
+ properties.className = className;
83
+ elm.properties = properties;
84
+ }
85
+ function addStyleToHastElement(elm, style) {
86
+ const properties = elm.properties || {};
87
+ const styles = properties.style || [];
88
+ styles.push(style);
89
+ properties.style = styles;
90
+ elm.properties = properties;
91
+ }
92
+ function tokensToHast(lines) {
93
+ const tree = [];
94
+ for (const line of lines) {
95
+ if (line.length === 0) {
96
+ tree.push({
97
+ type: 'text',
98
+ value: '\n',
99
+ });
100
+ }
101
+ else {
102
+ for (const token of line) {
103
+ tree.push({
104
+ type: 'element',
105
+ tagName: 'span',
106
+ properties: {
107
+ style: tokenToStyle(token),
108
+ },
109
+ children: [
110
+ {
111
+ type: 'text',
112
+ value: token.content,
113
+ },
114
+ ],
115
+ });
116
+ }
117
+ tree.push({
118
+ type: 'text',
119
+ value: '\n',
120
+ });
121
+ }
122
+ }
123
+ // Remove trailing newline at the end of the code block.
124
+ tree.pop();
125
+ return tree;
126
+ }
127
+ function tokenToStyle(token) {
128
+ const styles = [];
129
+ if (token.color) {
130
+ styles.push(`color: ${token.color}`);
131
+ }
132
+ if (token.fontStyle) {
133
+ if (token.fontStyle & Shiki.FontStyle.Bold) {
134
+ styles.push('font-weight: bold');
135
+ }
136
+ if (token.fontStyle & Shiki.FontStyle.Italic) {
137
+ styles.push('font-style: italic');
138
+ }
139
+ if (token.fontStyle & Shiki.FontStyle.Underline) {
140
+ styles.push('text-decoration: underline');
141
+ }
142
+ }
143
+ return styles;
144
+ }
54
145
  //# sourceMappingURL=highlighter.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.7.2",
7
+ "version": "0.8.0-alpha.2",
8
8
  "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
9
  "repository": {
10
10
  "type": "git",
@@ -30,29 +30,27 @@
30
30
  "build/**/*.d.ts"
31
31
  ],
32
32
  "dependencies": {
33
- "@lwrjs/base-template-engine": "0.7.2",
34
- "@lwrjs/shared-utils": "0.7.2",
33
+ "@lwrjs/base-template-engine": "0.8.0-alpha.2",
34
+ "@lwrjs/shared-utils": "0.8.0-alpha.2",
35
35
  "gray-matter": "^4.0.2",
36
36
  "hast-util-has-property": "^1.0.4",
37
37
  "hast-util-heading-rank": "^1.0.1",
38
+ "hast-util-is-element": "^1.1.0",
38
39
  "hast-util-to-string": "^1.0.4",
39
- "prismjs": "^1.21.0",
40
- "rehype-parse": "^7.0.1",
41
40
  "rehype-raw": "^5.0.0",
42
41
  "rehype-stringify": "^8.0.0",
43
42
  "remark-gfm": "^1.0.0",
44
43
  "remark-parse": "^9.0.0",
45
44
  "remark-rehype": "^8.0.0",
46
- "shiki": "^0.9.2",
45
+ "shiki": "^0.9.15",
47
46
  "unified": "^9.2.0",
48
- "unist-util-modify-children": "^2.0.0",
49
47
  "vfile": "^4.2.1"
50
48
  },
51
49
  "devDependencies": {
52
- "@lwrjs/types": "0.7.2"
50
+ "@lwrjs/types": "0.8.0-alpha.2"
53
51
  },
54
52
  "engines": {
55
53
  "node": ">=14.15.4 <19"
56
54
  },
57
- "gitHead": "1467a95aa5dcf6901e1122f6025ffd4f22237a3f"
55
+ "gitHead": "44bee038acb30418870678d886e3ded4a6afecf0"
58
56
  }