npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@canonical/summon-component

v0.12.0-experimental.0

Published

Component generators for Summon - React and Svelte component scaffolding

Readme

@canonical/summon-component

Component scaffolding for React and Svelte projects. Generates production-ready component structures with TypeScript, tests, stories, and styles — all following consistent conventions.

Why Use This?

Creating a new component by hand means creating 5-7 files with boilerplate that's almost identical every time. This generator handles that in one command, ensuring:

  • Consistent file structure across your codebase
  • Proper TypeScript types from the start
  • Test files that actually test something
  • Storybook stories ready to customize
  • Auto-registration in your component barrel

Installation

bun add @canonical/summon-component

Requires @canonical/summon as a peer dependency:

bun add @canonical/summon

Or link globally for use across projects:

cd /path/to/summon-component
bun link        # for bun
npm link        # for npm

Quick Start

React Component

# Interactive — prompts guide you
summon component react

# Direct — specify path
summon component react --component-path=src/components/Button

# Preview — see what would be created
summon component react --component-path=src/components/Button --dry-run

Svelte Component

# Interactive
summon component svelte

# Direct
summon component svelte --component-path=src/lib/components/Card

# With TypeScript stories instead of Svelte CSF
summon component svelte --component-path=src/lib/components/Card --use-ts-stories

What Gets Generated

React

For summon component react --component-path=src/components/Button:

src/components/Button/
├── Button.tsx           # Component implementation
├── types.ts             # Props interface
├── index.ts             # Barrel export
├── Button.test.tsx      # Unit tests (Testing Library)
├── Button.ssr.test.tsx  # SSR tests (optional)
├── Button.stories.tsx   # Storybook stories (optional)
└── styles.css           # Component styles (optional)

And appends to src/components/index.ts:

export * from "./Button";

Generated Component

// Button.tsx
import type { ButtonProps } from "./types";
import "./styles.css";

export const Button = ({
  className,
  children,
  ...props
}: ButtonProps): JSX.Element => {
  return (
    <div className={`button${className ? ` ${className}` : ""}`} {...props}>
      {children}
    </div>
  );
};

Generated Types

// types.ts
import type { HTMLAttributes, PropsWithChildren } from "react";

export interface ButtonProps
  extends PropsWithChildren<HTMLAttributes<HTMLDivElement>> {}

Generated Test

// Button.test.tsx
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { Button } from "./Button";

describe("Button", () => {
  it("renders children", () => {
    render(<Button>Hello</Button>);
    expect(screen.getByText("Hello")).toBeInTheDocument();
  });

  it("applies custom className", () => {
    render(<Button className="custom">Content</Button>);
    expect(screen.getByText("Content")).toHaveClass("custom");
  });
});

Svelte

For summon component svelte --component-path=src/lib/components/Card:

src/lib/components/Card/
├── Card.svelte            # Svelte 5 component with runes
├── types.ts               # Props interface
├── index.ts               # Barrel export
├── Card.svelte.test.ts    # Unit tests
├── Card.ssr.test.ts       # SSR tests (optional)
├── Card.stories.svelte    # Storybook CSF (optional)
└── styles.css             # External styles (optional, or inline <style>)

Generated Component

<!-- Card.svelte -->
<script lang="ts">
  import type { CardProps } from "./types";

  let {
    class: className = "",
    children,
    ...props
  }: CardProps = $props();
</script>

<div class="card{className ? ` ${className}` : ''}" {...props}>
  {@render children?.()}
</div>

<style>
  .card {
    /* Component styles */
  }
</style>

Uses Svelte 5 runes ($props()) and render tags (@render).


Options Reference

React Options

| Flag | Description | Default | |------|-------------|---------| | --component-path | Full path for the component (e.g., src/components/Button) | Interactive prompt | | --with-styles | Include styles.css file | true | | --no-with-styles | Skip styles file | — | | --with-stories | Include Storybook stories | true | | --no-with-stories | Skip stories file | — | | --with-ssr-tests | Include SSR test file | true | | --no-with-ssr-tests | Skip SSR tests | — |

Svelte Options

| Flag | Description | Default | |------|-------------|---------| | --component-path | Full path for the component (e.g., src/lib/components/Card) | Interactive prompt | | --with-styles | Include inline <style> block | true | | --no-with-styles | Skip styles | — | | --with-stories | Include Storybook stories | true | | --no-with-stories | Skip stories | — | | --use-ts-stories | Use .stories.ts instead of .stories.svelte | false | | --with-ssr-tests | Include SSR test file | true | | --no-with-ssr-tests | Skip SSR tests | — |

Global Options

| Flag | Description | |------|-------------| | --dry-run, -d | Preview without writing files | | --yes, -y | Skip confirmation prompts | | --no-preview | Skip the file preview step | | --help | Show all options |


