How to use JavaScript and Styles in HBStack

A comprehensive guide on how to use JavaScript and custom scripts in the hbstack framework, covering the assets folder structure and custom script implementation. And when to use the assets/hb folder structure.


A comprehensive guide on how to use JavaScript and custom scripts in the HBStack framework, covering the assets folder structure, custom script implementation, and when to use the assets/hb folder structure.


Overview

HBStack provides a powerful and flexible system for managing JavaScript files, CSS assets, and custom scripts. Understanding the proper folder structure and implementation patterns is crucial for extending your HBStack-powered Hugo site.

Assets Folder Structure

The assets/ folder in HBStack follows a specific organization pattern that Hugo uses for asset processing and bundling.

Standard Assets Organization

 1assets/
 2├── css/                    # Custom CSS files
 3│   ├── main.css           # Site-wide custom styles
 4│   └── components/        # Component-specific styles
 5├── js/                    # Custom JavaScript files
 6│   ├── main.js           # Site-wide custom scripts
 7│   └── modules/          # Modular JavaScript files
 8├── images/               # Static images and icons
 9│   ├── logo.png
10│   └── icons/
11├── hb/                   # HBStack-specific assets
12│   └── modules/          # HB module customizations
13└── fonts/                # Custom web fonts

When to Use Each Folder

FolderPurposeUse Cases
assets/css/Custom stylesheetsSite-wide styles, component overrides, theme customizations
assets/js/General JavaScriptSite functionality, third-party integrations, utilities
assets/hb/HBStack modulesFramework-specific customizations, module extensions
assets/images/Image assetsLogos, backgrounds, content images
assets/fonts/TypographyCustom web fonts, icon fonts

The assets/hb Folder

The assets/hb/ folder is specifically designed for HBStack framework customizations and module extensions.

Purpose and Structure

 1assets/hb/
 2└── modules/
 3    ├── custom/           # Your custom HB modules
 4    │   ├── js/
 5    │   │   └── index.ts  # Main custom script entry
 6    │   └── scss/
 7    │       └── index.scss # Custom styles
 8    ├── header/           # Header module customizations
 9    ├── footer/           # Footer module customizations
10    └── blog/             # Blog module customizations

When to Use assets/hb

Use the assets/hb/ folder when you need to:

  1. Extend HBStack modules - Add functionality to existing modules
  2. Create custom HB modules - Build new modules following HB conventions
  3. Override module behavior - Customize how HBStack modules work
  4. Integrate with HB’s build system - Leverage HB’s TypeScript and SCSS processing

Adding Custom Scripts

Method 1: Using assets/hb/modules/custom/

This is the recommended approach for HBStack-specific customizations.

Step 1: Create the Directory Structure

1mkdir -p assets/hb/modules/custom/js
2mkdir -p assets/hb/modules/custom/scss

Step 2: Create Your Custom Script

Create assets/hb/modules/custom/js/index.ts:

 1// Main custom script entry point
 2console.log('HBStack custom module loaded')
 3
 4// Example: Add custom functionality
 5document.addEventListener('DOMContentLoaded', function () {
 6  // Your custom JavaScript code here
 7  initCustomFeatures()
 8})
 9
