Documentation generator for React component libraries with live demos, API tables, and theming support
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
File system utilities, caching, and build helpers for processing documentation files and managing project structure.
Utilities for working with file paths and content extraction.
/**
* Convert filesystem path to route ID using kebab-case
* @param fsPath - Filesystem path to convert
* @returns Kebab-cased route identifier
*/
function getFileIdFromFsPath(fsPath: string): string;
/**
* Extract specific line ranges from file content
* @param content - File content as string
* @param range - Range specification (e.g., "L1-L10", "L5")
* @returns Extracted lines as string
*/
function getFileRangeLines(content: string, range: string): string;
/**
* Extract content from file using regular expression
* @param content - Source file content
* @param regexp - Regular expression string
* @param filePath - Source file path for error reporting
* @returns Matched content or full content on error
*/
function getFileContentByRegExp(
content: string,
regexp: string,
filePath: string
): string;
/**
* Parse frontmatter from code blocks (supports JS/HTML comments)
* @param raw - Raw code string with potential frontmatter
* @returns Object with separated code and frontmatter
*/
function parseCodeFrontmatter(raw: string): {
code: string;
frontmatter: Record<string, any> | null;
};Usage:
import {
getFileIdFromFsPath,
getFileRangeLines,
parseCodeFrontmatter
} from "dumi";
// Convert file paths to route IDs
const routeId = getFileIdFromFsPath('/src/components/ButtonGroup/index.tsx');
// Result: "button-group"
// Extract specific lines from content
const content = `line 1
line 2
line 3
line 4`;
const excerpt = getFileRangeLines(content, 'L2-L3');
// Result: "line 2\nline 3"
// Parse frontmatter from code
const codeWithMeta = `/**
* title: Button Demo
* description: Basic button component
*/
export default function Button() {
return <button>Click me</button>;
}`;
const { code, frontmatter } = parseCodeFrontmatter(codeWithMeta);
// frontmatter: { title: "Button Demo", description: "Basic button component" }
// code: "export default function Button() { ... }"Utilities for working with monorepo projects and build configurations.
/**
* Get root directory for monorepo projects
* @param cwd - Current working directory
* @returns Root directory path for monorepo or current directory
*/
function getProjectRoot(cwd: string): string;
/**
* Attempt to load Father build configurations
* @param cwd - Project directory
* @returns Array of Father build configurations
*/
function tryFatherBuildConfigs(cwd: string): Promise<any[]>;
/**
* Convert component path to webpack chunk name
* @param component - Component file path
* @param cwdPath - Current working directory path
* @returns Webpack-compatible chunk name
*/
function componentToChunkName(
component: string,
cwdPath?: string
): string;
/**
* Generate metadata chunk name for route
* @param path - Route path
* @param cwd - Current working directory
* @param locales - Available locales
* @returns Metadata chunk name
*/
function generateMetaChunkName(
path: string,
cwd: string,
locales?: string[]
): string;Usage:
import {
getProjectRoot,
componentToChunkName,
tryFatherBuildConfigs
} from "dumi";
// Find monorepo root
const projectRoot = getProjectRoot('/path/to/packages/ui-lib');
// Returns: "/path/to" (if workspace detected)
// Generate chunk names
const chunkName = componentToChunkName(
'/src/components/Button/index.tsx',
'/project/root'
);
// Result: "components__Button__index"
// Load Father configs for build integration
const buildConfigs = await tryFatherBuildConfigs(process.cwd());
console.log('Available build configurations:', buildConfigs);File system caching utilities for performance optimization.
/**
* Get filesystem cache instance for specific namespace
* @param ns - Cache namespace
* @returns Cache instance with get/set methods
*/
function getCache(ns: string): {
/** Set cache value */
set(key: string, value: any): Promise<void>;
/** Get cache value */
get(key: string): Promise<any>;
/** Set cache value synchronously */
setSync(key: string, value: any): void;
/** Get cache value synchronously */
getSync(key: string): any;
};
/**
* Set custom cache directory (internal use)
* @param dir - Cache directory path
*/
function _setFSCacheDir(dir: string): void;Usage:
import { getCache } from "dumi";
// Create namespace-specific cache
const apiCache = getCache('api-parsing');
const themeCache = getCache('theme-data');
// Cache expensive operations
async function parseComponentAPI(filePath: string) {
const cacheKey = `api-${getContentHash(filePath)}`;
// Try cache first
let apiData = await apiCache.get(cacheKey);
if (!apiData) {
// Expensive parsing operation
apiData = await parseTypeScriptFile(filePath);
// Cache result
await apiCache.set(cacheKey, apiData);
}
return apiData;
}
// Disable caching for development
process.env.DUMI_CACHE = 'none';Utilities for content hashing and processing.
/**
* Generate MD5 hash for content
* @param content - Content to hash
* @param length - Hash length (default: 8)
* @returns Hex hash string
*/
function getContentHash(content: string, length?: number): string;
/**
* Check if version satisfies range requirement
* @param version - Version string to check
* @param range - Semver range requirement
* @param options - Additional semver options
* @returns Whether version satisfies range
*/
function isVersionInRange(
version: string,
range: string | Range,
options?: RangeOptions
): boolean;Usage:
import { getContentHash, isVersionInRange } from "dumi";
// Generate content hashes for cache keys
const fileContent = 'export default function Component() {}';
const hash = getContentHash(fileContent);
// Result: "a1b2c3d4"
const longHash = getContentHash(fileContent, 16);
// Result: "a1b2c3d4e5f6g7h8"
// Check version compatibility
const isCompatible = isVersionInRange('2.4.21', '^2.0.0');
// Result: true
const supportsFeature = isVersionInRange('1.5.0', '>=1.6.0');
// Result: falseUtilities for webpack loader processing and integration.
/**
* Run webpack loaders programmatically
* @param options - Loader configuration options
* @returns Promise resolving to loader result
*/
function runLoaders(options: RunLoaderOption): Promise<RunLoaderResult>;
interface RunLoaderOption {
/** Resource path to process */
resource?: string;
/** Array of loaders to apply */
loaders?: Array<{
loader: string;
options?: any;
}>;
/** Loader context */
context?: any;
/** Custom resource reader function */
readResource?: (resource: string) => Promise<Buffer>;
}
interface RunLoaderResult {
/** Loader processing results */
result: Buffer[];
/** Original resource buffer */
resourceBuffer: Buffer;
/** Whether result is cacheable */
cacheable: boolean;
/** File dependencies */
fileDependencies: string[];
/** Context dependencies */
contextDependencies: string[];
}
/**
* Get loader context (internal webpack utility)
* @param loader - Loader function or object
* @returns Loader context
*/
function getContext(loader: any): any;Usage:
import { runLoaders } from "dumi";
// Process markdown files with custom loader
async function processMarkdownFile(filePath: string) {
const result = await runLoaders({
resource: filePath,
loaders: [
{
loader: require.resolve('./markdown-loader'),
options: {
extractToc: true,
generateDemos: true,
},
},
],
context: {
rootDir: process.cwd(),
},
});
return {
content: result.result[0].toString(),
dependencies: result.fileDependencies,
cacheable: result.cacheable,
};
}
// Custom loader implementation
function markdownLoader(content: string) {
const options = this.getOptions();
// Process markdown content
const processed = processMarkdown(content, options);
// Track dependencies
this.addDependency('./components/Button.tsx');
return processed;
}import {
parseCodeFrontmatter,
getContentHash,
getCache,
runLoaders
} from "dumi";
class DocumentProcessor {
private cache = getCache('doc-processing');
async processFile(filePath: string, content: string) {
const hash = getContentHash(content);
const cacheKey = `processed-${filePath}-${hash}`;
// Check cache
let result = await this.cache.get(cacheKey);
if (!result) {
// Parse frontmatter
const { code, frontmatter } = parseCodeFrontmatter(content);
// Process with loaders
const loaderResult = await runLoaders({
resource: filePath,
loaders: [
{ loader: require.resolve('./custom-loader') },
],
});
result = {
content: code,
meta: frontmatter,
processed: loaderResult.result[0].toString(),
dependencies: loaderResult.fileDependencies,
};
// Cache result
await this.cache.set(cacheKey, result);
}
return result;
}
}import {
getProjectRoot,
getFileIdFromFsPath,
tryFatherBuildConfigs
} from "dumi";
import path from 'path';
import fs from 'fs';
class ProjectAnalyzer {
async analyzeProject(startDir: string) {
const projectRoot = getProjectRoot(startDir);
const buildConfigs = await tryFatherBuildConfigs(projectRoot);
// Scan for component files
const components = this.scanComponents(projectRoot);
// Generate route mapping
const routes = components.map(filePath => ({
filePath,
routeId: getFileIdFromFsPath(
path.relative(projectRoot, filePath)
),
chunkName: componentToChunkName(filePath, projectRoot),
}));
return {
projectRoot,
buildConfigs,
components: routes,
};
}
private scanComponents(rootDir: string): string[] {
// Implementation for scanning component files
const componentFiles: string[] = [];
function scanDir(dir: string) {
const entries = fs.readdirSync(dir);
for (const entry of entries) {
const fullPath = path.join(dir, entry);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDir(fullPath);
} else if (/\.(tsx?|jsx?)$/.test(entry)) {
componentFiles.push(fullPath);
}
}
}
scanDir(path.join(rootDir, 'src'));
return componentFiles;
}
}Utilities for building custom tech stack integrations and API parsing.
/**
* Language metadata parser interface for tech stacks
*/
interface ILanguageMetaParser {
/** Language identifier */
language: string;
/** Parse component/function metadata from code */
parse(code: string, options: {
filename: string;
entryFile?: string;
}): Promise<AtomAsset[]>;
}
/**
* File patching interface for tech stack processing
*/
interface IPatchFile {
/** File path to patch */
path: string;
/** Patch content */
content: string;
/** Patch type */
type: 'add' | 'modify' | 'delete';
}
/**
* Base options for API parser creation
*/
interface IBaseApiParserOptions {
/** Entry file for parsing */
entryFile?: string;
/** Custom resolver filter function */
resolveFilter?: (args: {
id: string;
ids: string;
type: 'COMPONENT' | 'FUNCTION';
}) => boolean;
}
/**
* Create API parser for tech stack integration
* @param options - Parser configuration options
* @returns API parser instance
*/
function createApiParser(options: IBaseApiParserOptions): ILanguageMetaParser;
/**
* Get Babel core instance for code transformation
* @returns Babel core module
*/
function babelCore(): typeof import('@babel/core');
/**
* Get Babel TypeScript preset
* @returns Babel TypeScript preset configuration
*/
function babelPresetTypeScript(): any;
/**
* Get Babel environment preset
* @returns Babel environment preset configuration
*/
function babelPresetEnv(): any;Usage:
import {
createApiParser,
babelCore,
babelPresetTypeScript,
type ILanguageMetaParser
} from "dumi/tech-stack-utils";
// Create custom API parser
const parser = createApiParser({
entryFile: './src/index.ts',
resolveFilter: ({ id, type }) => {
// Skip private components
return !id.startsWith('_');
},
});
// Use Babel for code transformation
const babel = babelCore();
const result = babel.transformSync(code, {
presets: [babelPresetTypeScript()],
});
// Custom language parser implementation
class VueMetaParser implements ILanguageMetaParser {
language = 'vue';
async parse(code: string, options: { filename: string }) {
// Parse Vue SFC for component metadata
const component = parseVueComponent(code);
return [{
type: 'COMPONENT',
id: getComponentName(options.filename),
props: extractProps(component),
methods: extractMethods(component),
}];
}
}import { getCache, getContentHash } from "dumi";
class PerformanceOptimizer {
private parseCache = getCache('parsing');
private buildCache = getCache('build');
async optimizedParse(filePath: string, parser: Function) {
const content = fs.readFileSync(filePath, 'utf-8');
const hash = getContentHash(content);
const cacheKey = `parse-${filePath}-${hash}`;
let result = await this.parseCache.get(cacheKey);
if (!result) {
console.time(`Parsing ${filePath}`);
result = await parser(content, filePath);
console.timeEnd(`Parsing ${filePath}`);
await this.parseCache.set(cacheKey, result);
}
return result;
}
async batchProcess<T>(
items: T[],
processor: (item: T) => Promise<any>,
concurrency = 5
) {
const results = [];
for (let i = 0; i < items.length; i += concurrency) {
const batch = items.slice(i, i + concurrency);
const batchResults = await Promise.all(
batch.map(processor)
);
results.push(...batchResults);
}
return results;
}
}Install with Tessl CLI
npx tessl i tessl/npm-dumi