Component Path Convention

The --component-path determines:

  1. Directory location — where files are created
  2. Component name — derived from the last path segment
--component-path=src/components/UserProfile
#                └──────────────┘└─────────┘
#                   directory      name: UserProfile

The component name is used as-is for PascalCase (class names) and converted for other cases:

  • UserProfile.tsx (filename)
  • UserProfileProps (types)
  • user-profile (CSS class in styles)

Barrel Export Auto-Update

The generator automatically appends an export to the parent directory's index.ts:

summon component react --component-path=src/components/Button

Appends to src/components/index.ts:

export * from "./Button";

If the barrel file doesn't exist, it's created.


Customization

Override with Local Generators

Create a local generator to override the installed one. Local generators take precedence:

your-project/
└── generators/
    └── component/
        └── react/
            └── index.ts    # Your custom React component generator

Now summon component react uses your version.

Extend the Base Generators

Import and compose with the existing generators:

// generators/component/react/index.ts
import { generators } from "@canonical/summon-component";
import { sequence_, writeFile } from "@canonical/summon";

const baseGenerator = generators["component/react"];

export const generator = {
  ...baseGenerator,

  // Add custom prompts
  prompts: [
    ...baseGenerator.prompts,
    {
      name: "withI18n",
      type: "confirm",
      message: "Include i18n hooks?",
      default: false,
    },
  ],

  // Extend generation
  generate: (answers) => sequence_([
    // Run base generator
    baseGenerator.generate(answers),

    // Add your files
    answers.withI18n && writeFile(
      `${answers.componentPath}/useTranslations.ts`,
      `export const useTranslations = () => ({ t: (k: string) => k });\n`
    ),
  ].filter(Boolean)),
};

export const generators = {
  "component/react": generator,
};

Modify Templates

Fork the package and edit templates in src/react/templates/ or src/svelte/templates/. Templates use EJS syntax.


Testing Your Components

The generated test files use Vitest and Testing Library. Run them with:

# If you have vitest configured
bun test

# Or specifically
bun vitest src/components/Button/Button.test.tsx

SSR Tests

SSR tests verify the component renders without errors on the server:

// Button.ssr.test.tsx
import { renderToString } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { Button } from "./Button";

describe("Button SSR", () => {
  it("renders without hydration errors", () => {
    const html = renderToString(<Button>Click me</Button>);
    expect(html).toContain("Click me");
  });
});

Storybook Integration

Generated stories work with Storybook 7+:

// Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  component: Button,
  title: "Components/Button",
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Default: Story = {
  args: {
    children: "Click me",
  },
};

Svelte CSF vs TypeScript Stories

By default, Svelte components get .stories.svelte files using Svelte CSF. Use --use-ts-stories for traditional TypeScript stories if you prefer.


Programmatic Usage

Use the generators in your own code:

import { generators } from "@canonical/summon-component";
import { dryRun, getAffectedFiles } from "@canonical/summon";

const reactGenerator = generators["component/react"];

// Preview what would be created
const task = reactGenerator.generate({
  componentPath: "src/components/Button",
  withStyles: true,
  withStories: true,
  withSsrTests: false,
});

const { effects } = dryRun(task);
console.log("Would create:", getAffectedFiles(effects));

Troubleshooting

"Generator not found"

Ensure the package is installed and discoverable:

# Check if summon sees it
summon

# Should show:
# component [pkg] (has subtopics)
#   └─ react, svelte

If not, verify installation:

bun add @canonical/summon-component

"Cannot find module @canonical/summon"

Install the peer dependency:

bun add @canonical/summon

Barrel file not updated

The generator appends to {parentDir}/index.ts. If your project uses a different convention (e.g., index.tsx or no barrel), manually add the export.


Related


Known Limitations

Compound Components

The current generator creates single-level components. For compound components with subcomponents (e.g., Accordion.Item, Card.Header), you'll need to:

  1. Generate the parent component
  2. Manually create the common/ directory structure for subcomponents
  3. Attach subcomponents using dot notation

Expected structure for compound components:

Accordion/
├── Accordion.tsx           # Parent with Accordion.Item = Item
├── types.ts
├── index.ts
└── common/
    └── Item/
        ├── Item.tsx
        ├── types.ts
        ├── index.ts
        └── styles.css

Alternative: For ontology-driven component generation that handles compound components, see the component-from-ontology skill at /skills/component-from-ontology/SKILL.md.

Code Standards

Generated templates use generic patterns. For project-specific standards (CSS namespacing, className construction patterns, etc.), consider:

  1. Customizing templates in a local generator
  2. Post-generation linting/formatting
  3. Using the component-from-ontology skill which integrates with semantic code standards

Non-Interactive Environments

See Summon How-To: Non-Interactive Usage for CI/LLM usage patterns.

License

GPL-3.0