This guide explains the minimal setup required to launch a Synetics application properly.
{
"dependencies": {
"@synetics/synetics.dev": "^0.9.0"
},
"devDependencies": {
"@synetics/vite-plugin": "^0.8.0",
"typescript": "^5.3.3",
"vite": "^5.0.8"
}
}- synetics.dev - Core framework with reactivity, hooks, context, and components
- vite-plugin - Transforms JSX and enables deferred children for Context.Provider
- vite - Build tool and dev server
- typescript - Type safety and JSX transformation
my-app/
├── index.html # HTML entry point
├── package.json # Dependencies
├── tsconfig.json # TypeScript config
├── vite.config.ts # Vite + Synetics plugin
└── src/
├── main.tsx # Application bootstrap
└── App.tsx # Root component
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Synetics App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>Critical: The mount point (#app) must exist in the DOM before the script runs.
import { defineConfig } from 'vite';
import { syneticsPlugin } from '@synetics/vite-plugin';
export default defineConfig({
plugins: [syneticsPlugin()],
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
jsxInject: `import { h, Fragment } from '@synetics/synetics.dev'`,
},
optimizeDeps: {
include: ['@synetics/synetics.dev'],
},
});syneticsPlugin()- Transforms Context.Provider to defer children evaluationjsxFactory: 'h'- Uses Synetics's JSX pragmajsxInject- Auto-imports JSX functions in every fileoptimizeDeps- Pre-bundles Synetics for faster dev server
Without syneticsPlugin(), Context.Provider will fail with "must be used within Provider" errors.
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Critical: "jsx": "preserve" - Vite handles JSX transformation.
import { AppContextProvider, bootstrapApp } from '@synetics/synetics.dev';
import { App } from './App';
// 1. Create application root with lifecycle hooks
const appRoot = bootstrapApp()
.root('#app')
.onMount((element) => {
console.log('App mounted successfully', element);
})
.onError((error) => {
console.error('App error:', error);
})
.build();
// 2. Wrap application with context provider
const app = (
<AppContextProvider
root={appRoot}
context={{
appName: 'My Synetics App',
version: '1.0.0',
}}
>
<App />
</AppContextProvider>
);
// 3. Append to DOM (AppContextProvider handles root.mount() internally)
document.getElementById('app')?.appendChild(app);Why both bootstrapApp() AND AppContextProvider?
They serve different purposes:
| Component | Purpose | Responsibility |
|---|---|---|
bootstrapApp() |
Creates application root | Lifecycle hooks (onMount, onError), configuration |
AppContextProvider |
Wraps app with context | Provides AppContext, calls root.mount() |
Execution Flow:
bootstrapApp() ← Configure lifecycle hooks
↓
appRoot.build() ← Build root configuration
↓
AppContextProvider ← Wrap app + provide context
↓
root.mount() ← Mount to DOM (called internally)
↓
Component Tree ← Access context via useAppContext()
interface IAppContext {
appName: string;
version: string;
[key: string]: any; // Your custom context properties
}Access anywhere in your component tree:
import { useAppContext } from '@synetics/synetics.dev';
export const MyComponent = () => {
const context = useAppContext();
return <div>App: {context.appName}</div>;
};import { createSignal, useAppContext } from '@synetics/synetics.dev';
export const App = function App() {
const context = useAppContext();
const [count, setCount] = createSignal(0);
return (
<div style={{ padding: '2rem' }}>
<h1>Welcome to {context.appName}</h1>
<button onClick={() => setCount(count() + 1)}>
Count: {count()}
</button>
</div>
);
};Key Points:
- Use
createSignal()for reactive state - Access signal value with
count()(getter) - Update with
setCount(newValue)(setter) - Use
useAppContext()to access application context
# Install dependencies
npm install
# or
pnpm install
# Start dev server
npm run dev
# Build for production
npm run build
# Preview production build
npm run previewAdd to package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}Once configured, you have access to all framework components:
import {
// Reactivity
createSignal,
createEffect,
createMemo,
batch,
// Hooks
useState,
useEffect,
useMemo,
useRef,
// Context
createContext,
useContext,
useAppContext,
AppContextProvider,
// Lifecycle
bootstrapApp,
onMount,
onCleanup,
// Portal
Portal,
PortalSlot,
// Error Boundaries
Tryer,
Catcher,
// State Management
createStore,
dispatch,
select,
// Resources (async)
createResource,
} from '@synetics/synetics.dev';With syneticsPlugin(), Context.Provider children are wrapped in arrow functions at compile time:
// Your code:
<Context.Provider value={data}>
<Child />
</Context.Provider>
// Transformed by plugin:
Context.Provider({
value: data,
children: () => jsx(Child, {}) // ← Deferred evaluation
})This ensures Provider registers context before Child tries to access it via useContext().
const appRoot = bootstrapApp()
.root('#app')
.onMount((element) => {
// Called when app successfully mounts
// Perfect for: analytics, feature flags, initial data fetch
})
.onError((error) => {
// Called when app encounters errors
// Perfect for: error reporting, fallback UI
})
.build();// ❌ WRONG - Context won't work
export default defineConfig({
// Missing plugins: [syneticsPlugin()]
});Result: useContext() throws "must be used within Provider"
// ❌ OLD WAY - No context, no lifecycle
import { mount } from '@synetics/synetics.dev';
mount(App, '#app');Result: No AppContext, no lifecycle hooks, no proper initialization
// ❌ WRONG - AppContextProvider needs a root
const app = (
<AppContextProvider
root={undefined} // ← Missing root!
context={{}}
>
<App />
</AppContextProvider>
);Result: AppContextProvider has no mount target
// ❌ WRONG - React JSX, not Synetics
{
"compilerOptions": {
"jsx": "react", // ❌ Should be "preserve"
"jsxFactory": "React.createElement" // ❌ Should be "h"
}
}Result: JSX transformed incorrectly, runtime errors
Instead of manual setup, use the Synetics CLI:
npm install -g @synetics/cli
synetics create my-app --template basic
cd my-app
npm install
npm run devThe CLI generates all configuration files with proper setup.
- Install
@synetics/synetics.devand@synetics/vite-plugin - Configure Vite with
syneticsPlugin() - Set TypeScript
jsx: "preserve"andjsxFactory: "h" - Create HTML with
<div id="app"> - Bootstrap app with
bootstrapApp().root('#app').build() - Wrap app with
<AppContextProvider root={appRoot} context={{...}}> - Append to DOM:
document.getElementById('app')?.appendChild(app)
That's it! You now have a fully functional Synetics application with:
- Fine-grained reactivity
- Context system
- Lifecycle management
- Type safety
- Hot module replacement
- API Reference - Complete API documentation
- State Management - Redux-style stores
- Dependency Injection - IoC container
- Error Boundaries - Error handling patterns
- Async Resources - Data fetching
- GitHub Issues
- Examples - Working demo applications
- CLI Templates - Scaffolding templates