Creating Components in Ecopages
Components are the building blocks of your Ecopages project. They allow you to create reusable pieces of UI that can be easily composed to build complex pages. In this guide, we'll explore how to create components using the default ghtml approach with @ecopages/core.
Basic Component Structure
A typical component in Ecopages consists of two files:
- A
.tsfile for the component's structure and logic - A
.script.tsfile for additional client-side interactivity (optional) - A
.cssfile for styling (optional)
Let's create a simple counter component to demonstrate this structure, we will use the Radiant library from @ecopages/radiant to add client-side interactivity.
RadiantCounter.ts
import { eco, html } from '@ecopages/core';
import type { RadiantCounterProps } from './radiant-counter.script';
export const RadiantCounter = eco.component<RadiantCounterProps>({
dependencies: {
scripts: ['./radiant-counter.script.ts'],
stylesheets: ['./radiant-counter.css'],
},
render: ({ count }) => html`
<radiant-counter count="${count}">
<button type="button" data-ref="decrement" aria-label="Decrement">-</button>
<span data-ref="count">${count}</span>
<button type="button" data-ref="increment" aria-label="Increment">+</button>
</radiant-counter>
`,
});radiant-counter.script.ts
import { RadiantElement } from '@ecopages/radiant/core';
import { customElement } from '@ecopages/radiant/decorators/custom-element';
import { onEvent } from '@ecopages/radiant/decorators/on-event';
import { onUpdated } from '@ecopages/radiant/decorators/on-updated';
import { query } from '@ecopages/radiant/decorators/query';
import { reactiveProp } from '@ecopages/radiant/decorators/reactive-prop';
export type RadiantCounterProps = {
value?: number;
};
@customElement('radiant-counter')
export class RadiantCounter extends RadiantElement {
@reactiveProp({ type: Number, reflect: true }) declare value: number;
@query({ ref: 'count' }) countText!: HTMLElement;
@onEvent({ ref: 'decrement', type: 'click' })
decrement() {
if (this.value > 0) this.value--;
}
@onEvent({ ref: 'increment', type: 'click' })
increment() {
this.value++;
}
@onUpdated('value')
updateCount() {
this.countText.textContent = this.value.toString();
this.dispatchEvent(new Event('change'));
}
}
declare global {
namespace JSX {
interface IntrinsicElements {
'radiant-counter': HtmlTag & RadiantCounterProps;
}
}
}
Key Points:
- Use the
eco.componentfactory from@ecopages/core. - Pass dependencies and render function to
eco.component. - Define props using an interface and pass it as a generic type.
- Use custom elements for client-side interactivity.
Component Config
The eco.component factory automatically handles the configuration attachment. You simply define the dependencies within the factory options.
export const RadiantCounter = eco.component({
dependencies: {
scripts: ['./radiant-counter.script.ts'],
stylesheets: ['./radiant-counter.css'],
},
render: (props) => { ... }
});Mandatory fields in options:
render: Function that returns the component's HTML.dependencies: (Optional withineco.componentbut recommended if needed) Object specifying scripts, stylesheets, and sub-components.
Lazy Loading
Ecopages supports lazy loading of component scripts to improve initial page load performance. This is configured via the dependencies.lazy option.
export const RadiantCounter = eco.component({
dependencies: {
lazy: {
'on:interaction': 'mouseenter,focusin',
scripts: ['./radiant-counter.script.ts'],
},
stylesheets: ['./radiant-counter.css'],
},
render: (props) => { ... }
});When you look at the rendered HTML, you will see that Ecopages automatically wraps your component with a scripts-injector custom element. This element handles the loading of your scripts based on the defined triggers (e.g., interaction or visibility).
CSS in Ecopages Components
In Ecopages, CSS for components is typically defined in separate files and loaded through the component's dependencies.
Please note that tailwind and postcss are supported by default in Ecopages. Please refer to the configuration section for more details.
Here's how it works:
- Separate CSS Files: Each component can have its own CSS file. This promotes modularity and makes styles easier to manage.
- CSS File Naming: Usually, the CSS file is named the same as the component file, but with a .css extension. For example, radiant-counter.css for the RadiantCounter component.
- Loading CSS: The CSS file is specified in the dependencies.stylesheets array.
Using Components in Pages
To use your components in pages, import them and include them in your HTML template:
import { RadiantCounter } from '@/components/radiant-counter';
import { BaseLayout } from '@/layouts/base-layout';
import { eco, html } from '@ecopages/core';
export default eco.page({
metadata: () => ({
title: 'Home page',
description: 'This is the homepage of the website',
}),
dependencies: {
components: [BaseLayout, RadiantCounter],
},
render: () => html`${BaseLayout({
class: 'main-content',
children: html`<h1>Ecopages</h1>${RadiantCounter({ count: 5 })}`,
})}`,
});Best Practices for Component Creation
- Separation of Concerns: Keep your component's structure (
.ts) separate from its client-side logic (.script.ts). - Use HTML Templates: Leverage the
htmltemplate literal for creating your component's structure. - Custom Elements: Use custom elements for adding client-side interactivity to your components.
- TypeScript: Use TypeScript for better type checking and improved developer experience.
- Styling: Use separate CSS files for styling your components.
- Factory Pattern: Use
eco.componentto create consistent and correctly typed components.
By following these guidelines and leveraging the power of ghtml components with @ecopages/core, you can create robust, reusable, and interactive components that will help you build efficient and maintainable websites with Ecopages.