Skip to main content

Build Process Guide

This guide covers the complete build process for Qirvo plugins, including development builds, production optimization, bundling strategies, and deployment preparation.

Table of Contents

Build System Overview

Build Configuration

// Comprehensive build configuration
export interface BuildConfig {
mode: 'development' | 'production';
target: 'web' | 'node' | 'universal';
entry: string;
output: {
path: string;
filename: string;
format: 'cjs' | 'esm' | 'umd';
};
optimization: OptimizationConfig;
plugins: PluginConfig[];
externals: Record<string, string>;
}

export class BuildManager {
private config: BuildConfig;
private compiler: any;

constructor(config: BuildConfig) {
this.config = config;
this.setupCompiler();
}

private setupCompiler(): void {
const webpack = require('webpack');
const webpackConfig = this.generateWebpackConfig();
this.compiler = webpack(webpackConfig);
}

private generateWebpackConfig(): any {
return {
mode: this.config.mode,
entry: this.config.entry,
output: {
path: this.config.output.path,
filename: this.config.output.filename,
library: {
type: this.config.output.format === 'cjs' ? 'commonjs2' :
this.config.output.format === 'esm' ? 'module' : 'umd'
},
clean: true
},
externals: this.config.externals,
module: {
rules: this.getModuleRules()
},
plugins: this.getWebpackPlugins(),
optimization: this.getOptimizationConfig(),
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
alias: {
'@': path.resolve(process.cwd(), 'src')
}
}
};
}

private getModuleRules(): any[] {
return [
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json',
transpileOnly: this.config.mode === 'development'
}
}
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
this.config.mode === 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset/resource',
generator: {
filename: 'assets/images/[name].[hash][ext]'
}
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'assets/fonts/[name].[hash][ext]'
}
}
];
}

async build(): Promise<BuildResult> {
return new Promise((resolve, reject) => {
this.compiler.run((err: any, stats: any) => {
if (err) {
reject(err);
return;
}

const result: BuildResult = {
success: !stats.hasErrors(),
errors: stats.compilation.errors,
warnings: stats.compilation.warnings,
assets: this.extractAssetInfo(stats),
buildTime: stats.endTime - stats.startTime,
bundleSize: this.calculateBundleSize(stats)
};

resolve(result);
});
});
}

async watch(callback: (result: BuildResult) => void): Promise<void> {
this.compiler.watch({
aggregateTimeout: 300,
poll: undefined
}, (err: any, stats: any) => {
if (err) {
console.error('Build error:', err);
return;
}

const result: BuildResult = {
success: !stats.hasErrors(),
errors: stats.compilation.errors,
warnings: stats.compilation.warnings,
assets: this.extractAssetInfo(stats),
buildTime: stats.endTime - stats.startTime,
bundleSize: this.calculateBundleSize(stats)
};

callback(result);
});
}
}

interface BuildResult {
success: boolean;
errors: any[];
warnings: any[];
assets: AssetInfo[];
buildTime: number;
bundleSize: number;
}

interface AssetInfo {
name: string;
size: number;
type: string;
}

Development Builds

Development Configuration

// Development-specific build configuration
export class DevelopmentBuilder extends BuildManager {
constructor() {
super({
mode: 'development',
target: 'web',
entry: './src/index.ts',
output: {
path: path.resolve(process.cwd(), 'dist'),
filename: 'plugin.js',
format: 'cjs'
},
optimization: {
minimize: false,
splitChunks: false,
sideEffects: false
},
plugins: [
'HotModuleReplacementPlugin',
'SourceMapDevToolPlugin'
],
externals: {
'@qirvo/plugin-sdk': 'commonjs2 @qirvo/plugin-sdk',
'react': 'commonjs2 react',
'react-dom': 'commonjs2 react-dom'
}
});
}

async startDevServer(): Promise<DevServer> {
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');

const devServerConfig = {
static: {
directory: path.join(process.cwd(), 'public')
},
hot: true,
port: 3001,
open: false,
headers: {
'Access-Control-Allow-Origin': '*'
},
client: {
overlay: {
errors: true,
warnings: false
}
}
};

const server = new WebpackDevServer(devServerConfig, this.compiler);
await server.start();

return new DevServer(server, devServerConfig.port);
}
}

