Files
bamort/.github/instructions/js.instructions.md
T
Frank 2af477397e added more file type specific instructions
I hope that coding results will be better and more consistent
2025-12-21 08:39:29 +01:00

7.3 KiB

description, applyTo
description applyTo
Instructions for writing JavaScript following project conventions and ES6+ best practices **/*.js,**/*.mjs

JavaScript Development Instructions

Follow ES6+ best practices and project-specific patterns for JavaScript code.

Module System

ES6 Modules

Use ES6 import/export syntax:

// Named exports
export const API = axios.create({ ... })
export function helper() { ... }

// Default export
export default {
  messages: { ... }
}

// Imports
import API from '../utils/api'
import { createI18n } from 'vue-i18n'

API Configuration (utils/api.js)

Standard Axios Instance Pattern

import axios from 'axios'

const API = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8180'
})

// Request interceptor - adds auth token
API.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// Response interceptor - handles 401
API.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token')
      // Optional: redirect to login
    }
    return Promise.reject(error)
  }
)

export default API

Key points:

  • Single Axios instance for the entire app
  • Auto-adds Authorization header from localStorage
  • Auto-handles 401 responses
  • Uses Vite environment variables

Pinia Store Pattern (stores/)

Standard Store Structure

import { defineStore } from 'pinia'
import { createI18n } from 'vue-i18n'
import de from '@/locales/de'
import en from '@/locales/en'

export const i18n = createI18n({
  legacy: false,
  locale: localStorage.getItem('language') || 'de',
  fallbackLocale: 'en',
  messages: { de, en }
})

export const useLanguageStore = defineStore('language', {
  state: () => ({
    currentLanguage: localStorage.getItem('language') || 'de'
  }),
  actions: {
    setLanguage(lang) {
      this.currentLanguage = lang
      i18n.global.locale.value = lang
      localStorage.setItem('language', lang)
    }
  }
})

Locale Files (locales/de, locales/en)

Translation Object Structure

Important: Locale files use .js extension and export objects (not .json):

// locales/de
export default {
  char: 'Figur',
  menu: {
    Datasheet: 'Datenblatt',
    Skill: 'Fertigkeiten'
  },
  export: {
    selectTemplate: 'Vorlage wählen',
    exportPDF: 'PDF Export',
    exporting: 'Exportiere...',
    pleaseSelectTemplate: 'Bitte Vorlage auswählen'
  }
}

Conventions:

  • Nested objects for grouping related translations
  • camelCase for keys
  • Always add to both de and en files simultaneously
  • Keep structure identical between languages

Async/Await Patterns

Error Handling

try {
  const response = await API.get('/api/endpoint')
  return response.data
} catch (error) {
  console.error('Operation failed:', error)
  throw error // or handle gracefully
}

Multiple Parallel Requests

const [characters, templates] = await Promise.all([
  API.get('/api/characters'),
  API.get('/api/pdf/templates')
])

Browser APIs

LocalStorage Usage

// Save
localStorage.setItem('token', response.data.token)
localStorage.setItem('language', 'de')

// Retrieve
const token = localStorage.getItem('token')
const lang = localStorage.getItem('language') || 'de'

// Remove
localStorage.removeItem('token')

Blob/File Handling

// Create blob from response
const blob = new Blob([response.data], { type: 'application/pdf' })
const url = window.URL.createObjectURL(blob)

// Open in new window
const pdfWindow = window.open(url, '_blank')

// Clean up after use
setTimeout(() => window.URL.revokeObjectURL(url), 10000)

URL Parameters

// Build query string
const params = new URLSearchParams({
  template: templateId,
  showUserName: 'true'
})
const url = `/api/export?${params.toString()}`

// Extract from params
const queryParams = Object.fromEntries(params)

Event Handling

Debouncing/Throttling

For search inputs or resize events:

let debounceTimer
function debounce(func, delay = 300) {
  return (...args) => {
    clearTimeout(debounceTimer)
    debounceTimer = setTimeout(() => func(...args), delay)
  }
}

// Usage
const search = debounce(async (query) => {
  const results = await API.get(`/api/search?q=${query}`)
}, 300)

Cleanup

// Save timer ID for cleanup
this.timer = setTimeout(() => { ... }, 5000)

// Clean up in component lifecycle
beforeUnmount() {
  if (this.timer) clearTimeout(this.timer)
}

Array/Object Operations

Array Methods

// Filter
const learned = skills.filter(s => s.Fertigkeitswert > 0)

// Map
const names = characters.map(c => c.name)

// Find
const char = characters.find(c => c.id === 18)

// Some/Every
const hasSkills = character.fertigkeiten.some(f => f.Fertigkeitswert > 0)

Object Destructuring

// Response destructuring
const { data, headers, status } = response

// Props destructuring
const { character, template, showUserName = false } = options

Spread Operator

// Merge objects
const merged = { ...defaults, ...userOptions }

// Copy array
const copy = [...originalArray]

Common Patterns

Loading State Management

export default {
  data() {
    return {
      isLoading: false,
      data: null,
      error: null
    }
  },
  async created() {
    await this.loadData()
  },
  methods: {
    async loadData() {
      this.isLoading = true
      this.error = null
      try {
        const response = await API.get('/api/data')
        this.data = response.data
      } catch (error) {
        this.error = error.message
      } finally {
        this.isLoading = false
      }
    }
  }
}

Form Validation

validateForm() {
  if (!this.selectedTemplate) {
    alert(this.$t('export.pleaseSelectTemplate'))
    return false
  }
  return true
}

async submit() {
  if (!this.validateForm()) return
  
  // Proceed with submission
}

Best Practices

  1. Use const by default, let when reassignment needed, never var
  2. Prefer arrow functions for callbacks and short functions
  3. Use template literals for string interpolation
  4. Handle promise rejections with try/catch or .catch()
  5. Clean up timers and intervals in component lifecycle
  6. Use optional chaining ?. for nested properties
  7. Use nullish coalescing ?? instead of || for default values
  8. Keep functions small and single-purpose
  9. Document complex logic with comments
  10. Use meaningful variable names - avoid single letters except loops

Anti-Patterns to Avoid

Don't use var - use const or let Don't ignore promise rejections Don't mutate function parameters Don't create memory leaks (clean up listeners, timers) Don't use eval() or new Function() Don't mix callbacks and promises Don't forget to handle edge cases (null, undefined, empty arrays) Don't use == - always use === for comparisons

Environment Variables (Vite)

Accessing Variables

const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180'
const isDev = import.meta.env.DEV
const isProd = import.meta.env.PROD

Convention: Prefix all custom variables with VITE_