feat: restructure project and update documentation
This commit is contained in:
107
src/docs/plugins/core.debug.md
Normal file
107
src/docs/plugins/core.debug.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Development Tool: `_debug`
|
||||
|
||||
The **Debug Plugin** is a lightweight reactive listener. Once attached to a signal or a computed function, it automatically monitors changes, compares values, and formats the output in the browser console.
|
||||
|
||||
## 1. Core Features
|
||||
|
||||
* **Reactive Tracking:** Automatically logs whenever the tracked signal updates.
|
||||
* **Visual Grouping:** Uses styled console groups to keep your dev tools organized.
|
||||
* **Object Inspection:** Automatically uses `console.table()` when the signal contains an object or array.
|
||||
* **Efficient Comparison:** Uses `Object.is` to prevent redundant logging if the value hasn't actually changed.
|
||||
|
||||
---
|
||||
|
||||
## 2. Installation
|
||||
|
||||
To use `_debug`, you only need the SigPro core. Register the plugin in your `main.js`. You can conditionally load it so it only runs during development.
|
||||
|
||||
```javascript
|
||||
import { $ } from 'sigpro';
|
||||
import { Debug } from 'sigpro/plugins';
|
||||
|
||||
// Only load Debug in development mode
|
||||
const plugins = [];
|
||||
if (import.meta.env.DEV) plugins.push(Debug);
|
||||
|
||||
$.plugin(plugins).then(() => {
|
||||
import('./App.js').then(app => $.mount(app.default));
|
||||
});
|
||||
```
|
||||
|
||||
::: code-group
|
||||
```bash [NPM]
|
||||
npm install sigpro
|
||||
```
|
||||
|
||||
```bash [PNPM]
|
||||
pnpm add sigpro
|
||||
```
|
||||
|
||||
```bash [Yarn]
|
||||
yarn add sigpro
|
||||
```
|
||||
|
||||
```bash [Bun]
|
||||
bun add sigpro
|
||||
```
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. Basic Usage
|
||||
|
||||
Call `_debug` anywhere in your component. It stays active in the background, watching the signal's lifecycle.
|
||||
|
||||
```javascript
|
||||
export default () => {
|
||||
const $count = $(0);
|
||||
const $user = $({ name: "Guest", role: "Viewer" });
|
||||
|
||||
// Start tracking
|
||||
_debug($count, "Main Counter");
|
||||
_debug($user, "User Session");
|
||||
|
||||
return div([
|
||||
button({ onclick: () => $count(c => c + 1) }, "Increment"),
|
||||
button({ onclick: () => $user({ name: "Admin", role: "Super" }) }, "Promote")
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Console Output Breakdown
|
||||
|
||||
When a signal changes, the console displays a structured block:
|
||||
|
||||
1. **Header:** A styled badge with the name (e.g., `SigPro Debug: Main Counter`).
|
||||
2. **Previous Value:** The value before the update (in red).
|
||||
3. **Current Value:** The new value (in green).
|
||||
4. **Table View:** If the value is an object, a formatted table appears automatically.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 5. Debugging Computed Values
|
||||
|
||||
You can also debug **computed functions** to see exactly when derived state is recalculated.
|
||||
|
||||
```javascript
|
||||
const $price = $(100);
|
||||
const $tax = $(0.21);
|
||||
const $total = $(() => $price() * (1 + $tax()));
|
||||
|
||||
// Monitor the result of the calculation
|
||||
_debug($total, "Final Invoice Total");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Why use `_debug`?
|
||||
|
||||
1. **Clean Logic:** No need to scatter `console.log` inside your reactive functions.
|
||||
2. **State History:** Instantly see the "Before" and "After" of any user action.
|
||||
3. **No-Noise:** It only logs when a real change occurs, keeping the console clean.
|
||||
4. **Deep Inspection:** The automatic `console.table` makes debugging large API responses much faster.
|
||||
|
||||
80
src/docs/plugins/core.fetch.md
Normal file
80
src/docs/plugins/core.fetch.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Data Fetching: `_fetch`
|
||||
|
||||
The **Fetch Plugin** provides a reactive wrapper around the native browser Fetch API. Instead of managing complex `async/await` flows within your UI, `_fetch` returns a "Reactive Tripod" (Data, Loading, and Error) that your components can listen to automatically.
|
||||
|
||||
## 1. Core Concept
|
||||
|
||||
When you call `_fetch`, it returns three signals immediately. Your UI declares how to react to these signals as they change from their initial state to the final response.
|
||||
|
||||
* **`$data`**: Initialized as `null`. Automatically holds the JSON response on success.
|
||||
* **`$loading`**: Initialized as `true`. Flips to `false` once the request settles.
|
||||
* **`$error`**: Initialized as `null`. Holds the error message if the request fails.
|
||||
|
||||
---
|
||||
|
||||
## 2. Installation
|
||||
|
||||
Register the `Fetch` plugin in your `main.js`. By convention, we load it alongside the UI and Router to have the full SigPro ecosystem ready.
|
||||
|
||||
```javascript
|
||||
import { $ } from 'sigpro';
|
||||
import { Fetch } from 'sigpro/plugins';
|
||||
|
||||
$.plugin([Fetch]).then(() => {
|
||||
// Now _fetch() is available globally
|
||||
import('./App.js').then(app => $.mount(app.default));
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. Basic Usage
|
||||
|
||||
Use `_fetch` inside your component to get live updates. The UI updates surgically whenever a signal changes.
|
||||
|
||||
```javascript
|
||||
export default () => {
|
||||
const { $data, $loading, $error } = _fetch('https://api.github.com/users/octocat');
|
||||
|
||||
return div({ class: 'p-6 flex flex-col gap-4' }, [
|
||||
h1("Profile Details"),
|
||||
|
||||
// 1. Loading State (using SigPro UI button)
|
||||
() => $loading() && _button({ $loading: true }, "Fetching..."),
|
||||
|
||||
// 2. Error State
|
||||
() => $error() && div({ class: 'alert alert-error' }, $error()),
|
||||
|
||||
// 3. Success State
|
||||
() => $data() && div({ class: 'card bg-base-200 p-4' }, [
|
||||
img({ src: $data().avatar_url, class: 'w-16 rounded-full' }),
|
||||
h2($data().name),
|
||||
p($data().bio)
|
||||
])
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Advanced Configuration
|
||||
|
||||
`_fetch` accepts the same `RequestInit` options as the standard `fetch()` (methods, headers, body, etc.).
|
||||
|
||||
```javascript
|
||||
const { $data, $loading } = _fetch('/api/v1/update', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status: 'active' })
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Why use `_fetch` instead of native Fetch?
|
||||
|
||||
1. **Declarative UI**: You define the "Loading", "Error", and "Success" templates once, and they swap automatically.
|
||||
2. **No `useEffect` required**: Since SigPro is natively reactive, you don't need lifecycle hooks to trigger re-renders; the signals handle it.
|
||||
3. **Consistency**: It follows the same `_prefix` pattern as the rest of the official plugin ecosystem.
|
||||
4. **Automatic JSON Parsing**: It assumes JSON by default and handles 404/500 errors by populating the `$error` signal.
|
||||
110
src/docs/plugins/core.router.md
Normal file
110
src/docs/plugins/core.router.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Navigation Plugin: `Router`
|
||||
|
||||
The SigPro Router handles URL changes via hashes (`#`) and maps them to components. It supports dynamic parameters (like `:id`) and asynchronous loading for heavy pages.
|
||||
|
||||
## 1. Core Features
|
||||
|
||||
* **Hash-based:** Works everywhere without special server configuration.
|
||||
* **Lazy Loading:** Pages are only downloaded when the user visits the route.
|
||||
* **Reactive:** The view updates automatically when the hash changes.
|
||||
* **Dynamic Routes:** Supports paths like `/user/:id`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Installation
|
||||
|
||||
The Router is usually included in the official plugins package.
|
||||
|
||||
::: code-group
|
||||
```bash [NPM]
|
||||
npm install -D tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
|
||||
```bash [PNPM]
|
||||
pnpm add -D tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
|
||||
```bash [Yarn]
|
||||
yarn add -D tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
|
||||
```bash [Bun]
|
||||
bun add -d tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. Setting Up Routes
|
||||
|
||||
In your `App.js` (or a dedicated routes file), define your navigation map.
|
||||
|
||||
```javascript
|
||||
const routes = [
|
||||
{ path: '/', component: () => h1("Home Page") },
|
||||
{
|
||||
path: '/admin',
|
||||
// Lazy Loading: This file is only fetched when needed
|
||||
component: () => import('./pages/Admin.js')
|
||||
},
|
||||
{ path: '/user/:id', component: (params) => h2(`User ID: ${params.id}`) },
|
||||
{ path: '*', component: () => div("404 - Page Not Found") }
|
||||
];
|
||||
|
||||
export default () => div([
|
||||
_navbar({ title: "My App" }),
|
||||
_router(routes) // The router is now a global tag
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Navigation (`_router.go`)
|
||||
|
||||
To move between pages programmatically (e.g., inside an `onclick` event), use the global `_router.go` helper.
|
||||
|
||||
```javascript
|
||||
_button({
|
||||
onclick: () => _router.go('/admin')
|
||||
}, "Go to Admin")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. How it Works (Under the Hood)
|
||||
|
||||
The router tracks the `window.location.hash` and uses a reactive signal to trigger a re-render of the specific area where `_router(routes)` is placed.
|
||||
|
||||
1. **Match:** It filters your route array to find the best fit.
|
||||
2. **Resolve:** * If it's a standard function, it executes it immediately.
|
||||
* If it's a **Promise** (via `import()`), it shows a loading state and swaps the content once the module arrives.
|
||||
3. **Inject:** It replaces the previous DOM node with the new page content surgically.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6. Integration with UI Components
|
||||
|
||||
Since you are using the **UI Plugin**, you can easily create active states in your navigation menus by checking the current hash.
|
||||
|
||||
```javascript
|
||||
// Example of a reactive sidebar menu
|
||||
_menu({
|
||||
items: [
|
||||
{
|
||||
label: 'Dashboard',
|
||||
active: () => window.location.hash === '#/',
|
||||
onclick: () => _router.go('/')
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
active: () => window.location.hash === '#/settings',
|
||||
onclick: () => _router.go('/settings')
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
|
||||
106
src/docs/plugins/core.storage.md
Normal file
106
src/docs/plugins/core.storage.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Persistence Tool: `_storage`
|
||||
|
||||
The Storage plugin synchronizes a signal with a specific key in your browser's `localStorage`. It handles both the **initial hydration** (loading data when the app starts) and **automatic saving** whenever the signal's value changes.
|
||||
|
||||
## 1. Core Concept
|
||||
|
||||
When you "attach" a signal to `_storage`, two things happen:
|
||||
1. **Hydration:** The plugin checks if the key already exists in `localStorage`. If it does, it parses the JSON and updates the signal immediately.
|
||||
2. **Reactive Sync:** It creates a reactive watcher that stringifies and saves the signal's value to the disk every time it is updated.
|
||||
|
||||
---
|
||||
|
||||
## 2. Installation
|
||||
|
||||
Register the `Storage` plugin in your `main.js`. Since this is a logic-only plugin, it doesn't require any CSS or UI dependencies.
|
||||
|
||||
```javascript
|
||||
import { $ } from 'sigpro';
|
||||
import { Storage } from 'sigpro/plugins';
|
||||
|
||||
$.plugin(Storage).then(() => {
|
||||
import('./App.js').then(app => $.mount(app.default));
|
||||
});
|
||||
```
|
||||
|
||||
::: code-group
|
||||
```bash [NPM]
|
||||
npm install sigpro
|
||||
```
|
||||
|
||||
```bash [PNPM]
|
||||
pnpm add sigpro
|
||||
```
|
||||
|
||||
```bash [Yarn]
|
||||
yarn add sigpro
|
||||
```
|
||||
|
||||
```bash [Bun]
|
||||
bun add sigpro
|
||||
```
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. Basic Usage
|
||||
|
||||
You can wrap any signal with `_storage`. It is common practice to do this right after creating the signal.
|
||||
|
||||
```javascript
|
||||
export default () => {
|
||||
// 1. Create a signal with a default value
|
||||
const $theme = $( 'light' );
|
||||
|
||||
// 2. Persist it. If 'user_theme' exists in localStorage,
|
||||
// $theme will be updated to that value instantly.
|
||||
_storage($theme, 'user_theme');
|
||||
|
||||
return div({ class: () => `app-${$theme()}` }, [
|
||||
h1(`Current Theme: ${$theme()}`),
|
||||
button({
|
||||
onclick: () => $theme(t => t === 'light' ? 'dark' : 'light')
|
||||
}, "Toggle Theme")
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Complex Data (Objects & Arrays)
|
||||
|
||||
Since the plugin uses `JSON.parse` and `JSON.stringify` internally, it works perfectly with complex state structures.
|
||||
|
||||
```javascript
|
||||
const $settings = $({
|
||||
notifications: true,
|
||||
fontSize: 16
|
||||
});
|
||||
|
||||
// Automatically saves the whole object whenever any property changes
|
||||
_storage($settings, 'app_settings');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Why use `_storage`?
|
||||
|
||||
1. **Zero Boilerplate:** You don't need to manually write `localStorage.getItem` or `setItem` logic inside your components.
|
||||
2. **Chaining:** Because `_storage` returns the signal, you can persist it inline.
|
||||
3. **Error Resilience:** It includes a built-in `try/catch` block to prevent your app from crashing if the stored JSON is corrupted.
|
||||
4. **Surgical Persistence:** Only the signals you explicitly mark for storage are saved, keeping your `localStorage` clean.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6. Pro Tip: Combining with Debug
|
||||
|
||||
You can chain plugins to create a fully monitored and persistent state:
|
||||
|
||||
```javascript
|
||||
const $score = _storage($(0), 'high_score');
|
||||
|
||||
// Now it's saved to disk AND logged to console on every change
|
||||
_debug($score, "Game Score");
|
||||
```
|
||||
147
src/docs/plugins/core.ui.md
Normal file
147
src/docs/plugins/core.ui.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Official UI Plugin: `UI`
|
||||
|
||||
The **SigPro UI** plugin is a high-level component library built on top of the reactive core. It leverages **Tailwind CSS v4** for utility styling and **daisyUI v5** for semantic components.
|
||||
|
||||
## 1. Prerequisites & Installation
|
||||
|
||||
To use these components, you must install the styling engine. SigPro UI provides the logic, but Tailwind and daisyUI provide the visuals.
|
||||
|
||||
::: code-group
|
||||
```bash [NPM]
|
||||
npm install -D tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
|
||||
```bash [PNPM]
|
||||
pnpm add -D tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
|
||||
```bash [Yarn]
|
||||
yarn add -D tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
|
||||
```bash [Bun]
|
||||
bun add -d tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
:::
|
||||
|
||||
Would you like to continue with the **Router.md** documentation now?
|
||||
|
||||
### CSS Configuration (`app.css`)
|
||||
In Tailwind v4, configuration is handled directly in your CSS. Create a `src/app.css` file:
|
||||
|
||||
```css
|
||||
/* src/app.css */
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Import daisyUI v5 as a Tailwind v4 plugin */
|
||||
@plugin "daisyui";
|
||||
|
||||
/* Optional: Configure themes */
|
||||
@custom-variant dark (&:where(.dark, [data-theme="dark"], [data-theme="dark"] *)));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Initialization
|
||||
|
||||
You must import your CSS and register the `UI` plugin in your entry point. This populates the global scope with reactive component helpers (prefixed with `_`).
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import './app.css';
|
||||
import { $ } from 'sigpro';
|
||||
import { UI } from 'sigpro/plugins';
|
||||
|
||||
$.plugin(UI).then(() => {
|
||||
// Global components like _button and _input are now ready
|
||||
import('./App.js').then(app => $.mount(app.default));
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Core Component Tags (`_tags`)
|
||||
|
||||
SigPro UI components are more than just HTML; they are **Reactive Functional Components** that manage complex states (loading, errors, accessibility) automatically.
|
||||
|
||||
### A. Action Components (`_button`)
|
||||
The `_button` automatically handles spinners and disabled states based on signals.
|
||||
|
||||
| Property | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| **`$loading`** | `signal` | If true, shows a spinner and disables the button. |
|
||||
| **`$disabled`**| `signal` | Manually disables the button (logic-bound). |
|
||||
| **`icon`** | `node/str`| Prepends an icon to the text. |
|
||||
| **`badge`** | `string` | Appends a small badge to the button. |
|
||||
|
||||
```javascript
|
||||
_button({
|
||||
$loading: $isSaving,
|
||||
icon: '💾',
|
||||
class: 'btn-primary'
|
||||
}, "Save Data")
|
||||
```
|
||||
|
||||
### B. High-Density Forms (`_input`, `_select`, `_checkbox`)
|
||||
These components wrap the raw input in a `fieldset` with integrated labels and tooltips.
|
||||
|
||||
* **`label`**: Field title displayed above the input.
|
||||
* **`tip`**: Displays a `?` badge that shows a tooltip on hover.
|
||||
* **`$error`**: A signal that, when populated, turns the input red and displays the message.
|
||||
* **`$value`**: **Two-way binding**. Updates the signal on input and the input on signal change.
|
||||
|
||||
```javascript
|
||||
_input({
|
||||
label: "Username",
|
||||
tip: "Choose a unique name",
|
||||
$value: $name,
|
||||
$error: $nameError
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Complex UI Patterns
|
||||
|
||||
### Reactive Modals (`_modal`)
|
||||
The `_modal` is surgically mounted. If the `$open` signal is `false`, the component is completely removed from the DOM, optimizing performance.
|
||||
|
||||
```javascript
|
||||
const $showModal = $(false);
|
||||
|
||||
_modal({ $open: $showModal, title: "Alert" }, [
|
||||
p("Are you sure you want to proceed?"),
|
||||
_button({ onclick: () => doAction() }, "Confirm")
|
||||
])
|
||||
```
|
||||
|
||||
### Navigation & Layout (`_tabs`, `_drawer`, `_navbar`)
|
||||
Designed to work seamlessly with the **Router**.
|
||||
|
||||
| Component | Key Logic |
|
||||
| :--- | :--- |
|
||||
| **`_tabs`** | Accepts an `active` property (signal or function) to highlight the current tab. |
|
||||
| **`_drawer`** | A responsive sidebar that toggles via an ID or an `$open` signal. |
|
||||
| **`_navbar`** | Standard top bar with shadow and glass effect support. |
|
||||
| **`_menu`** | Vertical navigation list with active state support. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Summary Table: UI Globals
|
||||
|
||||
Once `$.plugin(UI)` is active, these tags are available project-wide:
|
||||
|
||||
| Tag | Category | Use Case |
|
||||
| :--- | :--- | :--- |
|
||||
| `_fieldset` | Layout | Grouping related inputs with a `legend`. |
|
||||
| `_accordion`| Content | Collapsible sections (FAQs). |
|
||||
| `_badge` | Feedback | Status indicators (Success, Warning). |
|
||||
| `_tooltip` | Feedback | Descriptive text on hover. |
|
||||
| `_range` | Input | Reactive slider for numerical values. |
|
||||
|
||||
---
|
||||
|
||||
### What's next?
|
||||
With the UI ready and styled via **Tailwind v4**, we can move to the **Router.md**. We will explain how to link `_tabs` and `_menu` to different URL paths for a full SPA experience.
|
||||
|
||||
**Would you like to start with the Router configuration?**
|
||||
123
src/docs/plugins/custom.md
Normal file
123
src/docs/plugins/custom.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Creating Custom Plugins
|
||||
|
||||
There are two main ways to expose a plugin's functionality: **Static/Manual Imports** (cleaner for large projects) or **Global/Automatic Window Injection** (easier for quick scripts and global helpers).
|
||||
|
||||
## 1. The Anatomy of a Plugin
|
||||
|
||||
A plugin is a standard JavaScript function. By convention, if a plugin adds a global helper or component, it should be prefixed with an underscore (`_`).
|
||||
|
||||
```javascript
|
||||
// plugins/my-utils.js
|
||||
export const MyUtils = ($) => {
|
||||
|
||||
// 1. Attach to the SigPro instance
|
||||
$.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
||||
|
||||
// 2. Attach to the Window (Global access)
|
||||
window._hello = (name) => div(`Hello, ${$.capitalize(name)}!`);
|
||||
|
||||
// 3. You can also return values if needed
|
||||
return { version: '1.0.0' };
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Integration Strategies
|
||||
|
||||
### Option A: Manual Import (Recommended)
|
||||
This approach keeps your global namespace clean. You import the logic only where you need it, but the plugin still initializes the core `$` extensions.
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import { $ } from 'sigpro';
|
||||
import { MyUtils } from './plugins/my-utils.js';
|
||||
|
||||
$.plugin(MyUtils);
|
||||
|
||||
// App.js
|
||||
export default () => {
|
||||
const name = "sigpro";
|
||||
// $.capitalize was added by the plugin
|
||||
return h1($.capitalize(name));
|
||||
};
|
||||
```
|
||||
|
||||
### Option B: Automatic Window Injection
|
||||
If your plugin defines global tags (like `_button` or `_hello`), you should attach them to the `window` object inside the plugin function. This makes them available everywhere without imports.
|
||||
|
||||
```javascript
|
||||
// plugins/theme.js
|
||||
export const Theme = ($) => {
|
||||
const $dark = $(false);
|
||||
|
||||
window._themeToggle = () => button({
|
||||
onclick: () => $dark(v => !v),
|
||||
class: () => $dark() ? 'bg-black text-white' : 'bg-white text-black'
|
||||
}, "Toggle Mode");
|
||||
};
|
||||
|
||||
// main.js
|
||||
$.plugin(Theme).then(() => {
|
||||
// _themeToggle is now a global function
|
||||
$.mount(App);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Asynchronous Plugins
|
||||
If your plugin needs to load external data or scripts before the app starts, make it `async`. SigPro will wait for it.
|
||||
|
||||
```javascript
|
||||
export const ConfigLoader = async ($) => {
|
||||
const res = await fetch('/config.json');
|
||||
const config = await res.json();
|
||||
|
||||
$.config = config; // Attach loaded config to SigPro
|
||||
};
|
||||
|
||||
// Usage
|
||||
$.plugin(ConfigLoader).then(() => {
|
||||
console.log("Config loaded:", $.config);
|
||||
$.mount(App);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Best Practices for Plugin Authors
|
||||
|
||||
| Rule | Description |
|
||||
| :--- | :--- |
|
||||
| **Prefixing** | Use `_` for UI components (`_modal`) and `$.` for logic (`$.fetch`). |
|
||||
| **Idempotency** | Ensure calling `$.plugin(MyPlugin)` twice doesn't break the app. |
|
||||
| **Encapsulation** | Use the `$` instance passed as an argument rather than importing it again inside the plugin. |
|
||||
| **Reactivity** | Always use `$(...)` for internal state so the app stays reactive. |
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 5. Installation
|
||||
|
||||
Custom plugins don't require extra packages, but ensure your build tool (Vite/Bun) is configured to handle the module imports.
|
||||
|
||||
::: code-group
|
||||
```bash [NPM]
|
||||
npm install sigpro
|
||||
```
|
||||
|
||||
```bash [PNPM]
|
||||
pnpm add sigpro
|
||||
```
|
||||
|
||||
```bash [Yarn]
|
||||
yarn add sigpro
|
||||
```
|
||||
|
||||
```bash [Bun]
|
||||
bun add sigpro
|
||||
```
|
||||
:::
|
||||
|
||||
101
src/docs/plugins/quick.md
Normal file
101
src/docs/plugins/quick.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Extending SigPro: `$.plugin`
|
||||
|
||||
The plugin system is the engine's way of growing. It allows you to inject new functionality directly into the `$` object or load external resources.
|
||||
|
||||
## 1. How Plugins Work
|
||||
|
||||
A plugin in **SigPro** is simply a function that receives the core instance. When you run `$.plugin(MyPlugin)`, the engine hands over the `$` object so the plugin can attach new methods or register global tags (like `div()`, `span()`, etc.).
|
||||
|
||||
### Functional Plugin Example
|
||||
```javascript
|
||||
// A plugin that adds a simple logger to any signal
|
||||
const Logger = ($) => {
|
||||
$.watch = (target, label = "Log") => {
|
||||
$(() => console.log(`[${label}]:`, target()));
|
||||
};
|
||||
};
|
||||
|
||||
// Activation
|
||||
$.plugin(Logger);
|
||||
const $count = $(0);
|
||||
$.watch($count, "Counter"); // Now available globally via $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Initialization Patterns
|
||||
|
||||
Since plugins often set up global variables (like the HTML tags), the order of initialization is critical. Here are the two ways to start your app:
|
||||
|
||||
### Option A: The "Safe" Async Start (Recommended)
|
||||
This is the most robust way. It ensures all global tags (`div`, `button`, etc.) are created **before** your App code is even read by the browser.
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import { $ } from 'sigpro';
|
||||
import { UI, Router } from 'sigpro/plugins';
|
||||
|
||||
// 1. Load plugins first
|
||||
$.plugin([UI, Router]).then(() => {
|
||||
|
||||
// 2. Import your app only after the environment is ready
|
||||
import('./App.js').then(appFile => {
|
||||
const MyApp = appFile.default;
|
||||
$.mount(MyApp, '#app');
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
### Option B: Static Start (No Global Tags)
|
||||
Use this only if you prefer **not** to use global tags and want to use `$.html` directly in your components. This allows for standard static imports.
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import { $ } from 'sigpro';
|
||||
import { UI } from 'sigpro/plugins';
|
||||
import MyApp from './App.js'; // Static import works here
|
||||
|
||||
$.plugin(UI);
|
||||
$.mount(MyApp, '#app');
|
||||
```
|
||||
> **Warning:** In this mode, if `App.js` uses `div()` instead of `$.html('div')`, it will throw a `ReferenceError`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Resource Plugins (External Scripts)
|
||||
|
||||
You can pass a **URL** or an **Array of URLs**. SigPro will inject them as `<script>` tags and return a Promise that resolves when the scripts are fully loaded and executed.
|
||||
|
||||
```javascript
|
||||
// Loading external libraries as plugins
|
||||
await $.plugin([
|
||||
'https://cdn.jsdelivr.net/npm/chart.js',
|
||||
'https://cdn.example.com/custom-ui-lib.js'
|
||||
]);
|
||||
|
||||
console.log("External resources are ready to use!");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Polymorphic Loading Reference
|
||||
|
||||
The `$.plugin` method adapts to whatever you throw at it:
|
||||
|
||||
| Input Type | Action | Behavior |
|
||||
| :--- | :--- | :--- |
|
||||
| **Function** | Executes `fn($)` | Synchronous / Immediate |
|
||||
| **String (URL)** | Injects `<script src="...">` | Asynchronous (Returns Promise) |
|
||||
| **Array** | Processes each item in the list | Returns Promise if any item is Async |
|
||||
|
||||
---
|
||||
|
||||
## 💡 Pro Tip: Why the `.then()`?
|
||||
|
||||
Using `$.plugin([...]).then(...)` is like giving your app a "Pre-flight Check". It guarantees that:
|
||||
1. All reactive methods are attached.
|
||||
2. Global HTML tags are defined.
|
||||
3. External libraries (like Chart.js) are loaded.
|
||||
4. **The result:** Your components are cleaner, smaller, and error-free.
|
||||
|
||||
Reference in New Issue
Block a user