10function initCustomFeatures() {
11  // Custom navigation enhancements
12  setupSmoothScrolling()
13
14  // Custom form handling
15  setupFormValidation()
16
17  // Custom analytics tracking
18  setupCustomTracking()
19}
20
21function setupSmoothScrolling() {
22  const links = document.querySelectorAll('a[href^="#"]')
23  links.forEach(link => {
24    link.addEventListener('click', function (e) {
25      e.preventDefault()
26      const target = document.querySelector(this.getAttribute('href'))
27      if (target) {
28        target.scrollIntoView({
29          behavior: 'smooth',
30          block: 'start'
31        })
32      }
33    })
34  })
35}
36
37function setupFormValidation() {
38  const forms = document.querySelectorAll('form[data-validate]')
39  forms.forEach(form => {
40    form.addEventListener('submit', function (e) {
41      if (!validateForm(this)) {
42        e.preventDefault()
43      }
44    })
45  })
46}
47
48function validateForm(form: HTMLFormElement): boolean {
49  const requiredFields = form.querySelectorAll('[required]')
50  let isValid = true
51
52  requiredFields.forEach(field => {
53    if (!(field as HTMLInputElement).value.trim()) {
54      isValid = false
55      field.classList.add('is-invalid')
56    } else {
57      field.classList.remove('is-invalid')
58    }
59  })
60
61  return isValid
62}
63
64function setupCustomTracking() {
65  // Example: Custom event tracking
66  document.addEventListener('click', function (e) {
67    const target = e.target as HTMLElement
68    if (target.matches('[data-track]')) {
69      const event = target.getAttribute('data-track')
70      console.log(`Tracking event: ${event}`)
71      // Send to your analytics service
72    }
73  })
74}
75
76// Export functions for use in other modules
77export {
78  initCustomFeatures,
79  setupSmoothScrolling,
80  setupFormValidation,
81  setupCustomTracking
82}

Step 3: Add Custom Styles (Optional)

Create assets/hb/modules/custom/scss/index.scss:

 1// Custom styles for your HBStack site
 2.custom-feature {
 3  background: var(--bs-primary);
 4  color: white;
 5  padding: 1rem;
 6  border-radius: 0.5rem;
 7
 8  &:hover {
 9    background: var(--bs-primary-dark);
10    transform: translateY(-2px);
11    transition: all 0.3s ease;
12  }
13}
14
15// Form validation styles
16.is-invalid {
17  border-color: var(--bs-danger) !important;
18  box-shadow: 0 0 0 0.2rem rgba(var(--bs-danger-rgb), 0.25);
19}
20
21// Smooth scroll enhancement
22html {
23  scroll-behavior: smooth;
24}
25
26// Custom button styles
27.btn-custom {
28  background: linear-gradient(45deg, var(--bs-primary), var(--bs-secondary));
29  border: none;
30  color: white;
31
32  &:hover {
33    transform: scale(1.05);
34    transition: transform 0.2s ease;
35  }
36}

Method 2: Using assets/js/ for General Scripts

For non-HBStack specific JavaScript, use the standard assets/js/ folder.

