🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Sign inDemoInstall
Socket

composably

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

composably

Composably is a build-time content processing and integration plugin for Vite and SvelteKit. It enhances the build process by automatically discovering, parsing, validating, and transforming content from files (like Markdown and YAML) into structured data

0.0.11
latest
Source
npm
Version published
Weekly downloads
391
74.55%
Maintainers
1
Weekly downloads
 
Created
Source

Composably ✨

Composably is a build-time content processing and integration plugin for Vite and SvelteKit. It enhances the build process by automatically discovering, parsing, validating, and transforming content from files (like Markdown and YAML) into structured data readily consumable by Svelte components, ensuring type safety along the way.

The content is analyzed before SvelteKit processes components (or do anything really), this makes it possible to inject virtual components from content, which is useful if you want to use components in your markdown (without making markdown components).

This effectively makes SvelteKit a Static Site Generator much like Astro, Hugo and Jekyll. For those of you who take progressive enhancement seriously: Here's your opportunity to really start from the bottom.

The API is currently a bit unpolished, but the showcase pages and composably.spec.svelte.ts describe most of the functionality that this plugin provides. Feel free to clone and hack.

1. Setup

Create a new SvelteKit (^2.20.0) project or use an existing, then install composably:

npm install composably

In your vite.config.ts, add composably() and pass a config with the locations for your content and components.

// vite.config.ts
import { composably } from 'composably/vite';

const config = {
  componentRoot: 'src/components',
  contentRoot: 'src/content',
  //optional
  remarkPlugins: [],
  rehypePlugins: []
};

export default defineConfig({
  plugins: [composably(config)]
});

The plugin exposes a pre-built virtual module composably:content with all content it has discovered under your contentRoot (e.g. src/lib/content):

// src/routes/your-ssg-site/[...path]/+page.ts
import content from 'composably:content';

export const load = async ({ params }) => {
  return await content(params.path);
};

// src/routes/your-ssg-site/[...path]/+page.svelte
<page.component {...page} />

2. Markdown example

Create this index.md directly under contentRoot:

---
component: Page # This will be replaced by a reference to the Page.svelte component
title: Hello Composably!
---

Welcome to your first **Composable** page!
Write standard Markdown here, it will be available as
`body` along with the frontmatter.

## Features

- The headings are extracted for a TOC
- Components can be inserted in the document using ::slots
- Frontmatter can be interpolated with double braces {{title}}
- Emojis, definition lists, extended tables and more...

Create a component Page.svelte in componentRoot (e.g. src/lib/components):

<script module>
  import { c } from 'composably/schemas'; // Schema helpers

  export const schema = c.content({
    title: c.string(), // Expect a string title
    body: c.markdown() // Markdown will be automatically processed
  });
</script>

<script>
  // Props are validated against the schema above!
  // Note: 'body' includes processed content AND metadata like headings
  let { title, body } = $props();
</script>

<h1>{title}</h1>

