{"aiPlatform":"claude-code@2025.06","category":"development","commandName":"/eleventy-asset-pipeline","content":"---\nname: Eleventy Asset Pipeline\ndescription: Scaffold and maintain a production-grade asset pipeline for Eleventy sites. Validates project structure, sets up JavaScript bundling with esbuild, CSS processing with PostCSS/Tailwind, and implements cache-busting with content hashing. Creates a complete build system with hot reloading.\nallowed_tools:\n  - filesystem      # Create pipeline files and configurations\ntags:\n  - eleventy\n  - build-tools\n  - asset-pipeline\n  - performance\ncategory: development\n---\n\n## Arguments\n\n```\n/eleventy-asset-pipeline\n[bundle_mode=<true|false>]\n[hash_length=<8|10|12|16>]\n[source_maps=<true|false>]\n[css_framework=<tailwind|none>]\n```\n*Defaults → `bundle_mode=false  hash_length=10  source_maps=true  css_framework=tailwind`*\n\n### Examples\n\n```bash\n# Basic setup with defaults\n/eleventy-asset-pipeline\n\n# Production-optimized setup\n/eleventy-asset-pipeline bundle_mode=true source_maps=false\n\n# Custom hash length without Tailwind\n/eleventy-asset-pipeline hash_length=16 css_framework=none\n```\n\n---\n\n## Context – what the AI should do\n\n1. **Validate Project Structure**\n   * Check if current directory is an Eleventy project (look for .eleventy.js or eleventy.config.js)\n   * Verify package.json exists\n   * Check for src directory structure or common Eleventy patterns\n   * Analyze existing assets to determine appropriate pipeline configuration\n   * Warn if incompatible setup detected\n\n2. **Parse Arguments**\n   * `bundle_mode` - Whether to bundle JS entry points (production) or keep separate (dev)\n   * `hash_length` - Length of content hash for cache busting\n   * `source_maps` - Whether to generate source maps in production\n   * `css_framework` - CSS framework to configure (currently Tailwind or none)\n\n3. **Dependency Management**\n   * Check which dependencies are already installed\n   * Generate npm install command for missing dependencies only\n   * Required: @11ty/eleventy esbuild postcss autoprefixer cssnano fast-glob dotenv npm-run-all\n   * Optional: tailwindcss (if css_framework=tailwind)\n\n4. **Project Analysis**\n   * Detect existing Eleventy configuration\n   * Identify current asset structure (src/assets, _includes, etc.)\n   * Check for existing build scripts to avoid conflicts\n   * Determine if project uses CommonJS or ES modules\n\n5. **Pipeline Implementation**\n   * All file creation follows the snippets below\n   * Adapt paths based on detected project structure\n   * Preserve existing files with .backup extension if conflicts\n   * Use the complete snippets to ensure one-shot success\n\n---\n\n## Implementation\n\n> **Note**: The following implementation uses the exact snippets that enable one-shot pipeline creation. The AI should use these verbatim after validating the project structure.\n\n### Step 1: Update package.json scripts\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE package.json BEGIN\nconst scripts = {\n  \"dev\": \"npm run clean:bundles && NODE_ENV=development npm-run-all --parallel dev:*\",\n  \"dev:eleventy\": \"eleventy --serve --quiet\",\n  \"dev:css\": \"mkdir -p _site/assets/styles && postcss src/assets/styles/main.css -o _site/assets/styles/main.css --watch\",\n  \"build\": \"npm run clean && NODE_ENV=production npm run build:css && eleventy\",\n  \"build:bundles\": \"npm run clean && BUNDLE_MODE=true node scripts/build-assets.js && NODE_ENV=production npm run build:css && eleventy\",\n  \"build:css\": \"mkdir -p _site/assets/styles && postcss src/assets/styles/main.css -o _site/assets/styles/main.css\",\n  \"build:assets\": \"node scripts/build-assets.js\",\n  \"clean\": \"rm -rf _site\",\n  \"clean:bundles\": \"node -e \\\"const fs=require('fs'); ['_site/assets/*-*.js', '_site/assets/*-*.js.map', '_site/assets/chunks/'].forEach(p => { try { const glob = require('fast-glob'); const files = glob.sync(p); files.forEach(f => fs.rmSync(f, {force:true})); } catch {} })\\\"\"\n};\n\n// Also add to devDependencies if not present:\nconst devDependencies = {\n  \"npm-run-all\": \"^4.1.5\"\n};\n// <<< ELEVENTY_ASSET_PIPELINE package.json END\n```\n\n### Step 2: Create directory structure\n```bash\nmkdir -p build scripts src/assets/{js,styles,bundles} src/assets/js/{admin,vendor} src/_includes/layouts src/_data\n\n# Make build scripts executable\nchmod +x build/*.mjs\n```\n\n### Step 3: Create PostCSS configuration\nCreate `postcss.config.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE postcss.config BEGIN\nmodule.exports = {\n  plugins: [\n    require('tailwindcss'),\n    require('autoprefixer'),\n    ...(process.env.NODE_ENV === 'production' ? [require('cssnano')] : [])\n  ]\n}\n// <<< ELEVENTY_ASSET_PIPELINE postcss.config END\n```\n\n### Step 4: Create Tailwind configuration\nCreate `tailwind.config.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE tailwind.config BEGIN\nmodule.exports = {\n  content: [\n    './src/**/*.{html,njk,md,js}',\n    './_site/**/*.html'\n  ],\n  theme: {\n    extend: {},\n  },\n  plugins: [],\n}\n// <<< ELEVENTY_ASSET_PIPELINE tailwind.config END\n```\n\n### Step 5: Create main CSS file\nCreate `src/assets/styles/main.css`:\n```css\n/* >>> ELEVENTY_ASSET_PIPELINE main.css BEGIN */\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/* Your custom styles here */\n/* <<< ELEVENTY_ASSET_PIPELINE main.css END */\n```\n\n### Step 6: Generate core asset bundler\nCreate `scripts/build-assets.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE build-assets BEGIN\nconst esbuild = require('esbuild');\nconst fs = require('fs');\nconst path = require('path');\n\nconst bundleMode = process.env.BUNDLE_MODE === 'true';\n\n// Define your bundles - customize based on your app structure\nconst bundles = bundleMode ? {\n  // Production bundles: single entry points that import multiple files\n  'main': ['src/assets/bundles/main-bundle.js'],\n  'admin': ['src/assets/bundles/admin-bundle.js'],\n  'vendor': ['src/assets/bundles/vendor-bundle.js']\n} : {\n  // Development mode: individual files for faster rebuilds\n  'main': [\n    'src/assets/js/app.js',\n    'src/assets/js/navigation.js',\n    'src/assets/js/utils.js'\n  ],\n  'admin': [\n    'src/assets/js/admin/dashboard.js',\n    'src/assets/js/admin/users.js'\n  ],\n  'vendor': [\n    'src/assets/js/vendor/analytics.js'\n  ]\n};\n\nconst outdir = '_site/assets';\nif (!fs.existsSync(outdir)) {\n  fs.mkdirSync(outdir, { recursive: true });\n}\n\nfunction cleanupOldBundles() {\n  console.log('🧹 Cleaning up old bundle files...');\n  const bundleNames = Object.keys(bundles).join('|');\n  const pattern = new RegExp(`^(${bundleNames})-[A-Z0-9]+\\\\.(js|js\\\\.map)$`);\n  \n  if (fs.existsSync(outdir)) {\n    const files = fs.readdirSync(outdir).filter(file => file.match(pattern));\n    files.forEach(file => fs.rmSync(path.join(outdir, file), { force: true }));\n  }\n  \n  const chunksDir = path.join(outdir, 'chunks');\n  if (fs.existsSync(chunksDir)) {\n    fs.rmSync(chunksDir, { recursive: true, force: true });\n  }\n}\n\nasync function buildAssets() {\n  try {\n    console.log(`🔧 Building in ${bundleMode ? 'BUNDLE' : 'INDIVIDUAL'} mode...`);\n    cleanupOldBundles();\n\n    // Build all bundles in parallel for better performance\n    const buildPromises = Object.entries(bundles).map(async ([bundleName, files]) => {\n      const existingFiles = files.filter(file => fs.existsSync(file));\n      if (existingFiles.length === 0) {\n        console.warn(`⚠️  No files found for bundle: ${bundleName}`);\n        return null;\n      }\n\n      console.log(`📦 Building ${bundleName} bundle...`);\n      return esbuild.build({\n        entryPoints: existingFiles,\n        bundle: true,\n        minify: process.env.NODE_ENV === 'production',\n        sourcemap: process.env.NODE_ENV === 'production' \n          ? (process.env.SOURCE_MAPS !== 'false' ? 'external' : false)\n          : true,\n        outdir,\n        format: 'esm',\n        splitting: true,\n        target: ['es2020'],\n        entryNames: `${bundleName}-[hash]`,\n        chunkNames: 'chunks/[name]-[hash]',\n        metafile: true,\n        write: true,\n        external: ['*.png', '*.jpg', '*.svg', '*.woff', '*.woff2'] // Don't bundle assets\n      });\n    });\n\n    await Promise.all(buildPromises);\n\n    // Generate bundle manifest for Eleventy\n    const bundleManifest = {};\n    for (const bundleName of Object.keys(bundles)) {\n      const bundleFile = fs.readdirSync(outdir).find(file => \n        file.startsWith(`${bundleName}-`) && file.endsWith('.js')\n      );\n      if (bundleFile) {\n        bundleManifest[bundleName] = `/assets/${bundleFile}`;\n      }\n    }\n    \n    // Ensure _data directory exists\n    const dataDir = 'src/_data';\n    if (!fs.existsSync(dataDir)) {\n      fs.mkdirSync(dataDir, { recursive: true });\n    }\n    \n    fs.writeFileSync(\n      path.join(dataDir, 'bundles.json'), \n      JSON.stringify(bundleManifest, null, 2)\n    );\n    \n    console.log('✅ Assets built successfully!');\n    console.log('📋 Bundle manifest:', bundleManifest);\n\n  } catch (error) {\n    console.error('❌ Build failed:', error);\n    process.exit(1);\n  }\n}\n\n// Run the build\nbuildAssets();\n// <<< ELEVENTY_ASSET_PIPELINE build-assets END\n```\n\n### Step 7: Create bundle entry points\nCreate `src/assets/bundles/main-bundle.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE main-bundle BEGIN\n// Main bundle entry point - imports all core functionality\nexport * from '../js/app.js';\nexport * from '../js/navigation.js';\nexport * from '../js/utils.js';\n\n// Mark as bundled to prevent double initialization\nwindow.__bundled = true;\n\n// Initialize on DOM ready\nif (document.readyState === 'loading') {\n  document.addEventListener('DOMContentLoaded', init);\n} else {\n  init();\n}\n\nfunction init() {\n  console.log('Main bundle initialized');\n  // Initialize imported modules\n  import('../js/app.js').then(m => m.initApp && m.initApp());\n  import('../js/navigation.js').then(m => m.initNavigation && m.initNavigation());\n}\n// <<< ELEVENTY_ASSET_PIPELINE main-bundle END\n```\n\nCreate `src/assets/bundles/admin-bundle.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE admin-bundle BEGIN\n// Admin bundle entry point\nexport * from '../js/admin/dashboard.js';\nexport * from '../js/admin/users.js';\n\n// Mark as bundled\nwindow.__bundled = true;\n\nconsole.log('Admin bundle loaded');\n// <<< ELEVENTY_ASSET_PIPELINE admin-bundle END\n```\n\nCreate `src/assets/bundles/vendor-bundle.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE vendor-bundle BEGIN\n// Third-party scripts bundle\nexport * from '../js/vendor/analytics.js';\n// <<< ELEVENTY_ASSET_PIPELINE vendor-bundle END\n```\n\n### Step 8: Create example JavaScript files\nCreate `src/assets/js/app.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE app.js BEGIN\nexport function initApp() {\n  console.log('App initialized');\n  \n  // Example: Dark mode toggle\n  const toggle = document.querySelector('[data-dark-mode-toggle]');\n  if (toggle) {\n    toggle.addEventListener('click', () => {\n      document.documentElement.classList.toggle('dark');\n      localStorage.setItem('darkMode', \n        document.documentElement.classList.contains('dark')\n      );\n    });\n  }\n}\n\n// Auto-init if not bundled\nif (typeof window !== 'undefined' && !window.__bundled) {\n  initApp();\n}\n// <<< ELEVENTY_ASSET_PIPELINE app.js END\n```\n\nCreate `src/assets/js/navigation.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE navigation.js BEGIN\nexport function initNavigation() {\n  console.log('Navigation initialized');\n  \n  // Mobile menu toggle\n  const menuToggle = document.querySelector('[data-menu-toggle]');\n  const menu = document.querySelector('[data-menu]');\n  \n  if (menuToggle && menu) {\n    menuToggle.addEventListener('click', () => {\n      menu.classList.toggle('hidden');\n      menuToggle.setAttribute('aria-expanded', \n        menu.classList.contains('hidden') ? 'false' : 'true'\n      );\n    });\n  }\n  \n  // Active link highlighting\n  const currentPath = window.location.pathname;\n  document.querySelectorAll('nav a').forEach(link => {\n    if (link.getAttribute('href') === currentPath) {\n      link.classList.add('active');\n    }\n  });\n}\n\n// Auto-init if not bundled\nif (typeof window !== 'undefined' && !window.__bundled) {\n  initNavigation();\n}\n// <<< ELEVENTY_ASSET_PIPELINE navigation.js END\n```\n\nCreate `src/assets/js/utils.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE utils.js BEGIN\n// Utility functions\nexport function debounce(func, wait) {\n  let timeout;\n  return function executedFunction(...args) {\n    const later = () => {\n      clearTimeout(timeout);\n      func(...args);\n    };\n    clearTimeout(timeout);\n    timeout = setTimeout(later, wait);\n  };\n}\n\nexport function formatDate(date, locale = 'en-US') {\n  return new Intl.DateTimeFormat(locale, {\n    year: 'numeric',\n    month: 'long',\n    day: 'numeric'\n  }).format(new Date(date));\n}\n\nexport function slugify(text) {\n  return text\n    .toString()\n    .toLowerCase()\n    .trim()\n    .replace(/[^\\w\\s-]/g, '')\n    .replace(/[\\s_-]+/g, '-')\n    .replace(/^-+|-+$/g, '');\n}\n\n// DOM ready helper\nexport function ready(fn) {\n  if (document.readyState !== 'loading') {\n    fn();\n  } else {\n    document.addEventListener('DOMContentLoaded', fn);\n  }\n}\n// <<< ELEVENTY_ASSET_PIPELINE utils.js END\n```\n\nCreate `src/assets/js/admin/dashboard.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE dashboard.js BEGIN\nexport function initDashboard() {\n  console.log('Admin dashboard initialized');\n  \n  // Example: Stats counter animation\n  const stats = document.querySelectorAll('[data-stat-value]');\n  stats.forEach(stat => {\n    const target = parseInt(stat.dataset.statValue);\n    let current = 0;\n    const increment = target / 100;\n    \n    const timer = setInterval(() => {\n      current += increment;\n      if (current >= target) {\n        current = target;\n        clearInterval(timer);\n      }\n      stat.textContent = Math.floor(current).toLocaleString();\n    }, 10);\n  });\n}\n\n// Initialize if on dashboard page\nif (document.querySelector('[data-admin-dashboard]')) {\n  initDashboard();\n}\n// <<< ELEVENTY_ASSET_PIPELINE dashboard.js END\n```\n\nCreate `src/assets/js/admin/users.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE users.js BEGIN\nexport function initUserManagement() {\n  console.log('User management initialized');\n  \n  // Example: User search\n  const searchInput = document.querySelector('[data-user-search]');\n  const userRows = document.querySelectorAll('[data-user-row]');\n  \n  if (searchInput && userRows.length) {\n    searchInput.addEventListener('input', (e) => {\n      const search = e.target.value.toLowerCase();\n      \n      userRows.forEach(row => {\n        const text = row.textContent.toLowerCase();\n        row.style.display = text.includes(search) ? '' : 'none';\n      });\n    });\n  }\n}\n\n// Initialize if on users page\nif (document.querySelector('[data-admin-users]')) {\n  initUserManagement();\n}\n// <<< ELEVENTY_ASSET_PIPELINE users.js END\n```\n\nCreate `src/assets/js/vendor/analytics.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE analytics.js BEGIN\n// Third-party analytics wrapper\nexport function initAnalytics(config = {}) {\n  // Only initialize in production (check if localhost)\n  if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {\n    console.log('Analytics disabled on localhost');\n    return;\n  }\n  \n  // Example analytics initialization\n  window.dataLayer = window.dataLayer || [];\n  \n  function gtag() {\n    window.dataLayer.push(arguments);\n  }\n  \n  window.gtag = gtag;\n  gtag('js', new Date());\n  gtag('config', config.measurementId || 'GA_MEASUREMENT_ID');\n}\n\n// Auto-init with default config\nif (typeof window !== 'undefined') {\n  initAnalytics();\n}\n// <<< ELEVENTY_ASSET_PIPELINE analytics.js END\n```\n\n### Step 9: Generate import rewriter with dependency-aware hashing\nCreate `build/rev-imports.mjs`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE rev-imports BEGIN\n#!/usr/bin/env node\nimport fg from \"fast-glob\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport crypto from \"crypto\";\nimport esbuild from \"esbuild\";\n\nconst SRC = \"src/assets\";\nconst OUT = \"_site/assets\";\nconst LEN = parseInt(process.env.HASH_LEN || \"10\");\n\nconst digest = (str) =>\n  crypto.createHash(\"md5\").update(str).digest(\"hex\").slice(0, LEN);\n\nconst manifest = {};\nconst fileHashes = new Map();\n\n// Detect ES module syntax\nconst hasModuleSyntax = (code) => \n  /(^\\s*import\\s)|(^\\s*export\\s)/m.test(code);\n\nconsole.log(\"🔍 Processing JavaScript files...\");\n\n// Skip if in bundle mode - build-assets.js handles everything\nif (process.env.BUNDLE_MODE === 'true') {\n  console.log(\"📦 Bundle mode active - skipping individual file processing\");\n  process.exit(0);\n}\n\n// First pass: process all files and compute their initial hashes\nconsole.log(\"🔍 Computing initial file hashes...\");\nconst dependencies = new Map(); // Track dependencies for each file\n\nfor (const file of fg.sync(`${SRC}/**/!(*.bundle).js`)) {\n  const src = fs.readFileSync(file, 'utf8');\n  let processedCode;\n  \n  if (process.env.NODE_ENV === \"production\") {\n    try {\n      if (hasModuleSyntax(src)) {\n        const result = await esbuild.transform(src, {\n          minify: true,\n          format: \"esm\",\n          target: \"es2020\"\n        });\n        processedCode = result.code;\n      } else {\n        const result = await esbuild.build({\n          stdin: { \n            contents: src, \n            resolveDir: path.dirname(file),\n            sourcefile: path.basename(file) \n          },\n          bundle: false,\n          minify: true,\n          format: \"iife\",\n          target: \"es2020\",\n          platform: \"browser\",\n          write: false\n        });\n        processedCode = result.outputFiles[0].text;\n      }\n    } catch (error) {\n      console.error(`⚠️  Error processing ${file}:`, error.message);\n      processedCode = src; // Fall back to original\n    }\n  } else {\n    processedCode = src;\n  }\n  \n  // Extract dependencies\n  const deps = [];\n  processedCode.replace(\n    /(?<=\\b(?:from\\s*|import\\s*\\(?\\s*)['\"])(\\.\\.?\\/[^'\"]+?\\.js)(?=['\"])/g,\n    (rel) => {\n      const depAbs = path.posix.join(path.dirname(file).replace(/\\\\/g, '/'), rel);\n      deps.push(depAbs.replace(/\\//g, path.sep));\n      return rel;\n    }\n  );\n  dependencies.set(file, deps);\n  \n  const hash = digest(processedCode);\n  fileHashes.set(file, { hash, processedCode, initialHash: hash });\n}\n\n// Second pass: include dependency hashes in the final hash\nconsole.log(\"🔄 Computing dependency-aware hashes...\");\nconst getDependencyHash = (file) => {\n  const deps = dependencies.get(file) || [];\n  const depHashes = deps.map(dep => {\n    const depData = fileHashes.get(dep);\n    return depData ? depData.hash : '';\n  }).sort().join(',');\n  \n  const fileData = fileHashes.get(file);\n  // Combine content hash with dependency hashes\n  return digest(fileData.initialHash + depHashes);\n};\n\n// Update all hashes to include dependencies\nfor (const file of fileHashes.keys()) {\n  const fileData = fileHashes.get(file);\n  fileData.hash = getDependencyHash(file);\n}\n\n// Third pass: rewrite imports and write files\nconsole.log(\"📝 Rewriting imports and generating files...\");\nfor (const [file, data] of fileHashes) {\n  const { hash, processedCode } = data;\n  const relPath = path.relative(SRC, file);\n  const outRel = relPath.replace(/\\.js$/, `.${hash}.js`);\n  const outAbs = path.join(OUT, outRel);\n\n  // Rewrite imports to hashed versions\n  const newCode = processedCode.replace(\n    /(?<=\\b(?:from\\s*|import\\s*\\(?\\s*)['\"])(\\.\\.?\\/[^'\"]+?\\.js)(?=['\"])/g,\n    (importPath) => {\n      const absoluteImportPath = path.join(path.dirname(file), importPath);\n      const normalizedPath = path.normalize(absoluteImportPath);\n      const importData = fileHashes.get(normalizedPath);\n      \n      if (!importData) {\n        return importPath; // Keep original if not found\n      }\n      \n      return importPath.replace(/\\.js$/, `.${importData.hash}.js`);\n    }\n  );\n\n  // Ensure output directory exists\n  fs.mkdirSync(path.dirname(outAbs), { recursive: true });\n  \n  // Clean up old versions - keep only the most recent\n  const dir = path.dirname(outAbs);\n  const baseFileName = path.basename(file, '.js');\n  const currentFileName = path.basename(outAbs);\n  \n  if (fs.existsSync(dir)) {\n    // Find all hashed versions of this file\n    const hashedFiles = fs.readdirSync(dir)\n      .filter(f => f.startsWith(baseFileName + '.') && f.endsWith('.js') && f !== currentFileName)\n      .map(f => ({\n        name: f,\n        path: path.join(dir, f),\n        mtime: fs.statSync(path.join(dir, f)).mtime\n      }))\n      .sort((a, b) => b.mtime - a.mtime); // Sort by modification time, newest first\n    \n    // Keep only the most recent old version, delete the rest\n    for (let i = 0; i < hashedFiles.length; i++) {\n      if (i >= 1) { // Keep index 0 (most recent), delete the rest\n        fs.rmSync(hashedFiles[i].path);\n      }\n    }\n  }\n  \n  // Write new file\n  fs.writeFileSync(outAbs, newCode);\n  \n  // Add to manifest\n  manifest[`assets/${relPath}`] = `/assets/${outRel}`;\n}\n\n// Helper to safely merge manifest updates\nfunction mergeManifest(updates) {\n  const manifestPath = \"_site/asset-manifest.json\";\n  let existing = {};\n  \n  try {\n    if (fs.existsSync(manifestPath)) {\n      existing = JSON.parse(fs.readFileSync(manifestPath, \"utf8\"));\n    }\n  } catch (e) {\n    console.warn(\"⚠️  Could not read existing manifest\");\n  }\n  \n  const merged = { ...existing, ...updates };\n  fs.mkdirSync(path.dirname(manifestPath), { recursive: true });\n  fs.writeFileSync(manifestPath, JSON.stringify(merged, null, 2));\n  return merged;\n}\n\n// Update manifest\nmergeManifest(manifest);\nconsole.log(`✅ Processed ${fileHashes.size} JavaScript files with dependency-aware hashing`);\n// <<< ELEVENTY_ASSET_PIPELINE rev-imports END\n```\n\n### Step 10: Generate CSS hasher\nCreate `build/hash-css.mjs`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE hash-css BEGIN\n#!/usr/bin/env node\nimport fs from 'fs';\nimport path from 'path';\nimport crypto from 'crypto';\nimport fg from 'fast-glob';\n\nconst siteDir = '_site';\nconst manifestPath = path.join(siteDir, 'asset-manifest.json');\nconst hashLen = parseInt(process.env.HASH_LEN || \"10\");\n\nfunction generateHash(filePath) {\n  const content = fs.readFileSync(filePath);\n  return crypto.createHash('md5').update(content).digest('hex').slice(0, hashLen);\n}\n\nfunction updateHtmlFiles(oldPath, newPath) {\n  const htmlFiles = fg.sync(`${siteDir}/**/*.html`);\n  let updatedCount = 0;\n  \n  for (const file of htmlFiles) {\n    let content = fs.readFileSync(file, 'utf8');\n    // Match both absolute and relative paths\n    const patterns = [\n      new RegExp(`(['\"])/?${oldPath}(['\"])`, 'g'),\n      new RegExp(`href=\"/${oldPath}\"`, 'g'),\n      new RegExp(`href=\"${oldPath}\"`, 'g')\n    ];\n    \n    let updated = false;\n    for (const pattern of patterns) {\n      if (pattern.test(content)) {\n        content = content.replace(pattern, (match, p1, p2) => {\n          if (p1 && p2) return `${p1}/${newPath}${p2}`;\n          return match.replace(oldPath, newPath);\n        });\n        updated = true;\n      }\n    }\n    \n    if (updated) {\n      fs.writeFileSync(file, content);\n      updatedCount++;\n    }\n  }\n  \n  return updatedCount;\n}\n\n// Helper to safely merge manifest updates\nfunction mergeManifest(updates) {\n  let manifest = {};\n  try {\n    if (fs.existsSync(manifestPath)) {\n      manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf8\"));\n    }\n  } catch (e) {\n    console.warn(\"⚠️  Could not read existing manifest\");\n  }\n  \n  const merged = { ...manifest, ...updates };\n  fs.writeFileSync(manifestPath, JSON.stringify(merged, null, 2));\n  return merged;\n}\n\nconsole.log('🎨 Hashing CSS files...');\nconst cssFiles = fg.sync(`${siteDir}/**/!(*.min).css`);\nconst cssManifest = {};\n\nif (cssFiles.length === 0) {\n  console.log('⚠️  No CSS files found to hash');\n  process.exit(0);\n}\n\nfor (const file of cssFiles) {\n  const hash = generateHash(file);\n  const ext = path.extname(file);\n  const name = path.basename(file, ext);\n  const dir = path.dirname(file);\n  \n  // Skip if already hashed\n  if (/\\.[a-f0-9]{10}\\.css$/.test(file)) {\n    console.log(`⏭️  Skipping already hashed: ${path.basename(file)}`);\n    continue;\n  }\n  \n  const newName = `${name}.${hash}${ext}`;\n  const newPath = path.join(dir, newName);\n  \n  // Remove old hashed versions\n  const pattern = new RegExp(`^${name}\\\\.[a-f0-9]{${hashLen}}\\\\.css$`);\n  fs.readdirSync(dir)\n    .filter(f => f !== newName && pattern.test(f))\n    .forEach(f => {\n      console.log(`🗑️  Removing old version: ${f}`);\n      fs.rmSync(path.join(dir, f), { force: true });\n    });\n  \n  // Rename file\n  fs.renameSync(file, newPath);\n  \n  const originalPath = path.relative(siteDir, file).replace(/\\\\/g, '/');\n  const hashedPath = path.relative(siteDir, newPath).replace(/\\\\/g, '/');\n  cssManifest[originalPath] = `/${hashedPath}`;\n  \n  // Update HTML references\n  const updateCount = updateHtmlFiles(originalPath, hashedPath);\n  console.log(`✅ ${originalPath} → ${hashedPath} (${updateCount} HTML files updated)`);\n}\n\nmergeManifest(cssManifest);\nconsole.log(`✅ CSS hashing complete! Processed ${cssFiles.length} files`);\n// <<< ELEVENTY_ASSET_PIPELINE hash-css END\n```\n\n### Step 11: Generate CSS minifier\nCreate `build/minify-css.mjs`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE minify-css BEGIN\n#!/usr/bin/env node\nimport fs from 'fs';\nimport postcss from 'postcss';\nimport cssnano from 'cssnano';\nimport fg from 'fast-glob';\n\nif (process.env.NODE_ENV !== 'production') {\n  console.log('🔧 Development mode: skipping CSS minification');\n  process.exit(0);\n}\n\nconsole.log('📦 Minifying CSS for production...');\nconst cssFiles = fg.sync('_site/**/*.css');\n\nif (cssFiles.length === 0) {\n  console.log('⚠️  No CSS files found to minify');\n  process.exit(0);\n}\n\nlet processedCount = 0;\nlet totalSaved = 0;\n\nfor (const file of cssFiles) {\n  // Skip already minified files\n  if (file.includes('.min.css')) {\n    console.log(`⏭️  Skipping minified: ${file}`);\n    continue;\n  }\n  \n  try {\n    const content = fs.readFileSync(file, 'utf8');\n    const originalSize = Buffer.byteLength(content, 'utf8');\n    \n    const result = await postcss([\n      cssnano({\n        preset: ['default', {\n          discardComments: {\n            removeAll: true,\n          },\n        }]\n      })\n    ]).process(content, { from: file, to: file });\n    \n    const minifiedSize = Buffer.byteLength(result.css, 'utf8');\n    const saved = originalSize - minifiedSize;\n    const percent = Math.round((saved / originalSize) * 100);\n    \n    fs.writeFileSync(file, result.css);\n    \n    if (result.map) {\n      fs.writeFileSync(`${file}.map`, result.map.toString());\n    }\n    \n    console.log(`✅ ${file} - saved ${saved} bytes (${percent}%)`);\n    processedCount++;\n    totalSaved += saved;\n    \n  } catch (error) {\n    console.error(`❌ Error minifying ${file}:`, error.message);\n  }\n}\n\nconst totalSavedKB = (totalSaved / 1024).toFixed(2);\nconsole.log(`✅ CSS minification complete! Processed ${processedCount} files, saved ${totalSavedKB}KB total`);\n// <<< ELEVENTY_ASSET_PIPELINE minify-css END\n```\n\n### Step 12: Generate hash-generated-assets script\nCreate `build/hash-generated-assets.mjs`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE hash-generated-assets BEGIN\n#!/usr/bin/env node\nimport fs from \"fs\";\nimport path from \"path\";\nimport crypto from \"crypto\";\nimport fg from \"fast-glob\";\n\nconst SITE_DIR = \"_site\";\nconst HASH_LEN = parseInt(process.env.HASH_LEN || \"10\");\n\n// Skip in development\nif (process.env.NODE_ENV === 'development') {\n  console.log(\"🔧 Development mode: skipping generated asset hashing\");\n  process.exit(0);\n}\n\n// Define generated assets to hash (customize as needed)\nconst GENERATED_ASSETS = [\n  `${SITE_DIR}/assets/config.js`,\n  `${SITE_DIR}/assets/env.js`,\n  // Add other dynamically generated files here\n];\n\nfunction generateHash(content) {\n  return crypto.createHash(\"md5\").update(content).digest(\"hex\").slice(0, HASH_LEN);\n}\n\nfunction updateHtmlReferences(oldPath, newPath) {\n  const htmlFiles = fg.sync(`${SITE_DIR}/**/*.html`);\n  let updateCount = 0;\n  \n  for (const file of htmlFiles) {\n    let content = fs.readFileSync(file, \"utf8\");\n    if (content.includes(oldPath)) {\n      content = content.replace(new RegExp(oldPath, 'g'), newPath);\n      fs.writeFileSync(file, content);\n      updateCount++;\n    }\n  }\n  \n  return updateCount;\n}\n\n// Helper to merge manifest\nfunction mergeManifest(updates) {\n  const manifestPath = path.join(SITE_DIR, \"asset-manifest.json\");\n  let manifest = {};\n  \n  try {\n    if (fs.existsSync(manifestPath)) {\n      manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf8\"));\n    }\n  } catch (e) {\n    console.warn(\"⚠️  Could not read existing manifest\");\n  }\n  \n  const merged = { ...manifest, ...updates };\n  fs.writeFileSync(manifestPath, JSON.stringify(merged, null, 2));\n  return merged;\n}\n\nconsole.log(\"🔍 Processing generated assets...\");\nconst updates = {};\nlet processedCount = 0;\n\nfor (const assetPath of GENERATED_ASSETS) {\n  if (!fs.existsSync(assetPath)) {\n    console.log(`⏭️  Skipping non-existent: ${assetPath}`);\n    continue;\n  }\n  \n  const content = fs.readFileSync(assetPath, 'utf8');\n  const hash = generateHash(content);\n  const ext = path.extname(assetPath);\n  const base = path.basename(assetPath, ext);\n  const dir = path.dirname(assetPath);\n  const hashedName = `${base}.${hash}${ext}`;\n  const hashedPath = path.join(dir, hashedName);\n  \n  // Clean up old versions\n  const pattern = new RegExp(`^${base}\\\\.[a-f0-9]{${HASH_LEN}}${ext}$`);\n  fs.readdirSync(dir)\n    .filter(f => pattern.test(f) && f !== hashedName)\n    .forEach(f => {\n      console.log(`🗑️  Removing old: ${f}`);\n      fs.rmSync(path.join(dir, f), { force: true });\n    });\n  \n  // Create hashed copy\n  fs.copyFileSync(assetPath, hashedPath);\n  \n  // Update references\n  const originalUrl = '/' + path.relative(SITE_DIR, assetPath).replace(/\\\\/g, '/');\n  const hashedUrl = '/' + path.relative(SITE_DIR, hashedPath).replace(/\\\\/g, '/');\n  const updateCount = updateHtmlReferences(originalUrl, hashedUrl);\n  \n  updates[originalUrl.substring(1)] = hashedUrl;\n  console.log(`✅ ${base}${ext} → ${hashedName} (${updateCount} HTML files updated)`);\n  processedCount++;\n}\n\nif (processedCount > 0) {\n  mergeManifest(updates);\n  console.log(`✅ Generated asset hashing complete! Processed ${processedCount} files`);\n} else {\n  console.log(\"ℹ️  No generated assets to process\");\n}\n// <<< ELEVENTY_ASSET_PIPELINE hash-generated-assets END\n```\n\n### Step 13: Update Eleventy config\nCreate or update `eleventy.config.js`:\n```javascript\n// >>> ELEVENTY_ASSET_PIPELINE eleventy.config BEGIN\nconst fs = require('fs');\nconst path = require('path');\nconst crypto = require('crypto');\nconst esbuild = require('esbuild');\n\n// Load environment variables if .env exists\nif (fs.existsSync('.env')) {\n  require('dotenv').config();\n}\n\nmodule.exports = function(eleventyConfig) {\n  // Ignore build artifacts and cache\n  eleventyConfig.ignores.add(\"src/_data/.cache/\");\n  eleventyConfig.ignores.add(\"**/.cache/**\");\n  eleventyConfig.watchIgnores.add(\"src/_data/.cache/**/*\");\n  eleventyConfig.watchIgnores.add(\"**/.cache/**\");\n  \n  // Asset pipeline: Before build\n  eleventyConfig.on(\"eleventy.before\", () => {\n    const { execSync } = require(\"child_process\");\n    \n    // Only run rev-imports if not in bundle mode\n    if (process.env.BUNDLE_MODE !== 'true') {\n      execSync(\"node build/rev-imports.mjs\", {\n        stdio: \"inherit\",\n        env: { ...process.env }\n      });\n    }\n  });\n  \n  // Asset pipeline: After build\n  eleventyConfig.on(\"eleventy.after\", () => {\n    const { execSync } = require(\"child_process\");\n    \n    // Run post-build scripts\n    const scripts = [\n      \"build/hash-generated-assets.mjs\",\n      \"build/hash-css.mjs\",\n      \"build/minify-css.mjs\"\n    ];\n    \n    for (const script of scripts) {\n      if (fs.existsSync(script)) {\n        execSync(`node ${script}`, { \n          stdio: \"inherit\",\n          env: { ...process.env }\n        });\n      }\n    }\n  });\n  \n  // Function to load bundle manifest dynamically\n  function getBundleManifest() {\n    const bundleManifestPath = path.join(__dirname, 'src/_data/bundles.json');\n    if (fs.existsSync(bundleManifestPath)) {\n      try {\n        return JSON.parse(fs.readFileSync(bundleManifestPath, 'utf8'));\n      } catch (e) {\n        console.warn('Could not parse bundles.json:', e.message);\n        return {};\n      }\n    }\n    return {};\n  }\n  \n  // Make bundles globally available\n  eleventyConfig.addGlobalData(\"bundles\", getBundleManifest());\n  \n  // Shortcode for bundle paths\n  eleventyConfig.addShortcode(\"bundle\", function(bundleName) {\n    const manifest = getBundleManifest();\n    return manifest[bundleName] || '';\n  });\n  \n  // Filter for cache-busted assets\n  eleventyConfig.addFilter(\"rev\", (src) => {\n    const manifestPath = path.join(__dirname, '_site/asset-manifest.json');\n    if (fs.existsSync(manifestPath)) {\n      const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n      \n      // Normalize path\n      let normalizedSrc = src.replace(/^\\/+/, '');\n      \n      // Try multiple variations\n      const variations = [\n        normalizedSrc,\n        normalizedSrc.replace('src/', ''),\n        `assets/${normalizedSrc}`,\n        normalizedSrc.replace('src/assets/', 'assets/')\n      ];\n      \n      for (const variant of variations) {\n        if (manifest[variant]) {\n          return manifest[variant];\n        }\n      }\n    }\n    \n    // If not in manifest, return normalized path\n    return '/' + src.replace(/^\\/+/, '').replace('src/', '');\n  });\n  \n  // Bundle filter for on-demand bundling\n  eleventyConfig.addFilter(\"bundleRev\", async (src) => {\n    src = src.replace(/^\\/+/, \"\");\n    \n    try {\n      const result = await esbuild.build({\n        entryPoints: [path.join(__dirname, src)],\n        bundle: true,\n        format: \"esm\",\n        write: false,\n        platform: 'browser',\n        target: 'es2020',\n        minify: process.env.NODE_ENV === 'production'\n      });\n      \n      const code = result.outputFiles[0].contents;\n      const hash = crypto.createHash(\"md5\").update(code).digest(\"hex\").slice(0, 10);\n      const ext = path.extname(src);\n      const name = path.basename(src, ext);\n      const outputDir = path.dirname(src).replace(/^src\\//, '');\n      const outPath = path.join(\"_site\", outputDir, `${name}.${hash}${ext}`);\n      \n      // Write bundled file\n      fs.mkdirSync(path.dirname(outPath), { recursive: true });\n      fs.writeFileSync(outPath, code);\n      \n      return `/${outputDir}/${name}.${hash}${ext}`;\n    } catch (error) {\n      console.error(`Bundle error for ${src}:`, error);\n      return eleventyConfig.getFilter(\"rev\")(src);\n    }\n  });\n  \n  // Copy static assets\n  eleventyConfig.addPassthroughCopy(\"src/assets/images\");\n  eleventyConfig.addPassthroughCopy(\"src/assets/fonts\");\n  eleventyConfig.addPassthroughCopy({\"src/favicon.ico\": \"/favicon.ico\"});\n  \n  // Watch targets\n  eleventyConfig.addWatchTarget(\"src/assets/**/*.js\");\n  eleventyConfig.addWatchTarget(\"src/assets/**/*.css\");\n  eleventyConfig.setWatchJavaScriptDependencies(false);\n  \n  return {\n    dir: {\n      input: \"src\",\n      includes: \"_includes\",\n      layouts: \"_includes/layouts\",\n      data: \"_data\",\n      output: \"_site\"\n    },\n    markdownTemplateEngine: \"njk\",\n    htmlTemplateEngine: \"njk\",\n    dataTemplateEngine: \"njk\"\n  };\n};\n// <<< ELEVENTY_ASSET_PIPELINE eleventy.config END\n```\n\n### Step 14: Create example layout\nCreate `src/_includes/layouts/base.njk`:\n```html\n<!-- >>> ELEVENTY_ASSET_PIPELINE base.njk BEGIN -->\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>{{ title or \"My Site\" }}</title>\n  \n  <!-- Cache-busted CSS -->\n  <link rel=\"stylesheet\" href=\"{{ '/assets/styles/main.css' | rev }}\">\n  \n  <!-- Preload critical bundles -->\n  {% if bundles.main %}\n  <link rel=\"modulepreload\" href=\"{{ bundles.main }}\">\n  {% endif %}\n</head>\n<body>\n  <main>\n    {{ content | safe }}\n  </main>\n  \n  <!-- JavaScript bundles -->\n  {% if bundles.main %}\n  \n  {% endif %}\n  \n  <!-- Page-specific bundles -->\n  {% if page.bundle == 'admin' and bundles.admin %}\n  \n  {% endif %}\n  \n  <!-- Or use individual files in dev -->\n  {% if not bundles.main %}\n  \n  \n  {% endif %}\n</body>\n</html>\n<!-- <<< ELEVENTY_ASSET_PIPELINE base.njk END -->\n```\n\n### Step 15: Create config.njk for dynamic config\nCreate `src/assets/config.njk`:\n```javascript\n---\npermalink: /assets/config.js\neleventyExcludeFromCollections: true\n---\n// >>> ELEVENTY_ASSET_PIPELINE config.njk BEGIN\n// Dynamically generated config - will be hashed in production\nwindow.APP_CONFIG = {\n  environment: \"{{ eleventy.env.runMode }}\",\n  buildTime: \"{{ page.date.toISOString() }}\",\n  version: \"{{ pkg.version or '1.0.0' }}\",\n  api: {\n    baseUrl: \"{{ env.API_URL or '/api' }}\"\n  }\n};\n// <<< ELEVENTY_ASSET_PIPELINE config.njk END\n```\n\n---\n\n## Using the Pipeline\n\n### In Templates\n```nunjucks\n<!-- Basic usage -->\n<link rel=\"stylesheet\" href=\"{{ '/assets/styles/main.css' | rev }}\">\n\n\n<!-- Bundle usage -->\n\n\n<!-- Conditional bundles -->\n{% if page.isAdmin %}\n\n{% endif %}\n\n<!-- Individual files (non-bundled) -->\n\n```\n\n### Accessing Manifest Data\n```javascript\n// In your templates via global data\nconst mainBundle = bundles.main; // \"/assets/main-a1b2c3d4e5.js\"\n\n// In Node.js scripts\nconst manifest = require('./_site/asset-manifest.json');\nconst cssPath = manifest['assets/styles/main.css']; // \"/assets/styles/main.1234567890.css\"\n```\n\n---\n\n## Re-running the Command\nThis command is **safe to re-run** anytime:\n* It will update code between sentinel comments only\n* Manual edits outside sentinels are preserved\n* Use it to upgrade or fix the pipeline\n\n---\n\n\n## Troubleshooting\n\n### Common Issues\n1. **Missing dependencies**: Run the npm install command from Prerequisites\n2. **Build fails**: Check that all directories exist (`mkdir -p` commands)\n3. **Bundles not found**: Ensure `BUNDLE_MODE=true` is set for production\n4. **CSS not building**: Verify PostCSS and Tailwind configs are in root\n5. **Windows paths**: The pipeline normalizes paths automatically\n6. **Source maps missing**: Check `SOURCE_MAPS` env var isn't set to `false`\n7. **Browser cache issues with modules**: The pipeline uses dependency-aware hashing - when any module changes, all importing modules get new hashes to break browser cache chains\n\n### Debug Mode\n```bash\nDEBUG=1 npm run build  # Verbose output\n```\n\n### Clean Start\n```bash\nnpm run clean\nrm -rf node_modules package-lock.json\nnpm install\nnpm run build\n```\n\n---\n\n## Validation & Safety\n\nThe AI should perform these checks before implementing:\n\n1. **Project Validation**\n   * ✓ Eleventy config file exists (.eleventy.js or eleventy.config.js)\n   * ✓ package.json is present\n   * ✓ Source directory structure matches Eleventy patterns\n   * ✓ No conflicting asset pipeline already exists\n\n2. **Backup Strategy**\n   * Create .backup files for any existing configs that will be modified\n   * List all files that will be created/modified before proceeding\n   * Warn about any potential conflicts\n\n3. **Adaptation Logic**\n   * If src/ directory exists, use it as base\n   * If _site/ doesn't exist, check for custom output directory in config\n   * Adapt script paths if package.json shows different structure\n   * Respect existing Eleventy configuration patterns\n\n4. **Success Indicators**\n   * All files created successfully\n   * No npm errors when installing dependencies\n   * Build scripts added to package.json\n   * Asset manifest generation confirmed\n\n---\n\n## Next Steps\n1. Run `npm install` to install any missing dependencies\n2. Use `npm run dev` for development with hot reloading\n3. Use `npm run build:bundles` for production builds\n4. Customize bundle definitions in `scripts/build-assets.js`\n5. Add your JavaScript files to appropriate bundle arrays\n6. Configure Tailwind theme in `tailwind.config.js`","contentHash":"1bf5cce4ce01b749777dc8c96c377265327d06f09203b5ea7431d9771c8cce6f","copies":12,"createdAt":"2025-07-19T16:00:33.736Z","description":"Validates and scaffolds a production-grade asset pipeline for Eleventy sites with JavaScript bundling, CSS processing, and cache-busting","downloads":24,"github":{"repoUrl":"https://github.com/Commands-com/ai-commands","lastSyncDirection":"from-github","metadata":{"connectedAt":"2025-07-23T03:13:09.653Z","connectedBy":"W0V8NAw5AhWRwcuwSoFLOi1Yem83","connectionMethod":"manual"},"lastSyncAt":"2025-08-17T16:56:10.898Z","fileMapping":{"readme":"eleventy-asset-pipeline/README.md","assets":[],"mainFile":"eleventy-asset-pipeline/command.md","yamlPath":"commands.yaml"},"selectedCommand":"eleventy-asset-pipeline","fileShas":{"mainFile":"20d64f0d92dee0bba18207e39f06d382d8092672","yamlPath":"4507cfda9afa7474437ab79914331e61ec82f4f7","readme":"3e6825c46537049404a573201c60f6f1e8eb37e3"},"branch":"main","connectionType":"commands_yaml","connected":true,"lastSyncCommit":"3f1460bda072265aa9ad932490481f0843dc66f8","connectedAt":"2025-07-23T03:13:09.653Z","installationId":69232615,"syncStatus":"synced"},"githubRepoUrl":"https://github.com/Commands-com/ai-commands","id":"f731d5d4-4327-49ed-b7cb-75891f747fd8","inputParameters":[{"defaultValue":"false","name":"bundle_mode","options":["true","false"],"description":"Enable bundle mode for production builds (bundle entry-points vs per-file dev builds)","label":"Bundle Mode","type":"select","required":false},{"defaultValue":"10","name":"hash_length","options":["8","10","12","16"],"description":"Length of fingerprint hash for cache-busted assets","label":"Hash Length","type":"select","required":false},{"defaultValue":"true","name":"source_maps","options":["true","false"],"description":"Generate source maps in production builds","label":"Generate Source Maps","type":"select","required":false},{"defaultValue":"tailwind","name":"css_framework","options":["tailwind","none"],"description":"CSS framework to configure (Tailwind CSS or none)","label":"CSS Framework","type":"select","required":false}],"instructions":"# Eleventy Asset Pipeline\n\nA comprehensive asset management solution for Eleventy projects that validates your setup and provides production-grade JavaScript bundling, CSS processing, and cache-busting with minimal configuration.\n\n## Overview\n\nThe Eleventy Asset Pipeline command first validates your Eleventy project structure, then scaffolds a complete build system for managing JavaScript and CSS assets. It provides:\n\n- **Project Validation** to ensure compatibility before making changes\n- **JavaScript bundling** with esbuild for optimal performance\n- **CSS processing** with PostCSS, Tailwind CSS, and automatic minification\n- **Cache-busting** with content-based hashing for all assets\n- **Development/Production modes** with appropriate optimizations\n- **Bundle splitting** for better caching and performance\n- **Source maps** for easier debugging\n\n## Features\n\n### Project Validation\n- Checks for Eleventy configuration files\n- Verifies package.json exists\n- Detects source directory structure\n- Warns about potential conflicts\n- Creates backups of modified files\n\n### JavaScript Management\n- ES module bundling with automatic code splitting\n- Development mode with individual file processing for faster rebuilds\n- Production mode with optimized bundles and minification\n- Automatic import path rewriting for hashed assets\n- Support for both bundled and individual file loading\n\n### CSS Processing\n- PostCSS pipeline with optional Tailwind CSS integration\n- Automatic vendor prefixing with Autoprefixer\n- Production minification with cssnano\n- Content-based hashing for cache-busting\n- Source map generation\n\n### Asset Pipeline Features\n- Content-based hashing for all assets (JS, CSS)\n- Automatic HTML reference updates\n- Bundle manifest generation for dynamic imports\n- Clean build process that removes old hashed files\n- Configurable hash length and source map generation\n\n## Prerequisites\n\nBefore running this command, ensure you have:\n- An existing Eleventy project\n- Node.js 14+ installed\n- npm or yarn package manager\n\n## How to Use\n\n1. **Bundle Mode**: Choose whether to bundle JavaScript for production or keep files separate for development\n2. **Hash Length**: Select the length of content hashes for cache-busting (8-16 characters)\n3. **Source Maps**: Enable/disable source map generation in production builds\n4. **CSS Framework**: Choose between Tailwind CSS or no framework\n\nThe command will:\n- Validate your Eleventy project structure\n- Check for existing configurations\n- Install required dependencies\n- Create the asset pipeline with all necessary files\n- Set up build scripts in package.json\n\n## Configuration Options\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `bundle_mode` | `false` | Enable bundle mode for production builds |\n| `hash_length` | `10` | Length of content hash for cache-busting |\n| `source_maps` | `true` | Generate source maps in production |\n| `css_framework` | `tailwind` | CSS framework to configure |\n\n## Project Structure\n\nAfter running the command, your project will have:\n\n```\nyour-project/\n├── build/                    # Build scripts\n│   ├── rev-imports.mjs      # JavaScript processor\n│   ├── hash-css.mjs         # CSS hasher\n│   ├── minify-css.mjs       # CSS minifier\n│   └── hash-generated-assets.mjs\n├── scripts/\n│   └── build-assets.js      # Main bundler script\n├── src/\n│   ├── assets/\n│   │   ├── js/             # JavaScript source files\n│   │   ├── styles/         # CSS source files\n│   │   └── bundles/        # Bundle entry points\n│   ├── _data/              # Eleventy data files\n│   └── _includes/\n│       └── layouts/        # Layout templates\n├── postcss.config.js       # PostCSS configuration\n├── tailwind.config.js      # Tailwind configuration\n└── eleventy.config.js      # Updated Eleventy config\n```\n\n## Development Workflow\n\n### Development Mode\n```bash\nnpm run dev\n```\n- Runs Eleventy with live reload\n- Processes CSS with PostCSS in watch mode\n- Individual JavaScript files for faster rebuilds\n- No minification for easier debugging\n\n### Production Build\n```bash\nnpm run build\n```\n- Minifies and optimizes all assets\n- Generates content-hashed filenames\n- Creates optimized bundles\n- Produces production-ready output\n\n### Bundle Mode\n```bash\nnpm run build:bundles\n```\n- Creates optimized JavaScript bundles\n- Enables code splitting for better caching\n- Ideal for production deployments\n\n## Using Assets in Templates\n\n### CSS\n```nunjucks\n<!-- Automatically hashed CSS -->\n<link rel=\"stylesheet\" href=\"{{ '/assets/styles/main.css' | rev }}\">\n```\n\n### JavaScript Bundles\n```nunjucks\n<!-- Use bundles in production -->\n{% if bundles.main %}\n<script src=\"{{ bundles.main }}\" type=\"module\"></script>\n{% endif %}\n\n<!-- Or individual files in development -->\n{% if not bundles.main %}\n<script src=\"{{ '/assets/js/app.js' | rev }}\" type=\"module\"></script>\n{% endif %}\n```\n\n### Dynamic Configuration\n```nunjucks\n<!-- Hashed configuration file -->\n<script src=\"{{ '/assets/config.js' | rev }}\"></script>\n```\n\n## Customization\n\n### Adding New Bundles\nEdit `scripts/build-assets.js` to define new bundles:\n\n```javascript\nconst bundles = bundleMode ? {\n  'main': ['src/assets/bundles/main-bundle.js'],\n  'admin': ['src/assets/bundles/admin-bundle.js'],\n  'your-bundle': ['src/assets/bundles/your-bundle.js']\n} : {\n  // Development mode mappings\n};\n```\n\n### Extending PostCSS\nAdd plugins to `postcss.config.js`:\n\n```javascript\nmodule.exports = {\n  plugins: [\n    require('tailwindcss'),\n    require('autoprefixer'),\n    require('your-plugin'),\n    // ... more plugins\n  ]\n}\n```\n\n### Custom Build Steps\nThe pipeline uses Eleventy events for extensibility:\n- `eleventy.before` - Runs before the build\n- `eleventy.after` - Runs after the build\n\n## Troubleshooting\n\n### Common Issues\n\n**Missing Dependencies**\n```bash\nnpm install --save-dev @11ty/eleventy esbuild postcss autoprefixer cssnano tailwindcss fast-glob dotenv npm-run-all\n```\n\n**Build Failures**\n- Check that all directories exist\n- Verify PostCSS and Tailwind configs are in the root\n- Ensure bundle mode is set correctly\n\n**Assets Not Found**\n- Run a clean build: `npm run clean && npm run build`\n- Check the asset manifest at `_site/asset-manifest.json`\n- Verify file paths in templates\n\n### Debug Mode\n```bash\nDEBUG=1 npm run build\n```\n\n## Best Practices\n\n1. **Development**: Use individual files for faster rebuilds\n2. **Production**: Always use bundle mode with minification\n3. **Caching**: Rely on content hashes, not query strings\n4. **Performance**: Enable code splitting for large applications\n5. **Security**: Keep dependencies updated regularly\n\n## Safety Features\n\n- Validates Eleventy project before making changes\n- Creates .backup files for any modified configurations\n- Lists all files to be created/modified before proceeding\n- Adapts to your existing project structure\n- Preserves your custom Eleventy settings\n\n## License\n\nThis command is provided under the MIT License. See the LICENSE file for details.","lastCopied":"2025-07-22T19:46:49.076Z","lastDownloaded":"2025-08-31T22:33:14.067Z","lastStarred":"2025-07-22T19:46:45.444Z","licenseType":"mit","likes":0,"loomId":"dd940151fec94dc0a0f3ec657df22ef4","mcpRequirements":[{"tier":"required","serverId":"filesystem"}],"mcp_search_content":"docker-filesystem","organizationUsername":"commands-com","price":"free","processedGalleryItems":[{"s3KeyThumbnail":"processed/commands-com/eleventy-asset-pipeline/ChatGPTImageJul20202512_01_59PM_thumb.webp","s3KeyLarge":"processed/commands-com/eleventy-asset-pipeline/ChatGPTImageJul20202512_01_59PM.webp","name":"ChatGPTImageJul20202512_01_59PM.webp","originalKey":"temp-uploads/W0V8NAw5AhWRwcuwSoFLOi1Yem83/1f5607c0-c2f1-4a3a-bbe2-17accbb877be/ChatGPTImageJul20202512_01_59PM.png","pathLarge":"commands-com/eleventy-asset-pipeline/ChatGPTImageJul20202512_01_59PM.webp","type":"image/webp","pathThumbnail":"commands-com/eleventy-asset-pipeline/ChatGPTImageJul20202512_01_59PM_thumb.webp"},{"s3KeyThumbnail":"processed/f731d5d4-4327-49ed-b7cb-75891f747fd8/1753200015552_preview_thumb.webp","s3KeyLarge":"processed/f731d5d4-4327-49ed-b7cb-75891f747fd8/1753200015552_preview.webp","name":"1753200015552_preview.webp","originalKey":"temp-uploads/f731d5d4-4327-49ed-b7cb-75891f747fd8/1753200015552_preview.png","pathLarge":"f731d5d4-4327-49ed-b7cb-75891f747fd8/1753200015552_preview.webp","type":"image/webp","pathThumbnail":"f731d5d4-4327-49ed-b7cb-75891f747fd8/1753200015552_preview_thumb.webp"}],"search_content":"11ty asset pipeline scaffold and maintain a production-grade asset pipeline for eleventy sites with javascript bundling, css processing, and cache-busting /11ty-asset-pipeline development claude","stars":3,"title":"Eleventy Asset Pipeline","type":"command","updatedAt":"2025-08-17T16:56:10.898Z","userId":"W0V8NAw5AhWRwcuwSoFLOi1Yem83","visibility":"public","name":"eleventy-asset-pipeline","userInteraction":{"userHasStarred":false}}