export class DevServer {
private server: any;
private port: number;

constructor(server: any, port: number) {
this.server = server;
this.port = port;
}

getUrl(): string {
return `http://localhost:${this.port}`;
}

async stop(): Promise<void> {
await this.server.stop();
}

onReload(callback: () => void): void {
this.server.middleware.waitUntilValid(callback);
}
}

Hot Module Replacement

// HMR setup for plugin development
export class HMRManager {
private static instance: HMRManager;
private updateHandlers: Map<string, () => void> = new Map();

static getInstance(): HMRManager {
if (!this.instance) {
this.instance = new HMRManager();
}
return this.instance;
}

setup(): void {
if (module.hot) {
module.hot.accept();

// Handle plugin updates
module.hot.accept('./plugin', () => {
this.handlePluginUpdate();
});

// Handle component updates
module.hot.accept('./components', () => {
this.handleComponentUpdate();
});

// Handle style updates
module.hot.accept('./styles', () => {
this.handleStyleUpdate();
});
}
}

private handlePluginUpdate(): void {
console.log('🔄 Plugin updated, reloading...');

// Notify update handlers
this.updateHandlers.forEach(handler => {
try {
handler();
} catch (error) {
console.error('HMR update handler failed:', error);
}
});

// Reload plugin instance
this.reloadPlugin();
}

private handleComponentUpdate(): void {
console.log('🎨 Components updated');
// React Fast Refresh will handle component updates
}

private handleStyleUpdate(): void {
console.log('💄 Styles updated');
// CSS updates are handled automatically
}

private reloadPlugin(): void {
// Implementation would reload the plugin instance
window.location.reload();
}

onUpdate(id: string, handler: () => void): void {
this.updateHandlers.set(id, handler);
}

offUpdate(id: string): void {
this.updateHandlers.delete(id);
}
}

Production Builds

Production Optimization

// Production build configuration with optimizations
export class ProductionBuilder extends BuildManager {
constructor() {
super({
mode: 'production',
target: 'web',
entry: './src/index.ts',
output: {
path: path.resolve(process.cwd(), 'dist'),
filename: 'plugin.[contenthash].js',
format: 'cjs'
},
optimization: {
minimize: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
sideEffects: false,
usedExports: true
},
plugins: [
'TerserPlugin',
'MiniCssExtractPlugin',
'CompressionPlugin',
'BundleAnalyzerPlugin'
],
externals: {
'@qirvo/plugin-sdk': 'commonjs2 @qirvo/plugin-sdk',
'react': 'commonjs2 react',
'react-dom': 'commonjs2 react-dom'
}
});
}

private getOptimizationConfig(): any {
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

return {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info', 'console.debug']
},
mangle: {
keep_fnames: false
},
format: {
comments: false
}
},
extractComments: false
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true
}
}
},
usedExports: true,
sideEffects: false
};
}

async buildForProduction(): Promise<ProductionBuildResult> {
console.log('🏗️ Starting production build...');

const startTime = Date.now();
const result = await this.build();
const buildTime = Date.now() - startTime;

if (result.success) {
console.log('✅ Production build completed successfully');

// Generate build report
const report = await this.generateBuildReport(result);

// Validate build
const validation = await this.validateBuild();

return {
...result,
buildTime,
report,
validation
};
} else {
console.error('❌ Production build failed');
throw new Error('Build failed');
}
}

private async generateBuildReport(result: BuildResult): Promise<BuildReport> {
return {
timestamp: new Date().toISOString(),
bundleSize: result.bundleSize,
assets: result.assets,
dependencies: await this.analyzeDependencies(),
performance: await this.analyzePerformance(result)
};
}

private async validateBuild(): Promise<ValidationResult> {
const validations: ValidationCheck[] = [];

// Check bundle size
const bundleSize = await this.getBundleSize();
validations.push({
name: 'Bundle Size',
passed: bundleSize < 1024 * 1024, // 1MB limit
message: bundleSize < 1024 * 1024 ?
`Bundle size OK (${(bundleSize / 1024).toFixed(2)}KB)` :
`Bundle size too large (${(bundleSize / 1024 / 1024).toFixed(2)}MB)`
});

// Check for source maps in production
const hasSourceMaps = await this.checkForSourceMaps();
validations.push({
name: 'Source Maps',
passed: !hasSourceMaps,
message: hasSourceMaps ?
'Source maps found in production build' :
'No source maps in production build'
});

// Check for console statements
const hasConsoleStatements = await this.checkForConsoleStatements();
validations.push({
name: 'Console Statements',
passed: !hasConsoleStatements,
message: hasConsoleStatements ?
'Console statements found in production build' :
'No console statements in production build'
});

return {
passed: validations.every(v => v.passed),
checks: validations
};
}
}

interface ProductionBuildResult extends BuildResult {
report: BuildReport;
validation: ValidationResult;
}

interface BuildReport {
timestamp: string;
bundleSize: number;
assets: AssetInfo[];
dependencies: DependencyInfo[];
performance: PerformanceInfo;
}

interface ValidationResult {
passed: boolean;
checks: ValidationCheck[];
}

interface ValidationCheck {
name: string;
passed: boolean;
message: string;
}

Bundle Optimization

Code Splitting Strategy

// Advanced code splitting and optimization
export class BundleOptimizer {
private analyzer: BundleAnalyzer;

constructor() {
this.analyzer = new BundleAnalyzer();
}

async optimizeBundle(config: BuildConfig): Promise<OptimizationResult> {
const analysis = await this.analyzer.analyze();
const optimizations: Optimization[] = [];

// Identify large dependencies
const largeDependencies = analysis.dependencies
.filter(dep => dep.size > 100 * 1024) // > 100KB
.sort((a, b) => b.size - a.size);

if (largeDependencies.length > 0) {
optimizations.push({
type: 'dependency-splitting',
description: 'Split large dependencies into separate chunks',
impact: 'Improved caching and loading performance',
dependencies: largeDependencies.map(dep => dep.name)
});
}

// Check for duplicate code
const duplicates = await this.findDuplicateCode(analysis);
if (duplicates.length > 0) {
optimizations.push({
type: 'deduplication',
description: 'Remove duplicate code across chunks',
impact: 'Reduced bundle size',
duplicates
});
}

// Analyze unused exports
const unusedExports = await this.findUnusedExports(analysis);
if (unusedExports.length > 0) {
optimizations.push({
type: 'tree-shaking',
description: 'Remove unused exports',
impact: 'Reduced bundle size',
unusedExports
});
}

return {
currentSize: analysis.totalSize,
optimizations,
estimatedSavings: this.calculateSavings(optimizations)
};
}

generateSplitChunksConfig(analysis: BundleAnalysis): any {
return {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
// Vendor libraries
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},

// Large libraries get their own chunk
...this.generateLargeLibraryChunks(analysis),

// Common code
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true
}
}
};
}

private generateLargeLibraryChunks(analysis: BundleAnalysis): any {
const largeLibs = analysis.dependencies
.filter(dep => dep.size > 200 * 1024) // > 200KB
.reduce((chunks, dep) => {
const chunkName = dep.name.replace(/[^a-zA-Z0-9]/g, '');
chunks[chunkName] = {
test: new RegExp(`[\\/]node_modules[\\/]${dep.name}[\\/]`),
name: chunkName,
chunks: 'all',
priority: 15
};
return chunks;
}, {} as any);

return largeLibs;
}

private async findDuplicateCode(analysis: BundleAnalysis): Promise<string[]> {
// Implementation would analyze for duplicate code patterns
return [];
}

private async findUnusedExports(analysis: BundleAnalysis): Promise<string[]> {
// Implementation would analyze for unused exports
return [];
}

private calculateSavings(optimizations: Optimization[]): number {
// Calculate estimated size savings from optimizations
return optimizations.reduce((total, opt) => {
switch (opt.type) {
case 'deduplication':
return total + 50 * 1024; // Estimated 50KB savings
case 'tree-shaking':
return total + 30 * 1024; // Estimated 30KB savings
default:
return total;
}
}, 0);
}
}