{#if body.headings && body.headings.length > 0}
<nav>
  <strong>On this page:</strong>
  <ul>
    {#each body.headings as heading}
    <li><a href="#{heading.id}">{heading.text}</a></li>
    {/each}
  </ul>
</nav>
{/if}

<body.component {...body} />

Load in src/routes/+page.ts:

import content from 'composably:content';

export const load = async ({ params }) => {
  return await content(params.path);
};

Render in src/routes/+page.svelte:

<script>
  let { page } = $props();
</script>

<page.component {...page} />

Boom! Validated, pre-loaded content from markdown.

3. YAMLs work as expected

content/features.yaml:

component: FeatureList
title: Awesome Features
features:
  - name: Type-Safe Content
    description: Catch errors at build time, not runtime!
  - name: Component-Driven
    description: Map content directly to Svelte components.
  - name: Flexible Formats
    description: Use Markdown OR YAML based on your needs.

Create src/components/FeatureList.svelte:

<script module>
  import { c } from 'composably/schemas';

  export const schema = c.content({
    title: c.string(),
    features: c.array(c.object({
      name: c.string(),
      description: c.string()
    }))
  });
</script>

<script>
  let { title, features } = $props();
</script>

<h2>{title}</h2>
<ul>
  {#each features as feature, key (key)}
    <li><strong>{feature.name}:</strong> {feature.description}</li>
  {/each}
</ul>

Load it: const features = await content('features');

4. Reusable Content

Define common content once, reuse everywhere.

Create content/_author-jane.yaml (leading _ ignored in route discovery):

component: AuthorBio
name: Jane Doe
bio: Expert writer exploring Composably.

Create src/components/AuthorBio.svelte:

<script module>
  import { c } from 'composably/schemas';
  export const schema = c.content({ name: c.string(), bio: c.string() });
</script>
<script> let { name, bio } = $props(); </script>

<span><strong>{name}</strong> ({bio})</span>

Reference it in content/blog/my-post.md:

---
component: BlogPost
title: My Awesome Post
author: _author-jane.yaml # <-- Reference the fragment!
---

Blog content here...

src/components/BlogPost.svelte schema expects it:

<script module>
  import { c } from 'composably/schemas';
  export const schema = c.content({
    title: c.string(),
    // Validate the linked fragment data against AuthorBio's schema!
    author: c.component(['AuthorBio']),
    body: c.markdown()
  });
</script>

<script>
  let { title, author, body } = $props();
</script>

<article>
  <h1>{title}</h1>
  <p>By <author.component {...author} /></p>
  <body.component {...body} />
</article>

5. Embedding Components

Need a carousel or alert inside your Markdown flow? Use slots!

Define slot data in content/another-post.md:

---
component: Page
title: Embedding with Slots
slots:
  carousel: # Slot name
    component: Swiper # Component for the slot
    slides: # Props for the Swiper component
      - image: /img1.jpg
      - image: /img2.jpg
---

Here is some introductory text.

Now, right here, I want my image carousel:

::carousel

And the text continues after the embedded component. How cool is that?

Ensure src/components/Page.svelte schema includes slots:

// <script module> in Page.svelte
import { c } from 'composably/schemas';
export const schema = c.content({
  title: c.string(),
  body: c.markdown(), // Processes ::carousel using 'slots' data
  slots: c.slots()
});
// ... rest of Page.svelte ...

Create src/components/Swiper.svelte with its schema (slides: c.array(...)). Composably's c.markdown processor magically replaces ::carousel with the rendered Swiper component!

6. Built-in Power & Extensibility

Composably's markdown parser comes with the following features out-of-the-box:

  • Markdown Processing: Includes standard Markdown, GitHub Flavored Markdown, and syntax highlighting for code blocks.
  • Heading Extraction: As seen in step 2, the body.headings prop on your c.markdown() field gives you structured access to all h1-h6 tags (text, id, depth) – perfect for auto-generating Tables of Contents!
  • Plugin Ecosystem: Need more? Composably integrates with the Remark (Markdown AST) and Rehype (HTML AST) plugin ecosystems. Add plugins to:
    • Automatically add CSS classes (e.g., integrate with Tailwind or DaisyUI).
    • Optimize images.
    • Add custom containers or directives.
    • Generate SEO tags.
    • ...and more!

Contribution and disclaimer

⚠️ Early Alpha - Use with Caution! ⚠️

This package is currently in the early alpha stage of development. While functional for its core purpose, APIs might change, bugs are likely present, and it has not yet been battle-tested in diverse production environments. Please do not rely on this for critical applications yet.

Testers and contributors are warmly welcome! Your feedback, bug reports, and code contributions are highly valuable at this stage.

Getting Started with Development

Follow these steps to set up the project locally for development or testing:

  • Clone the repository:

    git clone https://github.com/kompismoln/composably
    cd composably
    
  • Install dependencies:

    # Using npm
    npm install
    
    # Or using pnpm
    # pnpm install
    
    # Or using yarn
    # yarn install
    
  • Start the development server:

    💡 Set DEBUG=composably* for verbose logging during npm run dev, npm run test, or npm run build.

    This runs the example site included in the repository, using the local version of the plugin.

    npm run dev
    
  • Run tests:

    npm run test      # Runs unit tests once
    npm run test:unit # Runs unit tests in watch mode
    
  • Check code quality: Ensure your changes meet the project's standards before committing.

    npm run format    # Formats code using Prettier
    npm run lint      # Lints code using ESLint
    npm run check     # Runs svelte-check for type checking
    
  • Build the package: This compiles the plugin code into the /dist directory.

    npm run build
    

Using Nix (Optional)

If you use Nix, you can enter a reproducible development shell with all required dependencies activated:

nix develop

Keywords

svelte

FAQs

Package last updated on 05 May 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts