@unifast/shiki 0.0.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 kenzwada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,146 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _unifast_core = require("@unifast/core");
3
+ let shiki = require("shiki");
4
+ //#region src/transformer.ts
5
+ function resolveThemeConfig(opts) {
6
+ const { themes } = opts;
7
+ if (themes && typeof themes === "object" && !Array.isArray(themes) && "light" in themes) return {
8
+ kind: "dual",
9
+ light: themes.light,
10
+ dark: themes.dark,
11
+ allThemes: [themes.light, themes.dark],
12
+ defaultColor: opts.defaultColor ?? false
13
+ };
14
+ if (Array.isArray(themes)) return {
15
+ kind: "single",
16
+ theme: opts.defaultTheme ?? themes[0] ?? "github-dark",
17
+ allThemes: themes
18
+ };
19
+ if (typeof themes === "string") return {
20
+ kind: "single",
21
+ theme: themes,
22
+ allThemes: [themes]
23
+ };
24
+ const fallback = opts.defaultTheme ?? "github-dark";
25
+ return {
26
+ kind: "single",
27
+ theme: fallback,
28
+ allThemes: [fallback]
29
+ };
30
+ }
31
+ async function createShikiTransformer(options = {}) {
32
+ const themeConfig = resolveThemeConfig(options);
33
+ const langs = options.langs ?? [];
34
+ const highlighter = await (0, shiki.createHighlighter)({
35
+ themes: themeConfig.allThemes,
36
+ langs
37
+ });
38
+ const loadedLangs = new Set(highlighter.getLoadedLanguages());
39
+ function highlight(code, lang) {
40
+ if (themeConfig.kind === "dual") return highlighter.codeToHtml(code, {
41
+ lang,
42
+ themes: {
43
+ light: themeConfig.light,
44
+ dark: themeConfig.dark
45
+ },
46
+ defaultColor: themeConfig.defaultColor
47
+ });
48
+ return highlighter.codeToHtml(code, {
49
+ lang,
50
+ theme: themeConfig.theme
51
+ });
52
+ }
53
+ function highlightToHast(code, lang) {
54
+ if (themeConfig.kind === "dual") return highlighter.codeToHast(code, {
55
+ lang,
56
+ themes: {
57
+ light: themeConfig.light,
58
+ dark: themeConfig.dark
59
+ },
60
+ defaultColor: themeConfig.defaultColor
61
+ });
62
+ return highlighter.codeToHast(code, {
63
+ lang,
64
+ theme: themeConfig.theme
65
+ });
66
+ }
67
+ function extractLang(element) {
68
+ const code = (0, _unifast_core.findCodeChild)(element);
69
+ if (!code) return null;
70
+ return (0, _unifast_core.extractLang)(code);
71
+ }
72
+ function transformNode(node) {
73
+ if (node.type === "element") {
74
+ if (node.tagName === "pre") {
75
+ const lang = extractLang(node);
76
+ if (lang && loadedLangs.has(lang)) {
77
+ const pre = highlightToHast((0, _unifast_core.extractText)(node), lang).children[0];
78
+ if (pre && typeof pre === "object" && "type" in pre) return pre;
79
+ }
80
+ }
81
+ return {
82
+ ...node,
83
+ children: node.children.map(transformNode)
84
+ };
85
+ }
86
+ if (node.type === "root") return {
87
+ ...node,
88
+ children: node.children.map(transformNode)
89
+ };
90
+ return node;
91
+ }
92
+ function transformMdxJs(js) {
93
+ const pattern = /_jsx\("pre",\s*\{\s*children:\s*_jsx\("code",\s*\{(?:\s*children:\s*("(?:[^"\\]|\\.)*"),\s*className:\s*"language-([\w+-]+)"|\s*className:\s*"language-([\w+-]+)",\s*children:\s*("(?:[^"\\]|\\.)*"))\s*\}\)\s*\}\)/g;
94
+ const replacements = [];
95
+ let m;
96
+ while ((m = pattern.exec(js)) !== null) {
97
+ const lang = m[2] ?? m[3];
98
+ const codeStr = JSON.parse(m[1] ?? m[4]);
99
+ if (!loadedLangs.has(lang)) continue;
100
+ try {
101
+ const highlighted = highlight(codeStr, lang);
102
+ const replacement = `_jsx("div", { dangerouslySetInnerHTML: { __html: ${JSON.stringify(highlighted)} } })`;
103
+ replacements.push({
104
+ start: m.index,
105
+ end: m.index + m[0].length,
106
+ replacement
107
+ });
108
+ } catch {}
109
+ }
110
+ let result = js;
111
+ for (let i = replacements.length - 1; i >= 0; i--) {
112
+ const r = replacements[i];
113
+ result = result.slice(0, r.start) + r.replacement + result.slice(r.end);
114
+ }
115
+ return result;
116
+ }
117
+ return {
118
+ transform(hast) {
119
+ return {
120
+ ...hast,
121
+ children: hast.children.map(transformNode)
122
+ };
123
+ },
124
+ transformMdxJs
125
+ };
126
+ }
127
+ //#endregion
128
+ //#region src/plugin.ts
129
+ async function createShikiPlugin(options) {
130
+ const transformer = await createShikiTransformer(options);
131
+ return {
132
+ name: "shiki",
133
+ options: { highlight: { enabled: false } },
134
+ hastTransform: (hast) => transformer.transform(hast),
135
+ mdxJsTransform: (js) => transformer.transformMdxJs(js)
136
+ };
137
+ }
138
+ //#endregion
139
+ exports.createShikiPlugin = createShikiPlugin;
140
+ exports.createShikiTransformer = createShikiTransformer;
141
+ Object.defineProperty(exports, "hastToHtml", {
142
+ enumerable: true,
143
+ get: function() {
144
+ return _unifast_core.hastToHtml;
145
+ }
146
+ });
@@ -0,0 +1,23 @@
1
+ import { HastComment, HastDoctype, HastElement, HastNode, HastRaw, HastRoot, HastRoot as HastRoot$1, HastText, UnifastPlugin, hastToHtml } from "@unifast/core";
2
+ import { BundledLanguage, BundledTheme } from "shiki";
3
+
4
+ //#region src/transformer.d.ts
5
+ type ShikiTransformerOptions = {
6
+ themes?: BundledTheme | BundledTheme[] | {
7
+ light: BundledTheme;
8
+ dark: BundledTheme;
9
+ };
10
+ defaultTheme?: BundledTheme;
11
+ defaultColor?: string | false;
12
+ langs?: BundledLanguage[];
13
+ };
14
+ type ShikiTransformer = {
15
+ transform(hast: HastRoot$1): HastRoot$1;
16
+ transformMdxJs(js: string): string;
17
+ };
18
+ declare function createShikiTransformer(options?: ShikiTransformerOptions): Promise<ShikiTransformer>;
19
+ //#endregion
20
+ //#region src/plugin.d.ts
21
+ declare function createShikiPlugin(options?: ShikiTransformerOptions): Promise<UnifastPlugin>;
22
+ //#endregion
23
+ export { type HastComment, type HastDoctype, type HastElement, type HastNode, type HastRaw, type HastRoot, type HastText, type ShikiTransformer, type ShikiTransformerOptions, createShikiPlugin, createShikiTransformer, hastToHtml };
@@ -0,0 +1,23 @@
1
+ import { HastComment, HastDoctype, HastElement, HastNode, HastRaw, HastRoot, HastRoot as HastRoot$1, HastText, UnifastPlugin, hastToHtml } from "@unifast/core";
2
+ import { BundledLanguage, BundledTheme } from "shiki";
3
+
4
+ //#region src/transformer.d.ts
5
+ type ShikiTransformerOptions = {
6
+ themes?: BundledTheme | BundledTheme[] | {
7
+ light: BundledTheme;
8
+ dark: BundledTheme;
9
+ };
10
+ defaultTheme?: BundledTheme;
11
+ defaultColor?: string | false;
12
+ langs?: BundledLanguage[];
13
+ };
14
+ type ShikiTransformer = {
15
+ transform(hast: HastRoot$1): HastRoot$1;
16
+ transformMdxJs(js: string): string;
17
+ };
18
+ declare function createShikiTransformer(options?: ShikiTransformerOptions): Promise<ShikiTransformer>;
19
+ //#endregion
20
+ //#region src/plugin.d.ts
21
+ declare function createShikiPlugin(options?: ShikiTransformerOptions): Promise<UnifastPlugin>;
22
+ //#endregion
23
+ export { type HastComment, type HastDoctype, type HastElement, type HastNode, type HastRaw, type HastRoot, type HastText, type ShikiTransformer, type ShikiTransformerOptions, createShikiPlugin, createShikiTransformer, hastToHtml };
package/dist/index.mjs ADDED
@@ -0,0 +1,138 @@
1
+ import { extractLang, extractText, findCodeChild, hastToHtml } from "@unifast/core";
2
+ import { createHighlighter } from "shiki";
3
+ //#region src/transformer.ts
4
+ function resolveThemeConfig(opts) {
5
+ const { themes } = opts;
6
+ if (themes && typeof themes === "object" && !Array.isArray(themes) && "light" in themes) return {
7
+ kind: "dual",
8
+ light: themes.light,
9
+ dark: themes.dark,
10
+ allThemes: [themes.light, themes.dark],
11
+ defaultColor: opts.defaultColor ?? false
12
+ };
13
+ if (Array.isArray(themes)) return {
14
+ kind: "single",
15
+ theme: opts.defaultTheme ?? themes[0] ?? "github-dark",
16
+ allThemes: themes
17
+ };
18
+ if (typeof themes === "string") return {
19
+ kind: "single",
20
+ theme: themes,
21
+ allThemes: [themes]
22
+ };
23
+ const fallback = opts.defaultTheme ?? "github-dark";
24
+ return {
25
+ kind: "single",
26
+ theme: fallback,
27
+ allThemes: [fallback]
28
+ };
29
+ }
30
+ async function createShikiTransformer(options = {}) {
31
+ const themeConfig = resolveThemeConfig(options);
32
+ const langs = options.langs ?? [];
33
+ const highlighter = await createHighlighter({
34
+ themes: themeConfig.allThemes,
35
+ langs
36
+ });
37
+ const loadedLangs = new Set(highlighter.getLoadedLanguages());
38
+ function highlight(code, lang) {
39
+ if (themeConfig.kind === "dual") return highlighter.codeToHtml(code, {
40
+ lang,
41
+ themes: {
42
+ light: themeConfig.light,
43
+ dark: themeConfig.dark
44
+ },
45
+ defaultColor: themeConfig.defaultColor
46
+ });
47
+ return highlighter.codeToHtml(code, {
48
+ lang,
49
+ theme: themeConfig.theme
50
+ });
51
+ }
52
+ function highlightToHast(code, lang) {
53
+ if (themeConfig.kind === "dual") return highlighter.codeToHast(code, {
54
+ lang,
55
+ themes: {
56
+ light: themeConfig.light,
57
+ dark: themeConfig.dark
58
+ },
59
+ defaultColor: themeConfig.defaultColor
60
+ });
61
+ return highlighter.codeToHast(code, {
62
+ lang,
63
+ theme: themeConfig.theme
64
+ });
65
+ }
66
+ function extractLang$1(element) {
67
+ const code = findCodeChild(element);
68
+ if (!code) return null;
69
+ return extractLang(code);
70
+ }
71
+ function transformNode(node) {
72
+ if (node.type === "element") {
73
+ if (node.tagName === "pre") {
74
+ const lang = extractLang$1(node);
75
+ if (lang && loadedLangs.has(lang)) {
76
+ const pre = highlightToHast(extractText(node), lang).children[0];
77
+ if (pre && typeof pre === "object" && "type" in pre) return pre;
78
+ }
79
+ }
80
+ return {
81
+ ...node,
82
+ children: node.children.map(transformNode)
83
+ };
84
+ }
85
+ if (node.type === "root") return {
86
+ ...node,
87
+ children: node.children.map(transformNode)
88
+ };
89
+ return node;
90
+ }
91
+ function transformMdxJs(js) {
92
+ const pattern = /_jsx\("pre",\s*\{\s*children:\s*_jsx\("code",\s*\{(?:\s*children:\s*("(?:[^"\\]|\\.)*"),\s*className:\s*"language-([\w+-]+)"|\s*className:\s*"language-([\w+-]+)",\s*children:\s*("(?:[^"\\]|\\.)*"))\s*\}\)\s*\}\)/g;
93
+ const replacements = [];
94
+ let m;
95
+ while ((m = pattern.exec(js)) !== null) {
96
+ const lang = m[2] ?? m[3];
97
+ const codeStr = JSON.parse(m[1] ?? m[4]);
98
+ if (!loadedLangs.has(lang)) continue;
99
+ try {
100
+ const highlighted = highlight(codeStr, lang);
101
+ const replacement = `_jsx("div", { dangerouslySetInnerHTML: { __html: ${JSON.stringify(highlighted)} } })`;
102
+ replacements.push({
103
+ start: m.index,
104
+ end: m.index + m[0].length,
105
+ replacement
106
+ });
107
+ } catch {}
108
+ }
109
+ let result = js;
110
+ for (let i = replacements.length - 1; i >= 0; i--) {
111
+ const r = replacements[i];
112
+ result = result.slice(0, r.start) + r.replacement + result.slice(r.end);
113
+ }
114
+ return result;
115
+ }
116
+ return {
117
+ transform(hast) {
118
+ return {
119
+ ...hast,
120
+ children: hast.children.map(transformNode)
121
+ };
122
+ },
123
+ transformMdxJs
124
+ };
125
+ }
126
+ //#endregion
127
+ //#region src/plugin.ts
128
+ async function createShikiPlugin(options) {
129
+ const transformer = await createShikiTransformer(options);
130
+ return {
131
+ name: "shiki",
132
+ options: { highlight: { enabled: false } },
133
+ hastTransform: (hast) => transformer.transform(hast),
134
+ mdxJsTransform: (js) => transformer.transformMdxJs(js)
135
+ };
136
+ }
137
+ //#endregion
138
+ export { createShikiPlugin, createShikiTransformer, hastToHtml };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@unifast/shiki",
3
+ "version": "0.0.4",
4
+ "description": "Shiki syntax highlighting plugin for unifast via HAST transforms",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.cts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "dependencies": {
22
+ "shiki": "^3.23.0",
23
+ "@unifast/core": "0.0.4"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.9.3",
27
+ "vitest": "^4.1.0"
28
+ },
29
+ "scripts": {
30
+ "build": "tsdown",
31
+ "lint:oxc": "oxlint -c ../../.oxlintrc.json .",
32
+ "lint:oxc:fix": "oxlint -c ../../.oxlintrc.json --fix .",
33
+ "typecheck": "tsc --noEmit",
34
+ "fmt:oxc": "oxfmt --write .",
35
+ "fmt:oxc:check": "oxfmt --check .",
36
+ "test": "vitest run",
37
+ "test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=lcov"
38
+ }
39
+ }