@nuxtjs/mdc 0.1.6 → 0.2.1

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
@@ -9,6 +9,10 @@
9
9
 
10
10
  MDC supercharges regular Markdown to write documents interacting deeply with any Vue component. MDC stands for MarkDown Components.
11
11
 
12
+ - [✨  Release Notes](https://github.com/nuxt-modules/mdc/releases)
13
+ - [🏀  Online Playground](https://stackblitz.com/github/nuxt-modules/mdc?file=playground%2Fapp.vue)
14
+ - [🧩  VS Code Extension](https://marketplace.visualstudio.com/items?itemName=Nuxt.mdc)
15
+
12
16
  ## Features
13
17
 
14
18
  - Mix Markdown syntax with HTML tags or Vue components
@@ -83,7 +87,6 @@ Hello MDC
83
87
 
84
88
  [MIT](./LICENSE) - Made with 💚
85
89
 
86
-
87
90
  <!-- Badges -->
88
91
  [npm-version-src]: https://img.shields.io/npm/v/@nuxtjs/mdc/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
89
92
  [npm-version-href]: https://npmjs.com/package/@nuxtjs/mdc
package/dist/module.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { Theme as Theme$1 } from 'shiki-es';
2
+ import { BuiltinTheme } from 'shikiji';
3
3
 
4
- type Theme = Theme$1 | Record<string, Theme$1>;
4
+ type Theme = BuiltinTheme | Record<string, BuiltinTheme>;
5
5
 
6
6
  interface UnistPlugin {
7
7
  src?: string;
@@ -11,8 +11,21 @@ interface ModuleOptions {
11
11
  remarkPlugins?: Record<string, UnistPlugin>;
12
12
  rehypePlugins?: Record<string, UnistPlugin>;
13
13
  highlight?: {
14
- theme?: Theme;
15
14
  highlighter?: string;
15
+ /**
16
+ * Default theme that will be used for highlighting code blocks.
17
+ */
18
+ theme?: Theme;
19
+ /**
20
+ * Preloaded languages that will be available for highlighting code blocks.
21
+ */
22
+ preload?: string[];
23
+ /**
24
+ * Inject background color to code block wrapper
25
+ *
26
+ * @default false
27
+ */
28
+ wrapperStyle?: boolean | string;
16
29
  } | false;
17
30
  headings?: {
18
31
  anchorLinks?: {
@@ -28,6 +41,15 @@ interface ModuleOptions {
28
41
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
29
42
 
30
43
  declare module '@nuxt/schema' {
44
+ interface RuntimeConfig {
45
+ mdc: {
46
+ highlight: {
47
+ theme?: Theme;
48
+ preload?: string[];
49
+ wrapperStyle?: boolean | string;
50
+ };
51
+ };
52
+ }
31
53
  interface PublicRuntimeConfig {
32
54
  mdc: {
33
55
  components: {
package/dist/module.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { Theme as Theme$1 } from 'shiki-es';
2
+ import { BuiltinTheme } from 'shikiji';
3
3
 
4
- type Theme = Theme$1 | Record<string, Theme$1>;
4
+ type Theme = BuiltinTheme | Record<string, BuiltinTheme>;
5
5
 
6
6
  interface UnistPlugin {
7
7
  src?: string;
@@ -11,8 +11,21 @@ interface ModuleOptions {
11
11
  remarkPlugins?: Record<string, UnistPlugin>;
12
12
  rehypePlugins?: Record<string, UnistPlugin>;
13
13
  highlight?: {
14
- theme?: Theme;
15
14
  highlighter?: string;
15
+ /**
16
+ * Default theme that will be used for highlighting code blocks.
17
+ */
18
+ theme?: Theme;
19
+ /**
20
+ * Preloaded languages that will be available for highlighting code blocks.
21
+ */
22
+ preload?: string[];
23
+ /**
24
+ * Inject background color to code block wrapper
25
+ *
26
+ * @default false
27
+ */
28
+ wrapperStyle?: boolean | string;
16
29
  } | false;
17
30
  headings?: {
18
31
  anchorLinks?: {
@@ -28,6 +41,15 @@ interface ModuleOptions {
28
41
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
29
42
 
30
43
  declare module '@nuxt/schema' {
44
+ interface RuntimeConfig {
45
+ mdc: {
46
+ highlight: {
47
+ theme?: Theme;
48
+ preload?: string[];
49
+ wrapperStyle?: boolean | string;
50
+ };
51
+ };
52
+ }
31
53
  interface PublicRuntimeConfig {
32
54
  mdc: {
33
55
  components: {
package/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@nuxtjs/mdc",
3
3
  "configKey": "mdc",
4
- "version": "0.1.6"
4
+ "version": "0.2.1"
5
5
  }
package/dist/module.mjs CHANGED
@@ -2,6 +2,7 @@ import { createResolver, extendViteConfig, defineNuxtModule, addTemplate, addCom
2
2
  import fs from 'fs';
3
3
  import { pascalCase } from 'scule';
4
4
  import { defu } from 'defu';
5
+ import { pathToFileURL } from 'url';
5
6
 
6
7
  const mdcImportTemplate = async ({ nuxt, options }) => {
7
8
  const resolver = createResolver(import.meta.url);
@@ -114,6 +115,13 @@ const module = defineNuxtModule({
114
115
  },
115
116
  headings: options.headings
116
117
  });
118
+ nuxt.options.runtimeConfig.mdc = defu(nuxt.options.runtimeConfig.mdc, {
119
+ highlight: options.highlight ? {
120
+ theme: options.highlight.theme,
121
+ preload: options.highlight.preload,
122
+ wrapperStyle: options.highlight.wrapperStyle
123
+ } : {}
124
+ });
117
125
  nuxt.hook("vite:extendConfig", (viteConfig) => {
118
126
  viteConfig.optimizeDeps?.include?.push(
119
127
  "is-buffer",
@@ -124,7 +132,8 @@ const module = defineNuxtModule({
124
132
  "hast-util-raw"
125
133
  );
126
134
  });
127
- nuxt.options.alias["#mdc-imports"] = addTemplate({ filename: "mdc-imports.mjs", getContents: mdcImportTemplate, options, write: true }).dst;
135
+ const { dst: templatePath } = addTemplate({ filename: "mdc-imports.mjs", getContents: mdcImportTemplate, options, write: true });
136
+ nuxt.options.alias["#mdc-imports"] = process.env.NODE_ENV === "development" ? pathToFileURL(templatePath).href : templatePath;
128
137
  nuxt.options.nitro.alias = nuxt.options.nitro.alias || {};
129
138
  nuxt.options.nitro.alias["#mdc-imports"] = nuxt.options.alias["#mdc-imports"];
130
139
  addComponent({ name: "MDC", filePath: resolver.resolve("./runtime/components/MDC") });
@@ -139,7 +148,11 @@ const module = defineNuxtModule({
139
148
  global: true
140
149
  });
141
150
  }
142
- addServerHandler({ route: "/api/_mdc/highlight", handler: resolver.resolve("./runtime/shiki/event-handler") });
151
+ if (options.highlight) {
152
+ nuxt.options.nitro.experimental = nuxt.options.nitro.experimental || {};
153
+ nuxt.options.nitro.experimental.wasm = true;
154
+ addServerHandler({ route: "/api/_mdc/highlight", handler: resolver.resolve("./runtime/shiki/event-handler") });
155
+ }
143
156
  extendViteConfig((config) => {
144
157
  config.optimizeDeps = config.optimizeDeps || {};
145
158
  config.optimizeDeps.exclude = config.optimizeDeps.exclude || [];
@@ -3,9 +3,11 @@
3
3
  <slot
4
4
  :data="data?.data"
5
5
  :body="data?.body"
6
+ :toc="data?.toc"
7
+ :excerpt="data?.excerpt"
6
8
  >
7
9
  <MDCRenderer
8
- :body="data?.body"
10
+ :body="excerpt ? data?.excerpt : data?.body"
9
11
  :data="data?.data"
10
12
  />
11
13
  </slot>
@@ -16,22 +18,45 @@
16
18
  import { hash } from 'ohash'
17
19
  import { useAsyncData } from 'nuxt/app'
18
20
  import { parseMarkdown } from '../parser'
19
- import { watch, computed } from 'vue'
21
+ import { watch, computed, type PropType } from 'vue'
22
+ import { MDCParseOptions } from '../types'
20
23
 
21
24
  const props = defineProps({
22
25
  tag: {
23
26
  type: String,
24
27
  default: 'div'
25
28
  },
29
+ /**
30
+ * Raw markdown string or parsed markdown object from `parseMarkdown`
31
+ */
26
32
  value: {
27
- type: String,
33
+ type: [String, Object],
28
34
  required: true
35
+ },
36
+ /**
37
+ * Render only the excerpt
38
+ */
39
+ excerpt: {
40
+ type: Boolean,
41
+ default: false
42
+ },
43
+ /**
44
+ * Options for `parseMarkdown`
45
+ */
46
+ parserOptions: {
47
+ type: Object as PropType<MDCParseOptions>,
48
+ default: () => ({})
29
49
  }
30
50
  })
31
51
 
32
52
  const key = computed(() => hash(props.value))
33
53
 
34
- const { data, refresh } = await useAsyncData(key.value, async () => await parseMarkdown(props.value, { highlight: {}}))
54
+ const { data, refresh } = await useAsyncData(key.value, async () => {
55
+ if (typeof props.value !== 'string') {
56
+ return props.value
57
+ }
58
+ return await parseMarkdown(props.value, props.parserOptions)
59
+ })
35
60
 
36
61
  watch(() => props.value, () => {
37
62
  refresh()
@@ -21,17 +21,13 @@ export default defineComponent({
21
21
  type: Object,
22
22
  required: true
23
23
  },
24
+ /**
25
+ * Document meta data
26
+ */
24
27
  data: {
25
28
  type: Object,
26
29
  default: () => ({})
27
30
  },
28
- /**
29
- * Render only the excerpt
30
- */
31
- excerpt: {
32
- type: Boolean,
33
- default: false
34
- },
35
31
  /**
36
32
  * Root tag to use for rendering
37
33
  */
@@ -264,6 +260,9 @@ function mergeTextNodes(nodes) {
264
260
  return mergedNodes;
265
261
  }
266
262
  async function resolveContentComponents(body, meta) {
263
+ if (!body) {
264
+ return;
265
+ }
267
266
  const components = Array.from(new Set(loadComponents(body, meta)));
268
267
  await Promise.all(components.map(async (c) => {
269
268
  if (c?.render || c?.ssrRender) {
@@ -8,17 +8,13 @@ declare const _default: DefineComponent<{
8
8
  type: PropType<MDCRoot>;
9
9
  required: true;
10
10
  };
11
+ /**
12
+ * Document meta data
13
+ */
11
14
  data: {
12
15
  type: ObjectConstructor;
13
16
  default: () => {};
14
17
  };
15
- /**
16
- * Render only the excerpt
17
- */
18
- excerpt: {
19
- type: BooleanConstructor;
20
- default: boolean;
21
- };
22
18
  /**
23
19
  * Root tag to use for rendering
24
20
  */
@@ -50,17 +46,13 @@ declare const _default: DefineComponent<{
50
46
  type: PropType<MDCRoot>;
51
47
  required: true;
52
48
  };
49
+ /**
50
+ * Document meta data
51
+ */
53
52
  data: {
54
53
  type: ObjectConstructor;
55
54
  default: () => {};
56
55
  };
57
- /**
58
- * Render only the excerpt
59
- */
60
- excerpt: {
61
- type: BooleanConstructor;
62
- default: boolean;
63
- };
64
56
  /**
65
57
  * Root tag to use for rendering
66
58
  */
@@ -86,7 +78,6 @@ declare const _default: DefineComponent<{
86
78
  tag: string;
87
79
  data: Record<string, any>;
88
80
  components: Record<string, string | DefineComponent<any, any, any>>;
89
- excerpt: boolean;
90
81
  prose: boolean;
91
82
  }, {}>;
92
83
  export default _default;
@@ -32,7 +32,7 @@ export function compileHast() {
32
32
  return child;
33
33
  });
34
34
  }
35
- if (node.tagName.startsWith("h")) {
35
+ if (node.tagName?.match(/^h\d$/)) {
36
36
  node.properties = node.properties || {};
37
37
  node.properties.id = String(node.properties?.id || slugs.slug(toString(node))).replace(/-+/g, "-").replace(/^-|-$/g, "").replace(/^(\d)/, "_$1");
38
38
  }
@@ -10,10 +10,10 @@ export default (state, node) => {
10
10
  properties: { __ignoreMap: "" },
11
11
  children: [{ type: "text", value }]
12
12
  };
13
- if (node.meta) {
13
+ if (meta) {
14
14
  result.data = {
15
15
  // @ts-ignore
16
- meta: node.meta
16
+ meta
17
17
  };
18
18
  }
19
19
  state.patch(node, result);
@@ -25,8 +25,8 @@ export default (state, node) => {
25
25
  meta,
26
26
  code: value
27
27
  };
28
- if (node.lang) {
29
- properties.className = ["language-" + node.lang];
28
+ if (language) {
29
+ properties.className = ["language-" + language];
30
30
  }
31
31
  result = { type: "element", tagName: "pre", properties, children: [result] };
32
32
  state.patch(node, result);
@@ -1,4 +1,5 @@
1
1
  export default function inlineCode(state, node) {
2
+ const language = node.attributes?.language || node.attributes?.lang;
2
3
  const text = { type: "text", value: node.value.replace(/\r?\n|\r/g, " ") };
3
4
  state.patch(node, text);
4
5
  const result = {
@@ -7,6 +8,14 @@ export default function inlineCode(state, node) {
7
8
  properties: node.attributes || {},
8
9
  children: [text]
9
10
  };
11
+ const classes = (result.properties.class || "").split(" ");
12
+ delete result.properties.class;
13
+ if (language) {
14
+ result.properties.language = language;
15
+ delete result.properties.lang;
16
+ classes.push("language-" + language);
17
+ }
18
+ result.properties.className = classes.join(" ");
10
19
  state.patch(node, result);
11
20
  return state.applyData(node, result);
12
21
  }
@@ -6,14 +6,12 @@
6
6
  export declare function parseThematicBlock(lang: string): {
7
7
  language: undefined;
8
8
  highlights: undefined;
9
- fileName: undefined;
9
+ filename: undefined;
10
10
  meta: undefined;
11
- filename?: undefined;
12
11
  } | {
13
12
  language: string | undefined;
14
13
  highlights: number[] | undefined;
15
14
  filename: string | undefined;
16
15
  meta: string;
17
- fileName?: undefined;
18
16
  };
19
17
  export declare function getTagName(value: string): string | null;
@@ -3,7 +3,7 @@ export function parseThematicBlock(lang) {
3
3
  return {
4
4
  language: void 0,
5
5
  highlights: void 0,
6
- fileName: void 0,
6
+ filename: void 0,
7
7
  meta: void 0
8
8
  };
9
9
  }
@@ -6,11 +6,22 @@ import { defu } from "defu";
6
6
  import { useProcessorPlugins } from "./utils/plugins.mjs";
7
7
  import { compileHast } from "./compiler.mjs";
8
8
  import { defaults } from "./options.mjs";
9
- import { rehypeShiki } from "./shiki.mjs";
9
+ import { rehypeShiki } from "../shiki/index.mjs";
10
10
  import { generateToc } from "./toc.mjs";
11
11
  import { nodeTextContent } from "../utils/node.mjs";
12
+ let moduleOptions;
12
13
  export const parseMarkdown = async (md, opts = {}) => {
13
- const options = defu(opts, defaults);
14
+ if (!moduleOptions) {
15
+ moduleOptions = await import(
16
+ "#mdc-imports"
17
+ /* @vite-ignore */
18
+ ).catch(() => ({}));
19
+ }
20
+ const options = defu(opts, {
21
+ remark: { plugins: moduleOptions?.remarkPlugins },
22
+ rehype: { plugins: moduleOptions?.rehypePlugins },
23
+ highlight: moduleOptions?.highlight
24
+ }, defaults);
14
25
  const { content, data: frontmatter } = await parseFrontMatter(md);
15
26
  const processor = unified();
16
27
  processor.use(remarkParse);
@@ -5,7 +5,6 @@ import rehypeExternalLinks from "rehype-external-links";
5
5
  import rehypeSortAttributeValues from "rehype-sort-attribute-values";
6
6
  import rehypeSortAttributes from "rehype-sort-attributes";
7
7
  import rehypeRaw from "rehype-raw";
8
- import { remarkPlugins, rehypePlugins, highlight } from "#mdc-imports";
9
8
  export const defaults = {
10
9
  remark: {
11
10
  plugins: {
@@ -14,8 +13,7 @@ export const defaults = {
14
13
  },
15
14
  "remark-gfm": {
16
15
  instance: remarkGFM
17
- },
18
- ...remarkPlugins
16
+ }
19
17
  }
20
18
  },
21
19
  rehype: {
@@ -39,11 +37,10 @@ export const defaults = {
39
37
  options: {
40
38
  passThrough: ["element"]
41
39
  }
42
- },
43
- ...rehypePlugins
40
+ }
44
41
  }
45
42
  },
46
- highlight,
43
+ highlight: false,
47
44
  toc: {
48
45
  searchDepth: 2,
49
46
  depth: 2
@@ -1,8 +1,2 @@
1
- import type { TokenStyleMap } from './types';
2
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
3
- tree: import("../types/hast").Element[];
4
- className: string;
5
- style: string;
6
- styleMap: TokenStyleMap;
7
- }>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<import("./types").HighlightResult>>;
8
2
  export default _default;
@@ -1,15 +1,20 @@
1
- import { eventHandler, getQuery } from "h3";
1
+ import { loadWasm } from "shikiji";
2
+ import { eventHandler, getQuery, lazyEventHandler } from "h3";
2
3
  import { useShikiHighlighter } from "./highlighter.mjs";
3
- export default eventHandler(async (event) => {
4
- const { code, lang, theme: themeString } = getQuery(event);
5
- const theme = JSON.parse(themeString);
6
- const shikiHighlighter = useShikiHighlighter({});
7
- const styleMap = {};
8
- const { tree, className } = await shikiHighlighter.getHighlightedAST(code, lang, theme, { styleMap });
9
- return {
10
- tree,
11
- className,
12
- style: shikiHighlighter.generateStyles(styleMap),
13
- styleMap
14
- };
4
+ import { useRuntimeConfig } from "#imports";
5
+ export default lazyEventHandler(async () => {
6
+ const { highlight } = useRuntimeConfig().mdc;
7
+ try {
8
+ const wasm = await import("shikiji/onig.wasm").then((r) => r.default);
9
+ await loadWasm(async (obj) => WebAssembly.instantiate(wasm, obj));
10
+ } catch {
11
+ await loadWasm({ data: await import("shikiji/wasm").then((r) => r.getWasmInlined()).then((r) => r.data) });
12
+ }
13
+ const shiki = useShikiHighlighter(highlight);
14
+ return eventHandler(async (event) => {
15
+ const { code, lang, theme: themeString, highlights: highlightsString } = getQuery(event);
16
+ const theme = JSON.parse(themeString);
17
+ const highlights = highlightsString ? JSON.parse(highlightsString) : void 0;
18
+ return await shiki.getHighlightedAST(code, lang, theme, { highlights });
19
+ });
15
20
  });
@@ -1,25 +1,5 @@
1
- import { Lang } from 'shiki-es';
2
- import type { HighlighterOptions, Theme, TokenStyleMap } from './types';
3
- import type { Element } from '../types/hast';
1
+ import { type BuiltinLanguage } from 'shikiji';
2
+ import type { HighlightResult, HighlighterOptions, Theme } from './types';
4
3
  export declare const useShikiHighlighter: (opts?: any) => {
5
- getHighlightedTokens: (code: string, lang: Lang, theme: Theme) => Promise<{
6
- code: string;
7
- lang: Lang;
8
- theme: {
9
- [k: string]: import("shiki-es").IShikiTheme;
10
- };
11
- tokens: {
12
- content: string;
13
- }[][];
14
- }>;
15
- getHighlightedAST: (code: string, lang: Lang, theme: Theme, opts?: Partial<HighlighterOptions>) => Promise<{
16
- tree: Element[];
17
- className: string;
18
- }>;
19
- getHighlightedCode: (code: string, lang: Lang, theme: Theme, opts?: Partial<HighlighterOptions>) => Promise<{
20
- code: string;
21
- className: string;
22
- styles: string;
23
- }>;
24
- generateStyles: (styleMap: TokenStyleMap) => string;
4
+ getHighlightedAST: (code: string, lang: BuiltinLanguage, theme: Theme, opts?: Partial<HighlighterOptions>) => Promise<HighlightResult>;
25
5
  };