Create assets/js/custom.js:

 1// General purpose custom JavaScript
 2;(function () {
 3  'use strict'
 4
 5  // Initialize when DOM is ready
 6  document.addEventListener('DOMContentLoaded', function () {
 7    console.log('Custom scripts loaded')
 8
 9    // Example: Image lazy loading
10    setupLazyLoading()
11
12    // Example: Custom search functionality
13    setupCustomSearch()
14
15    // Example: Social media integration
16    setupSocialShare()
17  })
18
19  function setupLazyLoading() {
20    if ('IntersectionObserver' in window) {
21      const imageObserver = new IntersectionObserver((entries, observer) => {
22        entries.forEach(entry => {
23          if (entry.isIntersecting) {
24            const img = entry.target
25            img.src = img.dataset.src
26            img.classList.remove('lazy')
27            imageObserver.unobserve(img)
28          }
29        })
30      })
31
32      document.querySelectorAll('img[data-src]').forEach(img => {
33        imageObserver.observe(img)
34      })
35    }
36  }
37
38  function setupCustomSearch() {
39    const searchInput = document.querySelector('#custom-search')
40    if (searchInput) {
41      searchInput.addEventListener(
42        'input',
43        debounce(function (e) {
44          const query = e.target.value
45          performSearch(query)
46        }, 300)
47      )
48    }
49  }
50
51  function setupSocialShare() {
52    document.querySelectorAll('[data-share]').forEach(button => {
53      button.addEventListener('click', function (e) {
54        e.preventDefault()
55        const platform = this.dataset.share
56        const url = encodeURIComponent(window.location.href)
57        const title = encodeURIComponent(document.title)
58
59        const shareUrls = {
60          twitter: `https://twitter.com/intent/tweet?url=${url}&text=${title}`,
61          facebook: `https://www.facebook.com/sharer/sharer.php?u=${url}`,
62          linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`
63        }
64
65        if (shareUrls[platform]) {
66          window.open(shareUrls[platform], '_blank', 'width=600,height=400')
67        }
68      })
69    })
70  }
71
72  function debounce(func, wait) {
73    let timeout
74    return function executedFunction(...args) {
75      const later = () => {
76        clearTimeout(timeout)
77        func(...args)
78      }
79      clearTimeout(timeout)
80      timeout = setTimeout(later, wait)
81    }
82  }
83
84  function performSearch(query) {
85    // Implement your custom search logic here
86    console.log('Searching for:', query)
87  }
88})()

Including Scripts in Your Site

Method 1: Hugo’s Asset Pipeline

Create a partial template layouts/partials/custom-scripts.html:

1{{- $customJS := resources.Get "js/custom.js" -}} {{- if $customJS -}} {{-
2$customJS = $customJS | js.Build | minify -}}
3<script src="{{ $customJS.RelPermalink }}" defer></script>
4{{- end -}} {{- $hbCustom := resources.Get "hb/modules/custom/js/index.ts" -}}
5{{- if $hbCustom -}} {{- $hbCustom = $hbCustom | js.Build | minify -}}
6<script src="{{ $hbCustom.RelPermalink }}" defer></script>
7{{- end -}}

Method 2: Direct Script Tags

Add to your layout templates or config:

1<!-- In layouts/_default/baseof.html or specific templates -->
2<script src="{{ "js/custom.js" | relURL }}" defer></script>

Method 3: Hugo Configuration

Add to config/_default/params.yaml:

1hb:
2  custom_js:
3    - 'js/custom.js'
4    - 'js/analytics.js'
5    - 'js/third-party.js'

Advanced Script Management

TypeScript Support

HBStack supports TypeScript out of the box. Create .ts files in assets/hb/modules/custom/js/:

 1// assets/hb/modules/custom/js/advanced.ts
 2interface CustomConfig {
 3  apiUrl: string
 4  timeout: number
 5  retries: number
 6}
 7
 8class CustomAPI {
 9  private config: CustomConfig
10
11  constructor(config: CustomConfig) {
12    this.config = config
13  }
14
15  async fetchData(endpoint: string): Promise<any> {
16    try {
17      const response = await fetch(`${this.config.apiUrl}/${endpoint}`, {
18        signal: AbortSignal.timeout(this.config.timeout)
19      })
20
21      if (!response.ok) {
22        throw new Error(`HTTP error! status: ${response.status}`)
23      }
24
25      return await response.json()
26    } catch (error) {
27      console.error('API fetch error:', error)
28      throw error
29    }
30  }
31}
32
33// Export for use in other modules
34export { CustomAPI, CustomConfig }

Module Dependencies

Create assets/hb/modules/custom/js/package.json:

1{
2  "name": "hbstack-custom-module",
3  "version": "1.0.0",
4  "description": "Custom HBStack module",
5  "main": "index.ts",
6  "dependencies": {
7    "@types/node": "^20.0.0"
8  }
9}

SCSS Processing

Create advanced styles in assets/hb/modules/custom/scss/:

 1// assets/hb/modules/custom/scss/variables.scss
 2:root {
 3  --custom-primary: #007bff;
 4  --custom-secondary: #6c757d;
 5  --custom-success: #28a745;
 6  --custom-danger: #dc3545;
 7  --custom-warning: #ffc107;
 8  --custom-info: #17a2b8;
 9}
10
11// assets/hb/modules/custom/scss/mixins.scss
12@mixin button-variant($bg-color, $text-color: white) {
13  background-color: $bg-color;
14  color: $text-color;
15  border: 1px solid $bg-color;
16
17  &:hover {
18    background-color: darken($bg-color, 10%);
19    border-color: darken($bg-color, 10%);
20  }
21
22  &:focus {
23    box-shadow: 0 0 0 0.2rem rgba($bg-color, 0.25);
24  }
25}
26
27// assets/hb/modules/custom/scss/components.scss
28.custom-card {
29  border-radius: 0.5rem;
30  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
31  transition: all 0.3s ease;
32
33  &:hover {
34    transform: translateY(-4px);
35    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
36  }
37}
38
39.custom-btn {
40  @include button-variant(var(--custom-primary));
41  padding: 0.5rem 1rem;
42  border-radius: 0.25rem;
43  text-decoration: none;
44  display: inline-block;
45  transition: all 0.2s ease;
46}

Best Practices

1. Performance Optimization

 1// Use event delegation for better performance
 2document.addEventListener('click', function (e) {
 3  if (e.target.matches('.custom-button')) {
 4    handleButtonClick(e.target)
 5  }
 6})
 7
 8// Debounce expensive operations
 9function debounce(func, wait) {
10  let timeout
11  return function executedFunction(...args) {
12    const later = () => {
13      clearTimeout(timeout)
14      func(...args)
15    }
16    clearTimeout(timeout)
17    timeout = setTimeout(later, wait)
18  }
19}

2. Error Handling

 1// Wrap your code in try-catch blocks
 2try {
 3  initCustomFeatures()
 4} catch (error) {
 5  console.error('Error initializing custom features:', error)
 6  // Fallback behavior
 7}
 8
 9// Use proper error boundaries for async operations
10async function safeAsyncOperation() {
11  try {
12    const result = await fetch('/api/data')
13    return await result.json()
14  } catch (error) {
15    console.error('Async operation failed:', error)
16    return null
17  }
18}

3. Progressive Enhancement

 1// Check for feature support before using
 2if ('IntersectionObserver' in window) {
 3  setupLazyLoading()
 4} else {
 5  // Fallback for older browsers
 6  loadAllImages()
 7}
 8
 9// Check for API availability
10if ('serviceWorker' in navigator) {
11  registerServiceWorker()
12}

4. Module Organization

 1// Create reusable modules
 2// assets/hb/modules/custom/js/utils.ts
 3export const utils = {
 4  formatDate: (date: Date): string => {
 5    return date.toLocaleDateString('en-US', {
 6      year: 'numeric',
 7      month: 'long',
 8      day: 'numeric'
 9    })
10  },
11
12  throttle: (func: Function, limit: number) => {
13    let inThrottle: boolean
14    return function (this: any, ...args: any[]) {
15      if (!inThrottle) {
16        func.apply(this, args)
17        inThrottle = true
18        setTimeout(() => (inThrottle = false), limit)
19      }
20    }
21  }
22}

Troubleshooting

Common Issues and Solutions

Based on our Hello World implementation, here are some common issues you might encounter:

Issue 1: “template for shortcode ‘partial’ not found”

Problem: Using {{< partial "filename.html" . >}} in markdown content.

Solution: Create a proper Hugo shortcode instead of trying to call partials directly from markdown.

1# Create the shortcode file
2mkdir -p layouts/shortcodes

File: layouts/shortcodes/your-script.html

1{{- $customJS := resources.Get "js/your-script.js" -}} {{- if $customJS -}} {{-
2$customJS = $customJS | js.Build | minify -}}
3<script src="{{ $customJS.RelPermalink }}" defer></script>
4{{- end -}}

Then use in markdown:

1{{< your-script >}}

Issue 2: JavaScript Not Loading

Symptoms: No console messages, no visual effects.

Debugging Steps:

  1. Check browser Developer Tools → Network tab
  2. Look for your JavaScript file in the network requests
  3. Verify the file path in assets/js/
  4. Ensure the shortcode is properly included
  5. Check for JavaScript errors in the Console tab

Issue 3: Script Loads But Functions Don’t Work

Common Causes:

  • DOM elements don’t exist when script runs
  • CSS selectors are incorrect
  • Event listeners aren’t properly attached

Solution: Always wrap your code in DOMContentLoaded:

1document.addEventListener('DOMContentLoaded', () => {
2  // Your code here
3})

Issue 4: Hugo Build Errors

Problem: “failed to extract shortcode” or similar build errors.

Solutions:

  1. Check shortcode syntax use the follwing format
  1. Verify file paths are correct
  2. Ensure shortcode files are in layouts/shortcodes/
  3. Restart Hugo server after creating new shortcodes

Integration Examples

Example 1: Custom Search Enhancement

 1// Enhance HBStack's search functionality
 2import { utils } from './utils'
 3
 4interface SearchResult {
 5  title: string
 6  url: string
 7  excerpt: string
 8}
 9
10class CustomSearch {
11  private searchIndex: SearchResult[] = []
12
13  async init() {
14    try {
15      const response = await fetch('/search-index.json')
16      this.searchIndex = await response.json()
17      this.setupSearch()
18    } catch (error) {
19      console.error('Failed to load search index:', error)
20    }
21  }
22
23  private setupSearch() {
24    const searchInput = document.querySelector(
25      '#search-input'
26    ) as HTMLInputElement
27    if (searchInput) {
28      searchInput.addEventListener(
29        'input',
30        utils.throttle(this.performSearch.bind(this), 300)
31      )
32    }
33  }
34
35  private performSearch(event: Event) {
36    const query = (event.target as HTMLInputElement).value.toLowerCase()
37    const results = this.searchIndex.filter(
38      item =>
39        item.title.toLowerCase().includes(query) ||
40        item.excerpt.toLowerCase().includes(query)
41    )
42    this.displayResults(results)
43  }
44
45  private displayResults(results: SearchResult[]) {
46    const container = document.querySelector('#search-results')
47    if (container) {
48      container.innerHTML = results
49        .map(
50          result => `
51                    <div class="search-result">
52                        <h3><a href="${result.url}">${result.title}</a></h3>
53                        <p>${result.excerpt}</p>
54                    </div>
55                `
56        )
57        .join('')
58    }
59  }
60}
61
62// Initialize when DOM is ready
63document.addEventListener('DOMContentLoaded', () => {
64  new CustomSearch().init()
65})

Example 2: Custom Analytics Integration

  1// Custom analytics tracking for HBStack
  2interface AnalyticsEvent {
  3  category: string
  4  action: string
  5  label?: string
  6  value?: number
  7}
  8
  9class CustomAnalytics {
 10  private trackingId: string
 11
 12  constructor(trackingId: string) {
 13    this.trackingId = trackingId
 14    this.init()
 15  }
 16
 17  private init() {
 18    // Track page views
 19    this.trackPageView()
 20
 21    // Track scroll depth
 22    this.trackScrollDepth()
 23
 24    // Track click events
 25    this.trackClicks()
 26  }
 27
 28  private trackEvent(event: AnalyticsEvent) {
 29    // Send to your analytics service
 30    console.log('Analytics Event:', event)
 31
 32    // Example: Google Analytics 4
 33    if (typeof gtag !== 'undefined') {
 34      gtag('event', event.action, {
 35        event_category: event.category,
 36        event_label: event.label,
 37        value: event.value
 38      })
 39    }
 40  }
 41
 42  private trackPageView() {
 43    this.trackEvent({
 44      category: 'page',
 45      action: 'view',
 46      label: window.location.pathname
 47    })
 48  }
 49
 50  private trackScrollDepth() {
 51    let maxScroll = 0
 52    const milestones = [25, 50, 75, 100]
 53
 54    window.addEventListener(
 55      'scroll',
 56      utils.throttle(() => {
 57        const scrollPercent = Math.round(
 58          (window.scrollY / (document.body.scrollHeight - window.innerHeight)) *
 59            100
 60        )
 61
 62        if (scrollPercent > maxScroll) {
 63          maxScroll = scrollPercent
 64
 65          milestones.forEach(milestone => {
 66            if (scrollPercent >= milestone && maxScroll < milestone + 5) {
 67              this.trackEvent({
 68                category: 'scroll',
 69                action: 'depth',
 70                label: `${milestone}%`,
 71                value: milestone
 72              })
 73            }
 74          })
 75        }
 76      }, 1000)
 77    )
 78  }
 79
 80  private trackClicks() {
 81    document.addEventListener('click', e => {
 82      const target = e.target as HTMLElement
 83
 84      // Track external links
 85      if (
 86        target.tagName === 'A' &&
 87        target.getAttribute('href')?.startsWith('http')
 88      ) {
 89        this.trackEvent({
 90          category: 'link',
 91          action: 'external_click',
 92          label: target.getAttribute('href') || ''
 93        })
 94      }
 95
 96      // Track button clicks
 97      if (target.tagName === 'BUTTON' || target.classList.contains('btn')) {
 98        this.trackEvent({
 99          category: 'button',
100          action: 'click',
101          label: target.textContent?.trim() || ''
102        })
103      }
104    })
105  }
106}
107
108// Initialize analytics
109document.addEventListener('DOMContentLoaded', () => {
110  new CustomAnalytics('YOUR_TRACKING_ID')
111})

FAQs

The assets/js/ folder is for general-purpose JavaScript files that don’t specifically relate to HBStack modules, while assets/hb/modules/ is specifically for HBStack framework customizations. Use assets/hb/modules/custom/ when you want to leverage HBStack’s TypeScript compilation, module system, and integration with the framework’s build pipeline.

Yes, you should restart the Hugo server when creating new files in the assets/hb/modules/ directory to ensure the newer created files are properly loaded by the build system. However, changes to existing files are usually picked up automatically in development mode.

Absolutely! HBStack has built-in TypeScript support. Create .ts files in the assets/hb/modules/custom/js/ directory, and they will be automatically compiled to JavaScript. You can also add type definitions and use modern ES6+ features.

You can include third-party libraries in several ways:

  1. Add them to assets/js/ and import using Hugo’s asset pipeline
  2. Use CDN links in your templates
  3. Install via npm and import in your TypeScript modules
  4. Download and place in static/js/ for direct linking

For CSS organization:

  • Use assets/css/ for general site styles
  • Use assets/hb/modules/custom/scss/ for HBStack-specific styles
  • Create subdirectories like components/, layouts/, utilities/ for better organization
  • Use SCSS features like variables, mixins, and imports for maintainable code

To debug JavaScript issues:

  1. Open browser Developer Tools (F12)
  2. Check the Console tab for error messages
  3. Use console.log() statements in your code
  4. Use the Network tab to verify scripts are loading
  5. Enable Hugo’s development mode for unminified assets
  6. Use source maps for easier debugging of compiled TypeScript

Yes, you can extend or override HBStack modules by:

  1. Creating custom modules in assets/hb/modules/custom/
  2. Using Hugo’s template override system in layouts/
  3. Modifying module parameters in your site configuration
  4. Creating custom partials that extend existing functionality

For optimal performance:

  1. Use Hugo’s asset bundling and minification
  2. Implement lazy loading for non-critical scripts
  3. Use event delegation instead of multiple event listeners
  4. Debounce/throttle expensive operations
  5. Use modern browser APIs when available with fallbacks
  6. Bundle related scripts together to reduce HTTP requests

For responsive JavaScript behavior:

  1. Use CSS media queries in JavaScript: window.matchMedia()
  2. Listen for resize events with debouncing
  3. Use Intersection Observer for element visibility
  4. Implement feature detection before using APIs
  5. Design mobile-first JavaScript interactions
  6. Test across different device sizes and orientations

To implement custom analytics:

  1. Create a dedicated analytics module in assets/hb/modules/custom/js/
  2. Track page views, scroll depth, and user interactions
  3. Use event delegation for efficient tracking
  4. Implement privacy-compliant tracking methods
  5. Use Google Analytics 4, Plausible, or custom solutions
  6. Test tracking implementation in browser dev tools