2022-10-21 00:54:56 +01:00

75 lines
2.7 KiB
TypeScript

import type { Plugin } from 'vite';
import { createFilter } from 'vite';
const isStorybookMdx = (id: string) => id.endsWith('stories.mdx') || id.endsWith('story.mdx');
function injectRenderer(code: string) {
return `
import React from 'react';
${code}
`;
}
/**
* Storybook uses two different loaders when dealing with MDX:
*
* - *stories.mdx and *story.mdx are compiled with the CSF compiler
* - *.mdx are compiled with the MDX compiler directly
*
* @see https://github.com/storybookjs/storybook/blob/next/addons/docs/docs/recipes.md#csf-stories-with-arbitrary-mdx
*/
export function mdxPlugin(): Plugin {
let reactRefresh: Plugin | undefined;
const include = /\.mdx?$/;
const filter = createFilter(include);
return {
name: 'storybook:mdx-plugin',
enforce: 'pre',
configResolved({ plugins }) {
// @vitejs/plugin-react-refresh has been upgraded to @vitejs/plugin-react,
// and the name of the plugin performing `transform` has been changed from 'react-refresh' to 'vite:react-babel',
// to be compatible, we need to look for both plugin name.
// We should also look for the other plugins names exported from @vitejs/plugin-react in case there are some internal refactors.
const reactRefreshPlugins = plugins.filter(
(p) =>
p.name === 'react-refresh' ||
p.name === 'vite:react-babel' ||
p.name === 'vite:react-refresh' ||
p.name === 'vite:react-jsx'
);
reactRefresh = reactRefreshPlugins.find((p) => p.transform);
},
async transform(src, id, options) {
if (!filter(id)) return undefined;
// @ts-expect-error typescript doesn't think compile exists, but it does.
const { compile } = await import('@storybook/mdx2-csf');
const mdxCode = String(await compile(src, { skipCsf: !isStorybookMdx(id) }));
const modifiedCode = injectRenderer(mdxCode);
// Hooks in recent rollup versions can be functions or objects, and though react hasn't changed, the typescript defs have
const rTransform = reactRefresh?.transform;
const transform = rTransform && 'handler' in rTransform ? rTransform.handler : rTransform;
// It's safe to disable this, because we know it'll be there, since we added it ourselves.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const result = await transform!.call(this, modifiedCode, `${id}.jsx`, options);
if (!result) return modifiedCode;
if (typeof result === 'string') return result;
const { code, map: resultMap } = result;
return {
code,
map:
!resultMap || typeof resultMap === 'string' ? resultMap : { ...resultMap, sources: [id] },
};
},
};
}