@comark/vue 0.3.1 β†’ 0.4.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 ADDED
@@ -0,0 +1,104 @@
1
+ <img src="https://github.com/comarkdown/comark/blob/main/assets/banner.jpg" width="100%" alt="Comark banner" />
2
+
3
+ # @comark/vue
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@comark/vue?color=black)](https://npmx.dev/@comark/vue)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@comark/vue?color=black)](https://npm.chart.dev/@comark/vue)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/comarkdown/comark/ci.yml?branch=main&color=black)](https://github.com/comarkdown/comark/actions/workflows/ci.yml)
8
+ [![Documentation](https://img.shields.io/badge/Documentation-black?logo=readme&logoColor=white)](https://comark.dev/rendering/vue)
9
+ [![license](https://img.shields.io/github/license/comarkdown/comark?color=black)](https://github.com/comarkdown/comark/blob/main/LICENSE)
10
+
11
+ Vue renderer for [Comark](https://comark.dev) β€” render markdown with custom Vue components, streaming support, and SSR.
12
+
13
+ ## Features
14
+
15
+ - 🧩 `<Comark>` component for one-shot markdown rendering
16
+ - 🎯 Map any Comark tag to a custom Vue component
17
+ - 🌊 Streaming-friendly with auto-close and caret support
18
+ - πŸ–₯️ SSR-safe, works in Nuxt and Vite
19
+ - 🎨 Optional Vite plugin for compile-time component resolution
20
+ - πŸ”Œ Plugin ecosystem (math, mermaid, highlight, binding…)
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @comark/vue
26
+ # or
27
+ pnpm add @comark/vue
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```vue
33
+ <script setup lang="ts">
34
+ import { Comark } from '@comark/vue'
35
+ import math, { Math } from '@comark/vue/plugins/math'
36
+
37
+ const content = `# Hello\n\nThis is **Comark** in Vue.`
38
+ </script>
39
+
40
+ <template>
41
+ <Comark :components="{ math: Math }" :plugins="[math()]">
42
+ {{ content }}
43
+ </Comark>
44
+ </template>
45
+ ```
46
+
47
+ ### Custom components
48
+
49
+ ```vue
50
+ <script setup lang="ts">
51
+ import { Comark } from '@comark/vue'
52
+ import Alert from './components/Alert.vue'
53
+ </script>
54
+
55
+ <template>
56
+ <Comark :components="{ alert: Alert }">{{ content }}</Comark>
57
+ </template>
58
+ ```
59
+
60
+ ```mdc
61
+ ::alert{type="warning"}
62
+ Heads up!
63
+ ::
64
+ ```
65
+
66
+ ### Streaming
67
+
68
+ ```vue
69
+ <Comark :streaming="isStreaming" caret>{{ content }}</Comark>
70
+ ```
71
+
72
+ ## Using with Vite
73
+
74
+ `@comark/vue` ships an optional Vite plugin that:
75
+
76
+ - Enables `<slot unwrap="...">` inside custom markdown components.
77
+ - Auto-registers every `.vue` file under `src/components/prose` (or `components/prose`) as a global component.
78
+
79
+ ```ts
80
+ // vite.config.ts
81
+ import { defineConfig } from 'vite'
82
+ import vue from '@vitejs/plugin-vue'
83
+ import comark from '@comark/vue/vite'
84
+
85
+ export default defineConfig({
86
+ plugins: [vue(), comark()],
87
+ })
88
+ ```
89
+
90
+ Pass `comark({ prose: false })` to opt out of the auto-registered prose components.
91
+
92
+ ## Using with Nuxt
93
+
94
+ For Nuxt, use [`@comark/nuxt`](https://comark.dev/rendering/nuxt) which auto-imports the `<Comark>` component and wires up `~/components/prose` overrides.
95
+
96
+ ## Documentation
97
+
98
+ Full guide and API reference at [comark.dev/rendering/vue](https://comark.dev/rendering/vue).
99
+
100
+ ## License
101
+
102
+ Made with ❀️
103
+
104
+ Published under [MIT License](https://github.com/comarkdown/comark/blob/main/LICENSE).
@@ -118,9 +118,8 @@ export const Comark = defineComponent({
118
118
  });
119
119
  const parsed = shallowRef(null);
120
120
  const parse = createSerializedParse({ ...props.options, plugins: props.plugins });
121
- watch(() => [markdown.value, props.streaming], () => parse(markdown.value, { streaming: props.streaming }).then(result => parsed.value = result));
122
- await parse(markdown.value, { streaming: props.streaming })
123
- .then(result => parsed.value = result);
121
+ watch(() => [markdown.value, props.streaming], () => parse(markdown.value, { streaming: props.streaming }).then((result) => (parsed.value = result)));
122
+ await parse(markdown.value, { streaming: props.streaming }).then((result) => (parsed.value = result));
124
123
  return () => {
125
124
  // Render using ComarkRenderer
126
125
  return h(ComarkRenderer, {
@@ -1,8 +1,14 @@
1
- import { computed, defineAsyncComponent, defineComponent, getCurrentInstance, h, inject, onErrorCaptured, ref, toRaw } from 'vue';
1
+ import { computed, defineAsyncComponent, defineComponent, getCurrentInstance, h, inject, onErrorCaptured, ref, toRaw, } from 'vue';
2
2
  import { findLastTextNodeAndAppendNode, getCaret } from "../utils/caret.js";
3
3
  import { pascalCase, resolveAttributes } from 'comark/utils';
4
4
  // Cache for dynamically resolved components
5
5
  const asyncComponentCache = new Map();
6
+ function isPromiseLike(value) {
7
+ return !!value && typeof value.then === 'function';
8
+ }
9
+ function unwrapComponent(mod) {
10
+ return mod && typeof mod === 'object' && 'default' in mod ? mod.default : mod;
11
+ }
6
12
  /**
7
13
  * Helper to get tag from a ComarkNode
8
14
  */
@@ -34,20 +40,23 @@ function resolveComponent(tag, components, componentsManifest) {
34
40
  const appComponents = getCurrentInstance()?.appContext?.components;
35
41
  const pascalTag = pascalCase(tag);
36
42
  const proseTag = `Prose${pascalTag}`;
37
- let resolvedComponent = components[proseTag]
38
- || components[tag]
39
- || components[pascalTag]
43
+ let resolvedComponent = components[proseTag] ||
44
+ components[tag] ||
45
+ components[pascalTag] ||
40
46
  // If the component is not found in the components map, try to find it in the app context
41
- || appComponents?.[proseTag]
42
- || appComponents?.[pascalTag];
47
+ appComponents?.[proseTag] ||
48
+ appComponents?.[pascalTag];
43
49
  // If not in components map and manifest is provided, try dynamic resolution
44
50
  if (!resolvedComponent && componentsManifest) {
45
51
  // Check cache first to avoid creating duplicate async components
46
52
  const cacheKey = tag;
47
53
  if (!asyncComponentCache.has(cacheKey)) {
48
- const promise = componentsManifest(tag);
49
- if (promise) {
50
- asyncComponentCache.set(cacheKey, defineAsyncComponent(() => promise));
54
+ const resolved = componentsManifest(tag);
55
+ if (isPromiseLike(resolved)) {
56
+ asyncComponentCache.set(cacheKey, defineAsyncComponent(() => resolved));
57
+ }
58
+ else if (resolved) {
59
+ asyncComponentCache.set(cacheKey, unwrapComponent(resolved));
51
60
  }
52
61
  }
53
62
  resolvedComponent = asyncComponentCache.get(cacheKey);
@@ -1,4 +1,3 @@
1
- import 'katex/dist/katex.min.css';
2
1
  export declare const Math: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
2
  content: {
4
3
  type: StringConstructor;
@@ -1,6 +1,5 @@
1
1
  import { defineComponent, h, computed, watch, ref } from 'vue';
2
2
  import katex from 'katex';
3
- import 'katex/dist/katex.min.css';
4
3
  export const Math = defineComponent({
5
4
  name: 'Math',
6
5
  props: {
@@ -86,15 +86,15 @@ export const Mermaid = defineComponent({
86
86
  });
87
87
  return () => {
88
88
  return h('div', {
89
- 'class': `mermaid ${props.class}`,
90
- 'style': {
89
+ class: `mermaid ${props.class}`,
90
+ style: {
91
91
  display: 'flex',
92
92
  justifyContent: 'center',
93
93
  width: props.width,
94
94
  height: props.height,
95
95
  },
96
96
  'data-error': error.value,
97
- 'innerHTML': svgContent.value,
97
+ innerHTML: svgContent.value,
98
98
  });
99
99
  };
100
100
  },
package/dist/index.js CHANGED
@@ -73,10 +73,7 @@ export function defineComarkComponent(config = {}) {
73
73
  ...parseOptions,
74
74
  ...props.options,
75
75
  }));
76
- const plugins = computed(() => [
77
- ...(config.plugins || []),
78
- ...(props.plugins || []),
79
- ]);
76
+ const plugins = computed(() => [...(config.plugins || []), ...(props.plugins || [])]);
80
77
  const components = computed(() => ({
81
78
  ...config.components,
82
79
  ...props.components,
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/syntax';
2
+ export { default } from 'comark/plugins/syntax';
@@ -0,0 +1,2 @@
1
+ export * from 'comark/plugins/syntax';
2
+ export { default } from 'comark/plugins/syntax';
@@ -38,12 +38,12 @@ export function nodeTextContent(node) {
38
38
  }
39
39
  export function unwrap(vnode, tags = []) {
40
40
  if (Array.isArray(vnode)) {
41
- return vnode.flatMap(node => unwrap(node, tags));
41
+ return vnode.flatMap((node) => unwrap(node, tags));
42
42
  }
43
43
  let result = vnode;
44
- if (tags.some(tag => tag === '*' || isTag(vnode, tag))) {
44
+ if (tags.some((tag) => tag === '*' || isTag(vnode, tag))) {
45
45
  result = nodeChildren(vnode) || vnode;
46
- if (!Array.isArray(result) && TEXT_TAGS.some(tag => isTag(vnode, tag))) {
46
+ if (!Array.isArray(result) && TEXT_TAGS.some((tag) => isTag(vnode, tag))) {
47
47
  result = [result];
48
48
  }
49
49
  }
@@ -55,18 +55,20 @@ function _flatUnwrap(vnodes, tags = []) {
55
55
  return vnodes;
56
56
  }
57
57
  return vnodes
58
- .flatMap(vnode => _flatUnwrap(unwrap(vnode, [tags[0]]), tags.slice(1)))
59
- .filter(vnode => !(isText(vnode) && nodeTextContent(vnode).trim() === ''));
58
+ .flatMap((vnode) => _flatUnwrap(unwrap(vnode, [tags[0]]), tags.slice(1)))
59
+ .filter((vnode) => !(isText(vnode) && nodeTextContent(vnode).trim() === ''));
60
60
  }
61
61
  export function flatUnwrap(vnodes, tags = []) {
62
62
  if (typeof tags === 'string') {
63
- tags = tags.split(/[,\s]/).map(tag => tag.trim()).filter(Boolean);
63
+ tags = tags
64
+ .split(/[,\s]/)
65
+ .map((tag) => tag.trim())
66
+ .filter(Boolean);
64
67
  }
65
68
  if (!tags.length) {
66
69
  return vnodes;
67
70
  }
68
- return _flatUnwrap(vnodes, tags)
69
- .reduce((acc, item) => {
71
+ return _flatUnwrap(vnodes, tags).reduce((acc, item) => {
70
72
  if (isText(item)) {
71
73
  if (typeof acc[acc.length - 1] === 'string') {
72
74
  acc[acc.length - 1] += item.children;
package/dist/vite.js CHANGED
@@ -3,13 +3,18 @@ import { readdir } from 'node:fs/promises';
3
3
  import { join, basename } from 'node:path';
4
4
  import { existsSync } from 'node:fs';
5
5
  const runtimeDir = fileURLToPath(new URL('./utils', import.meta.url));
6
+ function toImportPath(filePath) {
7
+ return filePath.replace(/\\/g, '/');
8
+ }
6
9
  function viteComarkSlot(node, context) {
7
- const isVueSlotWithUnwrap = node.tag === 'slot'
8
- && node.props.find(p => p.name === 'unwrap' || p.name === 'mdc-unwrap' || (p.name === 'bind' && p.rawName === ':comark-unwrap'));
10
+ const isVueSlotWithUnwrap = node.tag === 'slot' &&
11
+ node.props.find((p) => p.name === 'unwrap' ||
12
+ p.name === 'mdc-unwrap' ||
13
+ (p.name === 'bind' && p.rawName === ':comark-unwrap'));
9
14
  if (isVueSlotWithUnwrap) {
10
15
  const transform = context.ssr
11
- ? context.nodeTransforms.find(nt => nt.name === 'ssrTransformSlotOutlet')
12
- : context.nodeTransforms.find(nt => nt.name === 'transformSlotOutlet');
16
+ ? context.nodeTransforms.find((nt) => nt.name === 'ssrTransformSlotOutlet')
17
+ : context.nodeTransforms.find((nt) => nt.name === 'transformSlotOutlet');
13
18
  return () => {
14
19
  node.tag = 'slot';
15
20
  node.type = 1;
@@ -20,16 +25,16 @@ function viteComarkSlot(node, context) {
20
25
  const importExp = context.ssr
21
26
  ? '{ ssrRenderSlot as _ssrRenderComarkSlot }'
22
27
  : '{ renderSlot as _renderComarkSlot }';
23
- if (!context.imports.some(i => String(i.exp) === importExp)) {
28
+ if (!context.imports.some((i) => String(i.exp) === importExp)) {
24
29
  context.imports.push({
25
30
  exp: importExp,
26
- path: `${runtimeDir}/${context.ssr ? 'ssrSlot' : 'slot'}`,
31
+ path: `${toImportPath(runtimeDir)}/${context.ssr ? 'ssrSlot' : 'slot'}`,
27
32
  });
28
33
  }
29
34
  };
30
35
  }
31
36
  if (context.nodeTransforms[0]?.name !== 'viteComarkSlot') {
32
- const index = context.nodeTransforms.findIndex(f => f.name === 'viteComarkSlot');
37
+ const index = context.nodeTransforms.findIndex((f) => f.name === 'viteComarkSlot');
33
38
  if (index !== -1) {
34
39
  const nt = context.nodeTransforms.splice(index, 1);
35
40
  context.nodeTransforms.unshift(nt[0]);
@@ -64,7 +69,7 @@ function generateComponentsModule(files) {
64
69
  }
65
70
  const lines = [];
66
71
  for (let i = 0; i < files.length; i++) {
67
- lines.push(`import __comp_${i} from ${JSON.stringify(files[i].replace(/\\/g, '/'))}`);
72
+ lines.push(`import __comp_${i} from ${JSON.stringify(toImportPath(files[i]))}`);
68
73
  }
69
74
  lines.push('');
70
75
  lines.push('export default {');
@@ -122,7 +127,7 @@ export default function comark(opts = {}) {
122
127
  if (existsSync(join(config.root, 'components', 'prose'))) {
123
128
  proseDir = join(config.root, 'components', 'prose');
124
129
  }
125
- const vuePlugin = config.plugins.find(p => p.name === 'vite:vue');
130
+ const vuePlugin = config.plugins.find((p) => p.name === 'vite:vue');
126
131
  if (!vuePlugin) {
127
132
  console.warn('[comark-vue] @vitejs/plugin-vue not found. Make sure vue() is in your plugins.');
128
133
  return;
@@ -156,8 +161,8 @@ export default function comark(opts = {}) {
156
161
  const files = await resolveProseFiles();
157
162
  if (!files.length)
158
163
  return null;
159
- const injected = `import __comarkProse from ${JSON.stringify(VIRTUAL_COMPONENTS_ID)};\n`
160
- + code.replace(/\.mount\s*\(/, '.use(__comarkProse).mount(');
164
+ const injected = `import __comarkProse from ${JSON.stringify(VIRTUAL_COMPONENTS_ID)};\n` +
165
+ code.replace(/\.mount\s*\(/, '.use(__comarkProse).mount(');
161
166
  return { code: injected, map: null };
162
167
  },
163
168
  configureServer(server) {
package/package.json CHANGED
@@ -1,16 +1,31 @@
1
1
  {
2
2
  "name": "@comark/vue",
3
- "type": "module",
4
- "version": "0.3.1",
5
- "description": "Vue components for Comark",
3
+ "version": "0.4.0",
4
+ "description": "Vue renderer for Comark (Components in Markdown)",
5
+ "keywords": [
6
+ "comark",
7
+ "markdown",
8
+ "mdc",
9
+ "renderer",
10
+ "streaming",
11
+ "vue"
12
+ ],
13
+ "homepage": "https://comark.dev/rendering/vue",
14
+ "bugs": {
15
+ "url": "https://github.com/comarkdown/comark/issues"
16
+ },
6
17
  "repository": {
7
18
  "type": "git",
8
19
  "url": "git+https://github.com/comarkdown/comark.git"
9
20
  },
10
- "bugs": {
11
- "url": "https://github.com/comarkdown/comark/issues"
12
- },
13
- "homepage": "https://comark.dev/rendering/vue",
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
14
29
  "exports": {
15
30
  ".": "./dist/index.js",
16
31
  "./vite": "./dist/vite.js",
@@ -20,15 +35,19 @@
20
35
  "./render": "./dist/render.js",
21
36
  "./components/*": "./dist/components/*.js"
22
37
  },
23
- "main": "./dist/index.js",
24
- "module": "./dist/index.js",
25
- "types": "./dist/index.d.ts",
26
- "files": [
27
- "dist"
28
- ],
29
38
  "publishConfig": {
30
39
  "access": "public"
31
40
  },
41
+ "dependencies": {
42
+ "comark": "^0.4.0"
43
+ },
44
+ "devDependencies": {
45
+ "@vue/compiler-core": "^3.5.32",
46
+ "@vue/server-renderer": "^3.5.32",
47
+ "vite": "^8.0.8",
48
+ "vitest": "^4.1.4",
49
+ "vue": "^3.5.32"
50
+ },
32
51
  "peerDependencies": {
33
52
  "beautiful-mermaid": "^1.1.3",
34
53
  "katex": "^0.16.45",
@@ -46,16 +65,6 @@
46
65
  "optional": true
47
66
  }
48
67
  },
49
- "dependencies": {
50
- "comark": "^0.3.1"
51
- },
52
- "devDependencies": {
53
- "@vue/compiler-core": "^3.5.32",
54
- "@vue/server-renderer": "^3.5.32",
55
- "vite": "^8.0.8",
56
- "vitest": "^4.1.4",
57
- "vue": "^3.5.32"
58
- },
59
68
  "scripts": {
60
69
  "stub": "node ../../scripts/stub.mjs",
61
70
  "build": "tsc",