Contributing
Internal guide for developing the evjs monorepo.
Project Identity
- Name: evjs (fullstack framework),
@evjs/*(package scope) - Repository: evaijs/evjs
- CLI command:
ev(binary from@evjs/cli) - Linter: Biome (
npx biome check --write) - Module type: ESM-only (
"type": "module"in all packages)
Setup
git clone https://github.com/evaijs/evjs.git
cd evjs
npm install
Commands
npm run build # Build all packages + examples
npm run test # Unit tests (vitest)
npm run test:e2e # E2E tests (playwright)
npm run dev # Dev mode (turborepo)
npx biome check --write # Fix lint/format
Coding Rules
- Imports — All imports at top of file. Use
import typefor type-only imports - Linting — Biome enforced; no
any, noimport * asunless necessary - Page routes — Source of truth is
src/pagesby default. Route files use.tsx,.jsx,.ts, or.js; dynamic segments use$param;indexmaps to the directory root;_-prefixed files/folders are private; bracket, catch-all, empty, and optional segments are unsupported - Layouts — The SPA root layout convention is exactly
src/layout/index.tsxfor default routes or the siblinglayout/index.tsxbeside a custom route directory. MPA routing does not consume framework layouts - Server functions — Must start with
"use server";, use.server.tsorsrc/api/ - Server function exports — Named callable exports only: function
declarations or
constarrow/function expressions. No default exports, cross-module re-exports, or exported non-function values - Config file — Named
ev.config.ts(notevjs.config.ts) - Package boundaries — Config/build imports stay on
@evjs/ev; runtime imports use@evjs/clientand@evjs/server. Use subpath exports on the package that owns the behavior before adding another distributed package. Subpath exports stay intentional and documented; do not add convenience aliases.@evjs/cliowns the default Utoopack adapter;@evjs/sharedis a shared contract package, not an app API - Rendering contracts — Non-CSR render modes require
serveroutput. PPR and RSC require component page modules withrender: "ssr", and PPR + RSC on the same page is unsupported until the runtime supports that combination - Remote contracts — Host apps use
remotes; remote packages use singularremote. Remote builds need a non-empty name and at least one entry with a non-emptyappmodule path
Common Tasks
Add a new server function
- Create
src/api/[name].server.ts - Add
"use server";at the top - Export named function declarations or
constasync function expressions - Import and use in client with
useQuery(fn)oruseMutation(fn)
Add a new route
- Add a page file under
src/pages - Use
$paramfor dynamic segments andindex.tsxfor directory roots - Put page-local loader/search/render metadata next to the default component export
Add a new example
- Create directory under
examples/ - Add
package.jsonwith"@evjs/cli": "*"as devDep - Add
src/pages/index.tsx+index.html - Create symlink in
packages/create-app/templates/ - Add an e2e test in
e2e/cases/
Release a new version
- Create a GitHub Release with a tag like
v0.1.0 - The release workflow automatically syncs versions and publishes to npm
- Do NOT bump versions locally — the codebase uses
"*"for internal deps