# Frontend Code Conventions in Macro: A Complete Style Guide for TypeScript and Vite

> Discover Macro's frontend code conventions. Enforce strict TypeScript, ESLint, Vite, Vitest, and Prettier rules for absolute imports, kebab-case files, and type safety in apps web.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: best-practices
- Published: 2026-08-21

---

**The Macro front-end enforces strict TypeScript and ESLint rules through Vite, Vitest, and Prettier, requiring absolute imports, kebab-case file names, and comprehensive type safety across all `apps/web` modules.**

The open-source Macro repository (`macro-inc/macro`) maintains a rigorous set of frontend code conventions designed to keep the web application scalable and maintainable. Built on Vite and TypeScript, the project in `apps/web` follows enterprise-grade standards for linting, testing, and module organization that every contributor must follow.

## Build System and Project Configuration

The Macro frontend uses **Vite** as its primary build tool. All configuration resides in [`apps/web/vite.config.ts`](https://github.com/macro-inc/macro/blob/main/apps/web/vite.config.ts), which imports shared settings from [`apps/web/vite.base.ts`](https://github.com/macro-inc/macro/blob/main/apps/web/vite.base.ts).

The base configuration establishes critical build parameters including path aliasing and plugin initialization:

```typescript
// apps/web/vite.base.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';

export const createAppViteConfig = () =>
  defineConfig({
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src'), // absolute import alias
      },
    },
    plugins: [vue()],
  });

```

This setup enforces consistent build behavior across development and production environments while enabling the `@/` import alias used throughout the codebase.

## TypeScript and Type Safety Standards

All frontend code must be written in TypeScript using `.ts` or `.tsx` extensions. The project maintains strict type safety through parser configuration in the ESLint setup.

Key type safety rules include:
- **No explicit `any`**: The `@typescript-eslint/no-explicit-any` rule prevents loose typing except in whitelisted overrides.
- **No non-null assertions**: Usage of the `!` operator is forbidden unless explicitly permitted in ESLint configuration overrides.
- **Strict type checking**: The parser references `project: "./tsconfig.eslint.json"` to enable type-aware linting rules.

## ESLint Configuration and Linting Rules

The canonical ESLint configuration lives in [`infra/stacks/fusion-auth/.eslintrc.js`](https://github.com/macro-inc/macro/blob/main/infra/stacks/fusion-auth/.eslintrc.js) and extends to the frontend codebase. The setup uses `@typescript-eslint/parser` with a comprehensive plugin stack.

Active ESLint plugins and configurations include:
- `eslint:recommended`
- `@typescript-eslint/recommended`
- `@typescript-eslint/recommended-requiring-type-checking`
- `import`
- `jest`
- `prettier`

Critical enforced rules include `@typescript-eslint/no-unused-vars` for dead code elimination, `@typescript-eslint/no-unsafe-member-access` for type safety, and `import/order` for consistent module organization.

## Code Formatting with Prettier

Prettier integration runs through the ESLint plugin and `eslint-config-prettier`. Contributors must format code before committing using the project's task runner commands.

Formatting commands available in the repository:
- `npm run fmt` for JavaScript/TypeScript files
- `just fmt` as the unified task runner equivalent

This ensures consistent code style across all frontend modules without manual formatting debates.

## Testing Standards with Vitest

The Macro frontend uses **Vitest** for unit and integration testing, configured in [`apps/web/vitest.config.ts`](https://github.com/macro-inc/macro/blob/main/apps/web/vitest.config.ts). Tests must be colocated with source files using the `*.test.ts` naming convention.

```typescript
// apps/web/vitest.config.ts
import { defineConfig } from 'vitest/config';
import { createAppViteConfig } from './vite.base';

export default defineConfig({
  ...createAppViteConfig(),
  test: {
    globals: true,
    environment: 'jsdom',
  },
});

```

All tests run in a Node.js environment with jsdom for DOM simulation. The configuration inherits the Vite base settings to ensure test environment parity with production builds.

## Import and Module Resolution

**Absolute imports** are strongly preferred using the `@/` alias defined in [`vite.base.ts`](https://github.com/macro-inc/macro/blob/main/vite.base.ts). Relative imports are permitted only for sibling files within the same directory.

Preferred pattern:

```typescript
// Correct: Absolute import
import { unwrap } from '@/lib/utils/unwrap';

```

Discouraged pattern:

```typescript
// Avoid: Deep relative paths
import { unwrap } from '../../../lib/utils/unwrap';

```

This convention prevents fragile relative path chains and clarifies module boundaries across the application structure.

## Naming Conventions

The repository follows strict casing rules for file and symbol naming:

- **Files**: `kebab-case` (e.g., [`my-component.ts`](https://github.com/macro-inc/macro/blob/main/my-component.ts), [`user-service.ts`](https://github.com/macro-inc/macro/blob/main/user-service.ts))
- **Functions and constants**: `camelCase` (e.g., `getUserData`, `MAX_RETRY_COUNT`)
- **Types and interfaces**: `PascalCase` (e.g., `UserProfile`, `ApiResponse`)

Generated SDK code in directories like [`apps/web/src/lib/service-clients/service-storage/generated/zod.ts`](https://github.com/macro-inc/macro/blob/main/apps/web/src/lib/service-clients/service-storage/generated/zod.ts) demonstrates the PascalCase convention for schema types:

```typescript
import { z } from 'zod';

export const FileTypeSchema = z.enum([
  'docx',
  'pdf',
  'md',
]);

export type FileType = z.infer<typeof FileTypeSchema>;

```

## Error Handling Patterns

The frontend prefers explicit error handling over null assertions. The codebase provides utility functions like `unwrap` in [`apps/web/src/lib/utils/unwrap.ts`](https://github.com/macro-inc/macro/blob/main/apps/web/src/lib/utils/unwrap.ts) for type-safe value extraction:

```typescript
// apps/web/src/lib/utils/unwrap.ts
export function unwrap<T>(value: T | undefined | null, msg = 'Unexpected undefined'): T {
  if (value == null) {
    // eslint-disable-next-line @typescript-eslint/no-throw-literal
    throw new Error(msg);
  }
  return value;
}

```

This pattern eliminates the need for non-null assertions while providing clear error messages during failures. Async operations use `try/catch` blocks with structured logging following the `tracing` style conventions from the Rust backend.

## Summary

- **Build Tool**: Vite with shared configuration in [`vite.base.ts`](https://github.com/macro-inc/macro/blob/main/vite.base.ts) and entry point at [`vite.config.ts`](https://github.com/macro-inc/macro/blob/main/vite.config.ts)
- **Type Safety**: Strict TypeScript enforced via `@typescript-eslint/parser` and type-aware rules
- **Linting**: Comprehensive ESLint setup in [`infra/stacks/fusion-auth/.eslintrc.js`](https://github.com/macro-inc/macro/blob/main/infra/stacks/fusion-auth/.eslintrc.js) with no-explicit-any and no-unused-vars rules
- **Formatting**: Prettier integration via ESLint with `npm run fmt` automation
- **Testing**: Vitest with jsdom environment and colocated `*.test.ts` files
- **Imports**: Absolute `@/` aliases preferred over relative paths
- **Naming**: Kebab-case files, camelCase functions, PascalCase types
- **Error Handling**: Utility-based unwrapping instead of non-null assertions

## Frequently Asked Questions

### How do I configure the import alias in a new Macro frontend module?

Import aliases are defined in [`apps/web/vite.base.ts`](https://github.com/macro-inc/macro/blob/main/apps/web/vite.base.ts) within the `resolve.alias` configuration. The standard `@` alias points to the `src` directory. When adding new path aliases, update both the Vite configuration and the [`tsconfig.json`](https://github.com/macro-inc/macro/blob/main/tsconfig.json) paths to maintain TypeScript resolution parity.

### What ESLint rules are strictly enforced in the Macro frontend?

The configuration in [`infra/stacks/fusion-auth/.eslintrc.js`](https://github.com/macro-inc/macro/blob/main/infra/stacks/fusion-auth/.eslintrc.js) enforces `@typescript-eslint/no-explicit-any`, `@typescript-eslint/no-unused-vars`, `@typescript-eslint/no-unsafe-member-access`, and `import/order`. Non-null assertions (`!`) are forbidden unless explicitly overridden for specific lines or files.

### Where should I place unit tests in the Macro repository?

Tests must be colocated with source files using the [`.test.ts`](https://github.com/macro-inc/macro/blob/main/.test.ts) suffix (e.g., [`utils.ts`](https://github.com/macro-inc/macro/blob/main/utils.ts) pairs with [`utils.test.ts`](https://github.com/macro-inc/macro/blob/main/utils.test.ts)). The Vitest configuration in [`apps/web/vitest.config.ts`](https://github.com/macro-inc/macro/blob/main/apps/web/vitest.config.ts) automatically discovers these files, running them in a jsdom environment with access to Vite's resolved aliases.

### How does Macro handle TypeScript type checking during builds?

Type checking occurs through the ESLint parser configuration using `project: "./tsconfig.eslint.json"` in the ESLint settings. This enables type-aware linting rules that catch type errors alongside style issues, while Vite handles the actual compilation and bundling separately.