Framejs
An ultra light library for writing custom elements
Install / Use
/learn @framejs/FramejsREADME
FrameJS
Welcome to FrameJS! This documentation tries to help answer any questions you may have about what FrameJS is, how to use it and what its APIs are.
A web component library for building reusable elements
Build encapsulated elements that manage their own state, then reuse them in any web project to make complex UIs. With its small size (~1.5kb gzipped) it fits well for simple elements as well as for complex components.
Why FrameJS?
FrameJS tries to make it easy and safe to build UI elements you can use across projects and frameworks.
It doesn't rely on specific build pipelines or compilers, so it fits right into existing projects. It supports and provides decorators for Typescript, using JSX and other templating languages. And you can choose to use features like shadow dom and rendering per element as fits.
It won't stop you from using any existing techniques for custom elements. Its purpose is to aid you as a developer by providing tested functionality and speed up your work.
Try FrameJS
Try FrameJS online or set up your local development environment.
Online
If you’re interested in playing around with FrameJS, you can use an online code playground. Try a Hello World template on Stackblitz
Other Hello World templates on stackblitz:
Demos on stackblitz:
Local development
Prequisitions
Make sure you've installed and/or updated Node before continuing.
It's recommended for setting up tools to bundle and minify for production. A modern build pipeline typically consists of:
-
A package manager, such as Yarn or NPM. It lets you take advantage of a vast ecosystem of third-party packages, and easily install or update them.
-
A bundler, such as Webpack or Browserify. It lets you write modular code and bundle it together into small packages to optimize load time.
The custom elements API dictates elements to we written as es6 classes. A good practice is to let a consumer application transform the code to es5, or compile to both. The browser runtime cannot mix es5 and es6 custom elements.
Usage
Install with npm
npm install @framejs/core
// hello-world.js
import { FrameElement } from './node_modules/@framejs/core/dist/frame-element.js';
class HelloWorld extends FrameElement {
render() {
return `<h1>Hello World!</h1>`
}
}
customElements.define('hello-world', HelloWorld);
<!-- index.html -->
<hello-world></hello-world>
<script type="module" src="./hello-world.js">
The default render function replaces innerHTML, and it's recommended to use an advanced renderer like the lit-html renderer or preact renderer. See examples on stackblitz
Polyfills
For more information on the polyfills, see the web components polyfill documentation.
Examples
These examples expect that you are using a module bundler of some kind.
Example: Callback after first render and on destroy
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
elementDidMount() {
console.log('Hello!');
}
elementDidUnmount() {
console.log('Bye!')
}
render() {
return `Hello World!`
}
}
customElements.define('hello-world', HelloWorld);
Example: Using properties
Properties are available as this.props[property], this[property] and destructuring
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get props() {
return {
greeting: 'Hello World!'
}
}
static get propTypes() {
return {
greeting: String
}
}
render() {
return `${this.props.greeting}`;
}
}
customElements.define('hello-world', HelloWorld);
Example: Using attribute reflections
This sets the attribute greeting="Hello World!" on the element.
Changing the attribute on the element updates the prop and triggers are re-render.
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get props() {
return {
greeting: 'Hello World!'
};
}
static get propTypes() {
return {
greeting: String
};
}
static get reflectedProps() {
return ['greeting'];
}
render() {
return `${this.props.greeting}`;
}
}
customElements.define('hello-world', HelloWorld);
Example: Using Prop Observers
Prop observers are running every time a specified prop changes
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get props() {
return {
greeting: 'Hello World!'
};
}
static get propTypes() {
return {
greeting: String
};
}
static get propObservers() {
return {
greeting: '_greetingObserver'
};
}
_greetingObserver(oldValue, newValue) {
console.log(oldValue, newValue)
}
render() {
return `${this.props.greeting}`;
}
}
customElements.define('hello-world', HelloWorld);
Example: Using event listeners
Event listeners are added on connectedCallback and removed on disconnectedCallback.
The syntax for a listener is:
'Event'- event listener on element'Event:#child- event listener on child element. the string after:is used for selecting the element.
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get props() {
return {
greeting: 'Hello World!'
};
}
static get propTypes() {
return {
greeting: String
};
}
static get eventListeners() {
return {
'click': '_handleClick',
'click:#myButton': '_handleButtonClick'
};
}
_handleClick(event) {
console.log(event, 'element clicked!')
}
_handleButtonClick(event) {
console.log(event, 'button clicked!')
}
render() {
return `${this.props.greeting} <button id="myButton">Click me!</button>`;
}
}
customElements.define('hello-world', HelloWorld);
Example: Setting style that supports ShadyCSS if polyfill is loaded and needed
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get style() {
return `
:host {
color: dodgerBlue;
}
`;
}
render() {
return html`
<style>${this.constructor.style}</style>
Hello World!`;
}
}
customElements.define('hello-world', HelloWorld);
You should not set
<style>in the template if you are using VDOM like renderer-preact
Example: Custom element without shadow dom
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get shadow() {
return false;
}
}
customElements.define('hello-world', HelloWorld);
Example: Custom element with custom shadow mode
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get shadowMode() {
return 'closed';
}
}
customElements.define('hello-world', HelloWorld);
Example: Prevent re-render on prop changes
Manually trigger re-render by using this.invalidate();
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
_invalidateOnPropChanges = false;
}
customElements.define('hello-world', HelloWorld);
Access element properties and methods from Destructuring
this is passed to render(), but you can still reference them manually via this.
import { FrameElement } from '@framejs/core';
class HelloWorld extends FrameElement {
static get props() {
return {
greeting: 'Hello'
}
}
render({ greeting }) {
return `${greeting} World!`
}
}
customElements.define('hello-world', HelloWorld);
Extentions for typescript
It's possible to use type reflection if loading the reflect-metadata polyfill, alternatively pass in type to @Attribute({type: String});
@Attribute() automatically gets reflected as attributes and can be of type: String | Boolean | Number.
@Attribute() myProp: String; will be set as attribute my-prop;
import {
Define,
FrameElement,
Property,
Attribute,
Listen,
Observe,
Event,
EventEmitter
} from '@framejs/core';
@Define({
tag: 'hello-world',
style: `:host { color: dodgerBlue; }`, // optional
shadow: true, // default true
mode: 'open', // default 'open'
invalidateOnPropChanges: true // default true
})
class HelloWorld extends FrameElement {
@Property() greeting: String = 'Hello';
@Attribute() target: String = 'World'!
@Event() targetChanged: EventEmitter;
@Observe('target')
_handleTargetChange(oldValue, newValue) {
this.targetChanged.emi
Related Skills
qqbot-channel
353.3kQQ 频道管理技能。查询频道列表、子频道、成员、发帖、公告、日程等操作。使用 qqbot_channel_api 工具代理 QQ 开放平台 HTTP 接口,自动处理 Token 鉴权。当用户需要查看频道、管理子频道、查询成员、发布帖子/公告/日程时使用。
docs-writer
100.7k`docs-writer` skill instructions As an expert technical writer and editor for the Gemini CLI project, you produce accurate, clear, and consistent documentation. When asked to write, edit, or revie
model-usage
353.3kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
project-overview
FlightPHP Skeleton Project Instructions This document provides guidelines and best practices for structuring and developing a project using the FlightPHP framework. Instructions for AI Coding A
