插件开发

  1. // context.ts
  2. import {
  3. defineComponent,
  4. Proptype,
  5. vnodeTypes,
  6. computed,
  7. provide,
  8. inject,
  9. } from 'vue'
  10. export const contextKey = '__MDX_PROVIDE_key__'
  11. export const MDXProvider = defineComponent({
  12. name: 'MDXProvider',
  13. props: {
  14. components: {
  15. type: Object as Proptype<Record<string, vnodeTypes>>,
  16. required: true,
  17. },
  18. },
  19. setup(props, { slots }) {
  20. const componentRef = computed(() => props.components)
  21. provide(contextKey, componentRef)
  22. return () => slots.default && slots.default()
  23. },
  24. })
  25. const defaultComponentRef = computed(() => ({}))
  26. export const useMDXComponents = (
  27. getPropsComponents: () => Record<string, vnodeTypes>
  28. ) => {
  29. const providedComponentsRef = inject(contextKey, defaultComponentRef)
  30. const mergedComponentsRef = computed(() => ({
  31. ...providedComponentsRef.value,
  32. ...getPropsComponents(),
  33. }))
  34. return mergedComponentsRef
  35. }
// 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)
  })
}