@asaidimu/vite-autoload is an enhanced Vite plugin for automatic route and module loading, designed to streamline development workflows by dynamically generating and managing application components and data based on your file system.
- Overview & Features
- Installation & Setup
- Usage Documentation
- Project Architecture
- Development & Contributing
- Additional Information
@asaidimu/vite-autoload is a powerful Vite plugin designed to automate the discovery, generation, and management of application assets such as routes, components, and data structures. It addresses the common challenge of manual import management and boilerplate code in modern web development by dynamically generating application code based on your file system or custom data sources.
This plugin streamlines development workflows by:
- Eliminating Manual Imports and Boilerplate: Automates the often tedious and error-prone process of defining and updating imports for routes, components, and other modules. This significantly reduces repetitive coding, allowing developers to focus on core application logic.
- Enhancing Developer Productivity: By automating asset management, it accelerates development cycles and iteration speeds, leading to a more efficient and productive coding experience.
- Ensuring Type Safety for Dynamic Assets: Automatically generates TypeScript declaration files (
.d.ts) for all virtual modules. This provides robust type safety for dynamically loaded assets, improving code reliability and developer experience by catching potential errors at compile time. - Improving Project Maintainability: As applications scale, manually maintaining large routing tables or component registries becomes challenging. The plugin's automatic generation capabilities ensure consistency and reduce long-term maintenance overhead by keeping configurations synchronized with your source files.
- Simplifying SEO and PWA Integration: Integrates the generation of essential web artifacts like
sitemap.xmlfor search engine optimization andmanifest.webmanifestfor Progressive Web App (PWA) capabilities directly into the build pipeline, simplifying crucial setup steps. - Enabling Metadata-Driven Development: Provides a robust utility for extracting structured metadata from your source files using TypeScript AST analysis and Zod schema validation. This allows for dynamic UI rendering, routing, and application logic based on declarative metadata embedded directly in your code.
By providing these capabilities, @asaidimu/vite-autoload facilitates more efficient development paradigms, such as:
- File-System Driven Routing and Component Discovery: Leverage your existing file and directory structure to automatically define routes or expose component collections, creating a natural and intuitive organization for your application.
- Automated Data Layer Generation: Transform file-based content (e.g., Markdown for blog posts) or programmatic data into type-safe virtual modules, providing a unified and easily consumable data layer for your application.
- Integrated Build Artifacts: Ensure that critical SEO and PWA files are always up-to-date and correctly generated as part of your standard build process, without requiring separate tooling or manual updates.
This plugin is designed to be a fundamental tool for developers building scalable and maintainable applications with Vite, providing a robust foundation for automated asset management.
package.json to avoid unexpected disruptions from new releases.
We highly encourage feedback and contributions, but please be prepared for a rapidly changing codebase.
- 🚀 Automatic Route & Module Generation: Dynamically discover and expose application routes (e.g., pages) and other reusable modules (e.g., components, hooks) from specified directories based on flexible glob patterns, or from custom data sources. This means less manual import management and more focus on your code's core logic.
- 📦 Virtual Modules: All processed data is exposed as Vite virtual modules (e.g.,
virtual:views,virtual:components), allowing direct and type-safe import into your application code, reducing boilerplate and enhancing code clarity. - 🔄 Hot Module Replacement (HMR): Provides seamless HMR for changes in watched files. When files tracked by the plugin are added, modified, or removed, virtual modules and their importers are intelligently invalidated to ensure a fast, efficient, and uninterrupted development experience.
- 🗺️ Sitemap Generation: Automatically generates a
sitemap.xmlduring the build process, configured with your base URL and exclusion patterns, to enhance SEO and improve discoverability of your application. - 🌐 PWA Manifest Generation: Integrates with Vite's build pipeline to generate a
manifest.webmanifestfile, enabling your application to function as a Progressive Web App (PWA) with configurable icons, display modes, and more, all with minimal effort. - 📄 Type Definition Generation: Automatically generates TypeScript declaration files (
.d.ts) for your generated modules. This ensures strong type safety and significantly improves developer experience when consuming the virtual modules, leading to fewer runtime errors. - 🔑 Metadata Extraction: A powerful feature that allows extracting structured metadata directly from your source files (e.g., page titles, authentication requirements) using TypeScript AST analysis and validation against Zod schemas. This metadata can then be included in the generated virtual modules, empowering dynamic content and routing decisions.
- 🛠️ Highly Configurable: Offers extensive options for defining file matching rules, transforming discovered file or data, customizing output formats, and fine-tuning build-time and development-time behaviors. The plugin introduces a unified
componentsmodel for defining module groups, replacing previousroutesandmodulesroot properties, offering unparalleled flexibility to adapt to your project's unique needs.
- Node.js: LTS version (e.g., 18.x or 20.x)
- Vite: Version
7.0.0or higher (as perpackage.json) - Package Manager: Bun (recommended as per
package.jsonscripts), npm, Yarn, or pnpm - TypeScript:
~5.8.3or higher (as perpackage.json)
Install the plugin using your preferred package manager:
# Using bun (recommended)
bun add @asaidimu/vite-autoload
# Using npm
npm install @asaidimu/vite-autoload
# Using yarn
yarn add @asaidimu/vite-autoload
# Using pnpm
pnpm add @asaidimu/vite-autoloadFor the full capabilities, especially metadata extraction using Zod schemas, you will also need to install these development dependencies:
# Using bun
bun add @babel/parser @babel/traverse zod --development
# Using npm
npm install --save-dev @babel/parser @babel/traverse zodTo integrate @asaidimu/vite-autoload into your Vite project, update your vite.config.ts file:
// vite.config.ts
import { defineConfig } from "vite";
import { createAutoloadPlugin, extract } from "@asaidimu/vite-autoload"; // Import `extract` utility
import createAutoloadConfig from "./example.config"; // Your custom autoload config file
export default defineConfig({
plugins: [
// Pass the `extract` utility to your config function, as it's required by TransformConfig
createAutoloadPlugin(createAutoloadConfig({ extract })),
],
});Next, create an example.config.ts (or autoload.config.ts) file in your project root. This file defines how the plugin will discover, process, and expose your application's components and data. The configuration is an object adhering to the PluginOptions type.
// example.config.ts
import { z } from "zod";
import type {
PluginOptions,
ComponentConfig,
TransformConfig,
ResolvedFile,
ExtractFunction,
} from "@asaidimu/vite-autoload"; // Adjust import paths based on your setup
interface ConfigOptions {
readonly extract: ExtractFunction;
}
export default function createAutoloadConfig({
extract,
}: ConfigOptions): PluginOptions {
// Example demonstrating file-system driven routing with metadata extraction
const viewsTransformConfig: TransformConfig<ResolvedFile, any, any> = {
name: "views", // This name will be used for the virtual module: `virtual:views`
description: "Transforms UI view files into routable data.",
input: {
directory: "ui", // Base directory to scan, e.g., `src/ui/pages`
match: ["**/*.ts", "**/*.tsx"], // Glob patterns to match files
ignore: ["**/__tests__/**", "*.d.ts"], // Optional: ignore patterns for tests or type declarations
prefix: "/_views/", // Optional: prefix for URIs in the generated output, useful for lazy loading
},
output: {
// Defines the structure of the virtual module, e.g., `export const views = [{ route: '/', ... }, ...];`
template: "export const views = {{ data }};",
// Generates a TypeScript type like `type ViewKeys = '/home' | '/about';` for type-safe route keys.
types: { name: "ViewKeys", property: "route" },
},
transform: async (item: ResolvedFile) => {
// Custom transformation logic for each discovered file.
// This example constructs a clean route path from the file's relative path.
const route = item.path
.replace(/^ui/, "") // Adjust based on your `directory` path
.replace(/\\/g, "/") // Normalize path separators for cross-OS compatibility
.replace(/\.tsx?$/, "") // Remove file extensions
.replace(/\/index$/, ""); // Remove /index for root routes, e.g., `ui/home/index.tsx` becomes `/home`
// This demonstrates the "Metadata Extraction" feature.
// It statically analyzes `item.file` (the source file) to find an exported `metadata` constant
// and validates it against the provided Zod schema.
const metadata = await extract({
filePath: item.file,
schema: z.object({
title: z.string().describe("Page title for display."),
description: z
.string()
.optional()
.describe("Optional page description for SEO."),
authRequired: z
.boolean()
.optional()
.describe(
"Indicates if authentication is required for this route.",
),
}),
name: "metadata", // Looks for `export const metadata = {...}` or `export default {...}`
});
return {
route: route || "/", // Ensure '/' for root path if the transformed route is empty
path: item.uri, // The full URI for the module, useful for dynamic imports
metadata: metadata, // The extracted and validated metadata
};
},
// aggregate: (items) => { /* Optional: Custom aggregation for all transformed 'views' items */ },
};
// Example demonstrating "Automated Data Layer Generation" from a programmatic source.
const dataTransformConfig: TransformConfig<Array<number>, any, any> = {
name: "data", // This will be exposed as `virtual:data`
description: "Generates arbitrary numeric data from a programmatic source.",
// The `input` function can fetch data from an API, database, or generate it on the fly.
input: () =>
Promise.resolve(Array.from(new Array(20)).map((_, i) => i + 1)),
output: {
template: "export const data = {{ data }};",
},
transform: async (item) => {
return item as any; // Simple passthrough for numeric data
},
};
// Defines a higher-level "component" that groups related transformation configurations.
// This helps in organizing and applying global strategies (like sitemap generation) to multiple data sources.
const routes: ComponentConfig = {
name: "routes", // A logical name for this high-level component category (e.g., 'routes', 'ui-modules')
description:
"Defines the routing structure and data for application views and pages.",
strategy: {
// "Sitemap Generation": Uses the `route` property from the aggregated `views` data to build sitemap entries.
sitemap: {
property: "route", // Property from transformed data to use for sitemap entries
},
// "Type Definition Generation": Creates a union type of all `route` strings found in this component's data.
types: {
name: "AppRouteData", // Name for the generated TypeScript type (e.g., ApplicationRoute)
property: "route", // Property to use for type generation (e.g., 'route' will create a union type of all route strings)
},
},
groups: [viewsTransformConfig, dataTransformConfig], // Includes our defined transformation groups
};
return {
settings: {
rootDir: process.cwd(), // Base directory for resolving relative paths in plugin options
export: {
// "Type Definition Generation": Specifies the output path for the auto-generated TypeScript types.
types: "src/app/config/autogen.d.ts",
routeLimit: 1000, // Limits the number of entries in generated union types, preventing excessively large types.
},
// "Sitemap Generation": Comprehensive configuration for the `sitemap.xml` file.
sitemap: {
output: "sitemap.xml", // Output filename for the sitemap (placed in Vite's build output)
baseUrl: "[https://example.com](https://example.com)", // Base URL for sitemap entries (essential for absolute URLs)
exclude: ["/admin/*", "/private/*"], // Glob patterns to exclude specific routes from the sitemap
},
// "PWA Manifest Generation": Comprehensive configuration for the `manifest.webmanifest` file.
manifest: {
name: "My PWA App",
shortName: "PWA App",
description:
"A Progressive Web Application demonstrating vite-autoload.",
theme_color: "#4a90e2",
background_color: "#ffffff",
display: "standalone",
start_url: "/",
icons: [
{
src: "/icons/icon-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "/icons/icon-512x512.png",
sizes: "512x512",
type: "image/png",
},
],
output: "manifest.webmanifest",
},
logLevel: "info", // 'debug', 'info', 'warn', 'error' for plugin's console output
chunkSize: 100, // Number of items processed in a single batch for memory optimization during large scans
watch: {
// "Hot Module Replacement (HMR)": Debounce settings for file watcher to ensure stability and efficiency.
debounceTime: 1000, // Debounce time for file changes (ms)
stabilityThreshold: 300, // Time to wait for file system write stability (ms)
},
extract: extract, // The required metadata extraction utility
},
// Define different component categories and their groups
components: [routes], // Add your defined components here
};
}After configuring the plugin, run your Vite development server:
bun run devYou should see log messages prefixed with [vite-autoload] in your console, indicating that the file watcher has started and modules/types are being generated. Check for any errors or warnings. During a production build (bun run build), the plugin will generate sitemap.xml and manifest.webmanifest in your output directory. You should also find your autogen.d.ts file generated at the configured path, containing types like:
// src/app/config/autogen.d.ts (example content)
declare module "virtual:views" {
export const views: Array<{
route: ViewKeys;
path: string;
metadata: {
title: string;
description?: string;
authRequired?: boolean;
};
}>;
}
declare module "virtual:data" {
export const data: Array<number>;
}
type ViewKeys = "/" | "/home" | "/about" | "/products/[id]" | "/blog/post"; // Example union type
type AppRouteData = ViewKeys; // Example based on `strategy.types.name` for 'routes' componentThe createAutoloadPlugin function is the main entry point to integrate the plugin into your Vite configuration. It accepts a PluginOptions object that dictates how files are matched, transformed, and exposed as virtual modules.
// vite.config.ts
import { defineConfig } from "vite";
import { createAutoloadPlugin, extract } from "@asaidimu/vite-autoload";
import createAutoloadConfig from "./example.config";
export default defineConfig({
plugins: [createAutoloadPlugin(createAutoloadConfig({ extract }))],
});The PluginOptions object, which defines the comprehensive configuration for the plugin, is structured around settings (global options) and components (defining content categories and their transformation groups).
interface PluginOptions {
settings: {
rootDir?: string;
export?: {
types?: string;
routeLimit?: number;
};
sitemap?: SitemapConfig;
manifest?: ManifestConfig;
logLevel?: LogLevel;
extract?: ExtractFunction;
chunkSize?: number;
watch?: WatchOptions;
};
components: Array<ComponentConfig>;
}
type ComponentConfig = {
name: string;
description?: string;
metadata?: Record<string, unknown>;
strategy: {
sitemap?: { property: string };
types?: { name: string; property: string };
};
groups: Array<TransformConfig<any, any, any>>;
};
interface TransformConfig<InputData, TransformedOutput, AggregatedOutput> {
name: string;
description?: string;
metadata?: Record<string, unknown>;
input: FileMatchConfig | (() => Promise<Array<InputData>> | Array<InputData>);
output?: {
template?: string;
types?: { name: string; property: string };
};
transform?: (
item: ResolvedFile | InputData,
context: TransformContext,
) => TransformedOutput | Promise<TransformedOutput>;
aggregate?: (
items: Array<TransformedOutput | Promise<TransformedOutput>>,
) => AggregatedOutput | Promise<AggregatedOutput>;
}
// Full type definitions for `SitemapConfig`, `ManifestConfig`, `FileMatchConfig`,
// `ResolvedFile`, `TransformContext`, `ExtractFunction`, `LogLevel`, `WatchOptions`
// are available in the plugin's `src/types/` directory.Let's break down the key options:
A top-level object containing global configurations for the plugin.
rootDir:string(Optional, Default:process.cwd()) The base directory from which all relative paths (e.g., forexport.types) are resolved.export:object(Optional) Configuration for generating supplementary output files like TypeScript types, directly supporting the "Type Definition Generation" feature.types:string(Optional) The file path (relative torootDir) where the TypeScript declaration file (.d.ts) should be generated (e.g.,'src/app/config/autogen.d.ts'). This file will contain union types for module keys based on your configuration, ensuring type safety when consuming virtual modules.routeLimit:number(Optional, Default:1000) Limits the number of entries (e.g., routes) included in a generated TypeScript union type. Useful for very large projects to prevent excessively large type declarations that could impact editor performance.
sitemap:SitemapConfig(Optional) Configuration forsitemap.xmlgeneration, directly supporting the "Sitemap Generation" feature.output:string- The output filename for the generated sitemap XML file (e.g.,'sitemap.xml'). This file will be placed in your Vite build output directory.baseUrl:string- The base URL of your website (e.g.,'https://example.com'). Used to construct absolute URLs in the sitemap.exclude:ReadonlyArray<string>(Optional) - An array of glob patterns or regex strings to exclude specific routes from the sitemap (e.g.,['/admin/*', '/private/*']).
manifest:ManifestConfig(Optional) Configuration for PWA Web App Manifest (manifest.webmanifest) generation, directly supporting the "PWA Manifest Generation" feature. This object directly maps to standard Web App Manifest properties. Seesrc/types/manifest.tsfor full details.name:string- Full name of the application.shortName:string(Optional) - Short name, used when space is limited.description:string(Optional) - Description of the application.theme_color:string(Optional) - Default theme color.background_color:string(Optional) - Background color.display:"fullscreen" | "standalone" | "minimal-ui" | "browser"(Optional, Default:'standalone') - Preferred display mode.orientation:"any" | "natural" | "landscape" | "portrait"(Optional) - Preferred orientation.scope:string(Optional, Default:'/') - Navigation scope.start_url:string(Optional, Default:'/') - URL that loads when the user launches the app.icons:Array<{ src: string; sizes: string; type?: string; purpose?: "any" | "maskable" | "monochrome"; }>(Optional) - Array of icon objects.output:string(Optional, Default:'manifest.webmanifest') - Output filename for the manifest.
extract:(options: { filePath: string; schema: z.ZodType; name: string }) => Promise<Record<string, unknown> | null>(Required) A function that provides the logic for extracting metadata from source files, a core component of the "Metadata Extraction" feature. Theextractutility exported from@asaidimu/vite-autoload(re-exporting fromsrc/utils/metadata.ts) is specifically designed for this purpose, leveraging TypeScript's AST for static analysis and Zod for validation.filePath: The absolute path to the file to extract metadata from.schema: A Zod schema to validate the extracted metadata against.name: The name of the exported constant (e.g.,export const metadata = {...}orexport default {...}) to extract. Use'default'for default exports.
logLevel:LogLevel('debug'|'info'|'warn'|'error') (Optional, Default:'info') Controls the verbosity of the plugin's console output. Set to'debug'for detailed internal logs, useful for troubleshooting.chunkSize:number(Optional, Default:100) Controls the number of routes/modules processed in a single batch during the data generation phase. Adjusting this can help manage memory usage, especially for applications with a very large number of files.watch:WatchOptions(Optional) Configuration for the underlyingchokidarfile watcher, essential for "Hot Module Replacement (HMR)" during development.debounceTime:number(Optional, Default:1000) - The time in milliseconds to wait before triggering an update after a file change.stabilityThreshold:number(Optional, Default:300) - The time in milliseconds to wait for write operations to finish before processing a file change.
This is the core of the plugin's configuration. It is an Array<ComponentConfig>, where each ComponentConfig defines a logical category of content (e.g., "routes", "ui-modules") and how its associated groups of files or data sources should be processed. This section demonstrates the "Highly Configurable" nature of the plugin.
name:string(Required) A unique logical name for this high-level component category (e.g.,'routes','ui-modules','blog-posts'). This name is primarily for internal organization and helps categorize groups.description:string(Optional) A brief description for this component category.metadata:Record<string, unknown>(Optional) Optional arbitrary metadata associated with this component category.strategy:object(Required) Defines the global strategy for how the groups within this component category contribute to features like sitemap or type generation.sitemap:object(Optional) Sitemap generation output settings for this component's groups. Thepropertyspecified here is used to find the URL for sitemap entries within the transformed data from its groups.property:string- The property name from the transformed data (returned bytransformoraggregate) to use for sitemap entries (e.g.,'route'or'url').
types:object(Optional) Type generation output settings for this component's groups.name:string- The name of the generated TypeScript type for this component (e.g.,'ApplicationRoute','ComponentKey').property:string- The property to extract from the transformed/aggregated data for type generation (e.g.,'route'will create a union type of allroutestrings found in this component's data).
groups:Array<TransformConfig<any, any, any>>(Required) An array of individual transformation configurations (groups) that belong to this component category. EachTransformConfigdefines how specific input data is transformed and aggregated, directly substantiating the "Automatic Route & Module Generation" and "Automated Data Layer Generation" claims.name:string(Required) A unique name for this specific group (e.g.,'views','pages','components'). This name directly corresponds to the virtual module ID (e.g.,virtual:<group-name>).description:string(Optional) An optional description for the transformation pipeline.metadata:Record<string, unknown>(Optional) Optional arbitrary metadata associated with this transformation pipeline.input:FileMatchConfig | (() => Promise<Array<InputData>> | Array<InputData>)(Required) Configuration for matching input files, or a function providing direct data. This flexibility is key to "File-System Driven Routing" and "Automated Data Layer Generation".- If
FileMatchConfig(for file-based sources):directory:string- The base directory to scan for files (e.g.,'src/pages').match:Array<string> | string- Glob patterns (e.g.,'*.tsx','*/index.tsx') to match files within thedirectory.ignore:Array<string> | string(Optional) - Glob patterns to exclude files from matching.prefix:string(Optional) - A string prefix to add to theuriof resolved files (e.g.,/_assets/). Useful for organizing dynamically imported modules.data:Record<string, unknown>(Optional) - Additional static data to inject into each transformed item.
- If
() => Promise<Array<InputData>> | Array<InputData>(for programmatic data sources):- A function that returns an array of input data directly, allowing for non-file-system based data. This function can be
async(e.g., fetching data from a headless CMS API at build time).
- A function that returns an array of input data directly, allowing for non-file-system based data. This function can be
- If
output:object(Optional) Controls the output format of the virtual module, directly supporting "Virtual Modules".template:string- A template string for the generated virtual module code. Use{{ data }}as a placeholder for the processed JSON stringified data. Example:'export const {{ name }} = {{ data }};'.types:object(Optional) - Configuration for generating TypeScript types specific to this group, further ensuring "Type Definition Generation".name:string- The name of the TypeScript type to generate (e.g.,'DashboardViewData').property:string- The property name from the transformeditem(from thetransformfunction's return value) to use for constructing the union type.
transform:(item: ResolvedFile | InputData, context: TransformContext) => TransformedOutput | Promise<TransformedOutput>(Optional) A powerful function to transform each individualResolvedFileorInputDataobject into a desired custom data structure. This is where you can extract metadata (with theextractutility), construct routes, or format data, directly underpinning the "Metadata Extraction" and flexible "Automatic Route & Module Generation". This function can beasyncas well.item: TheResolvedFileobject (for file inputs) orInputDataobject (for programmatic inputs).context: Providesdata(from previous transformations),environment(e.g.,'build','dev').
aggregate:(items: Array<TransformedOutput | Promise<TransformedOutput>>) => AggregatedOutput | Promise<AggregatedOutput>(Optional) A function to perform aggregation on all transformed items for a given group, combining them into a single data structure (e.g., converting an array of objects into a map keyed by a specific property). This was introduced inv1.1.0. This function can also beasync.
The plugin exposes your configured groups as virtual modules. You can import them directly in your application code using the virtual: prefix, followed by the name of your group defined in TransformConfig. This demonstrates the "Virtual Modules" and "Automated Data Layer Generation" in action, providing type-safe and direct access to your processed assets.
// For a group named 'views' (configured as above, likely from `src/ui/pages`):
// The 'views' object will contain the data structure returned by your 'transform' and 'aggregate' functions.
import { views } from "virtual:views";
console.log("Available Views:", views);
/*
Example output in your console (after transformation based on your files):
[
{
route: '/dashboard',
path: '/_views/dashboard.js', // Path for dynamic import
metadata: { title: 'Dashboard', authRequired: true }
},
{
route: '/settings',
path: '/_views/settings.js',
metadata: { title: 'User Settings', description: 'Configure application preferences' }
}
// ... more views from your 'ui' directory
]
*/
// For a group named 'data' (from the programmatic source example defined in example.config.ts):
import { data } from "virtual:data";
console.log("Generated Data:", data);
/*
Example output:
[1, 2, 3, ..., 20]
*/
// Example of dynamically importing a view component based on the generated data:
// (Requires a routing library like React Router, Vue Router, etc.)
// In a routing setup, you might map the `route` to the actual component file:
async function loadViewComponent(routePath: ViewKeys) {
// `ViewKeys` is type-safe due to `output.types`
// Find the corresponding entry in the generated 'views' data
const viewEntry = views.find((v) => v.route === routePath);
if (!viewEntry) {
console.error(`View for route ${routePath} not found.`);
return null;
}
// Dynamically import the component using the `path` provided by the transform function
const componentModule = await import(/* @vite-ignore */ viewEntry.path);
return componentModule.default; // Assuming components are default exports
}
// Usage in a framework, e.g., React:
// const MyDynamicComponent = React.lazy(() => loadViewComponent('/dashboard'));@asaidimu/vite-autoload is structured around a core Vite plugin that orchestrates file watching, module generation, and integrates with specialized generators for sitemaps, PWA manifests, and powerful metadata extraction. This section provides insights into how the plugin achieves its advertised features internally, demonstrating its robust design.
createAutoloadPlugin: The central orchestrator of the plugin. It initializes collection generators, sets up the file watcher, registers Vite hooks, and coordinates the generation of sitemaps and PWA manifests. It manages the virtual module lifecycle and Hot Module Replacement (HMR).createCollectionGenerator: Responsible for discovering files based onFileMatchConfigor resolving data from aDataSourcefunction. It appliestransformfunctions to each item's data, optionally appliesaggregatefunctions to the collection, and ultimately prepares the data for code generation for the virtual modules (e.g.,virtual:views,virtual:components). It maintains an internal cache of resolved data.createFileResolver: Manages the initial discovery and ongoing tracking of files that match configured glob patterns within specified directories. It efficiently resolves paths and maintains an internal cache ofResolvedFileobjects.createDataResolver: Handles the resolution of data from programmaticDataSourcefunctions, caching their results.createDataProcessor: Handles the transformation and aggregation pipeline for the data associated with discovered files or resolved data sources. It applies thetransformfunction to individual entries and theaggregatefunction to the collection of transformed data, preparing it for code generation.createCodeGenerator: Generates the final JavaScript code string that constitutes the virtual module, based on the processed data and an optional output template.createFileWatcher: Utilizes thechokidarlibrary to efficiently monitor specified directories for file additions, changes, and deletions. When changes are detected, it debounces and triggers an update callback within the plugin, leading to regeneration of module data and HMR.createMetadataExtractor: A sophisticated utility that performs static code analysis on TypeScript files using thetypescriptAST parser. It can extract the values of specificexport constorexport defaultobject literals, handling various literal types, array literals, object literals, and even transforming imported modules into dynamic import functions. Extracted data is validated against provided Zod schemas.NameIndex: A utility for ensuring uniqueness of component and group names within the plugin's configuration, preventing naming conflicts.
- Initialization:
- During Vite's
configResolvedhook, the plugin stores the Vite configuration. - On
configureServer(development) orbuildStart(production),createAutoloadPlugininitializescreateCollectionGeneratorinstances for all configuredcomponentsand theirgroups. Each generator uses an internalFileResolverto perform an initial file scan andDataResolverfor programmatic data, building its cache of matched files/data. - Internal caches (
fileToExportMap,virtualModuleCache,importerToVirtualDeps,virtualModuleDeps) are populated based on the initial file/data scan.
- During Vite's
- File Watching & HMR (Development):
createFileWatcherstarts monitoring the directories specified in yourPluginOptions.components[].groups[].input. This is fundamental for the "Hot Module Replacement (HMR)" feature.- When a file within a watched directory is added, changed, or removed, the watcher triggers a debounced callback.
- The callback triggers regeneration of data for affected virtual modules within their respective
createCollectionGeneratorinstances. - The plugin compares the new data hash with the cached hash for each virtual module. If a change is detected, it invalidates the corresponding virtual module in Vite's module graph.
- Crucially, it also uses
es-module-lexerto find all modules (importers) thatimportthese virtual modules. These importers are then also invalidated, triggering a fast HMR update in the browser without a full page reload.
- Virtual Module Loading:
- When application code imports a virtual module (e.g.,
import { views } from 'virtual:views';), Vite'sresolveIdhook routes the request to the plugin. - The
loadhook then uses the relevantcreateCollectionGeneratorto produce the JavaScript code string containing the processed data, which Vite serves to the browser, enabling the "Virtual Modules" feature.
- When application code imports a virtual module (e.g.,
- Build Phase:
- During
buildStart, the plugin tells Vite to emit all source files identified by thecreateCollectionGeneratoras separate chunks, allowing for optimal code splitting. It also generates the TypeScript types as part of "Type Definition Generation". - During
closeBundle(after Vite's main build is complete),generateManifestusesPluginOptions.settings.manifestto createmanifest.webmanifest, andgenerateSitemapusesPluginOptions.settings.sitemapand the final route data to createsitemap.xml. These are emitted as assets, directly enabling "Sitemap Generation" and "PWA Manifest Generation".
- During
transformFunction: This is the primary way to customize the data payload for each discovered file or data item. It allows you to define how aResolvedFileobject orInputDatais processed into the desired structure, enabling custom route generation, metadata enrichment, and more. This function can be asynchronous.aggregateFunction: Provides a powerful hook to combine all transformed items for a given group into a single, aggregated data structure. This is ideal for converting arrays of items into objects/maps keyed by a specific property, optimizing data access in your application. This function can also be asynchronous.extractFunction: A crucial extension point for performing custom static analysis on your source files. By providing an implementation (like the one fromsrc/utils/metadata.ts), you can read and validate specific exported metadata from your components or pages, making it available in the generated virtual modules. This function is asynchronous.
To set up the @asaidimu/vite-autoload project for local development:
-
Clone the repository:
git clone [https://github.com/asaidimu/vite-autoload.git](https://github.com/asaidimu/vite-autoload.git) cd vite-autoload -
Install dependencies: The project uses Bun as the preferred package manager, but npm/yarn will also work.
# Using bun (recommended) bun install # Or using npm npm install
The package.json defines several useful scripts for development and building:
bun ci: Installs project dependencies. Used in CI environments.bun dev: Starts the Vite development server with the plugin active, allowing you to test changes locally. This runsvitefrom the root, utilizingvite.config.ts.bun clean: Removes thedistoutput directory.bun prebuild: Cleans thedistdirectory and runs an internal synchronization script (.sync-package.ts) before building.bun build: Compiles the TypeScript source files (src/) into JavaScript and TypeScript declaration files (.d.ts) in thedistdirectory.bun postbuild: Copies essential files (README.md,LICENSE.md,dist.package.json,example.config.ts) into thedistdirectory, preparing the package for publishing.
Currently, explicit unit or integration test files (.test.ts, .spec.ts) are not present in the provided codebase snapshot. For thorough testing of a plugin like this, you would typically include:
- Unit Tests: For utilities (e.g.,
debounce,hash,checkers,logger,uri) and smaller components (e.g.,metadataextractor logic,sitemapgeneration). - Integration Tests: To verify the plugin's behavior within a Vite environment, ensuring virtual modules load correctly, HMR works, and assets are generated as expected during build.
If tests were available, you would typically run them with a command like:
# Placeholder for running tests (if tests were present)
bun test
# or
npm testContributions are highly welcome! If you're interested in contributing to @asaidimu/vite-autoload, please follow these guidelines:
- Fork the Repository: Start by forking the repository on GitHub.
- Clone Your Fork: Clone your forked repository to your local development machine.
git clone [https://github.com/YOUR_USERNAME/vite-autoload.git](https://github.com/YOUR_USERNAME/vite-autoload.git) cd vite-autoload - Create a New Branch: Always create a new branch for your feature or bug fix. Use descriptive names like
feature/add-new-config-optionorfix/hmr-issue.git checkout -b feature/your-feature-name
- Make Your Changes: Implement your changes, ensuring they align with the existing code style and architecture.
- Write Tests (if applicable): If you're adding new features or fixing bugs, please consider adding relevant tests to ensure correctness and prevent regressions.
- Commit Messages: Write clear, concise, and descriptive commit messages. Follow Conventional Commits guidelines (e.g.,
feat: Add new configuration option,fix: Resolve HMR issue). Thepackage.jsonreleaseconfiguration uses@semantic-release/commit-analyzer, indicating strict adherence to these. - Push to Your Fork: Push your changes to your fork on GitHub.
git push origin feature/your-feature-name
- Open a Pull Request: Go to the original
@asaidimu/vite-autoloadrepository on GitHub and open a Pull Request from your new branch. Provide a detailed description of your changes.
If you encounter any bugs, have feature requests, or need assistance, please open an issue on our GitHub Issues page. When reporting bugs, please provide:
- A clear and concise description of the issue.
- Steps to reproduce the behavior.
- Expected behavior.
- Actual behavior.
- Screenshots or code snippets if helpful.
- Your environment details (Node.js version, Vite version, OS).
This plugin is designed with performance and developer experience in mind. Here are some guidelines and troubleshooting tips to help you get the most out of it:
- Memory Usage Guidelines:
- Limit Watched Directories: Configure
input.directoryandinput.ignoreprecisely to only watch files and directories that are directly relevant to your generated modules. Avoid watching unnecessary large directories likenode_modulesor extensive test folders.// In example.config.ts components: [ { name: "routes", groups: [ { name: "views", input: { directory: "src/app/views", match: ["**/*.vue", "**/*.tsx"], ignore: ["**/node_modules/**", "**/__tests__/**"], }, }, ], }, ];
- Module Processing Chunking: For very large applications with thousands of routes or modules, adjusting the
chunkSizeoption inPluginOptions.settingscan help manage memory usage by processing files in smaller batches. A smallerchunkSize(e.g.,50) reduces peak memory consumption, though it might slightly increase overall processing time.// In example.config.ts settings: { // ... chunkSize: 50, // Process 50 items at a time within a group },
- Type Generation Limit: The
export.routeLimitoption (inPluginOptions.settings.export) helps prevent generating excessively large TypeScript union types for applications with a very high route count. Large type definitions can sometimes impact editor responsiveness and type-checking performance.// In example.config.ts settings: { export: { types: 'src/types/autogen.d.ts', routeLimit: 500 // Limit generated route type to 500 entries }, // ... },
- Limit Watched Directories: Configure
- File Watching Stability: The
watchoptions (debounceTime,stabilityThreshold) can be fine-tuned to improve the file watcher's responsiveness and stability, especially in environments with slow I/O or network drives, or when dealing with applications that generate many temporary files.debounceTime: Increase this value if rapid successive file saves lead to multiple, redundant rebuilds or HMR updates.stabilityThreshold: Increase this value if you notice that file changes are sometimes processed before the files are fully written to disk, leading to partial or empty content being read.
- HMR Not Triggering Correctly:
- Virtual Module Imports: Ensure that your application's entry points or other modules actually
importthe virtual modules (e.g.,import { views } from 'virtual:views';). The plugin's HMR mechanism relies on Vite's module graph to track importers. logLevelDebugging: SetlogLevel: 'debug'in yourPluginOptions.settingsto get detailed console output about file changes, module invalidations, and HMR events. This can help you diagnose why HMR might not be triggering as expected.- Consistent Hashing: The
getDataHashfunction insrc/plugin/utils.tsusesJSON.stringify. For complex data structures, inconsistent key order in stringification might lead to false positives for changes. If you experience unexpected HMR, consider a stable stringify library or a dedicated object hashing library forgetDataHashif your data structures are highly dynamic.
- Virtual Module Imports: Ensure that your application's entry points or other modules actually
- Metadata Extraction Errors:
- Schema Mismatch: If your
extractfunction is failing or returning unexpected results, carefully review theschema(Zod type) you are providing. It must accurately match the structure of the metadata object you are attempting to extract from your source files. - Syntax Errors: Ensure the source files from which you are extracting metadata have valid TypeScript/JavaScript syntax. Parsing errors in the source file can prevent metadata extraction.
- Static Analysis Limitations: The
extractutility performs static analysis of your code. It can successfully extract values from literal expressions (strings, numbers, booleans, arrays, objects) and identifiers that resolve to such literals or imported modules. It cannot evaluate complex runtime logic, function calls, or dynamic computations within the metadata object.
- Schema Mismatch: If your
For a detailed history of changes, new features, and bug fixes, please refer to the CHANGELOG.md file. Note the significant breaking changes introduced in versions 3.0.0 and 4.0.0 related to plugin configuration structure.
This project is licensed under the MIT License.
This project is developed and maintained by Saidimu. It leverages several excellent open-source libraries and tools, including Vite, Chokidar, Zod, es-module-lexer, Babel, and Playwright (for dev environment), for which we are immensely grateful.