do11y 0.1.2 → 0.2.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.
package/README.md CHANGED
@@ -4,5 +4,7 @@ A bare-bones tool to document Vue components.
4
4
 
5
5
  - Write documentation in markdown files that turn into single file components.
6
6
  - Import markdown files as routes through `do11y:routes`.
7
+ - Code blocks highlighted with [Shiki](https://shiki.style).
8
+ - Import Vue files as highlighted code blocks through `.vue?shiki`, or `.vue?shiki&lang=css` (if you want the output to include the compiled style tags). These imports return a HTML string meaning you have to render the code block with `v-html`.
7
9
  - Create sandbox components - e.g. `Button.sandbox.vue` will be available under the url `/sandbox?id=button`, and if importing the component it will be wrapped inside an iframe component that has access to the source code with or without compiled CSS.
8
10
  - Easily document components through meta imports which give a simplified result from [vue-component-meta](https://www.npmjs.com/package/vue-component-meta) - e.g. `Button.vue?meta`.
@@ -16,16 +16,17 @@ export interface SandboxIframeProps {
16
16
  */
17
17
  url: string;
18
18
  /**
19
- * The source code.
19
+ * HTML string containing the highlighted source code.
20
20
  *
21
21
  * @do11y Automatically passed.
22
22
  */
23
- source?: string;
23
+ highlightedSource: string;
24
24
  /**
25
- * The source code with compiled CSS.
26
- * Only included if the style tag has lang `scss` or `sass`.
25
+ * HTML string containing the highlighted source code,
26
+ * with all the style tags compiled to CSS.
27
27
  *
28
28
  * @do11y Automatically passed.
29
+ * @caveat Only works in a built solution - during development this returns the same as `highlightedSource`.
29
30
  */
30
- sourceWithCompiledCss?: string;
31
+ highlightedCssSource: string;
31
32
  }
@@ -1,5 +1,5 @@
1
- import type { Options } from "./plugins/options.js";
1
+ import type { ResolvedOptions } from "./plugins/options.js";
2
2
  /**
3
- * Access plugin options (`docs/do11y/index.ts`).
3
+ * Access plugin options (`docs/do11y/do11y.ts`).
4
4
  */
5
- export declare const do11yOptions: Options;
5
+ export declare const do11yOptions: ResolvedOptions;
@@ -1,5 +1,32 @@
1
1
  import { do11y } from "./files.js";
2
+ const resolveOptions = async () => {
3
+ const options = (await import(do11y)).default;
4
+ const themes = options.highlighter?.themes.map(async (theme) => {
5
+ if (typeof theme === "string") {
6
+ return theme;
7
+ }
8
+ else {
9
+ const resolvedTheme = await (typeof theme === "function" ? await theme() : theme);
10
+ return "default" in resolvedTheme ? resolvedTheme.default.name : resolvedTheme.name;
11
+ }
12
+ });
13
+ /* prettier-ignore */
14
+ let resolvedThemes = (await Promise.all(themes ?? [])).filter((theme) => !!theme);
15
+ if (!resolvedThemes.length) {
16
+ resolvedThemes = ["vitesse-light", "vitesse-black", "vitesse-dark"];
17
+ }
18
+ const themesInput = {};
19
+ resolvedThemes.forEach((theme) => (themesInput[theme] = theme));
20
+ return {
21
+ ...options,
22
+ highlighter: {
23
+ themes: options.highlighter?.themes ?? [],
24
+ themesInput,
25
+ defaultTheme: options.highlighter?.defaultTheme || resolvedThemes[0],
26
+ },
27
+ };
28
+ };
2
29
  /**
3
- * Access plugin options (`docs/do11y/index.ts`).
30
+ * Access plugin options (`docs/do11y/do11y.ts`).
4
31
  */
5
- export const do11yOptions = (await import(do11y)).default;
32
+ export const do11yOptions = await resolveOptions();
@@ -0,0 +1,9 @@
1
+ import type { Plugin } from "vite";
2
+ export declare const highlightCode: (code: string, lang: string) => string;
3
+ export declare const highlightAndFormatCode: (path: string, code: string) => Promise<string>;
4
+ /**
5
+ * Adds `.vue?highlight` imports which returns the
6
+ * highlighted code from Shiki.
7
+ */
8
+ declare const _default: () => Plugin;
9
+ export default _default;
@@ -0,0 +1,90 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { parse as parseVue } from "vue/compiler-sfc";
3
+ import { format } from "oxfmt";
4
+ import { createHighlighter, bundledLanguages, bundledThemes } from "shiki";
5
+ import { do11yOptions } from "../options.js";
6
+ import { transformerNotationDiff, transformerNotationErrorLevel, transformerNotationHighlight, } from "@shikijs/transformers";
7
+ const shiki = await createHighlighter({
8
+ langs: Object.keys(bundledLanguages),
9
+ themes: [...Object.values(bundledThemes), ...do11yOptions.highlighter.themes],
10
+ });
11
+ export const highlightCode = (code, lang) => {
12
+ return shiki.codeToHtml(code, {
13
+ lang,
14
+ themes: do11yOptions.highlighter.themesInput,
15
+ defaultColor: do11yOptions.highlighter.defaultTheme,
16
+ transformers: [
17
+ transformerNotationHighlight(),
18
+ transformerNotationDiff(),
19
+ transformerNotationErrorLevel(),
20
+ ],
21
+ });
22
+ };
23
+ export const highlightAndFormatCode = async (path, code) => {
24
+ return highlightCode((await format(path, code)).code, "vue");
25
+ };
26
+ /**
27
+ * Adds `.vue?highlight` imports which returns the
28
+ * highlighted code from Shiki.
29
+ */
30
+ export default () => {
31
+ let viteDevServer;
32
+ return {
33
+ name: "do11y:shiki",
34
+ configureServer(server) {
35
+ viteDevServer = server;
36
+ },
37
+ async resolveId(id) {
38
+ if (id === "do11y:css") {
39
+ return "\0dolly:css.css";
40
+ }
41
+ },
42
+ async load(id) {
43
+ if (id === "\0dolly:css.css") {
44
+ const generateThemeCss = (theme) => `
45
+ [data-theme="${theme}"] .shiki,
46
+ [data-theme="${theme}"] .shiki span {
47
+ background-color: var(--shiki-${theme}-bg) !important;
48
+ color: var(--shiki-${theme}) !important;
49
+ }
50
+ `;
51
+ return Object.keys(do11yOptions.highlighter.themesInput)
52
+ .filter((theme) => do11yOptions.highlighter.defaultTheme !== theme)
53
+ .map((theme) => generateThemeCss(theme))
54
+ .join("\n");
55
+ }
56
+ else if (id.endsWith(".vue?highlight") || id.endsWith(".vue?highlight&lang=css")) {
57
+ const path = id.replace("?highlight", "").replace("&lang=css", "");
58
+ const source = readFileSync(path, "utf-8");
59
+ /**
60
+ * Getting the code from `load` does not work during development,
61
+ * so we return the original code during development.
62
+ *
63
+ * We also return the original code if the import does not specifically ask
64
+ * to compile the style tags to CSS.
65
+ */
66
+ if (viteDevServer?.config.command === "serve" || !id.endsWith("lang=css")) {
67
+ return `export default ${JSON.stringify(await highlightAndFormatCode(path, source))};`;
68
+ }
69
+ const loadCss = async (index, lang) => {
70
+ const { code } = await this.load({
71
+ id: path + `?vue&type=style&index=${index}&lang.${lang}?inline`,
72
+ });
73
+ return code?.replace(/^(export default ")/, "").replace(/"$/, "");
74
+ };
75
+ const sourceWithoutStyles = source.replace(/\n<style\b[^>]*>[\s\S]*?<\/style>/gi, "");
76
+ const stylesheets = parseVue(source).descriptor.styles.map((style, i) => {
77
+ /* prettier-ignore */
78
+ return !style.lang || style.lang === "css"
79
+ ? style.content
80
+ : loadCss(i, style.lang);
81
+ });
82
+ const css = (await Promise.all(stylesheets))
83
+ .filter((stylesheet) => !!stylesheet)
84
+ .map((stylesheet) => `<style>${stylesheet}</style>`)
85
+ .join("\n");
86
+ return `export default ${JSON.stringify(await highlightAndFormatCode(path, sourceWithoutStyles + css))};`;
87
+ }
88
+ },
89
+ };
90
+ };
@@ -1,16 +1,11 @@
1
1
  import { type MarkdownSfcBlocks } from "@mdit-vue/plugin-sfc";
2
2
  import type { PluginSimple } from "markdown-it";
3
3
  import type { Plugin } from "vite";
4
- import type MarkdownIt from "markdown-it";
5
4
  export interface MarkdownPluginOptions {
6
5
  /**
7
6
  * Additional markdown-it setup.
8
7
  */
9
8
  markdownSetup?: PluginSimple;
10
- /**
11
- * The highlight option for `markdown-it`.
12
- */
13
- markdownHighlight?: (md: MarkdownIt, code: string, lang: string, attrs: string) => string;
14
9
  }
15
10
  export interface MarkdownItEnv {
16
11
  /**
@@ -3,6 +3,7 @@ import { sfcPlugin } from "@mdit-vue/plugin-sfc";
3
3
  import { frontmatterPlugin } from "@mdit-vue/plugin-frontmatter";
4
4
  import attrsPlugin from "markdown-it-attrs";
5
5
  import markdown from "markdown-it";
6
+ import { highlightCode } from "./highlight.js";
6
7
  /**
7
8
  * Processes blocks with the lang set to `md` into HTML,
8
9
  * and turns `.md` files into single file vue components
@@ -11,8 +12,8 @@ import markdown from "markdown-it";
11
12
  export default (options) => {
12
13
  const md = markdown({
13
14
  html: true,
14
- highlight(code, lang, attrs) {
15
- return options?.markdownHighlight?.(md, code, lang, attrs) ?? "";
15
+ highlight(code, lang) {
16
+ return highlightCode(code, lang);
16
17
  },
17
18
  });
18
19
  md.use(frontmatterPlugin);
@@ -1,6 +1,7 @@
1
1
  import type { Plugin } from "vite";
2
2
  import type { App, Component } from "vue";
3
3
  import type { Router } from "vue-router";
4
+ import type { BundledTheme, StringLiteralUnion, ThemeInput } from "shiki";
4
5
  import type { MarkdownPluginOptions } from "./markdown.js";
5
6
  export interface Options extends MarkdownPluginOptions {
6
7
  /**
@@ -27,6 +28,26 @@ export interface Options extends MarkdownPluginOptions {
27
28
  * Custom wrapper component for `?iframe` imports.
28
29
  */
29
30
  SandboxIframe?: () => Promise<Component>;
31
+ /**
32
+ * The code highlighter.
33
+ * - Markdown code blocks
34
+ * - `.vue?highlight` & `.vue?highlight&lang=css` imports
35
+ * - `highlightedSource` and `highlightedCssSource` props in SandboxIframe
36
+ *
37
+ * If using multiple themes - you can set the `data-theme` attribute
38
+ * to switch between them, e.g. `data-theme="vitesse-light"`.
39
+ */
40
+ highlighter?: {
41
+ themes: (ThemeInput | StringLiteralUnion<BundledTheme, string>)[];
42
+ defaultTheme?: string | StringLiteralUnion<BundledTheme, string>;
43
+ };
44
+ }
45
+ export interface ResolvedOptions extends Omit<Options, "highlighter"> {
46
+ highlighter: {
47
+ themes: (ThemeInput | StringLiteralUnion<BundledTheme, string>)[];
48
+ themesInput: Record<string, string>;
49
+ defaultTheme: string;
50
+ };
30
51
  }
31
52
  /**
32
53
  * Add ability to access options (`docs/do11y/do11y.ts`)
@@ -1,5 +1,6 @@
1
1
  import ui from "./ui.js";
2
2
  import sandbox from "./sandbox.js";
3
+ import highlight from "./highlight.js";
3
4
  import meta from "./meta/meta.js";
4
5
  import markdown from "./markdown.js";
5
6
  import block from "v-custom-block";
@@ -9,6 +10,7 @@ import { do11yOptions } from "../options.js";
9
10
  export const plugins = () => [
10
11
  ui(),
11
12
  sandbox(),
13
+ highlight(),
12
14
  meta(),
13
15
  markdown(do11yOptions),
14
16
  block("docs"),
@@ -1,7 +1,5 @@
1
1
  import { join, parse } from "node:path";
2
- import { readFileSync } from "node:fs";
3
2
  import { globSync } from "tinyglobby";
4
- import { parse as parseVue } from "vue/compiler-sfc";
5
3
  import { root, ui } from "../files.js";
6
4
  import { indexHtml } from "../html/plugin.js";
7
5
  const toParamId = (path) => parse(path).name.toLowerCase().replace(".sandbox", "");
@@ -37,40 +35,21 @@ export default () => {
37
35
  return `export default [${stringifiedSandboxes.join(",\n")}];`;
38
36
  }
39
37
  },
40
- async transform(_, id) {
38
+ transform(_, id) {
41
39
  if (id.endsWith(".sandbox.vue")) {
42
- const source = readFileSync(id, "utf-8");
43
- const sourceWithoutStyles = source.replace(/\n<style\b[^>]*>[\s\S]*?<\/style>/gi, "");
44
- const { descriptor } = parseVue(source, {
45
- filename: id,
46
- ignoreEmpty: true,
47
- });
48
- const usesSass = descriptor.styles.some((s) => s.lang && ["sass", "scss"].includes(s.lang));
49
- const imports = descriptor.styles
50
- .map((_, i) => `import Css${i} from "${id}?vue&type=style&index=${i}&lang.scss?inline";`)
51
- .join("");
52
- const stylesheets = descriptor.styles.map((_, i) => `Css${i}`).join(", ");
53
40
  const code = `
54
41
  import { defineComponent, h } from "vue";
55
42
 
56
43
  import SandboxIframe from "${join(ui, "sandbox-iframe.js")}";
57
44
 
58
- ${imports}
59
-
60
- const sourceWithoutStyleTags = ${JSON.stringify(sourceWithoutStyles)};
61
-
62
- const cssStylesheet = [${stylesheets}].join("");
63
-
64
- const sourceWithStylesheet = !!cssStylesheet
65
- ? sourceWithoutStyleTags + '<style>' + cssStylesheet + '</style>'
66
- : sourceWithoutStyleTags;
45
+ import highlightedSource from "${id}?highlight";
46
+ import highlightedCssSource from "${id}?highlight&lang=css";
67
47
 
68
48
  export default defineComponent((props) => {
69
49
  return () => h(SandboxIframe, {
70
50
  id: "${toParamId(id)}",
71
- source: ${JSON.stringify(source)},
72
- sourceWithCompiledCss: ${usesSass ? "sourceWithStylesheet" : "undefined"},
73
- passedProps: props,
51
+ highlightedSource,
52
+ highlightedCssSource,
74
53
  });
75
54
  });
76
55
  `.trim();
@@ -39,7 +39,7 @@ const getUserPlugins = async (userViteConfig) => {
39
39
  VuePlugin({
40
40
  ...vuePluginApi.options,
41
41
  include: [/\.vue$/, /\.md$/],
42
- exclude: [/\.vue\?meta$/],
42
+ exclude: [/\.vue\?meta$/, /\.vue\?highlight$/, /\.vue\?highlight&lang=css$/],
43
43
  }),
44
44
  ...resolvedUserPlugins.filter((i) => i.name !== "vite:vue"),
45
45
  ];
package/dist/ui/index.js CHANGED
@@ -1 +1 @@
1
- import{createApp as e,createBlock as t,createCommentVNode as n,defineComponent as r,onBeforeMount as i,openBlock as a,renderSlot as o,shallowRef as s,unref as c,withCtx as l}from"vue";import u from"do11y:options";import{createRouter as d,createWebHistory as f}from"vue-router";import p from"do11y:routes";var m=r({__name:`Page`,setup(e){let n=s();return i(async()=>{n.value=(await u.Layout?.())?.default}),(e,r)=>n.value?(a(),t(c(n),{key:0},{default:l(()=>[o(e.$slots,`default`)]),_:3})):o(e.$slots,`default`,{key:1})}});(async()=>{let t=d({history:f(`/`),routes:p}),n=e(m);await u.setup?.(n,t),n.use(t),n.mount(`#app`)})();
1
+ import{createApp as e,createBlock as t,createCommentVNode as n,defineComponent as r,onBeforeMount as i,openBlock as a,renderSlot as o,shallowRef as s,unref as c,withCtx as l}from"vue";import u from"do11y:options";import{createRouter as d,createWebHistory as f}from"vue-router";import p from"do11y:routes";import"do11y:css";var m=r({__name:`Page`,setup(e){let n=s();return i(async()=>{n.value=(await u.Layout?.())?.default}),(e,r)=>n.value?(a(),t(c(n),{key:0},{default:l(()=>[o(e.$slots,`default`)]),_:3})):o(e.$slots,`default`,{key:1})}});(async()=>{let t=d({history:f(`/`),routes:p}),n=e(m);await u.setup?.(n,t),n.use(t),n.mount(`#app`)})();
@@ -1 +1 @@
1
- import{computed as e,createBlock as t,createCommentVNode as n,createElementBlock as r,defineComponent as i,mergeProps as a,onBeforeMount as o,openBlock as s,shallowRef as c,unref as l}from"vue";import u from"do11y:options";var d=[`src`],f=i({__name:`SandboxIframe`,props:{id:{},url:{},source:{},sourceWithCompiledCss:{},passedProps:{}},setup(n){let i=n,f=e(()=>`/sandbox?id=${i.id}`),p=c();return o(async()=>{p.value=(await u.SandboxIframe?.())?.default}),(e,i)=>p.value?(s(),t(l(p),a({key:0,id:n.id,url:f.value,source:n.source,"source-with-compiled-css":n.sourceWithCompiledCss},n.passedProps),null,16,[`id`,`url`,`source`,`source-with-compiled-css`])):(s(),r(`iframe`,{key:1,src:f.value},null,8,d))}});export{f as default};
1
+ import{computed as e,createBlock as t,createCommentVNode as n,createElementBlock as r,defineComponent as i,mergeProps as a,onBeforeMount as o,openBlock as s,shallowRef as c,unref as l}from"vue";import u from"do11y:options";var d=[`src`],f=i({__name:`SandboxIframe`,props:{id:{},url:{},highlightedSource:{},highlightedCssSource:{},passedProps:{}},setup(n){let i=n,f=e(()=>`/sandbox?id=${i.id}`),p=c();return o(async()=>{p.value=(await u.SandboxIframe?.())?.default}),(e,i)=>p.value?(s(),t(l(p),a({key:0,id:n.id,url:f.value,"highlighted-source":n.highlightedSource,"highlighted-css-source":n.highlightedCssSource},n.passedProps),null,16,[`id`,`url`,`highlighted-source`,`highlighted-css-source`])):(s(),r(`iframe`,{key:1,src:f.value},null,8,d))}});export{f as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "do11y",
3
- "version": "0.1.2",
3
+ "version": "0.2.2",
4
4
  "description": "A bare-bones tool to document Vue components.",
5
5
  "license": "MIT",
6
6
  "repository": "github:sq11y/do11y",
@@ -21,11 +21,16 @@
21
21
  "@mdit-vue/plugin-component": "^3.0.2",
22
22
  "@mdit-vue/plugin-frontmatter": "^3.0.2",
23
23
  "@mdit-vue/plugin-sfc": "^3.0.2",
24
+ "@shikijs/engine-oniguruma": "^3.22.0",
25
+ "@shikijs/langs": "^3.22.0",
26
+ "@shikijs/themes": "^3.22.0",
27
+ "@shikijs/transformers": "^3.22.0",
24
28
  "front-matter": "^4.0.2",
25
29
  "jsdom": "^28.1.0",
26
30
  "markdown-it": "^14.1.1",
27
31
  "markdown-it-attrs": "^4.3.1",
28
32
  "sass": "^1.97.3",
33
+ "shiki": "^3.22.0",
29
34
  "tinyglobby": "^0.2.15",
30
35
  "v-custom-block": "^1.0.67",
31
36
  "vue-component-meta": "^3.2.4"
package/src/types.d.ts CHANGED
@@ -25,6 +25,12 @@ declare module "do11y:options" {
25
25
  export default options;
26
26
  }
27
27
 
28
+ declare module "do11y:css" {
29
+ /**
30
+ * The CSS required for dual themes.
31
+ */
32
+ }
33
+
28
34
  declare module "do11y:sandbox" {
29
35
  import type { Component } from "vue";
30
36