@ecopages/core
The foundational engine for the Ecopages framework. It provides the core build pipeline, development server, routing, and plugin architecture required to run an Ecopages application.
Overview
Ecopages is an extensible static site generator (SSG) built around a Bun-first core with Node fallback support and Vite-hosted compatibility for advanced dev and build orchestration. It embraces a strictly MPA (Multi-Page Application) architecture by default, rendering HTML at build-time or request-time, and hydrating interactive islands only where necessary.
- Runtime Boundary:
createApp() prefers Bun when available and falls back to Node otherwise. Vite and Nitro still own host-side dev and build orchestration.
- Build Ownership: Bun-native execution keeps a core-owned build path. Vite and Nitro own host-side transforms, module graph behavior, and deployment-oriented builds.
- Framework agnostic: First-class support for KitaJS, Lit, React, and MDX via official integration plugins.
- Extensible: Hook into the build process with custom processors or rendering integrations.
Current Architecture
The current core package is organized around app-owned runtime state and explicit service boundaries.
The important ownership rules are:
ConfigBuilder.build() finalizes app-owned build and runtime services.
- browser bundling and server module loading are explicit, separate paths.
- runtime hosts stay thin and delegate framework work into core services.
- HMR and invalidation use shared graph-aware services instead of runtime-specific ad hoc wiring.
Bootstrap And Runtime Ownership
flowchart TD
A[eco.config.ts] --> B[ConfigBuilder.build]
B --> C[App build adapter]
B --> D[App build manifest]
B --> E[Build executor]
B --> F[Dev graph service]
B --> G[Host module loader boundary]
G --> H[PageModuleImportService]
E --> H
E --> I
E --> I[BrowserBundleService]
H --> J[Runtime app adapter]
J --> K[Bun adapter or Node adapter]
D --> I
Development Invalidation And HMR Flow
flowchart TD
A[File change] --> B[ProjectWatcher]
B --> C[DevelopmentInvalidationService]
C --> D{Change kind}
D -->|Route or server source| E[Invalidate server modules]
D -->|Public or include| F[Reload browser]
D -->|Processor-owned asset| G[Notify processor only]
D -->|HMR-eligible source| H[Core HMR manager]
H --> I[Strategy selection]
I --> J[Core JsHmrStrategy]
I --> K[Integration strategy e.g. ReactHmrStrategy]
J --> L[BrowserBundleService]
K --> L
K --> M[importServerModule]
M --> N[ServerModuleTranspiler]
L --> O[Updated browser bundle]
O --> P[Client bridge broadcast]
The manager/orchestration layer is core-owned, but framework-specific strategies such as React HMR are contributed by integrations and registered with the shared HMR manager.
Practical Summary
ConfigBuilder seeds one app-owned build ownership path, adapter, manifest, executor, dev graph, and runtime registry.
BrowserBundleService is the shared browser build seam used by HMR and asset-oriented browser output paths.
ServerModuleTranspiler is the shared server-side source loading seam used by runtime bootstrap and HMR metadata loading.
createApp() stays the universal runtime entrypoint, while Vite and Nitro hosts own their advanced dev and build workflows.
- One bundled adapter is the default bundler. Vite-based apps route through the
ViteHostBuildAdapter boundary marker instead.
Documentation Map
Use this package README as the top-level map, then drill into the focused subsystem READMEs:
src/config/README.md: config finalization and app-owned runtime/build state
src/plugins/README.md: integration and processor authoring contracts
src/build/README.md: build adapter, executor, and development build coordination
src/services/README.md: cross-cutting runtime services and orchestration helpers
src/adapters/README.md: Bun, Node, and shared adapter boundaries
src/hmr/README.md: HMR strategy and update-layer ownership
src/router/README.md: route discovery, matching, and browser navigation coordination
src/route-renderer/README.md: rendering orchestration and dependency resolution
src/static-site-generator/README.md: static build execution path
src/eco/README.md: eco authoring APIs for pages, layouts, and components
The intended reading order is:
- this file for the big-picture architecture
src/config/README.md for config and lifecycle ownership
src/plugins/README.md and src/build/README.md for contribution contracts
src/services/README.md and src/adapters/README.md for runtime execution
src/router/README.md and src/route-renderer/README.md for request-time flow
Installation
bun add @ecopages/core
(You can also use npm, yarn, or pnpm)
Basic Usage
The Ecopages architecture revolves around an eco.config.ts file and an application entry point.
1. Configuration (eco.config.ts)
Configure your integratons, processors, and default metadata. Ecopages uses a builder pattern:
import { ConfigBuilder } from '@ecopages/core/config-builder';
const config = await new ConfigBuilder()
.setRootDir(import.meta.dirname)
.setBaseUrl(import.meta.env.ECOPAGES_BASE_URL ?? 'http://localhost:3000')
.setDefaultMetadata({
title: 'My Ecopages Site',
description: 'Built with Ecopages',
})
.build();
export default config;
2. Application Entry (app.ts)
Start the application using createApp. It will choose the Bun adapter when Bun is available and fall back to Node otherwise.
import { createApp } from '@ecopages/core/create-app';
import appConfig from './eco.config';
const app = await createApp({ appConfig });
await app.start();
3. Creating Pages
Use the eco.page() factory to define static routes. Place these in src/pages/:
import { eco } from '@ecopages/core';
import { BaseLayout } from '@/layouts/base-layout';
export default eco.page({
layout: BaseLayout,
metadata: () => ({
title: 'Home',
}),
render: () => (
<div>
<h1>Welcome to Ecopages</h1>
</div>
),
});
4. Reusable Components
Define components with eco.component() to automatically inject necessary stylesheets or scripts only when that component is rendered:
import { eco } from '@ecopages/core';
export const MyButton = eco.component({
dependencies: {
stylesheets: ['./button.css'],
},
render: ({ label }) => <button className="my-button">{label}</button>,
});
Dependency ownership affects final asset packaging:
- Stylesheets and scripts declared from
eco.html() stay Html-owned and can be emitted as shared app-wide assets.
- Stylesheets and scripts declared from Pages, Layouts, or Components are resolved into page-owned assets for the rendered route.
- This split is intentional. Shared Html assets can be cached across routes, while page-owned assets can change without invalidating the global shell.
5. API Handlers
Add server-side routes using defineApiHandler. Register them on your app instance before starting:
import { defineApiHandler } from '@ecopages/core';
export const helloWorld = defineApiHandler({
path: '/api/hello',
method: 'GET',
handler: async ({ response }) => {
return response.json({ message: 'Hello World' });
},
});
Attach the handler in your app.ts entry:
import { createApp } from '@ecopages/core/create-app';
import { helloWorld } from './handlers/hello';
import appConfig from './eco.config';
const app = await createApp({ appConfig });
app.get(helloWorld);
await app.start();
See the official documentation for advanced usage, API handlers, and integrations.
Import Structure
Use the create-app subpath for runtime startup and the root package for standard authoring helpers:
import { createApp } from '@ecopages/core/create-app';
import { defineApiHandler, defineGroupHandler, eco } from '@ecopages/core';
[!NOTE]
Use createApp() from @ecopages/core/create-app as the application entrypoint.
Runtime Escape Hatches
Use runtime-specific subpaths only when you explicitly need Bun-native APIs that bypass the universal abstractions:
Entry Point Roles
The published subpaths are grouped by architectural role rather than by source folder.
App Authoring
Use these entrypoints when building an Ecopages app:
@ecopages/core
@ecopages/core/create-app
@ecopages/core/config-builder
@ecopages/core/errors
@ecopages/core/html
@ecopages/core/hash
@ecopages/core/declarations
@ecopages/core/env
@ecopages/core/bun
Browser Navigation
Use these entrypoints when a browser runtime needs to coordinate document ownership and link intent:
@ecopages/core/router/navigation-coordinator
@ecopages/core/router/link-intent
Extension Authoring
Use these entrypoints when implementing integrations, processors, or source transforms:
@ecopages/core/plugins/integration-plugin
@ecopages/core/plugins/processor
@ecopages/core/plugins/source-transform
@ecopages/core/route-renderer/integration-renderer
@ecopages/core/services/asset-processing-service
@ecopages/core/hmr/hmr-strategy
@ecopages/core/integrations/ghtml
Host And Runtime Composition
Use these entrypoints only when implementing host adapters or framework-owned bundling seams:
@ecopages/core/dev/host-runtime
@ecopages/core/build/build-adapter
@ecopages/core/build/build-types
@ecopages/core/plugins/foreign-jsx-override-plugin
These host-facing entrypoints are narrower compatibility seams. App code and most extensions should prefer the app-authoring or extension-authoring surfaces.