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

@melodicdev/core

v1.2.5

Published

A lightweight, modern web component framework built on native browser APIs.

Readme

Melodic

A lightweight, modern web component framework built on native browser APIs with TypeScript decorators, reactive signals, and an ultra-fast template system.

Install

npm install @melodicdev/core

Install the CLI globally (optional but recommended):

npm install -g @melodicdev/cli
melodic --help

Quick Start (CLI)

melodic init my-app
cd my-app
npm install
npm run dev

Quick Start (Manual)

import { MelodicComponent, html, css, signal } from '@melodicdev/core';

@MelodicComponent({
	selector: 'hello-world',
	template: (self) => html`
		<section>
			<h1>Hello, ${self.name()}!</h1>
			<button @click=${self.increment}>Clicks: ${self.count()}</button>
		</section>
	`,
	styles: () => css`
		:host {
			display: block;
			font-family: sans-serif;
		}
		button {
			margin-top: 0.5rem;
		}
	`
})
export class HelloWorldComponent {
	name = signal('World');
	count = signal(0);

	increment = () => this.count.update((value) => value + 1);
}

Use it in HTML:

<hello-world></hello-world>

Web Apps

  • web/example is the demo app. Run with npm run dev.
  • web/benchmark is the performance suite. Run with npm run dev:benchmark and update bundle sizes with npm run benchmark:update.

Core Concepts

Components

import { MelodicComponent, html, css } from '@melodicdev/core';

@MelodicComponent({
	selector: 'user-card',
	template: (self) => html`
		<article>
			<h2>${self.name}</h2>
			<p>${self.role}</p>
		</article>
	`,
	styles: () => css`
		:host { display: block; }
		article { padding: 1rem; border: 1px solid #eee; }
	`
})
export class UserCardComponent {
	name = 'Ada Lovelace';
	role = 'Engineer';
}

Import template and styles from separate files:

import { MelodicComponent } from '@melodicdev/core';
import { profileTemplate } from './profile.template';
import { profileStyles } from './profile.styles';

@MelodicComponent({
	selector: 'user-profile',
	template: profileTemplate,
	styles: profileStyles
})
export class UserProfileComponent {
	name = 'Ada Lovelace';
	role = 'Engineer';
}

Global styles: if you add a <style> or <link> tag with the melodic-styles attribute, Melodic will share it across components. If you never call setGlobalStylesAttribute, Melodic defaults to melodic-styles. For Vite builds, link a file under /src (for example /src/styles/global.css) to get minified, hashed output; link a public asset (for example /styles/global.css) if you want to preserve the path.

Lifecycle hooks available on the component instance:

  • onInit, onCreate, onRender, onDestroy
  • onPropertyChange(name, oldVal, newVal)
  • onAttributeChange(name, oldVal, newVal)

Templates and Bindings

import { html, classMap, styleMap } from '@melodicdev/core';

html`
	<input .value=${value} @input=${onInput} />
	<button class=${classMap({ active: isActive })}>Toggle</button>
	<div style=${styleMap({ backgroundColor: color })}></div>
`;

Binding prefixes:

  • text interpolation: ${value}
  • attribute: attr=${value}
  • property: .prop=${value}
  • event: @event=${handler}
  • attribute directive: :directive=${value}

Signals

import { signal, computed } from '@melodicdev/core';

const price = signal(10);
const qty = signal(2);
const total = computed(() => price() * qty());

Signals on component instances are automatically subscribed to and re-render the component when they change.

Routing

import type { IRoute } from '@melodicdev/core';

const routes: IRoute[] = [
	{ path: '', redirectTo: '/home' },
	{ path: 'home', component: 'home-page', name: 'home' },
	{ path: 'users/:id', component: 'user-detail', name: 'user.detail' },
	{
		path: 'settings',
		component: 'settings-page',
		loadComponent: () => import('./settings-page.component')
	}
];
<nav>
	<a :routerLink="/home">Home</a>
	<a :routerLink="/settings">Settings</a>
</nav>
<router-outlet .routes=${routes}></router-outlet>

State (Signal Store)

import { createState, createAction, props, onAction, createReducer, provideRX } from '@melodicdev/core/state';

const addTodo = createAction('[Todos] Add', props<{ text: string }>());

const state = createState({ todos: [] as string[] });
const reducers = {
	todos: createReducer(
		onAction(addTodo, (current, action) => [...current, action.payload.text])
	)
};

// register in bootstrap: provideRX(state, reducers, { todos: TodosEffects })

Forms

import { createFormGroup, createFormControl, Validators } from '@melodicdev/core/forms';

const form = createFormGroup({
	email: createFormControl('', { validators: [Validators.required, Validators.email] })
});
import { html } from '@melodicdev/core/template';

html`
	<input type="email" :formControl=${form.get('email')} />
`;

HTTP Client

import { HttpClient } from '@melodicdev/core/http';

const http = new HttpClient({ baseURL: 'https://api.example.com' });
const response = await http.get('/users');

Dependency Injection

import { Injectable, Service, html, MelodicComponent } from '@melodicdev/core';

@Injectable()
class ApiService {
	getStatus(): string {
		return 'ok';
	}
}

@MelodicComponent({ selector: 'status-pill', template: () => html`<div></div>` })
class StatusPillComponent {
	@Service(ApiService) private api!: ApiService;
}

Bootstrap

import { bootstrap } from '@melodicdev/core';
import './components/app-root.component';

await bootstrap({
	rootComponent: 'app-root',
	target: '#app',
	devMode: true
});

Documentation

Publishing

Framework build and publish:

npm run build:lib
npm publish --access public

CLI build and publish:

npm --workspace @melodicdev/cli run build
npm publish --workspace @melodicdev/cli --access public

License

MIT