Getting Started with Libraries

On this page 26

This guide walks you through creating, developing, and publishing your first Stacks library.

Prerequisites

Before creating a library, ensure you have:

  • Bun v1.0 or higher
  • A Stacks project (or start fresh with a library)

Creating a Library

Option 1: New Library Project

Create a dedicated library project:

panx @stacksjs/buddy new my-library

cd my-library
bun install

Option 2: Add to Existing Project

Add library support to your existing Stacks project. There is no dedicated scaffolding command for this; create the library structure shown below and scaffold the individual pieces as needed:

buddy make:component MyComponent
buddy make:function my-function

Project Structure

After setup, your library structure looks like this:

my-library/
├── components/           # STX components
│   └── .gitkeep
├── functions/            # TypeScript functions
│   └── .gitkeep
├── docs/                 # Documentation site
│   └── index.md
├── tests/                # Test files
│   └── .gitkeep
├── library.config.ts     # Library configuration
├── package.json          # Package metadata
├── tsconfig.json         # TypeScript config
└── README.md

Your First Component

Create a simple button component:

<!-- components/Button.stx -->
<template>
  <button
    :class="['stacks-button', `stacks-button--${variant}`]"
    :disabled="disabled"
    @click="emit('click', $event)"
  >
    <slot />
  </button>
</template>

<script>
export interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'outline'
  disabled?: boolean
}

const props = withDefaults(defineProps<ButtonProps>(), {
  variant: 'primary',
  disabled: false,
})

const emit = defineEmits<{
  click: [event: MouseEvent]
}>()
</script>

<style scoped>
.stacks-button {
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  transition: all 0.2s;
}

.stacks-button--primary {
  background-color: #3b82f6;
  color: white;
}

.stacks-button--primary:hover {
  background-color: #2563eb;
}

.stacks-button--secondary {
  background-color: #6b7280;
  color: white;
}

.stacks-button--outline {
  background-color: transparent;
  border: 2px solid #3b82f6;
  color: #3b82f6;
}

.stacks-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

Your First Function

Create a utility function:

// functions/useToggle.ts
import { ref, type Ref } from '@stacksjs/stx'

export interface UseToggleReturn {
  value: Ref<boolean>
  toggle: () => void
  setTrue: () => void
  setFalse: () => void
}

export function useToggle(initial = false): UseToggleReturn {
  const value = ref(initial)

  function toggle() {
    value.value = !value.value
  }

  function setTrue() {
    value.value = true
  }

  function setFalse() {
    value.value = false
  }

  return {
    value,
    toggle,
    setTrue,
    setFalse,
  }
}

Export Your Library

Create the main entry point:

// index.ts

// Export components
export { default as Button } from './components/Button.stx'

// Export functions
export { useToggle } from './functions/useToggle'

// Export types
export type { ButtonProps } from './components/Button.stx'
export type { UseToggleReturn } from './functions/useToggle'

Development

Start Dev Server

Preview your components in isolation:

buddy dev:components

This opens a development environment at <http://localhost:3333> where you can:

  • View all components
  • Test different props
  • See documentation

Watch Mode

The library build commands have no --watch flag today; re-run the builds as you develop:

buddy build:components
buddy build:functions

Testing

Write Tests

// tests/Button.test.ts
import { describe, it, expect } from 'bun:test'
import { mount } from '@stacksjs/stx/testing'
import Button from '../components/Button.stx'

describe('Button', () => {
  it('renders with default props', () => {
    const wrapper = mount(Button, {
      slots: { default: 'Click me' },
    })

    expect(wrapper.text()).toBe('Click me')
    expect(wrapper.classes()).toContain('stacks-button--primary')
  })

  it('applies variant class', () => {
    const wrapper = mount(Button, {
      props: { variant: 'secondary' },
    })

    expect(wrapper.classes()).toContain('stacks-button--secondary')
  })

  it('emits click event', async () => {
    const wrapper = mount(Button)

    await wrapper.trigger('click')

    expect(wrapper.emitted('click')).toHaveLength(1)
  })

  it('can be disabled', () => {
    const wrapper = mount(Button, {
      props: { disabled: true },
    })

    expect(wrapper.attributes('disabled')).toBeDefined()
  })
})

Run Tests

# Run all tests
bun test

# Run with coverage
bun test --coverage

# Watch mode
bun test --watch

Building

Production Build

buddy build:components
buddy build:functions

This generates:

dist/
├── index.mjs         # ESM bundle
├── index.cjs         # CommonJS bundle
├── index.d.ts        # TypeScript declarations
├── components/
│   ├── Button.stx.mjs
│   └── Button.stx.d.ts
└── functions/
    ├── useToggle.mjs
    └── useToggle.d.ts

Configuration

Library Config

// library.config.ts
export default defineLibraryConfig({
  name: 'my-library',

  // Entry points
  entries: {
    '.': './index.ts',
    './components': './components/index.ts',
    './functions': './functions/index.ts',
  },

  // Build options
  build: {
    formats: ['esm', 'cjs'],
    minify: true,
    sourcemap: true,
  },

  // External dependencies (not bundled)
  external: ['@stacksjs/stx'],

  // Documentation
  docs: {
    enabled: true,
    title: 'My Library',
  },
})

Package.json

{
  "name": "my-library",
  "version": "0.1.0",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"],
  "peerDependencies": {
    "@stacksjs/stx": "^3.0.0"
  }
}

Documentation

Generate Docs

buddy build:docs   # generate the documentation site
buddy dev:docs     # serve it locally while you write

Component Docs

Add documentation directly in components:

<docs>
# Button

A customizable button component.

## Basic Usage

```html

<Button>Click me</Button>

Variants


<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>

Props

PropTypeDefaultDescription
variantstring'primary'Button style
disabledbooleanfalseDisable button

## Publishing

### Prepare for Publish

```bash
# Run all checks
buddy prepublish

Publish to npm

# Login to npm
npm login

# Publish
buddy publish

# Or with npm directly
npm publish

Next Steps