@md-plugins/md-plugin-codeblocks 0.1.0-beta.17 → 0.1.0-beta.19

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.
package/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # md-plugin-codeblocks
2
2
 
3
- A **Markdown-It** plugin that enhances code block rendering by providing syntax highlighting, line numbering, and support for advanced features like tabbed code blocks. It integrates with Prism.js for syntax highlighting and allows customization for various use cases.
3
+ A **Markdown-It** plugin that enhances code block rendering by providing syntax highlighting, line numbering, and support for advanced features like tabbed code blocks. It integrates with Shiki for syntax highlighting and allows customization for various use cases.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Syntax Highlighting**: Automatically highlights code blocks using **Prism.js**.
7
+ - **Syntax Highlighting**: Automatically highlights code blocks using **Shiki**.
8
+ - **TwoSlash Hovers**: Opt in to TypeScript-powered hover and query output for richer examples.
8
9
  - **Line Numbering**: Optionally adds line numbers to code blocks.
9
10
  - **Magic Comments**: Supports special comments like `[[! highlight]]`, `[[! add]]`, and `[[! rem]]` for inline code annotations.
10
11
  - **Tabbed Code Blocks**: Enables the creation of tabbed code blocks for multi-language or multi-file examples.
@@ -101,6 +102,17 @@ console.log('Line 3')
101
102
  ```
102
103
  ````
103
104
 
105
+ ### TwoSlash Type Hovers
106
+
107
+ Add the `twoslash` attribute to TypeScript or JavaScript examples when you want inferred type information, compiler diagnostics, or `^?` query output.
108
+
109
+ ````markup
110
+ ```ts [twoslash]
111
+ const count = 1
112
+ // ^?
113
+ ```
114
+ ````
115
+
104
116
  ### Line Highlighting and Annotations
105
117
 
106
118
  ````markup
package/dist/index.d.mts CHANGED
@@ -46,7 +46,7 @@ interface CodeblockPluginOptions {
46
46
  */
47
47
  pageScripts?: string[];
48
48
  /**
49
- * Optional Prism languages configuration array. This allows you to override or add custom language definitions.
49
+ * Optional Shiki languages configuration array. This allows you to override or add custom language definitions.
50
50
  * Each item can have a `name`, optional `aliases`, and `customCopy` boolean.
51
51
  */
52
52
  langList?: Lang[];
package/dist/index.d.ts CHANGED
@@ -46,7 +46,7 @@ interface CodeblockPluginOptions {
46
46
  */
47
47
  pageScripts?: string[];
48
48
  /**
49
- * Optional Prism languages configuration array. This allows you to override or add custom language definitions.
49
+ * Optional Shiki languages configuration array. This allows you to override or add custom language definitions.
50
50
  * Each item can have a `name`, optional `aliases`, and `customCopy` boolean.
51
51
  */
52
52
  langList?: Lang[];
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import prism from 'prismjs';
2
- import loadLanguages from 'prismjs/components/index.js';
1
+ import { addClassToHast } from 'shiki/core';
2
+ import { createHighlighter } from 'shiki';
3
+ import { transformerNotationHighlight, transformerNotationDiff, transformerNotationFocus, transformerNotationErrorLevel, transformerNotationWordHighlight } from '@shikijs/transformers';
3
4
 
4
5
  function resolvePluginOptions(options, key, defaults) {
5
6
  if (options && typeof options === "object" && key in options) {
@@ -8,11 +9,173 @@ function resolvePluginOptions(options, key, defaults) {
8
9
  return { ...defaults, ...options };
9
10
  }
10
11
 
12
+ const shikiLangs = [
13
+ "bash",
14
+ "css",
15
+ "diff",
16
+ "html",
17
+ "javascript",
18
+ "json",
19
+ "nginx",
20
+ "sass",
21
+ "scss",
22
+ "typescript",
23
+ "vue",
24
+ "xml",
25
+ "yaml"
26
+ ];
27
+ const shikiLangAlias = {
28
+ js: "javascript",
29
+ markup: "html",
30
+ shell: "bash",
31
+ sh: "bash",
32
+ ts: "typescript",
33
+ yml: "yaml"
34
+ };
35
+ const highlighter = await createHighlighter({
36
+ themes: ["github-light", "github-dark"],
37
+ langs: shikiLangs,
38
+ langAlias: shikiLangAlias
39
+ });
40
+ const debugEnvValue = process.env.DEBUG;
41
+ delete process.env.DEBUG;
42
+ const twoslashModule = await import('@shikijs/twoslash').finally(() => {
43
+ if (debugEnvValue !== void 0) {
44
+ process.env.DEBUG = debugEnvValue;
45
+ }
46
+ });
47
+ const { rendererRich, transformerTwoslash } = twoslashModule;
48
+ const supportedLangSet = new Set(highlighter.getLoadedLanguages());
49
+ const themeOptions = {
50
+ themes: {
51
+ light: "github-light",
52
+ dark: "github-dark"
53
+ },
54
+ defaultColor: false
55
+ };
56
+ function normalizeShikiLang(lang) {
57
+ return supportedLangSet.has(lang) ? lang : "text";
58
+ }
59
+ function buildCodeBlockTransformers({
60
+ codeClass,
61
+ lineList,
62
+ maxheight,
63
+ preClass,
64
+ twoslash
65
+ }) {
66
+ return [
67
+ preClassTransformer(preClass, maxheight),
68
+ codeClassTransformer(codeClass),
69
+ ...twoslash === true ? [twoslashTransformer] : [],
70
+ transformerNotationHighlight(),
71
+ transformerNotationDiff(),
72
+ transformerNotationFocus(),
73
+ transformerNotationErrorLevel(),
74
+ transformerNotationWordHighlight(),
75
+ lineDecorTransformer(lineList)
76
+ ];
77
+ }
78
+ const twoslashTransformer = transformerTwoslash({
79
+ renderer: rendererRich(),
80
+ twoslashOptions: {
81
+ compilerOptions: {
82
+ traceResolution: false
83
+ }
84
+ }
85
+ });
86
+ function preClassTransformer(preClass, maxheight) {
87
+ return {
88
+ name: "md-plugins:pre-class",
89
+ pre(node) {
90
+ addClassToHast(node, preClass);
91
+ if (maxheight !== void 0) {
92
+ const style = typeof node.properties?.style === "string" ? node.properties.style : "";
93
+ node.properties = {
94
+ ...node.properties,
95
+ style: `${style}${style.length > 0 ? ";" : ""}max-height:${maxheight}`
96
+ };
97
+ }
98
+ }
99
+ };
100
+ }
101
+ function codeClassTransformer(codeClass) {
102
+ return {
103
+ name: "md-plugins:code-class",
104
+ code(node) {
105
+ if (codeClass !== void 0 && codeClass.length > 0) {
106
+ addClassToHast(node, codeClass);
107
+ }
108
+ }
109
+ };
110
+ }
111
+ function lineDecorTransformer(lineList) {
112
+ return {
113
+ name: "md-plugins:line-decor",
114
+ code(codeNode) {
115
+ const lines = codeNode.children.filter(
116
+ (child) => child.type === "element" && child.tagName === "span" && lineHasClass(child, "line")
117
+ );
118
+ lines.forEach((line, index) => {
119
+ const target = lineList[index];
120
+ const classList = [...target?.classList ?? []];
121
+ const prefix = target?.prefix ?? [];
122
+ if (this.options.lang === "diff") {
123
+ const first = lineTextContent(line).charAt(0);
124
+ if (first === "+") {
125
+ classList.push("line-add");
126
+ } else if (first === "-") {
127
+ classList.push("line-rem");
128
+ }
129
+ }
130
+ if (classList.length > 0) {
131
+ line.children?.unshift({
132
+ type: "element",
133
+ tagName: "span",
134
+ properties: { class: ["c-line", ...classList] },
135
+ children: []
136
+ });
137
+ }
138
+ if (prefix.length > 0) {
139
+ line.children?.unshift({
140
+ type: "element",
141
+ tagName: "span",
142
+ properties: { class: "c-lpref" },
143
+ children: [{ type: "text", value: prefix.join(" ") }]
144
+ });
145
+ }
146
+ });
147
+ }
148
+ };
149
+ }
150
+ function lineHasClass(line, className) {
151
+ const classValue = line.properties?.class;
152
+ if (typeof classValue === "string") {
153
+ return classValue.split(/\s+/).includes(className);
154
+ }
155
+ if (Array.isArray(classValue)) {
156
+ return classValue.includes(className);
157
+ }
158
+ return false;
159
+ }
160
+ function lineTextContent(line) {
161
+ let acc = "";
162
+ const walk = (node) => {
163
+ if (node.type === "text") {
164
+ acc += node.value ?? "";
165
+ return;
166
+ }
167
+ node.children?.forEach(walk);
168
+ };
169
+ line.children?.forEach(walk);
170
+ return acc;
171
+ }
172
+
11
173
  const defaultLangList = [
12
174
  { name: "markup" },
13
175
  { name: "bash", customCopy: true },
14
176
  { name: "javascript", aliases: "javascript|js" },
15
177
  { name: "typescript", aliases: "typescript|ts" },
178
+ { name: "yaml", aliases: "yaml|yml" },
16
179
  { name: "sass" },
17
180
  { name: "scss" },
18
181
  { name: "css" },
@@ -20,6 +183,7 @@ const defaultLangList = [
20
183
  { name: "xml" },
21
184
  { name: "nginx" },
22
185
  { name: "html" },
186
+ { name: "vue" },
23
187
  { name: "diff" }
24
188
  // special grammars
25
189
  ];
@@ -54,7 +218,6 @@ const codeblocksPlugin = (md, options) => {
54
218
  pageScripts = DEFAULT_CODEBLOCK_PLUGIN_OPTIONS.pageScripts,
55
219
  langList = defaultLangList
56
220
  } = resolvedOptions;
57
- loadLanguages(langList.map((l) => l.name));
58
221
  const customCopyLangList = langList.filter((l) => l.customCopy === true).map((l) => l.name);
59
222
  const langMatch = langList.map((l) => l.aliases || l.name).join("|");
60
223
  const definitionLineRE = new RegExp(
@@ -71,11 +234,13 @@ const codeblocksPlugin = (md, options) => {
71
234
  const tabsMatch = line.match(tabsLineRE);
72
235
  if (tabsMatch !== null) {
73
236
  const { lang, attrs, title } = tabsMatch.groups || {};
74
- currentTabName = title?.trim() || `Tab ${list.length + 1}`;
237
+ const bareAttrs = extractBareAttrs(title?.trim() || null);
238
+ currentTabName = bareAttrs.title ?? `Tab ${list.length + 1}`;
75
239
  list.push(currentTabName);
76
240
  tabMap[currentTabName] = {
77
241
  attrs: {
78
242
  ...parseAttrs(attrs?.trim() || null),
243
+ ...bareAttrs.attrs,
79
244
  lang
80
245
  },
81
246
  content: []
@@ -94,6 +259,7 @@ const codeblocksPlugin = (md, options) => {
94
259
  };
95
260
  }
96
261
  const magicCommentList = ["highlight", "rem", "add"];
262
+ const bareAttrList = ["twoslash"];
97
263
  const magicCommentRE = new RegExp(` *\\[\\[! (?<type>(${magicCommentList.join("|")}))\\]\\] *`);
98
264
  const magicCommentGlobalRE = new RegExp(magicCommentRE, "g");
99
265
  function extractCodeLineProps(lines, attrs) {
@@ -152,35 +318,25 @@ const codeblocksPlugin = (md, options) => {
152
318
  }
153
319
  return acc;
154
320
  }
155
- function renderCodeBlock(html, codeClass2) {
156
- return `<code${codeClass2 ? ` class="${codeClass2}"` : ""}>${html}</code>`;
157
- }
158
- function getPrismHighlightedContent(rawContent, lang) {
159
- const content = rawContent.trim();
160
- return prism.highlight(content, prism.languages[lang], lang);
161
- }
162
321
  function getHighlightedContent(rawContent, attrs) {
163
- const { lang, maxheight } = attrs;
322
+ const { lang, maxheight, twoslash } = attrs;
164
323
  let content = rawContent.trim();
165
324
  const lineList = parseCodeLine(content, attrs);
166
325
  if (lang !== "markup") {
167
326
  content = content.trim().replace(magicCommentGlobalRE, "");
168
327
  }
169
- const html = getPrismHighlightedContent(content, lang).split("\n").map((line, lineIndex) => {
170
- const target = lineList[lineIndex];
171
- if (target === void 0) return line;
172
- let lineHtml = "";
173
- lineHtml += target.classList.length !== 0 ? `<span class="c-line ${target.classList.join(" ")}"></span>` : "";
174
- lineHtml += target.prefix.length !== 0 ? `<span class="c-lpref">${target.prefix.join(" ")}</span>` : "";
175
- lineHtml += line;
176
- return lineHtml;
177
- }).join("\n");
178
- const preAttrs = maxheight !== void 0 ? ` style="max-height:${maxheight}"` : "";
179
328
  const langProp = customCopyLangList.includes(lang) === true ? ` lang="${lang}"` : "";
180
- return (
181
- // `<pre v-pre class="${preClass}${langClass}"${preAttrs}>` +
182
- `<pre v-pre class="${preClass}"${preAttrs}>` + renderCodeBlock(html, codeClass) + `</pre><${copyButtonComponent}${langProp} />`
183
- );
329
+ return highlighter.codeToHtml(content, {
330
+ lang: normalizeShikiLang(lang),
331
+ ...themeOptions,
332
+ transformers: buildCodeBlockTransformers({
333
+ codeClass,
334
+ lineList,
335
+ maxheight,
336
+ preClass: preClass ?? "markdown-code",
337
+ twoslash: twoslash === true || twoslash === "true"
338
+ })
339
+ }).replace("<pre ", "<pre v-pre ") + `<${copyButtonComponent}${langProp} />`;
184
340
  }
185
341
  function parseAttrs(rawAttrs) {
186
342
  if (rawAttrs === null) return {};
@@ -192,6 +348,27 @@ const codeblocksPlugin = (md, options) => {
192
348
  }
193
349
  return acc;
194
350
  }
351
+ function extractBareAttrs(title) {
352
+ if (title === null) {
353
+ return {
354
+ attrs: {},
355
+ title: null
356
+ };
357
+ }
358
+ const attrs = {};
359
+ const remainingTitle = [];
360
+ for (const chunk of title.split(/\s+/)) {
361
+ if (remainingTitle.length === 0 && bareAttrList.includes(chunk)) {
362
+ attrs[chunk] = true;
363
+ continue;
364
+ }
365
+ remainingTitle.push(chunk);
366
+ }
367
+ return {
368
+ attrs,
369
+ title: remainingTitle.length > 0 ? remainingTitle.join(" ") : null
370
+ };
371
+ }
195
372
  function parseDefinitionLine(token) {
196
373
  const match = token.info.trim().match(definitionLineRE);
197
374
  if (match === null) {
@@ -201,10 +378,12 @@ const codeblocksPlugin = (md, options) => {
201
378
  };
202
379
  }
203
380
  const { lang, attrs, title } = match.groups || {};
381
+ const bareAttrs = extractBareAttrs(title?.trim() || null);
204
382
  const acc = {
205
383
  ...parseAttrs(attrs?.trim() || null),
384
+ ...bareAttrs.attrs,
206
385
  lang,
207
- title: title?.trim() || null
386
+ title: bareAttrs.title
208
387
  };
209
388
  if (acc.lang === "tabs") {
210
389
  acc.tabs = extractTabs(token.content);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@md-plugins/md-plugin-codeblocks",
3
- "version": "0.1.0-beta.17",
3
+ "version": "0.1.0-beta.19",
4
4
  "description": "A markdown-it plugin for code blocks.",
5
5
  "keywords": [
6
6
  "markdown-it",
@@ -35,16 +35,18 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
+ "dependencies": {
39
+ "@shikijs/transformers": "^4.1.0",
40
+ "@shikijs/twoslash": "^4.1.0",
41
+ "shiki": "^4.1.0"
42
+ },
38
43
  "devDependencies": {
39
44
  "@types/markdown-it": "^14.1.2",
40
- "@types/prismjs": "^1.26.6",
41
45
  "markdown-it": "^14.2.0",
42
- "prismjs": "^1.30.0",
43
- "@md-plugins/shared": "0.1.0-beta.17"
46
+ "@md-plugins/shared": "0.1.0-beta.19"
44
47
  },
45
48
  "peerDependencies": {
46
- "markdown-it": "^14.2.0",
47
- "prismjs": "^1.29.0"
49
+ "markdown-it": "^14.2.0"
48
50
  },
49
51
  "scripts": {
50
52
  "build": "unbuild",