diff --git a/docs/0.index.md b/docs/0.index.md index 3b04ea36..6a8b969a 100644 --- a/docs/0.index.md +++ b/docs/0.index.md @@ -255,6 +255,72 @@ export default defineNuxtConfig({ Checkout [`ModuleOptions` types↗︎](https://github.com/nuxt-content/mdc/blob/main/src/module.ts). +## Using Custom Elements +When using custom elements in Vue, you can configure Vue to recognize them by adding the `compilerOptions.isCustomElement` function in your `nuxt.config.ts` file. + +For example, if you include +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + vue: { + compilerOptions: { + isCustomElement: (tag) => tag.startsWith('mjx') + } + } +}) +``` +in your `nuxt.config.ts`, Vue will allow tags starting with `mjx` like ``. +However, this function is unavailable to MDC at runtime, thus adding an MDC component to your markdown will yield a warning suggesting to add the element to `compilerOptions.isCustomElement`. + +In order to avoid this warning, you must export a default function inside `utils/compilerOptions/isCustomElement.ts` which will be used when ignoring custom elements in the MDC markdown. +```ts [~/utils/compilerOptions/isCustomElement.ts] + +export default function isCustomElement(tag: string) { + return tag.startsWith('mjx') +} +``` + +Additionally, this function can be imported and used in your `nuxt.config.ts` to ensure consistency between Vue and MDC. + +```ts [nuxt.config.ts] + +// Import from ./utils/compilerOptions/isCustomElement.ts if using Nuxt 3 or below +import isCustomElement from './app/utils/compilerOptions/isCustomElement' + +export default defineNuxtConfig({ + vue: { + compilerOptions: { + isCustomElement: (tag) => tag.startsWith('mjx') + } + } +}) +``` +Now you can use custom elements as below. + +```html [custom-element.vue] + + + + + +``` + ## Contributing You can contribute to this module online with CodeSandbox: diff --git a/src/module.ts b/src/module.ts index 892ce4d2..ae8349a9 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,5 +1,5 @@ import fs from 'node:fs' -import { defineNuxtModule, extendViteConfig, addComponent, addComponentsDir, createResolver, addServerHandler, addTemplate, addImports, addServerImports } from '@nuxt/kit' +import { defineNuxtModule, extendViteConfig, addComponent, addComponentsDir, createResolver, addServerHandler, addTemplate, addImports, addServerImports, type Resolver } from '@nuxt/kit' import { defu } from 'defu' import { resolve } from 'pathe' import type { BundledLanguage } from 'shiki' @@ -189,6 +189,17 @@ export default defineNuxtModule({ }, }) + // Add isCustomElement util from app utils if defined by user, otherwise use default + const appResolver = createResolver(nuxt.options.srcDir) + const cusElemPath = "./utils/compilerOptions/isCustomElement" + addImports({ + from: !(await fileExists(appResolver, cusElemPath)) + ? resolver.resolve("./runtime", cusElemPath) + : appResolver.resolve(cusElemPath), + name: 'default', + as: 'isCustomElement' + }) + // Update Vite optimizeDeps extendViteConfig((config) => { const include = [ @@ -290,3 +301,9 @@ declare module '@nuxt/schema' { } } } + +// resolver.resolvePath returns file path with extension (e.g. filepath/my-file.ts) if file exists +// Returns same path as resolver.resolve if file doesn't exist +async function fileExists(resolver: Resolver, path: string) { + return resolver.resolve(path) !== await resolver.resolvePath(path) +} diff --git a/src/runtime/components/MDCRenderer.vue b/src/runtime/components/MDCRenderer.vue index e95049f3..0c027ebd 100644 --- a/src/runtime/components/MDCRenderer.vue +++ b/src/runtime/components/MDCRenderer.vue @@ -8,6 +8,7 @@ import type { MDCElement, MDCNode, MDCRoot, MDCData, MDCRenderOptions } from '@n import htmlTags from '../parser/utils/html-tags-list' import { flatUnwrap, nodeTextContent } from '../utils/node' import { pick } from '../utils' +import { isCustomElement } from '#imports'; type CreateElement = typeof h @@ -99,7 +100,7 @@ export default defineComponent({ const contentKey = computed(() => { const components = (props.body?.children || []) .map(n => (n as any).tag || n.type) - .filter(t => !htmlTags.includes(t)) + .filter(t => !ignoreTag(t)) return Array.from(new Set(components)).sort().join('.') }) @@ -400,7 +401,7 @@ function propsToDataRxBind(key: string, value: any, data: any, documentMeta: MDC */ const resolveComponentInstance = (component: any) => { if (typeof component === 'string') { - if (htmlTags.includes(component)) { + if (ignoreTag(component)) { return component } @@ -514,7 +515,7 @@ async function resolveContentComponents(body: MDCRoot, meta: Record const components: string[] = [] - if (node.type !== 'root' && !htmlTags.includes(renderTag as any)) { + if (node.type !== 'root' && !ignoreTag(renderTag as any)) { components.push(renderTag) } for (const child of (node.children || [])) { @@ -533,4 +534,12 @@ function findMappedTag(node: MDCElement, tags: Record) { return tags[tag] || tags[pascalCase(tag)] || tags[kebabCase(node.tag)] || tag } + +function ignoreTag(tag: string) { + // Checks if input tag is an html tag or + let isCustomEl = (typeof tag === "string") + ? isCustomElement(tag) + : false + return htmlTags.includes(tag) || isCustomEl +} diff --git a/src/runtime/utils/compilerOptions/isCustomElement.ts b/src/runtime/utils/compilerOptions/isCustomElement.ts new file mode 100644 index 00000000..55cf21fc --- /dev/null +++ b/src/runtime/utils/compilerOptions/isCustomElement.ts @@ -0,0 +1,4 @@ +// Default isCustomElement function +export default function (tag: string) { + return false +} \ No newline at end of file