Peachy is a very minimal, lightweight, secure, and reactive front-end framework designed for building Single Page Applications (SPAs). It offers a robust state management system (local, global and global with persistence), file-based routing, and a component-based architecture with full reactivity. It's a fun project where I have learned a lot about how frontend framework works, still a proof of concept though.
- Getting Started
- Project Structure
- Core Concepts
- Configuration
- Usage
- Advanced Topics
- Contributing
- License
To get started with Peachy, clone the repository and install the dependencies:
npx create-peachy-app peachy-app
cd peachy-app
To start the development server:
npm start
To build the project for production:
npm run build
peachy/
├── public/
│ └── index.html
├── src/
│ ├── app/
│ │ ├── about/
│ │ │ └── page.js
│ │ ├── blog/
│ │ │ └── [id]/
│ │ │ └── page.js
│ │ ├── layout.js
│ │ ├── loading.js
│ │ ├── not-found.js
│ │ └── page.js
│ ├── components/
│ │ └── Header.js
│ ├── peach/
│ │ ├── component.js
│ │ ├── fetch.js
│ │ ├── router.js
│ │ ├── state.js
│ │ └── utils.js
│ ├── index.css
│ └── index.js
├── babel.config.js
├── package.json
├── postcss.config.mjs
├── tailwind.config.js
└── webpack.config.js
Components are the building blocks of a Peachy application. They are defined using functions that return JSX.
Example:
import { Peachy } from "@peach/component";
import { Link } from "@peach/router";
export default function Header() {
return (
<header className="w-full flex justify-between items-center px-4 py-2 bg-black text-xl">
<h1>Peachy App</h1>
<nav className="flex space-x-2 items-center">
<Link className="cursor-pointer" href="/">
Home
</Link>
<Link className="cursor-pointer" href="/about">
About
</Link>
</nav>
</header>
);
}
Peachy components support lifecycle methods that allow you to execute code at specific points in a component's lifecycle. These lifecycle methods are defined as properties on the JSX element returned by the component.
- beforemount: Runs before the component is mounted to the DOM.
- mount: Runs immediately after the component is mounted to the DOM.
- unmount: Runs when the component is removed from the DOM.
Example:
import { Peachy } from "@peach/component";
export default function ExampleComponent() {
const element = <div>Hello, Peachy!</div>;
// Define lifecycle methods.
element.__lifecycle = {
beforemount() {
console.log("Component is about to mount.");
},
mount() {
console.log("Component has been mounted.");
},
unmount() {
console.log("Component is being unmounted.");
},
};
return element;
}
The Peach3dModel
component demonstrates the use of lifecycle methods to manage animations and event listeners. For example, it uses the mount
method to initialize animations and the unmount
method to clean up resources like requestAnimationFrame
and event listeners.
Peachy provides a robust state management system with both local and global state capabilities.
Local state is managed using the useState
hook.
Example:
import { useState, Peachy } from "@peach/component";
export default function HomePage() {
const [getCount, setCount] = useState(0);
return (
<div>
<p>Count: {String(getCount)}</p>
<button onClick={() => setCount(getCount + 1)}>Increment</button>
</div>
);
}
Global state is managed using the AppState
and PersistedAppState
classes.
Example:
import { Peachy } from "@peach/component";
import { AppState } from "@peach/state";
export default function AboutPage() {
AppState.set("lastVisited", "About");
return (
<div>
<h2>About Peachy</h2>
<p>Last visited: {AppState.get("lastVisited")}</p>
</div>
);
}
Peachy uses a file-based routing system similar to Next.js. Routes are defined by the file structure in the src/app
directory.
Example:
import { useState, Peachy } from "@peach/component";
import { AppState } from "@peach/state";
export default function BlogPostPage({ params }) {
const { id } = params;
const [getLikes, setLikes] = useState(0);
AppState.set("lastVisited", `Blog Post ${id}`);
return (
<div>
<h2>Blog Post #{id}</h2>
<p>Likes: {String(getLikes)}</p>
<button onClick={() => setLikes(getLikes + 1)}>Like</button>
</div>
);
}
Peachy provides several hooks for managing state and side effects.
useState
: Manages local component state.useGlobalState
: Manages global state with reactivity.
Babel is configured to transpile JSX and modern JavaScript features.
module.exports = {
presets: [
["@babel/preset-env", { targets: "> 0.25%, not dead" }],
[
"@babel/preset-react",
{ pragma: "Peachy.createElement", runtime: "classic" },
],
],
};
Webpack is used to bundle the application.
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
alias: {
"@peach": path.resolve(__dirname, "src/peach"),
"@app": path.resolve(__dirname, "src/app"),
"@components": path.resolve(__dirname, "src/components"),
"@utils": path.resolve(__dirname, "src/utils"),
"@hooks": path.resolve(__dirname, "src/hooks"),
"@assets": path.resolve(__dirname, "src/assets"),
},
},
output: {
filename: "bundle.[contenthash].js",
path: path.resolve(__dirname, "dist"),
clean: true,
publicPath: "/",
},
devServer: {
historyApiFallback: true,
port: 3000,
open: true,
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: "babel-loader",
},
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
inject: "body",
}),
],
optimization: {
minimize: true,
},
};
Tailwind CSS is used for styling.
module.exports = {
content: ["./src/**/*.{js,jsx}", "./public/index.html"],
theme: { extend: {} },
plugins: [],
};
-
Define a Component: Create a function that returns JSX.
-
Example:
import { Peachy } from "@peach/component"; import { Link } from "@peach/router"; export default function Header() { return ( <header className="w-full flex justify-between items-center px-4 py-2 bg-black text-xl"> <h1>Peachy App</h1> <nav className="flex space-x-2 items-center"> <Link className="cursor-pointer" href="/"> Home </Link> <Link className="cursor-pointer" href="/about"> About </Link> </nav> </header> ); }
-
Local State: Use the
useState
hook to manage local state within a component. -
Example:
import { useState, Peachy } from "@peach/component"; export default function HomePage() { const [getCount, setCount] = useState(0); return ( <div> <p>Count: {String(getCount)}</p> <button onClick={() => setCount(getCount + 1)}>Increment</button> </div> ); }
-
Global State: Use the
AppState
andPersistedAppState
classes to manage global state. -
Example:
import { Peachy } from "@peach/component"; import { AppState } from "@peach/state"; export default function AboutPage() { AppState.set("lastVisited", "About"); return ( <div> <h2>About Peachy</h2> <p>Last visited: {AppState.get("lastVisited")}</p> </div> ); }
-
Define Routes: Create files in the
src/app
directory to define routes. -
Example:
import { useState, Peachy } from "@peach/component"; import { AppState } from "@peach/state"; export default function BlogPostPage({ params }) { const { id } = params; const [getLikes, setLikes] = useState(0); AppState.set("lastVisited", `Blog Post ${id}`); return ( <div> <h2>Blog Post #{id}</h2> <p>Likes: {String(getLikes)}</p> <button onClick={() => setLikes(getLikes + 1)}>Like</button> </div> ); }
-
Persistent State: Use
PersistedAppState
to manage state that persists across sessions using IndexedDB. -
Example:
import { useGlobalState, Peachy } from "@peach/component"; import { PersistedAppState } from "@peach/state"; export default function HomePage() { const [getTheme, setTheme] = useGlobalState(PersistedAppState, "theme"); return ( <div> <p>Persistent Global Theme: {String(getTheme) || "default"}</p> <button onClick={() => setTheme("dark")}>Set Dark Theme</button> </div> ); }
-
Dynamic Routing: Use bracket notation in file names to define dynamic routes.
-
Example:
import { useState, Peachy } from "@peach/component"; import { AppState } from "@peach/state"; export default function BlogPostPage({ params }) { const { id } = params; const [getLikes, setLikes] = useState(0); AppState.set("lastVisited", `Blog Post ${id}`); return ( <div> <h2>Blog Post #{id}</h2> <p>Likes: {String(getLikes)}</p> <button onClick={() => setLikes(getLikes + 1)}>Like</button> </div> ); }
Contributions are welcome! Please open an issue or submit a pull request.