Vite - a script for quick template-based project scaffolding
Photo by Luis González Sosa / Unsplash

Vite - a script for quick template-based project scaffolding

This script gives me a consistent start for a new project, with my standard logging, utilities, and other things I rely on and would otherwise copy and paste from previous projects.

The motivation for this was that I develop with the Konva canvas lib - if you don't know it check Konva out here - and found myself starting a lot of similar projects lately. I'd evolved a mental template for these projects, but was setting up each by hand. I decided to take time out to see if I could automate that. This is the result.

TLDR: The script installs Vite into the target folder, copies over the files from the template folder, replacing specific tokens defined in the script as it goes. All we need to do is open the folder in the editor and start the dev server.

This doesn't need a lot of explaining so here's the script.

/**
 * Setup VW project using Vite template
 * Usage: node vwSetup.cjs --targetPath=<new-project-dir> --projectName=<name> [--copyFrom=<template-dir>]
 */
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

// Get configuration from arguments or npm config
function getArg(name) {
    // Try npm config first
    const npmValue = process.env[`npm_config_${name}`];
    if (npmValue) return npmValue;
    
    // Fall back to command line args
    const arg = process.argv.find(arg => arg.startsWith(`--${name}=`));
    return arg ? arg.split('=')[1] : null;
}

// Get configuration
const targetPath = getArg('targetPath');
const templatePath = getArg('copyFrom');
const projectName = getArg('projectName') || (targetPath ? path.basename(targetPath) : null);

if (!targetPath) {
    console.error('Error: --targetPath is required');
    process.exit(1);
}

// Token mappings
const tokens = {
    '##productname##': projectName,
};

// Copy template files
function copyTemplate(source, target) {
    const files = fs.readdirSync(source);
    
    files.forEach(file => {
        const sourcePath = path.join(source, file);
        const targetPath = path.join(target, file);
        console.log(`Processing: ${sourcePath} -> ${targetPath}`);
        const stats = fs.statSync(sourcePath);
        
        if (stats.isDirectory()) {
            if (!fs.existsSync(targetPath)) {
                fs.mkdirSync(targetPath, { recursive: true });
            }
            copyTemplate(sourcePath, targetPath);
        } else {
            // Read and process file content
            let content = fs.readFileSync(sourcePath, 'utf8');
            
            // Replace tokens
            Object.entries(tokens).forEach(([token, value]) => {
                content = content.replace(new RegExp(token, 'g'), value);
            });
            
            // Write processed content
            fs.writeFileSync(targetPath, content);
        }
    });
}

// Main setup process
try {
    console.log(`Setting up project "${projectName}"...`);
    
    // Change to parent directory first
    const parentDir = path.dirname(targetPath);
    const projectDir = path.basename(targetPath);
    
    console.log(`Creating in: ${parentDir}`);
    console.log(`Project folder: ${projectDir}`);
    
    // Create parent directory if it doesn't exist
    if (!fs.existsSync(parentDir)) {
        fs.mkdirSync(parentDir, { recursive: true });
    }
    
    // Change to parent directory
    process.chdir(parentDir);
    
    // Create Vite project using just the project folder name
    console.log('Creating Vite project...');
    execSync(`npm create vite@latest "${projectDir}" -y -- --template vanilla-ts --yes`, { stdio: 'inherit' });
    
    // Change to project directory and install dependencies
    process.chdir(projectDir);
    console.log(`Installing dependencies in ${projectDir}...`);
    execSync('npm install', { stdio: 'inherit' });

    // Apply template customizations if template path is provided
    if (templatePath) {
        console.log('Applying template customizations...');
        console.log(`Template source: ${templatePath}`);
        copyTemplate(templatePath, '.');
    }

    console.log('\nSetup completed successfully! 🎉');
    console.log('\nNext steps:');
    console.log(`1. Change to the project directory:`);
    console.log(`   cd "${process.cwd()}"`);
    console.log(`2. Go to File -> Open Folder`);
    console.log(`3. Navigate to: ${process.cwd()}`);
    console.log(`4. Start the development server:`);
    console.log('   npm run dev\n');
    console.log('[When you open the folder in the editor this text will be lost!]\n');
    
    console.log('Happy coding! 🚀\n');
} catch (error) {
    console.error('Setup failed:', error.message);
    process.exit(1);
}

I really wanted to be able to run the script and just have the editor switch to the new project folder, but because of the sensible security constraints a script running in the command window cannot directly affect the editor it is running within, so we have to get the developer to run the last couple of steps. But that's what we would have had to do for the standard Vite setup anyway, so no net loss.

Summary

We set up our template folder, run the script, switch folders, and we hit the ground running with a beautifully formed project with no need to waste time setting up our preferred project config. Sweet.

Thanks for reading.

VW. April 2025