@nuxtjs/mdc 0.4.0 → 0.6.0

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
@@ -23,6 +23,11 @@ MDC supercharges regular Markdown to write documents interacting deeply with any
23
23
 
24
24
  Learn more about the MDC syntax on https://content.nuxtjs.org/guide/writing/mdc
25
25
 
26
+ > [!Note]
27
+ > You may utilize this package inside of your Nuxt project (standard configuration) or within any Vue project.
28
+ >
29
+ > See [Rendering in your Vue project](#rendering-in-your-vue-project) below for more information.
30
+
26
31
  ## Install
27
32
 
28
33
  ```bash
@@ -44,7 +49,7 @@ export default defineNuxtConfig({
44
49
  })
45
50
  ```
46
51
 
47
- That's it! You can start writing and rendering markdown files ✨
52
+ That's it! You can start writing and rendering markdown files in your Nuxt project
48
53
 
49
54
  ## Rendering
50
55
 
@@ -262,6 +267,145 @@ export default defineNuxtConfig({
262
267
 
263
268
  Checkout [`ModuleOptions` types↗︎](https://github.com/nuxt-modules/mdc/blob/main/src/types.ts).
264
269
 
270
+ ## Rendering in your Vue project
271
+
272
+ The `<MDCRenderer>` component in combination with a few exported package utilities may also be utilized inside a normal (non-Nuxt) Vue project.
273
+
274
+ To implement in your standard Vue project, follow the instructions below.
275
+
276
+ ### Install the package
277
+
278
+ Follow the [install instructions above](#install), ignoring the step of adding the Nuxt module to a `nuxt.config.ts` file.
279
+
280
+ ### Stub Nuxt module imports
281
+
282
+ Since you're not using Nuxt, you'll need to stub a few of the module's imports in your Vue projects's Vite config file. This is necessary to avoid errors when the module tries to access Nuxt-specific imports.
283
+
284
+ Create a new file in your Vue project's root directory, such as `stub-mdc-imports.js`, and add the following content:
285
+
286
+ ```ts
287
+ // stub-mdc-imports.js
288
+ export default {}
289
+ ```
290
+
291
+ Next, update your Vue project's Vite config file (e.g. `vite.config.ts`) to alias the module's imports to the stub file:
292
+
293
+ ```ts
294
+ import { defineConfig } from 'vite'
295
+ import path from 'path'
296
+
297
+ export default defineConfig({
298
+ resolve: {
299
+ alias: {
300
+ '#mdc-imports': path.resolve(__dirname, './stub-mdc-imports.js'),
301
+ '#mdc-configs': path.resolve(__dirname, './stub-mdc-imports.js'),
302
+ }
303
+ }
304
+ })
305
+ ```
306
+
307
+ ### Usage
308
+
309
+ Next, let's create a new [Vue composable](https://vuejs.org/guide/reusability/composables.html) to handle parsing the markdown content, as well as adding syntax highlighting to code blocks with [Shiki](https://shiki.style/).
310
+
311
+ ```ts
312
+ // composables/useMarkdownParser.ts
313
+ // Import package exports
314
+ import {
315
+ createMarkdownParser,
316
+ rehypeHighlight,
317
+ createShikiHighlighter,
318
+ } from '@nuxtjs/mdc/runtime'
319
+ // Import desired Shiki themes and languages
320
+ import MaterialThemePalenight from 'shiki/themes/material-theme-palenight.mjs'
321
+ import HtmlLang from 'shiki/langs/html.mjs'
322
+ import MdcLang from 'shiki/langs/mdc.mjs'
323
+ import TsLang from 'shiki/langs/typescript.mjs'
324
+ import VueLang from 'shiki/langs/vue.mjs'
325
+ import ScssLang from 'shiki/langs/scss.mjs'
326
+ import YamlLang from 'shiki/langs/yaml.mjs'
327
+
328
+ export default function useMarkdownParser() {
329
+ let parser: Awaited<ReturnType<typeof createMarkdownParser>>
330
+
331
+ const parse = async (markdown: string) => {
332
+ if (!parser) {
333
+ parser = await createMarkdownParser({
334
+ rehype: {
335
+ plugins: {
336
+ highlight: {
337
+ instance: rehypeHighlight,
338
+ options: {
339
+ // Pass in your desired theme(s)
340
+ theme: 'material-theme-palenight',
341
+ // Create the Shiki highlighter
342
+ highlighter: createShikiHighlighter({
343
+ bundledThemes: {
344
+ 'material-theme-palenight': MaterialThemePalenight,
345
+ },
346
+ // Configure the bundled languages
347
+ bundledLangs: {
348
+ html: HtmlLang,
349
+ mdc: MdcLang,
350
+ vue: VueLang,
351
+ yml: YamlLang,
352
+ scss: ScssLang,
353
+ ts: TsLang,
354
+ typescript: TsLang,
355
+ },
356
+ }),
357
+ },
358
+ },
359
+ },
360
+ },
361
+ })
362
+ }
363
+ return parser(markdown)
364
+ }
365
+
366
+ return parse
367
+ }
368
+ ```
369
+
370
+ Now import the `useMarkdownParser` composable we just created along with an exported type interface into your host project's Vue component, and utilize them to process the raw markdown and initialize the `<MDCRenderer>` component.
371
+
372
+ ```vue
373
+ <script setup lang="ts">
374
+ import { onBeforeMount, ref, watch } from 'vue'
375
+ // Import package exports
376
+ import { MDCRenderer } from '@nuxtjs/mdc/runtime/components/MDCRenderer'
377
+ import type { MDCParserResult } from '@nuxtjs/mdc/runtime/types/index'
378
+ import { useMarkdownParser } from './composables/useMarkdownParser';
379
+
380
+ const md = ref(`
381
+ # Just a Vue app
382
+
383
+ This is markdown content rendered via the \`<MDCRenderer>\` component, including MDC below.
384
+
385
+ ::alert
386
+ Hello MDC
387
+ ::
388
+
389
+ \`\`\`ts
390
+ const a = 1;
391
+ \`\`\`
392
+ `);
393
+
394
+ const ast = ref<MDCParserResult | null>(null)
395
+ const parse = useMarkdownParser()
396
+
397
+ onBeforeMount(async () => {
398
+ ast.value = await parse(md.value)
399
+ })
400
+ </script>
401
+
402
+ <template>
403
+ <Suspense>
404
+ <MDCRenderer v-if="ast?.body" :body="ast.body" :data="ast.data" />
405
+ </Suspense>
406
+ </template>
407
+ ```
408
+
265
409
  ## Contributing
266
410
 
267
411
  You can contribute to this module online with CodeSandbox:
package/dist/module.d.mts CHANGED
@@ -32,6 +32,12 @@ interface ModuleOptions {
32
32
  theme?: MdcThemeOptions;
33
33
  /**
34
34
  * Languages to be bundled loaded by Shiki
35
+ *
36
+ * All languages used has to be included in this list at build time, to create granular bundles.
37
+ *
38
+ * Unlike the `preload` option, when this option is provided, it will override the default languages.
39
+ *
40
+ * @default ['js','jsx','json','ts','tsx','vue','css','html','vue','bash','md','mdc','yaml']
35
41
  */
36
42
  langs?: (BundledLanguage | LanguageRegistration)[];
37
43
  /**
@@ -56,12 +62,14 @@ interface ModuleOptions {
56
62
  [heading in 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6']?: boolean;
57
63
  };
58
64
  };
65
+ keepComments?: boolean;
59
66
  components?: {
60
67
  prose?: boolean;
61
68
  map?: Record<string, string>;
62
69
  };
63
70
  }
64
71
 
72
+ declare const DefaultHighlightLangs: BundledLanguage[];
65
73
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
66
74
 
67
75
  declare module '@nuxt/schema' {
@@ -92,4 +100,4 @@ declare module '@nuxt/schema' {
92
100
  }
93
101
  }
94
102
 
95
- export { _default as default };
103
+ export { DefaultHighlightLangs, _default as default };
package/dist/module.d.ts CHANGED
@@ -32,6 +32,12 @@ interface ModuleOptions {
32
32
  theme?: MdcThemeOptions;
33
33
  /**
34
34
  * Languages to be bundled loaded by Shiki
35
+ *
36
+ * All languages used has to be included in this list at build time, to create granular bundles.
37
+ *
38
+ * Unlike the `preload` option, when this option is provided, it will override the default languages.
39
+ *
40
+ * @default ['js','jsx','json','ts','tsx','vue','css','html','vue','bash','md','mdc','yaml']
35
41
  */
36
42
  langs?: (BundledLanguage | LanguageRegistration)[];
37
43
  /**
@@ -56,12 +62,14 @@ interface ModuleOptions {
56
62
  [heading in 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6']?: boolean;
57
63
  };
58
64
  };
65
+ keepComments?: boolean;
59
66
  components?: {
60
67
  prose?: boolean;
61
68
  map?: Record<string, string>;
62
69
  };
63
70
  }
64
71
 
72
+ declare const DefaultHighlightLangs: BundledLanguage[];
65
73
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
66
74
 
67
75
  declare module '@nuxt/schema' {
@@ -92,4 +100,4 @@ declare module '@nuxt/schema' {
92
100
  }
93
101
  }
94
102
 
95
- export { _default as default };
103
+ export { DefaultHighlightLangs, _default as default };
package/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@nuxtjs/mdc",
3
3
  "configKey": "mdc",
4
- "version": "0.4.0"
4
+ "version": "0.6.0"
5
5
  }
package/dist/module.mjs CHANGED
@@ -55,7 +55,8 @@ async function mdcConfigs({ options }) {
55
55
  async function mdcHighlighter({
56
56
  options: {
57
57
  shikiPath,
58
- options
58
+ options,
59
+ useWasmAssets
59
60
  }
60
61
  }) {
61
62
  if (!options || !options.highlighter)
@@ -67,7 +68,14 @@ async function mdcHighlighter({
67
68
  ].find((file2) => existsSync(file2));
68
69
  if (!file)
69
70
  throw new Error(`[@nuxtjs/mdc] Could not find shiki highlighter: ${shikiPath}`);
70
- const code = await fs.readFile(file, "utf-8");
71
+ let code = await fs.readFile(file, "utf-8");
72
+ if (useWasmAssets) {
73
+ code = code.replace(
74
+ /import\((['"])shiki\/wasm\1\)/,
75
+ // We can remove the .client condition once Vite supports WASM ESM import
76
+ "import.meta.client ? import('shiki/wasm') : import('shiki/onig.wasm')"
77
+ );
78
+ }
71
79
  const { bundledLanguagesInfo } = await import('shiki/langs');
72
80
  const langsMap = /* @__PURE__ */ new Map();
73
81
  options.langs?.forEach((lang) => {
@@ -149,16 +157,32 @@ function processUnistPlugins(plugins) {
149
157
  const imports = [];
150
158
  const definitions = [];
151
159
  Object.entries(plugins).forEach(([name, plugin]) => {
152
- imports.push(`import ${pascalCase(name)} from '${plugin.src || name}'`);
160
+ const instanceName = `_${pascalCase(name).replace(/\W/g, "")}`;
161
+ imports.push(`import ${instanceName} from '${plugin.src || name}'`);
153
162
  if (Object.keys(plugin).length) {
154
- definitions.push(` '${name}': { instance: ${pascalCase(name)}, options: ${JSON.stringify(plugin.options || plugin)} },`);
163
+ definitions.push(` '${name}': { instance: ${instanceName}, options: ${JSON.stringify(plugin.options || plugin)} },`);
155
164
  } else {
156
- definitions.push(` '${name}': { instance: ${pascalCase(name)} },`);
165
+ definitions.push(` '${name}': { instance: ${instanceName} },`);
157
166
  }
158
167
  });
159
168
  return { imports, definitions };
160
169
  }
161
170
 
171
+ const DefaultHighlightLangs = [
172
+ "js",
173
+ "jsx",
174
+ "json",
175
+ "ts",
176
+ "tsx",
177
+ "vue",
178
+ "css",
179
+ "html",
180
+ "vue",
181
+ "bash",
182
+ "md",
183
+ "mdc",
184
+ "yaml"
185
+ ];
162
186
  const module = defineNuxtModule({
163
187
  meta: {
164
188
  name: "@nuxtjs/mdc",
@@ -179,6 +203,7 @@ const module = defineNuxtModule({
179
203
  h6: false
180
204
  }
181
205
  },
206
+ keepComments: false,
182
207
  components: {
183
208
  prose: true,
184
209
  map: {}
@@ -223,7 +248,7 @@ const module = defineNuxtModule({
223
248
  });
224
249
  options.rehypePlugins ||= {};
225
250
  options.rehypePlugins.highlight ||= {};
226
- options.rehypePlugins.highlight.src ||= await resolver.resolvePath("./runtime/highlighter/rehype");
251
+ options.rehypePlugins.highlight.src ||= await resolver.resolvePath("./runtime/highlighter/rehype-nuxt");
227
252
  options.rehypePlugins.highlight.options ||= {};
228
253
  }
229
254
  const registerTemplate = (options2) => {
@@ -266,7 +291,9 @@ const module = defineNuxtModule({
266
291
  getContents: mdcHighlighter,
267
292
  options: {
268
293
  shikiPath: resolver.resolve("../dist/runtime/highlighter/shiki"),
269
- options: options.highlight
294
+ options: options.highlight,
295
+ // When WASM support enabled in Nitro, we could use the .wasm file directly for Cloudflare Workers
296
+ useWasmAssets: !nuxt.options.dev && !!nuxt.options.nitro.experimental?.wasm
270
297
  }
271
298
  });
272
299
  registerTemplate({
@@ -354,19 +381,11 @@ function resolveOptions(options) {
354
381
  default: "github-light",
355
382
  dark: "github-dark"
356
383
  };
357
- options.highlight.langs ||= [
358
- "js",
359
- "ts",
360
- "vue",
361
- "css",
362
- "html",
363
- "vue",
364
- "shell"
365
- ];
384
+ options.highlight.langs ||= DefaultHighlightLangs;
366
385
  if (options.highlight.preload) {
367
386
  options.highlight.langs.push(...options.highlight.preload || []);
368
387
  }
369
388
  }
370
389
  }
371
390
 
372
- export { module as default };
391
+ export { DefaultHighlightLangs, module as default };
@@ -4,6 +4,7 @@
4
4
  :body="data?.body"
5
5
  :toc="data?.toc"
6
6
  :excerpt="data?.excerpt"
7
+ :error="error"
7
8
  >
8
9
  <MDCRenderer
9
10
  v-if="body"
@@ -59,7 +60,7 @@ const props = defineProps({
59
60
 
60
61
  const key = computed(() => hash(props.value))
61
62
 
62
- const { data, refresh } = await useAsyncData(key.value, async () => {
63
+ const { data, refresh, error } = await useAsyncData(key.value, async () => {
63
64
  if (typeof props.value !== 'string') {
64
65
  return props.value
65
66
  }
@@ -1,9 +1,8 @@
1
1
  <script>
2
- import { h, resolveComponent, Text, defineComponent, toRaw, computed } from "vue";
2
+ import { h, resolveComponent, Text, Comment, defineComponent, toRaw, computed, getCurrentInstance } from "vue";
3
3
  import destr from "destr";
4
4
  import { kebabCase, pascalCase } from "scule";
5
5
  import { find, html } from "property-information";
6
- import { useRoute, useRuntimeConfig } from "#imports";
7
6
  import htmlTags from "../parser/utils/html-tags-list";
8
7
  const DEFAULT_SLOT = "default";
9
8
  const rxOn = /^@|^v-on:/;
@@ -51,10 +50,12 @@ export default defineComponent({
51
50
  }
52
51
  },
53
52
  async setup(props) {
54
- const { mdc } = useRuntimeConfig().public;
53
+ const $nuxt = getCurrentInstance()?.appContext?.app?.$nuxt;
54
+ const route = $nuxt?.$route;
55
+ const { mdc } = $nuxt?.$config?.public || {};
55
56
  const tags = {
56
- ...mdc.components.prose && props.prose !== false ? proseComponentMap : {},
57
- ...mdc.components.map,
57
+ ...mdc?.components?.prose && props.prose !== false ? proseComponentMap : {},
58
+ ...mdc?.components?.map || {},
58
59
  ...toRaw(props.data?.mdc?.components || {}),
59
60
  ...props.components
60
61
  };
@@ -63,14 +64,14 @@ export default defineComponent({
63
64
  return Array.from(new Set(components)).sort().join(".");
64
65
  });
65
66
  await resolveContentComponents(props.body, { tags });
66
- return { tags, contentKey };
67
+ return { tags, contentKey, route };
67
68
  },
68
69
  render(ctx) {
69
- const { tags, tag, body, data, contentKey } = ctx;
70
+ const { tags, tag, body, data, contentKey, route } = ctx;
70
71
  if (!body) {
71
72
  return null;
72
73
  }
73
- const meta = { ...data, tags };
74
+ const meta = { ...data, tags, $route: route };
74
75
  const component = tag !== false ? resolveVueComponent(tag || meta.component?.name || meta.component || "div") : void 0;
75
76
  const childrenRenderer = renderSlots(body, h, meta, meta);
76
77
  return component ? h(component, { ...meta.component?.props, ...this.$attrs, key: contentKey }, childrenRenderer) : childrenRenderer.default?.();
@@ -80,6 +81,9 @@ function renderNode(node, h2, documentMeta, parentScope = {}) {
80
81
  if (node.type === "text") {
81
82
  return h2(Text, node.value);
82
83
  }
84
+ if (node.type === "comment") {
85
+ return h2(Comment, null, node.value);
86
+ }
83
87
  const originalTag = node.tag;
84
88
  const renderTag = findMappedTag(node, documentMeta.tags);
85
89
  if (node.tag === "binding") {
@@ -99,7 +103,6 @@ function renderNode(node, h2, documentMeta, parentScope = {}) {
99
103
  function renderBinding(node, h2, documentMeta, parentScope = {}) {
100
104
  const data = {
101
105
  ...parentScope,
102
- $route: () => useRoute(),
103
106
  $document: documentMeta,
104
107
  $doc: documentMeta
105
108
  };
@@ -207,7 +210,7 @@ function propsToDataRxBind(key, value, data, documentMeta) {
207
210
  return data;
208
211
  }
209
212
  const resolveVueComponent = (component) => {
210
- if (!htmlTags.includes(component) && !component?.render) {
213
+ if (!htmlTags.includes(component) && !component?.render && !component?.ssrRender) {
211
214
  const componentFn = resolveComponent(pascalCase(component), false);
212
215
  if (typeof componentFn === "object") {
213
216
  return componentFn;
@@ -261,7 +264,7 @@ async function resolveContentComponents(body, meta) {
261
264
  }));
262
265
  function loadComponents(node, documentMeta) {
263
266
  const tag = node.tag;
264
- if (node.type === "text" || tag === "binding") {
267
+ if (node.type === "text" || tag === "binding" || node.type === "comment") {
265
268
  return [];
266
269
  }
267
270
  const renderTag = findMappedTag(node, documentMeta.tags);
@@ -39,6 +39,7 @@ declare const _default: DefineComponent<{
39
39
  }, {
40
40
  tags: any;
41
41
  contentKey: import("vue").ComputedRef<string>;
42
+ route: any;
42
43
  }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
43
44
  /**
44
45
  * Content to render
@@ -5,13 +5,11 @@
5
5
  </template>
6
6
 
7
7
  <script setup lang="ts">
8
- import { computed } from 'vue'
9
-
10
8
  defineProps({
11
9
  src: {
12
10
  type: String,
13
11
  default: ''
14
12
  }
15
13
  })
16
- const isDev = computed(() => process.dev)
14
+ const isDev = import.meta.env
17
15
  </script>
@@ -0,0 +1,3 @@
1
+ import { type RehypeHighlightOption } from './rehype';
2
+ export default rehypeHighlight;
3
+ export declare function rehypeHighlight(opts?: Partial<RehypeHighlightOption>): (tree: import("hast").Root) => Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { rehypeHighlight as rehypeHighlightUniversal } from "./rehype.mjs";
2
+ const defaults = {
3
+ theme: {},
4
+ async highlighter(code, lang, theme, options) {
5
+ try {
6
+ if (process.browser && window.sessionStorage.getItem("mdc-shiki-highlighter") === "browser") {
7
+ return import("#mdc-highlighter").then((h) => h.default(code, lang, theme, options)).catch(() => ({}));
8
+ }
9
+ return await $fetch("/api/_mdc/highlight", {
10
+ params: {
11
+ code,
12
+ lang,
13
+ theme: JSON.stringify(theme),
14
+ options: JSON.stringify(options)
15
+ }
16
+ });
17
+ } catch (e) {
18
+ if (process.browser && e?.response?.status === 404) {
19
+ window.sessionStorage.setItem("mdc-shiki-highlighter", "browser");
20
+ return this.highlighter?.(code, lang, theme, options);
21
+ }
22
+ }
23
+ return Promise.resolve({ tree: [{ type: "text", value: code }], className: "", style: "" });
24
+ }
25
+ };
26
+ export default rehypeHighlight;
27
+ export function rehypeHighlight(opts = {}) {
28
+ const options = { ...defaults, ...opts };
29
+ if (typeof options.highlighter !== "function") {
30
+ options.highlighter = defaults.highlighter;
31
+ }
32
+ return rehypeHighlightUniversal(options);
33
+ }
@@ -5,4 +5,4 @@ export interface RehypeHighlightOption {
5
5
  highlighter?: Highlighter;
6
6
  }
7
7
  export default rehypeHighlight;
8
- export declare function rehypeHighlight(opts?: RehypeHighlightOption): (tree: Root) => Promise<void>;
8
+ export declare function rehypeHighlight(opts: RehypeHighlightOption): (tree: Root) => Promise<void>;
@@ -1,32 +1,8 @@
1
1
  import { visit } from "unist-util-visit";
2
2
  import { toString } from "hast-util-to-string";
3
- const defaults = {
4
- theme: {},
5
- async highlighter(code, lang, theme, options) {
6
- if (process.browser && window.sessionStorage.getItem("mdc-shiki-highlighter") === "browser") {
7
- return import("#mdc-highlighter").then((h) => h.default(code, lang, theme, options));
8
- }
9
- try {
10
- return await $fetch("/api/_mdc/highlight", {
11
- params: {
12
- code,
13
- lang,
14
- theme: JSON.stringify(theme),
15
- options: JSON.stringify(options)
16
- }
17
- });
18
- } catch (e) {
19
- if (process.browser && e?.response?.status === 404) {
20
- window.sessionStorage.setItem("mdc-shiki-highlighter", "browser");
21
- return this.highlighter?.(code, lang, theme, options);
22
- }
23
- }
24
- return Promise.resolve({ tree: [{ type: "text", value: code }], className: "", style: "" });
25
- }
26
- };
27
3
  export default rehypeHighlight;
28
- export function rehypeHighlight(opts = {}) {
29
- const options = { ...defaults, ...opts };
4
+ export function rehypeHighlight(opts) {
5
+ const options = opts;
30
6
  return async (tree) => {
31
7
  const tasks = [];
32
8
  const styles = [];
@@ -53,7 +53,7 @@ export function createShikiHighlighter({
53
53
  if (bundledLangs[lang]) {
54
54
  await shiki2.loadLanguage(bundledLangs[lang]);
55
55
  } else {
56
- if (process.dev) {
56
+ if (import.meta.env) {
57
57
  console.warn(`[@nuxtjs/mdc] Language "${lang}" is not loaded to the Shiki highlighter, fallback to plain text. Add the language to "mdc.highlight.langs" to fix this.`);
58
58
  }
59
59
  lang = "text";
@@ -64,7 +64,7 @@ export function createShikiHighlighter({
64
64
  if (bundledThemes[theme2]) {
65
65
  await shiki2.loadTheme(bundledThemes[theme2]);
66
66
  } else {
67
- if (process.dev) {
67
+ if (import.meta.env) {
68
68
  console.warn(`[@nuxtjs/mdc] Theme "${theme2}" is not loaded to the Shiki highlighter. Add the theme to "mdc.highlight.themes" to fix this.`);
69
69
  }
70
70
  themesObject[color] = "none";
@@ -1,2 +1,4 @@
1
- export { parseMarkdown } from './parser';
1
+ export { parseMarkdown, createMarkdownParser } from './parser';
2
+ export { rehypeHighlight } from './highlighter/rehype';
3
+ export { createShikiHighlighter } from './highlighter/shiki';
2
4
  export * from './utils/node';
@@ -1,2 +1,4 @@
1
- export { parseMarkdown } from "./parser/index.mjs";
1
+ export { parseMarkdown, createMarkdownParser } from "./parser/index.mjs";
2
+ export { rehypeHighlight } from "./highlighter/rehype.mjs";
3
+ export { createShikiHighlighter } from "./highlighter/shiki.mjs";
2
4
  export * from "./utils/node.mjs";
@@ -1 +1,2 @@
1
- export declare function compileHast(this: any): void;
1
+ import type { MDCParseOptions } from '../types';
2
+ export declare function compileHast(this: any, options?: MDCParseOptions): void;
@@ -1,7 +1,7 @@
1
1
  import { toString } from "hast-util-to-string";
2
2
  import Slugger from "github-slugger";
3
3
  import { validateProps } from "./utils/props.mjs";
4
- export function compileHast() {
4
+ export function compileHast(options = {}) {
5
5
  const slugs = new Slugger();
6
6
  function compileToJSON(node, parent) {
7
7
  if (node.type === "root") {
@@ -55,6 +55,12 @@ export function compileHast() {
55
55
  };
56
56
  }
57
57
  }
58
+ if (options.keepComments && node.type === "comment") {
59
+ return {
60
+ type: "comment",
61
+ value: node.value
62
+ };
63
+ }
58
64
  return null;
59
65
  }
60
66
  this.Compiler = (tree) => {
@@ -1,10 +1,6 @@
1
- import type { MDCData, MDCParseOptions, MDCRoot, Toc } from '../types';
2
- export declare const parseMarkdown: (md: string, inlineOptions?: MDCParseOptions) => Promise<{
3
- data: MDCData;
4
- body: MDCRoot;
5
- excerpt: MDCRoot | undefined;
6
- toc: Toc | undefined;
7
- }>;
1
+ import type { MDCParseOptions, MDCParserResult, MDCRoot } from '../types';
2
+ export declare const createMarkdownParser: (inlineOptions?: MDCParseOptions) => Promise<(md: string) => Promise<MDCParserResult>>;
3
+ export declare const parseMarkdown: (md: string, inlineOptions?: MDCParseOptions) => Promise<MDCParserResult>;
8
4
  export declare function contentHeading(body: MDCRoot): {
9
5
  title: string;
10
6
  description: string;
@@ -10,7 +10,7 @@ import { generateToc } from "./toc.mjs";
10
10
  import { nodeTextContent } from "../utils/node.mjs";
11
11
  let moduleOptions;
12
12
  let generatedMdcConfigs;
13
- export const parseMarkdown = async (md, inlineOptions = {}) => {
13
+ export const createMarkdownParser = async (inlineOptions = {}) => {
14
14
  if (!moduleOptions) {
15
15
  moduleOptions = await import(
16
16
  "#mdc-imports"
@@ -27,7 +27,7 @@ export const parseMarkdown = async (md, inlineOptions = {}) => {
27
27
  ...generatedMdcConfigs || [],
28
28
  ...inlineOptions.configs || []
29
29
  ];
30
- if (inlineOptions.highlight != null && inlineOptions.highlight != false && typeof inlineOptions.highlight.highlighter !== "function") {
30
+ if (inlineOptions.highlight != null && inlineOptions.highlight != false && inlineOptions.highlight.highlighter !== void 0 && typeof inlineOptions.highlight.highlighter !== "function") {
31
31
  if (import.meta.dev)
32
32
  console.warn("[@nuxtjs/mdc] `highlighter` passed to `parseMarkdown` is should be a function, but got " + JSON.stringify(inlineOptions.highlight.highlighter) + ", ignored.");
33
33
  inlineOptions = {
@@ -44,7 +44,10 @@ export const parseMarkdown = async (md, inlineOptions = {}) => {
44
44
  highlight: moduleOptions?.highlight
45
45
  }, defaults);
46
46
  if (options.rehype?.plugins?.highlight) {
47
- options.rehype.plugins.highlight.options = options.highlight || {};
47
+ options.rehype.plugins.highlight.options = {
48
+ ...options.rehype.plugins.highlight.options || {},
49
+ ...options.highlight || {}
50
+ };
48
51
  }
49
52
  let processor = unified();
50
53
  for (const config of mdcConfigs) {
@@ -60,34 +63,40 @@ export const parseMarkdown = async (md, inlineOptions = {}) => {
60
63
  processor = await config.unified?.rehype?.(processor) || processor;
61
64
  }
62
65
  await useProcessorPlugins(processor, options.rehype?.plugins);
63
- processor.use(compileHast);
66
+ processor.use(compileHast, options);
64
67
  for (const config of mdcConfigs) {
65
68
  processor = await config.unified?.post?.(processor) || processor;
66
69
  }
67
- const { content, data: frontmatter } = await parseFrontMatter(md);
68
- const processedFile = await processor.process({ value: content, data: frontmatter });
69
- const result = processedFile.result;
70
- const data = Object.assign(
71
- contentHeading(result.body),
72
- frontmatter,
73
- processedFile?.data || {}
74
- );
75
- let toc;
76
- if (data.toc !== false) {
77
- const tocOption = defu(data.toc || {}, options.toc);
78
- toc = generateToc(result.body, tocOption);
79
- }
80
- return {
81
- data,
82
- body: result.body,
83
- excerpt: result.excerpt,
84
- toc
70
+ return async (md) => {
71
+ const { content, data: frontmatter } = await parseFrontMatter(md);
72
+ const processedFile = await processor.process({ value: content, data: frontmatter });
73
+ const result = processedFile.result;
74
+ const data = Object.assign(
75
+ contentHeading(result.body),
76
+ frontmatter,
77
+ processedFile?.data || {}
78
+ );
79
+ let toc;
80
+ if (data.toc !== false) {
81
+ const tocOption = defu(data.toc || {}, options.toc);
82
+ toc = generateToc(result.body, tocOption);
83
+ }
84
+ return {
85
+ data,
86
+ body: result.body,
87
+ excerpt: result.excerpt,
88
+ toc
89
+ };
85
90
  };
86
91
  };
92
+ export const parseMarkdown = async (md, inlineOptions = {}) => {
93
+ const parser = await createMarkdownParser(inlineOptions);
94
+ return parser(md);
95
+ };
87
96
  export function contentHeading(body) {
88
97
  let title = "";
89
98
  let description = "";
90
- const children = body.children.filter((node) => node.type !== "text" && node.tag !== "hr");
99
+ const children = body.children.filter((node) => node.type === "element" && node.tag !== "hr");
91
100
  if (children.length && children[0].tag === "h1") {
92
101
  const node = children.shift();
93
102
  title = nodeTextContent(node);
@@ -1,6 +1,8 @@
1
1
  import type { Options as RehypeOption } from 'remark-rehype';
2
2
  import type { RehypeHighlightOption } from '../highlighter/rehype';
3
3
  import type { MdcConfig } from './config';
4
+ import type { MDCData, MDCRoot } from './tree';
5
+ import type { Toc } from './toc';
4
6
  export interface RemarkPlugin {
5
7
  instance?: any;
6
8
  options?: Array<any> | Record<string, any>;
@@ -25,8 +27,15 @@ export interface MDCParseOptions {
25
27
  depth?: number;
26
28
  searchDepth?: number;
27
29
  };
30
+ keepComments?: boolean;
28
31
  /**
29
32
  * Inline mdc.config.ts
30
33
  */
31
34
  configs?: MdcConfig[];
32
35
  }
36
+ export interface MDCParserResult {
37
+ data: MDCData;
38
+ body: MDCRoot;
39
+ excerpt: MDCRoot | undefined;
40
+ toc: Toc | undefined;
41
+ }
@@ -2,13 +2,17 @@ export type MDCText = {
2
2
  type: 'text';
3
3
  value: string;
4
4
  };
5
+ export type MDCComment = {
6
+ type: 'comment';
7
+ value: string;
8
+ };
5
9
  export type MDCElement = {
6
10
  type: 'element';
7
11
  tag: string;
8
12
  props: Record<string, any> | undefined;
9
- children: Array<MDCElement | MDCText>;
13
+ children: Array<MDCElement | MDCText | MDCComment>;
10
14
  };
11
- export type MDCNode = MDCElement | MDCText;
15
+ export type MDCNode = MDCElement | MDCText | MDCComment;
12
16
  export type MDCRoot = {
13
17
  type: 'root';
14
18
  children: Array<MDCNode>;
@@ -1,4 +1,7 @@
1
1
  export function flattenNodeText(node) {
2
+ if (node.type === "comment") {
3
+ return "";
4
+ }
2
5
  if (node.type === "text") {
3
6
  return node.value || "";
4
7
  } else {
package/dist/types.d.mts CHANGED
@@ -5,4 +5,4 @@ import type { } from './module.js'
5
5
 
6
6
 
7
7
 
8
- export type { default } from './module.js'
8
+ export type { DefaultHighlightLangs, default } from './module.js'
package/dist/types.d.ts CHANGED
@@ -5,4 +5,4 @@ import type { } from './module'
5
5
 
6
6
 
7
7
 
8
- export type { default } from './module'
8
+ export type { DefaultHighlightLangs, default } from './module'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxtjs/mdc",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Nuxt MDC module",
5
5
  "repository": "nuxt-modules/mdc",
6
6
  "license": "MIT",
@@ -16,10 +16,22 @@
16
16
  "import": "./dist/config.mjs",
17
17
  "require": "./dist/config.cjs"
18
18
  },
19
- "./runtime": "./dist/runtime/index.mjs",
20
- "./dist/runtime": "./dist/runtime/index.mjs",
21
- "./runtime/*": "./dist/runtime/*.mjs",
22
- "./dist/runtime/*": "./dist/runtime/*.mjs"
19
+ "./runtime": {
20
+ "types": "./dist/runtime/index.d.ts",
21
+ "import": "./dist/runtime/index.mjs"
22
+ },
23
+ "./dist/runtime": {
24
+ "types": "./dist/runtime/index.d.ts",
25
+ "import": "./dist/runtime/index.mjs"
26
+ },
27
+ "./runtime/*": {
28
+ "types": "./dist/runtime/*.d.ts",
29
+ "import": "./dist/runtime/*.mjs"
30
+ },
31
+ "./dist/runtime/*": {
32
+ "types": "./dist/runtime/*.d.ts",
33
+ "import": "./dist/runtime/*.mjs"
34
+ }
23
35
  },
24
36
  "main": "./dist/module.cjs",
25
37
  "types": "./dist/types.d.ts",
@@ -50,15 +62,15 @@
50
62
  "test:watch": "vitest watch"
51
63
  },
52
64
  "dependencies": {
53
- "@nuxt/kit": "^3.10.0",
54
- "@shikijs/transformers": "^1.0.0-beta.3",
65
+ "@nuxt/kit": "^3.10.3",
66
+ "@shikijs/transformers": "^1.1.7",
55
67
  "@types/hast": "^3.0.4",
56
68
  "@types/mdast": "^4.0.3",
57
- "@vue/compiler-core": "^3.4.15",
69
+ "@vue/compiler-core": "^3.4.21",
58
70
  "consola": "^3.2.3",
59
71
  "debug": "^4.3.4",
60
72
  "defu": "^6.1.4",
61
- "destr": "^2.0.2",
73
+ "destr": "^2.0.3",
62
74
  "detab": "^3.0.2",
63
75
  "github-slugger": "^2.0.0",
64
76
  "hast-util-to-string": "^3.0.0",
@@ -75,12 +87,12 @@
75
87
  "rehype-sort-attributes": "^5.0.0",
76
88
  "remark-emoji": "^4.0.1",
77
89
  "remark-gfm": "^4.0.0",
78
- "remark-mdc": "^3.0.2",
90
+ "remark-mdc": "^3.1.0",
79
91
  "remark-parse": "^11.0.0",
80
92
  "remark-rehype": "^11.1.0",
81
- "scule": "^1.2.0",
82
- "shiki": "^1.0.0-beta.3",
83
- "ufo": "^1.3.2",
93
+ "scule": "^1.3.0",
94
+ "shiki": "^1.1.7",
95
+ "ufo": "^1.4.0",
84
96
  "unified": "^11.0.4",
85
97
  "unist-builder": "^4.0.0",
86
98
  "unist-util-visit": "^5.0.0",
@@ -90,19 +102,19 @@
90
102
  "@nuxt/devtools": "latest",
91
103
  "@nuxt/eslint-config": "^0.2.0",
92
104
  "@nuxt/module-builder": "^0.5.5",
93
- "@nuxt/schema": "^3.10.0",
105
+ "@nuxt/schema": "^3.10.3",
94
106
  "@nuxt/test-utils": "^3.11.0",
95
- "@nuxt/ui": "^2.13.0",
107
+ "@nuxt/ui": "^2.14.1",
96
108
  "@types/mdurl": "^1.0.5",
97
- "@types/node": "^20.11.16",
109
+ "@types/node": "^20.11.24",
98
110
  "changelogen": "^0.5.5",
99
- "eslint": "^8.56.0",
100
- "nuxt": "^3.10.0",
111
+ "eslint": "^8.57.0",
112
+ "nuxt": "^3.10.3",
101
113
  "rehype": "^13.0.1",
102
- "release-it": "^17.0.3",
103
- "vitest": "^1.2.2"
114
+ "release-it": "^17.1.1",
115
+ "vitest": "^1.3.1"
104
116
  },
105
- "packageManager": "pnpm@8.15.1",
117
+ "packageManager": "pnpm@8.15.4",
106
118
  "release-it": {
107
119
  "git": {
108
120
  "commitMessage": "chore(release): release v${version}"