interface BundleAnalysis {
totalSize: number;
dependencies: DependencyInfo[];
chunks: ChunkInfo[];
}

interface DependencyInfo {
name: string;
size: number;
version: string;
used: boolean;
}

interface ChunkInfo {
name: string;
size: number;
modules: string[];
}

interface Optimization {
type: string;
description: string;
impact: string;
[key: string]: any;
}

interface OptimizationResult {
currentSize: number;
optimizations: Optimization[];
estimatedSavings: number;
}

Asset Management

Asset Processing Pipeline

// Comprehensive asset management
export class AssetManager {
private processors: Map<string, AssetProcessor> = new Map();

constructor() {
this.setupProcessors();
}

private setupProcessors(): void {
// Image processor
this.processors.set('image', new ImageProcessor());

// Font processor
this.processors.set('font', new FontProcessor());

// CSS processor
this.processors.set('css', new CSSProcessor());

// JavaScript processor
this.processors.set('js', new JavaScriptProcessor());
}

async processAssets(assets: Asset[]): Promise<ProcessedAsset[]> {
const processedAssets: ProcessedAsset[] = [];

for (const asset of assets) {
const processor = this.processors.get(asset.type);
if (processor) {
const processed = await processor.process(asset);
processedAssets.push(processed);
} else {
// No processor, copy as-is
processedAssets.push({
...asset,
processed: true,
optimizations: []
});
}
}

return processedAssets;
}

async optimizeAssets(assets: ProcessedAsset[]): Promise<OptimizedAsset[]> {
const optimized: OptimizedAsset[] = [];

for (const asset of assets) {
const processor = this.processors.get(asset.type);
if (processor && processor.optimize) {
const optimizedAsset = await processor.optimize(asset);
optimized.push(optimizedAsset);
} else {
optimized.push(asset as OptimizedAsset);
}
}

return optimized;
}
}

// Image processor
class ImageProcessor implements AssetProcessor {
async process(asset: Asset): Promise<ProcessedAsset> {
const optimizations: string[] = [];

// Compress images
if (asset.size > 100 * 1024) { // > 100KB
optimizations.push('compression');
}

// Generate WebP versions
if (asset.name.match(/\.(jpg|jpeg|png)$/i)) {
optimizations.push('webp-generation');
}

// Generate responsive sizes
if (asset.name.match(/\.(jpg|jpeg|png)$/i)) {
optimizations.push('responsive-sizes');
}

return {
...asset,
processed: true,
optimizations
};
}

async optimize(asset: ProcessedAsset): Promise<OptimizedAsset> {
let optimizedSize = asset.size;
const variants: AssetVariant[] = [];

// Apply compression
if (asset.optimizations.includes('compression')) {
optimizedSize *= 0.7; // Estimated 30% compression
}

// Generate WebP variant
if (asset.optimizations.includes('webp-generation')) {
variants.push({
format: 'webp',
size: optimizedSize * 0.8, // WebP is ~20% smaller
url: asset.url.replace(/\.(jpg|jpeg|png)$/i, '.webp')
});
}

// Generate responsive variants
if (asset.optimizations.includes('responsive-sizes')) {
const sizes = [480, 768, 1024, 1920];
sizes.forEach(width => {
variants.push({
format: 'responsive',
size: optimizedSize * (width / 1920), // Proportional size
url: asset.url.replace(/(\.[^.]+)$/, `@${width}w$1`),
width
});
});
}

return {
...asset,
optimizedSize,
variants,
compressionRatio: asset.size / optimizedSize
};
}
}

// CSS processor
class CSSProcessor implements AssetProcessor {
async process(asset: Asset): Promise<ProcessedAsset> {
const optimizations: string[] = [];

// Minification
optimizations.push('minification');

// Autoprefixer
optimizations.push('autoprefixer');

// Critical CSS extraction
if (asset.name.includes('main') || asset.name.includes('app')) {
optimizations.push('critical-css');
}

return {
...asset,
processed: true,
optimizations
};
}

async optimize(asset: ProcessedAsset): Promise<OptimizedAsset> {
let optimizedSize = asset.size;

// Minification saves ~30%
if (asset.optimizations.includes('minification')) {
optimizedSize *= 0.7;
}

return {
...asset,
optimizedSize,
variants: [],
compressionRatio: asset.size / optimizedSize
};
}
}

interface Asset {
name: string;
type: string;
url: string;
size: number;
content?: Buffer | string;
}

interface ProcessedAsset extends Asset {
processed: boolean;
optimizations: string[];
}

interface OptimizedAsset extends ProcessedAsset {
optimizedSize: number;
variants: AssetVariant[];
compressionRatio: number;
}

interface AssetVariant {
format: string;
size: number;
url: string;
width?: number;
height?: number;
}

interface AssetProcessor {
process(asset: Asset): Promise<ProcessedAsset>;
optimize?(asset: ProcessedAsset): Promise<OptimizedAsset>;
}

Build Automation

CI/CD Integration

// Build automation for CI/CD pipelines
export class BuildAutomation {
private config: AutomationConfig;

constructor(config: AutomationConfig) {
this.config = config;
}

async runPipeline(): Promise<PipelineResult> {
const steps: PipelineStep[] = [
{ name: 'install', fn: () => this.installDependencies() },
{ name: 'lint', fn: () => this.runLinting() },
{ name: 'test', fn: () => this.runTests() },
{ name: 'build', fn: () => this.runBuild() },
{ name: 'validate', fn: () => this.validateBuild() },
{ name: 'package', fn: () => this.packagePlugin() }
];

const results: StepResult[] = [];
let failed = false;

for (const step of steps) {
if (failed && !step.continueOnFailure) {
results.push({
name: step.name,
status: 'skipped',
duration: 0
});
continue;
}

console.log(`🔄 Running step: ${step.name}`);
const startTime = Date.now();

try {
await step.fn();
const duration = Date.now() - startTime;

results.push({
name: step.name,
status: 'success',
duration
});

console.log(`✅ Step ${step.name} completed in ${duration}ms`);
} catch (error) {
const duration = Date.now() - startTime;
failed = true;

results.push({
name: step.name,
status: 'failed',
duration,
error: error.message
});

console.error(`❌ Step ${step.name} failed: ${error.message}`);
}
}

return {
success: !failed,
steps: results,
totalDuration: results.reduce((sum, step) => sum + step.duration, 0)
};
}

private async installDependencies(): Promise<void> {
const { execSync } = require('child_process');
execSync('npm ci', { stdio: 'inherit' });
}

private async runLinting(): Promise<void> {
const { execSync } = require('child_process');
execSync('npm run lint', { stdio: 'inherit' });
}

private async runTests(): Promise<void> {
const { execSync } = require('child_process');
execSync('npm run test:ci', { stdio: 'inherit' });
}

private async runBuild(): Promise<void> {
const builder = new ProductionBuilder();
const result = await builder.buildForProduction();

if (!result.success) {
throw new Error('Build failed');
}
}

private async validateBuild(): Promise<void> {
// Validate build artifacts
const requiredFiles = ['dist/plugin.js', 'manifest.json', 'package.json'];

for (const file of requiredFiles) {
if (!fs.existsSync(file)) {
throw new Error(`Required file missing: ${file}`);
}
}
}

private async packagePlugin(): Promise<void> {
const packager = new PluginPackager();
await packager.createPackage();
}
}

interface AutomationConfig {
environment: 'development' | 'staging' | 'production';
skipTests?: boolean;
skipLinting?: boolean;
outputDir?: string;
}

interface PipelineStep {
name: string;
fn: () => Promise<void>;
continueOnFailure?: boolean;
}

interface StepResult {
name: string;
status: 'success' | 'failed' | 'skipped';
duration: number;
error?: string;
}

interface PipelineResult {
success: boolean;
steps: StepResult[];
totalDuration: number;
}

This build process guide provides comprehensive tools and strategies for building, optimizing, and deploying Qirvo plugins efficiently.

Next: Distribution Methods