Tiny framework based on native web components.
npm install piecesjsGetting Started with Piecesjs: Building Native Web Components with a Lightweight Framework
piecesjs is a lightweight JavaScript framework built upon native web components, offering a suite of tools and utilities tailored for creative websites.
---
At its core, a “Piece” is a modular component that can live anywhere on your webpage. Each Piece operates independently, with its own encapsulated styles and interactions, making it easy to manage and reuse across your site.
Piecesjs dynamically imports only the necessary JavaScript and CSS for each page, optimizing performance while maintaining flexibility. Unlike larger frameworks, it allows you to build exactly what you need, free from the overhead of unnecessary code or restrictive architectures.
Designed for creative websites that rely heavily on JavaScript logic—handling multiple steps, states, and events. Piecesjs offers a streamlined and scalable approach for developers looking to create highly interactive experiences.
Compiled with vitejs.
- Dynamic JS & CSS Import: Automatically loads only the necessary JavaScript and CSS for each page, improving performance.
- Scoped Event Management: Easily manage events within a specific component’s scope using this.on() and this.off() methods.
- Convenient Access to Scoped HTMLElements: Quickly access elements within the component using this.$() or this.domAttr('slug').
- Seamless Communication Between Active Components: Components can communicate effortlessly with each other using this.call() or this.emit().
- Efficient Global CSS Management: Streamlined handling of global CSS imports to keep your styles organized.
- PiecesManager: Provides centralized access to all active pieces, simplifying component management.
- Introduction
- Main features
- Installation
- TypeScript Support
- Create your first Piece
- Lifecycle
- Queries
- Events
- HTML Event System
- Communication between components
- PiecesManager
- Methods, props and attributes
- Support
```
npm i piecesjs --save
TypeScript definitions are included, so you get autocomplete and type checking out of the box.
`typescript
import { Piece } from 'piecesjs';
class MyComponent extends Piece {
mount(): void {
const button = this.$
}
}
`
`html`
`js
import { Piece } from 'piecesjs';
export class Counter extends Piece {
constructor() {
super('Counter', {
stylesheets: [() => import('/assets/css/components/counter.css')],
});
}
mount() {
this.$button = this.$('button');
this.on('click', this.$button, this.click);
}
unmount() {
this.off('click', this.$button, this.click);
}
render() {
return
Value: ${this.value}
;
} click() {
this.value = parseInt(this.value) + 1;
}
set value(value) {
return this.setAttribute('value', value);
}
get value() {
return this.getAttribute('value');
}
// Important to automatically call the update function if attribute is changing
static get observedAttributes() {
return ['value'];
}
}
// Register the custom element
customElements.define('c-counter', Counter);
`$3
`html
Hello world
``js
import { Piece } from 'piecesjs';class Header extends Piece {
constructor() {
// Set the name of your component and stylesheets directly with the super();
super('Header', {
stylesheets: [() => import('/assets/css/components/header.css')],
});
}
}
// Register the custom element
customElements.define('c-header', Header);
`$3
`js
import { load } from 'piecesjs';load('c-button', () => import('/assets/js/components/Button.js'));
`The load function can take a context (HTMLElement) as its third parameter. It's really usefull for page transitions or if you add dynamically some pieces in your DOM.
This will re-run a “check” of the pieces present in the "context", and mount them if there are any new ones.
`js
import { load } from 'piecesjs';load(
'c-button',
() => import('/assets/js/components/Button.js'),
document.querySelector('#wrapper'),
);
`---
Lifecycle
`js
premount(firstHit = true){}
render(){} // if you want to do a Javascript rendering
mount(firstHit = true){} // firstHit parameter is set to false if the function is called after an update or if its content is changed.
update(){} //Called if an attribute is changed. Then it will call unmount(), premount() and mount().
unmount(update = false){} // update = true if this unmount() is called after an attribute is changed.
`Queries
$3
Shortcut to query an element.
this.dom(query, context) is also available.`js
let myButton = this.$('button'); // returns a NodeList if there is more than one element otherwise returns the HTMLElement
`$3
`html
- Item 1
- Item 2
``js
/**
* Query by data-dom attribute
* @param {string} query - Value of data-dom attribute
* @param {Element} [context=this] - Context element, this by default
*/
let listItems = this.domAttr('listItem'); // returns a NodeList if there is more than one element otherwise returns the HTMLElement
`$3
If you prefer get a
NodeList even if there is just one element (useful for dynamic content), you can call these functions : this.$All(), this.domAll() and this.domAttrAll() with the same parameters as aboveEvents
Register an event listener with
this.on()`js
/**
* Register event listener - the function is automatically binded to this
* Tips: call event listeners in the mount()
* @param {string} type - Event type (e.g., 'click', 'mouseenter')
* @param {HTMLElement|HTMLElement[]} el - Target element(s), Document, or Window
* @param {Function} func - Event handler function
* @param {Object} [params] - Optional parameters
*/
mount() {
this.on('click', this.$button, this.click, {hello: 'world'}); // You can also use this.on() to add an event listener on global elements
// this.on('resize', window, this.resize);
}
// if you have set params, the eventObject will be available after
click(params, event) {}
`Unregister an event listener with
this.off()`js
/**
* Remove event listener
* Tips: remove event listeners in the unmount()
* @param {string} type - Event type (e.g., 'click', 'mouseenter')
* @param {HTMLElement} el - Target element(s), Document, or Window
* @param {Function} func - Event handler function to remove
*/
unmount() {
this.off('click', this.$button, this.click);
}
`$3
PiecesJS provides a declarative way to handle events directly in your HTML using
data-events-* attributes.data-events-*="functionName[,pieceName][,pieceId]"#### Syntax
`html
`#### Example
`html
``js
class Header extends Piece {
toggleMenu() {
console.log('Menu toggled!');
}
}class Counter extends Piece {
reset() {
this.value = 0;
}
increment(event) {
// Get the event, useful to get event.currentTarget
console.log(event);
this.value = parseInt(this.value) + 1;
}
get value() {
return this.getAttribute('value');
}
set value(val) {
this.setAttribute('value', val);
}
static get observedAttributes() {
return ['value'];
}
}
`You can use any DOM event:
data-events-click, data-events-mouseenter, data-events-input, etc.#### How it works
PiecesJS automatically scans for
data-events-* attributes during mount, binds the appropriate event listeners, and cleans them up on unmount.Communication between components
$3
Call a function of any components, from any components
`js
/**
* Call function of a component, from a component
* @param {string} func - Method name to call
* @param {Object} args - Arguments to pass
* @param {string} pieceName - Name of the target component(s)
* @param {string} [pieceId] - Specific component ID (optional)
*/
this.call('increment', {}, 'Counter', 'myCounterComponentId');
`It returns the value returned by the called function. Usefull if you want to return a Promise 👀
If no
pieceId are specified, all occurrences of the component will be called.
A pieceId can be set directly with an attribute cid`html
`$3
You can also emit a custom event with
this.emit()`js
/**
* Emit a custom event
* @param {string} eventName - Name of the custom event
* @param {HTMLElement} [el=document] - Element to dispatch on, document by default
* @param {Object} [params] - Parameters to pass in event.detail
*/
this.emit('buttonIsMounted', document, { value: 'A Button is mounted!' });
`Then, in a Piece you can use
this.on(), like the default events.`js
mount() {
this.on('buttonIsMounted', document, this.customEventTrigger);
}// You can get parameters with event.detail
customEventTrigger(event) {
console.log(event.detail); // { value: 'A Button is mounted! }
}
unmount() {
this.off('buttonIsMounted', document, this.customEventTrigger);
}
`PiecesManager
PiecesManager manage all active components.
Get access of all current components visible in the page:
`js
// From anywhere
import { piecesManager } from 'piecesjs';
console.log(piecesManager.currentPieces);// In a Piece
console.log(this.piecesManager);
class Header extends Piece {
mount() {
console.log(this.piecesManager.currentPieces);
}
}
/*
{
Counter: {
c0: {
name: 'Counter',
id: 'c0',
piece: HTMLElement
},
myCounterComponentId: {
name: 'Counter',
id: 'myCounterComponentId',
piece: HTMLElement
}
},
Button: {
c2: {
name: 'Button',
id: 'c2',
piece: HTMLElement
}
},
Header: {
c1: {
name: 'Header',
id: 'c1',
piece: HTMLElement
}
}
}
*/
`---
Memo
$3
| Attribute | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
log | You can log the lifecycle of your Piece with an attribute |
| cid | To override the generated id of your Piece. Usefull to communicate with this specific Piece |
| data-events- | Declarative way to handle events directly in your HTML. Format: data-events-="functionName[,pieceName][,pieceId]". Example: |$3
| Attribute | Description |
| ----------- | ---------------------------------------------------------------- |
|
this.cid | A generated id of the Piece, override if you set a cid attribute |
| this.name | Return the name of the Piece |$3
| Method | Description | Arguments |
| -------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
this.$ | Query an HTMLElement |
query: String context: HTMLElement, this by default |
| this.dom | this.$ clone |
query: String context: HTMLElement, this by default |
| this.domAttr | Query with a slug. If you have an element like | slug: String |$3
| Method | Description | Arguments |
| ----------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
this.on | Event listener for scoped and binded (this) events |
type: String (example: mouseenter, resize..)el: HTMLElement or HTMLElement[]func: Functionparams: {} (optional) |
| this.off | Remove event listener, the better way is to put it in the unmount(). |
type: String (example: mouseenter, resize..)el: HTMLElement or HTMLElement[]func: Function |
| this.call | Call of a function of a Piece or a specific Piece based on its cid |
func: String, the function nameargs: ObjectpieceName: StringpieceId: String (optional), linked to a cid attribute |
| this.emit | Emit a custom event. Can be listened by the other Pieces with a this.on() |
eventName: Stringel: HTMLElement, document by default (optional)params: Object (optional) |You want to collaborate ?
Clone the repo and at the root
/`
npm i
`In the test environment, link your local piecesjs to use it as an npm package
`
cd /test
npm link piecesjs
`Then back to the root with
cd ../
and build piecesjs`
npm run build
`Test environment :
In the folder
/test`
npm run dev
``Enjoy and feel free to create a pull request!
If you want to support me, and follow the journey of the creation of pieces 👀