UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

359 lines (339 loc) 13.5 kB
// Local type definition matching Next.js's internal JSONValue // Used for Turbopack loader options which require serializable values // Define webpack options interface based on Next.js webpack function signature /** * Get default MDX options for docs-infra */ export function getDocsInfraMdxOptions(customOptions = {}) { const { extractToIndex = true, baseDir, errorIfIndexOutOfDate = Boolean(process.env.CI), defaultInlineCodeLanguage, codeBlockEmphasisOptions } = customOptions; // Normalize extractToIndex to options object let extractToIndexOptions; if (extractToIndex === false) { extractToIndexOptions = false; } else if (extractToIndex === true) { // Default filter: include all files under app/ and src/app/ // Index files (pattern/page.mdx) are automatically excluded by the matching logic // Use process.cwd() as default baseDir (the directory where Next.js is running) extractToIndexOptions = { include: ['app', 'src/app'], exclude: [], baseDir: baseDir ?? process.cwd() }; } else { extractToIndexOptions = { ...extractToIndex, baseDir: baseDir ?? process.cwd() }; } const defaultRemarkPlugins = [['remark-gfm'], ['@mui/internal-docs-infra/pipeline/transformMarkdownMetadata', { extractToIndex: extractToIndexOptions, markerPath: '.next/cache/docs-infra/index-updates', errorIfIndexOutOfDate }], ['@mui/internal-docs-infra/pipeline/transformMarkdownRelativePaths'], ['@mui/internal-docs-infra/pipeline/transformMarkdownBlockquoteCallouts'], // Only pass options if explicitly set (undefined uses plugin default of 'tsx') defaultInlineCodeLanguage !== undefined ? ['@mui/internal-docs-infra/pipeline/transformMarkdownCode', { defaultInlineCodeLanguage }] : ['@mui/internal-docs-infra/pipeline/transformMarkdownCode'], ['@mui/internal-docs-infra/pipeline/transformMarkdownMetaLinks']]; const defaultRehypePlugins = [codeBlockEmphasisOptions ? ['@mui/internal-docs-infra/pipeline/transformHtmlCodeBlock', codeBlockEmphasisOptions] : ['@mui/internal-docs-infra/pipeline/transformHtmlCodeBlock'], ['@mui/internal-docs-infra/pipeline/transformHtmlCodeInline'], // enhancers ['@mui/internal-docs-infra/pipeline/enhanceCodeInline']]; // Build final plugin arrays const remarkPlugins = customOptions.remarkPlugins ?? [...defaultRemarkPlugins, ...(customOptions.additionalRemarkPlugins ?? [])]; const rehypePlugins = customOptions.rehypePlugins ?? [...defaultRehypePlugins, ...(customOptions.additionalRehypePlugins ?? [])]; return { remarkPlugins, rehypePlugins }; } /** * Next.js plugin for MUI docs infrastructure. * Configures webpack loaders, turbopack rules for docs sites. * Use getDocsInfraMdxOptions() with createMDX for MDX integration. */ export function withDocsInfra(options = {}) { const { additionalPageExtensions = [], enableExportOutput = true, demoPathPattern = './app/**/demos/*/index.ts', demoDataPathPattern = './demo-data/*/index.ts', clientDemoPathPattern = './app/**/demos/*/client.ts', additionalDemoPatterns = {}, additionalTurbopackRules = {}, performance = {}, deferCodeParsing = 'gzip', removeCommentsWithPrefix, notableCommentsPrefix, typesIndexFileName = 'page.mdx', errorIfTypesIndexOutOfDate = Boolean(process.env.CI), requireDemoClient, requireDemoPage = false, transformTypescriptToJavascript = false } = options; // Only include ordering in loader options if explicitly provided const ordering = options.ordering; const descriptionReplacements = options.descriptionReplacements; const demoEmphasisOptions = options.demoEmphasisOptions; const codeBlockEmphasisOptions = options.codeBlockEmphasisOptions; // Compute updateParentIndex options similar to how transformMarkdownMetadata does const updateParentIndex = { baseDir: process.cwd(), indexFileName: typesIndexFileName, markerDir: '.next/cache/docs-infra/types-index-updates', onlyUpdateIndexes: true, errorIfOutOfDate: errorIfTypesIndexOutOfDate }; let output = 'hastCompressed'; if (deferCodeParsing === 'json') { output = 'hastJson'; } else if (deferCodeParsing === 'none') { output = 'hast'; } return (nextConfig = {}) => { const basePageExtensions = ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx']; const pageExtensions = [...basePageExtensions, ...additionalPageExtensions]; // Build Turbopack rules // Filter out undefined values to satisfy Turbopack's JSONValue type requirement const codeHighlighterOptions = { performance, output, ...(removeCommentsWithPrefix && { removeCommentsWithPrefix }), ...(notableCommentsPrefix && { notableCommentsPrefix }), ...(demoEmphasisOptions && { emphasisOptions: demoEmphasisOptions }), ...(transformTypescriptToJavascript ? { transformTypescriptToJavascript: true } : {}) }; // The demo highlighter options carry `requireClient`/`requirePage` so the validate // CLI can discover both the import specifier and the patterns it should ensure // clients/pages for. The loader itself ignores these options. const demoCodeHighlighterOptions = { ...codeHighlighterOptions, ...(requireDemoClient ? { requireClient: requireDemoClient } : {}), ...(requireDemoPage ? { requirePage: true } : {}) }; const turbopackRules = { [demoPathPattern]: { loaders: [{ loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter', options: demoCodeHighlighterOptions }] }, [demoDataPathPattern]: { loaders: [{ loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter', options: codeHighlighterOptions }] }, [clientDemoPathPattern]: { loaders: [{ loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighterClient', options: { performance } }] }, './app/**/types.ts': { loaders: [{ loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedTypes', options: { performance, socketDir: '.next/docs-infra', updateParentIndex, ...(codeBlockEmphasisOptions ? { codeBlockEmphasisOptions: codeBlockEmphasisOptions } : {}), ...(ordering ? { ordering: ordering } : {}), ...(descriptionReplacements ? { descriptionReplacements: descriptionReplacements } : {}) } }] }, './app/sitemap/index.ts': { loaders: [{ loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedSitemap', options: { performance } }] } }; // Add additional demo patterns to Turbopack rules if (additionalDemoPatterns.index) { additionalDemoPatterns.index.forEach(pattern => { turbopackRules[pattern] = { loaders: [{ loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter', options: demoCodeHighlighterOptions }] }; }); } if (additionalDemoPatterns.client) { additionalDemoPatterns.client.forEach(pattern => { turbopackRules[pattern] = { loaders: [{ loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighterClient', options: { performance } }] }; }); } // Merge with additional turbopack rules Object.assign(turbopackRules, additionalTurbopackRules); const config = { ...nextConfig, pageExtensions, ...(enableExportOutput && { output: 'export' }), turbopack: { ...nextConfig.turbopack, rules: { ...nextConfig.turbopack?.rules, ...turbopackRules } }, webpack: (webpackConfig, webpackOptions) => { // Call existing webpack function if it exists if (nextConfig.webpack) { webpackConfig = nextConfig.webpack(webpackConfig, webpackOptions); } // Ensure module and rules exist if (!webpackConfig.module) { webpackConfig.module = {}; } if (!webpackConfig.module.rules) { webpackConfig.module.rules = []; } const { defaultLoaders } = webpackOptions; // Add loader for demo index files webpackConfig.module.rules.push({ test: new RegExp('[/\\\\]demos[/\\\\][^/\\\\]+[/\\\\]index\\.ts$'), use: [defaultLoaders.babel, { loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter', options: demoCodeHighlighterOptions }] }); // Add loader for demo data webpackConfig.module.rules.push({ test: new RegExp('[/\\\\]demo-data[/\\\\][^/\\\\]+[/\\\\]index\\.ts$'), use: [defaultLoaders.babel, { loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter', options: codeHighlighterOptions }] }); // Client files for live demos - processes externals webpackConfig.module.rules.push({ test: new RegExp('[/\\\\]demos[/\\\\][^/\\\\]+[/\\\\]client\\.ts$'), use: [defaultLoaders.babel, { loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighterClient', options: { performance } }] }); // Sitemap loader webpackConfig.module.rules.push({ test: new RegExp('[/\\\\]sitemap[/\\\\]index\\.ts$'), use: [defaultLoaders.babel, { loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedSitemap', options: { performance } }] }); // Types files for type metadata webpackConfig.module.rules.push({ test: new RegExp('[/\\\\]app[/\\\\].*[/\\\\]types\\.ts$'), use: [defaultLoaders.babel, { loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedTypes', options: { performance, socketDir: '.next/docs-infra', updateParentIndex, ...(codeBlockEmphasisOptions ? { codeBlockEmphasisOptions } : {}), ...(ordering ? { ordering } : {}), ...(descriptionReplacements ? { descriptionReplacements } : {}) } }] }); // Add webpack rules for additional demo patterns if (additionalDemoPatterns.index) { additionalDemoPatterns.index.forEach(pattern => { // Convert Turbopack pattern to webpack regex // Pattern like './app/**/demos/*/demo-*/index.ts' // Should match paths like '/app/components/demos/Button/demo-variant/index.ts' // Use placeholders to avoid corrupting character classes during replacement const SEP = 'PATH_SEP_PLACEHOLDER'; const NOT_SEP = 'NOT_PATH_SEP_PLACEHOLDER'; const regexPattern = pattern.replace(/^\.\//, '') // Remove leading ./ .replace(/\*\*\//g, 'DOUBLE_STAR_PLACEHOLDER') // Replace **/ with placeholder .replace(/\*/g, NOT_SEP) // Replace single * with placeholder .replace(/\./g, '\\.') // Escape dots .replace(/DOUBLE_STAR_PLACEHOLDER/g, `(?:${NOT_SEP}${SEP})*`) // Replace placeholder with zero or more directories .replace(/\//g, SEP) // Convert all path separators to placeholder .replace(new RegExp(NOT_SEP, 'g'), '[^/\\\\]+') // Replace NOT_SEP with actual pattern .replace(new RegExp(SEP, 'g'), '[/\\\\]'); // Replace SEP with actual pattern webpackConfig.module.rules.push({ test: new RegExp(`${regexPattern}$`), use: [defaultLoaders.babel, { loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter', options: demoCodeHighlighterOptions }] }); }); } if (additionalDemoPatterns.client) { additionalDemoPatterns.client.forEach(pattern => { // Convert Turbopack pattern to webpack regex const regexPattern = pattern.replace(/^\.\//, '/') // Remove leading ./ .replace(/\*\*\//g, 'DOUBLE_STAR_PLACEHOLDER') // Replace **/ with placeholder .replace(/\*/g, '[^/\\\\]+') // Replace single * with single dir pattern .replace(/\./g, '\\.') // Escape dots .replace(/DOUBLE_STAR_PLACEHOLDER/g, '(?:[^/\\\\]+/)*'); // Replace placeholder with zero or more directories webpackConfig.module.rules.push({ test: new RegExp(`${regexPattern}$`), use: [defaultLoaders.babel, { loader: '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighterClient', options: { performance } }] }); }); } return webpackConfig; } }; return config; }; }