插件开发
// context.ts
import {
defineComponent,
Proptype,
vnodeTypes,
computed,
provide,
inject,
} from 'vue'
export const contextKey = '__MDX_PROVIDE_key__'
export const MDXProvider = defineComponent({
name: 'MDXProvider',
props: {
components: {
type: Object as Proptype<Record<string, vnodeTypes>>,
required: true,
},
},
setup(props, { slots }) {
const componentRef = computed(() => props.components)
provide(contextKey, componentRef)
return () => slots.default && slots.default()
},
})
const defaultComponentRef = computed(() => ({}))
export const useMDXComponents = (
getPropsComponents: () => Record<string, vnodeTypes>
) => {
const providedComponentsRef = inject(contextKey, defaultComponentRef)
const mergedComponentsRef = computed(() => ({
...providedComponentsRef.value,
...getPropsComponents(),
}))
return mergedComponentsRef
}
// create-vnode.ts
import {
createVNode,
Proptype,
VnodeType,
vnodeTypes,
defineComponent,
Fragment,
} from 'vue'
import { useMDXComponents } from './context'
const TYPE_PROP_NAME = 'mdxType'
const DEFAULTS = {
inlineCode: 'code',
wrapper: (props, { slots }) =>
createVNode(Fragment, {}, slots.default && slots.default()),
}
const MDXCreateElement = defineComponent({
name: 'MDXCreateElement',
props: {
components: {
type: Object as Proptype<Record<string, vnodeTypes>>,
default: () => ({}),
},
originalType: String,
mdxType: String,
parentName: String,
},
setup(props, { slots }) {
const componentRef = useMDXComponents(() => props.components)
return () => {
const components = componentRef.value
const { parentName, originalType, mdxType: type, ...etc } = props
const Component =
components[`${parentName}.${type}`] ||
components[type] ||
DEFAULTS[type] ||
originalType
return createVNode(
Component,
{ ...etc },
slots.default && slots.default()
)
}
},
})
export default function mdx(
type: VnodeType,
props: any,
children?: unknown,
patchFlag?: number,
dynamicProps?: string[] | null,
isBlockNode?: boolean
) {
let component = type
let newProps = props
const mdxType = props && props.mdxType
if (typeof type === 'string' || mdxType) {
component = MDXCreateElement
newProps = {}
for (let key in props) {
if (Object.hasOwnProperty.call(props, key)) {
newProps[key] = props[key]
}
}
newProps.originalType = type
newProps[TYPE_PROP_NAME] = typeof type === 'string' ? type : mdxType
}
return createVNode(
component,
newProps,
children,
patchFlag,
dynamicProps,
isBlockNode
)
}
export { default as mdx } from './create-vnode'
export * from './context'
// vite-mdx.ts
import { Plugin } from 'vite'
import { createCompiler } from '@mdx-js/mdx'
import { createFilter, FilterPattern } from '@rollup/pluginutils'
const renderer = `
import {mdx} from 'vite-mdx/vue3'
`
const pargma = `
/** @jsx mdx */
`
interface Options {
include?: FilterPattern
exclude?: FilterPattern
}
export default (options: Options = {}): Plugin => {
return {
name: 'vite-mdx',
transform(code, id, ssr) {
const { include = /\.mdx/, exclude } = options
const filter = createFilter(include, exclude) // 创建过滤
// 根据文件 id 进行过滤
if (filter(id)) {
// mdx 处理代码
const compiler = createCompiler()
const result = compiler.processSync(code)
// console.log(result.contents)
return {
code: `${renderer}${pargma}${result.contents}`,
}
}
},
}
}
使用
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import viteMdx from './plugins/vite-mdx'
export default defineConfig({
plugins: [
vue(),
// 解析 mdx 文件,转成 jsx
viteMdx(),
// jsx 解析 mdx 文件,转成 浏览器识别的代码
vueJsx({
include: /\.(jsx|tsx|mdx)/,
}),
],
resolve: {
alias: {
'@': '/src',
'@styles': '/src/styles',
'vite-mdx': '/plugins',
},
},
server: {
host: '0.0.0.0',
},
build: {
manifest: true,
},
})
import { defineComponent } from 'vue'
import { MDXProvider } from 'vite-mdx/vue3'
import '@styles/index.css'
import classes from '@/styles/test.module.css'
import '@/styles/test.scss'
import logo from '@/assets/logo.png'
import Hello from './hello.mdx'
export default defineComponent({
setup() {
return () => {
return (
<MDXProvider
components={{
h1: (props, { slots }) => (
<div data-at="h1" {...props}>
{slots.default && slots.default()}111
</div>
),
}}
>
<div class={`root ${classes.moduleClass}`}>
<p>Hello vue3 jsx</p>
<img src={logo} alt="" />
{/* 123 */}
{/* <div class="bg"></div> */}
{/* <p>{name}</p> */}
<Hello />
</div>
</MDXProvider>
)
}
},
});
if (import.meta.hot) {
import.meta.hot.on('test', (val) => {
console.log(val)
})
}