https://github.com/dakirchik/simplepwa
Modern framework for creating adaptive, reactive Progressive Web Applications (PWA) with offline support. Built on Web Components with easy IndexedDB integration, responsive routing, and a reactive store for offline-first applications.
https://github.com/dakirchik/simplepwa
adaptive cli indexeddb manifest offline offline-first progressive-web-app pwa reactive routing store web-components
Last synced: 5 months ago
JSON representation
Modern framework for creating adaptive, reactive Progressive Web Applications (PWA) with offline support. Built on Web Components with easy IndexedDB integration, responsive routing, and a reactive store for offline-first applications.
- Host: GitHub
- URL: https://github.com/dakirchik/simplepwa
- Owner: Dakirchik
- License: mit
- Created: 2025-05-12T06:20:00.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-09-03T10:06:50.000Z (5 months ago)
- Last Synced: 2025-09-03T19:36:31.374Z (5 months ago)
- Topics: adaptive, cli, indexeddb, manifest, offline, offline-first, progressive-web-app, pwa, reactive, routing, store, web-components
- Language: TypeScript
- Homepage:
- Size: 227 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# π SimplePWA Framework
# Description in English
**SimplePWA** β It is a modern framework for creating adaptive, reactive web applications with offline mode support ΠΈ PWA (Progressive Web Applications).
The framework is built on [Web Components](https://developer.mozilla.org/ru/docs/Web/Web_Components) and provides easy integration [IndexedDB](https://developer.mozilla.org/ru/docs/Web/API/IndexedDB_API) for `offline-first` applications.
---
## β¨ Main features
| Feature | Description |
|--------|----------|
| π± Responsive Routing | Automatic detection of device type and loading of appropriate components |
| πΎ Offline Support | Data synchronization and operation without internet connection |
| π Reactive Store | Global state with subscriptions for changes |
| π¦ Web Components | Encapsulation of styles and logic in custom elements |
| π PWA out of the box | Service Worker, manifest, and tools for creating PWA applications |
| π οΈ CLI Tools | Rapid creation of new PWA projects |
---
## π¦ Installation
```bash
npm install @dakir/simple-pwa
# or
yarn add @dakir/simple-pwa
```
---
## π§ͺ Quick Start
```js
import { App } from '@dakir/simple-pwa';
// Create an instance of the application
const app = App.create({
rootElement: '#app',
pwa: {
enabled: true,
serviceWorkerPath: '/service-worker.js'
}
});
// Launch the application
app.start();
```
---
## π§ Routing
The framework automatically detects the device type (mobile / desktop) and loads the corresponding components:
```js
const routes = [
{
path: '/',
desktopComponent: DesktopHome,
mobileComponent: MobileHome
},
{
path: '/about',
desktopComponent: DesktopAbout,
mobileComponent: MobileAbout
}
];
const app = new App({ routes });
```
---
## π§© Components
Create responsive components by inheriting from the base class:
```js
import { Component } from '@dakir/simple-pwa';
class CustomComponent extends Component {
render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = `
:host { display: block; padding: 20px; }
h2 { color: #2c3e50; }
My component
Counter: ${this.state.count || 0}
+
`;
this.shadowRoot.getElementById('increment')?.addEventListener('click', () => {
this.setState({ count: (this.state.count || 0) + 1 });
});
}
}
}
customElements.define('custom-component', CustomComponent);
```
---
## ποΈ Working with Store
Global state with reactive updates:
```js
import { Store } from '@dakir/simple-pwa';
// Creating a global store
const appStore = new Store({
user: null,
theme: 'light',
counter: 0
});
// Updating state
appStore.setState({ theme: 'dark' });
// Subscribing to changes
const unsubscribe = appStore.subscribe(state => {
console.log('State updated:', state);
});
// Unsubscribe from updates
unsubscribe();
```
---
## π Offline-First with IndexedDB
SimplePWA includes a powerful sync manager for working with data in offline mode:
```js
import { App } from '@dakir/simple-pwa';
const app = App.create({
syncDataManagerOptions: {
dbName: 'my-app-db'
}
});
// Retrieving data from IndexedDB
const items = await app.getDBList();
// Adding an item
await app.addDBItem({
id: '123',
type: 'task',
data: JSON.stringify({ title: 'Task', completed: false }),
lastModified: new Date().toISOString(),
lastSynced: new Date().toISOString()
});
// Synchronizing with the server when the connection is restored
app.setupAutoSync({
tasks: () => fetch('/api/tasks').then(res => res.json()),
users: () => fetch('/api/users').then(res => res.json())
}, {
onNetworkRestore: true,
interval: 5 * 60 * 1000 // Every 5 minutes
});
```
---
## π οΈ Creating PWA via CLI
```bash
npx create-pwa my-project
```
CLI will ask questions about the name, description, icons, and other parameters. Upon completion, you'll get a ready-to-use PWA project that you can launch immediately.
### π§βπ§ Generating a Service Worker
```bash
npx create-sw
```
CLI will help set up caching strategies and handle offline states.
---
## π API Reference
### `App`
The `App` class encapsulates routing, global state, synchronization, and PWA operations.
#### π Creating an Application
```js
import { App } from '@dakir/simple-pwa';
const app = App.create({
rootElement: '#app', // or HTMLElement
routes: [ /* ... */ ],
pwa: { enabled: true },
syncDataManagerOptions: { dbName: 'my-db' }
});
```
#### π§ Main Methods
| Method | Description |
|------|----------|
| `start()` | Start the application |
| `navigate(path)` | Navigate to a route |
| `getDeviceType()` | Get current device type (`mobile` / `desktop`) |
| `getState()` | Get store state object |
| `setState(patch)` | Update store state |
| `addEventListener()` | Add a global event handler |
---
### π§² Working with IndexedDB (SyncDataManager)
| Method | Description |
|------|----------|
| `getDBList(...)` | Retrieve a list of objects from IndexedDB |
| `getDBListByIndex(...)` | Retrieve a list by index |
| `getDBItem(id)` | Retrieve an object by ID |
| `getDBItemByIndex(...)` | Retrieve an object by a specific index |
| `addDBItem(obj)` | Add an object |
| `updateDBItem(obj)` | Update an object |
| `deleteDBItem(id)` | Delete an object by ID |
| `syncDBList(fetchMethod)` | Synchronize a list with the server |
| `syncDBItem(fetchMethod)` | Synchronize a single object with the server |
| `isSyncNeeded(lastSynced, [timeDelay])` | Check if re-synchronization is needed |
| `deleteDB([nameDB])` | Delete a database |
| `isOfflineMode()` | Check if the app is in offline mode |
| `isSyncing()` | Check if synchronization is currently in progress |
| `syncAllData(methods)` | Run synchronization of all collections (e.g., tasks and users simultaneously) |
| `setupAutoSync(methods, options)` | Configure auto-sync on network restoration, by timer, or at startup |
| `checkAndSync(id, method, [timeDelay])` | Check and synchronize data as needed |
#### Example usage of synchronization methods
```js
// Retrieve all tasks
const tasks = await app.getDBList();
// Add a new task
await app.addDBItem({ id: '1', type: 'task', ... });
// Synchronize tasks with the server
await app.syncDBList(() => fetch('/api/tasks').then(r => r.json()));
// Auto-sync when the network is restored
app.setupAutoSync({
tasks: () => fetch('/api/tasks').then(r => r.json())
});
```
---
### π§ Store
Reactive global store for storing application state.
```js
import { Store } from '@dakir/simple-pwa';
// Create a store
const store = new Store({ theme: 'light', counter: 0 });
// Subscribe to changes
const unsubscribe = store.subscribe(state => {
console.log('New state:', state);
});
// Update state
store.setState({ theme: 'dark' });
// Unsubscribe
unsubscribe();
```
---
### π§± Components (Web Components)
Base class `Component` and derived classes `DesktopComponent`, `MobileComponent`.
```js
import { Component } from '@dakir/simple-pwa';
class MyCounter extends Component {
render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = `
Count: ${this.state.count || 0}
+
`;
this.shadowRoot.getElementById('inc')?.addEventListener('click', () => {
this.setState({ count: (this.state.count || 0) + 1 });
});
}
}
}
customElements.define('my-counter', MyCounter);
```
---
### π§ Adaptive Routing
Use mobile and desktop components for different devices:
```js
const routes = [
{
path: '/',
desktopComponent: DesktopHome,
mobileComponent: MobileHome
}
];
const app = new App({ routes });
```
---
## π License
MIT
---
## π¬ Feedback and Support
Found a bug or want to suggest an improvement?
Open an [issue](https://github.com/Dakirchik/simplePWA/issues) or [PR](https://github.com/Dakirchik/simplePWA/pulls)!
---
π **SimplePWA β your fast path to production-ready PWA!**
# ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ Π½Π° ΡΡΡΡΠΊΠΎΠΌ
**SimplePWA** β ΡΡΠΎ ΡΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΉ ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ Π°Π΄Π°ΠΏΡΠΈΠ²Π½ΡΡ
, ΡΠ΅Π°ΠΊΡΠΈΠ²Π½ΡΡ
Π²Π΅Π±-ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ Ρ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΎΠΉ ΠΎΡΡΠ»Π°ΠΉΠ½-ΡΠ΅ΠΆΠΈΠΌΠ° ΠΈ PWA (Progressive Web Applications).
Π€ΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ ΠΏΠΎΡΡΡΠΎΠ΅Π½ Π½Π° [Web Components](https://developer.mozilla.org/ru/docs/Web/Web_Components) ΠΈ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠΈΠ²Π°Π΅Ρ ΠΏΡΠΎΡΡΡΡ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ [IndexedDB](https://developer.mozilla.org/ru/docs/Web/API/IndexedDB_API) Π΄Π»Ρ `offline-first` ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ.
---
## β¨ ΠΡΠ½ΠΎΠ²Π½ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ
| Π€ΡΠ½ΠΊΡΠΈΡ | ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ |
|--------|----------|
| π± ΠΠ΄Π°ΠΏΡΠΈΠ²Π½Π°Ρ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΈΡ | ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ΅ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΡΠΈΠΏΠ° ΡΡΡΡΠΎΠΉΡΡΠ²Π° ΠΈ Π·Π°Π³ΡΡΠ·ΠΊΠ° ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΡ
ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² |
| πΎ ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠ° ΠΎΡΡΠ»Π°ΠΉΠ½-ΡΠ΅ΠΆΠΈΠΌΠ° | Π‘ΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ Π΄Π°Π½Π½ΡΡ
ΠΈ ΡΠ°Π±ΠΎΡΠ° Π±Π΅Π· ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΡ ΠΊ ΠΈΠ½ΡΠ΅ΡΠ½Π΅ΡΡ |
| π Π Π΅Π°ΠΊΡΠΈΠ²Π½ΡΠΉ ΡΡΠΎΡ | ΠΠ»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Ρ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠ°ΠΌΠΈ Π½Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ |
| π¦ Web Components | ΠΠ½ΠΊΠ°ΠΏΡΡΠ»ΡΡΠΈΡ ΡΡΠΈΠ»Π΅ΠΉ ΠΈ Π»ΠΎΠ³ΠΈΠΊΠΈ Π² ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ |
| π PWA ΠΈΠ· ΠΊΠΎΡΠΎΠ±ΠΊΠΈ | Service Worker, ΠΌΠ°Π½ΠΈΡΠ΅ΡΡ ΠΈ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ PWA |
| π οΈ CLI-ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ | ΠΡΡΡΡΠΎΠ΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π½ΠΎΠ²ΡΡ
PWA ΠΏΡΠΎΠ΅ΠΊΡΠΎΠ² |
---
## π¦ Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°
```bash
npm install @dakir/simple-pwa
# ΠΈΠ»ΠΈ
yarn add @dakir/simple-pwa
```
---
## π§ͺ ΠΡΡΡΡΡΠΉ ΡΡΠ°ΡΡ
```js
import { App } from '@dakir/simple-pwa';
// Π‘ΠΎΠ·Π΄Π°ΠΉΡΠ΅ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
const app = App.create({
rootElement: '#app',
pwa: {
enabled: true,
serviceWorkerPath: '/service-worker.js'
}
});
// ΠΠ°ΠΏΡΡΡΠΈΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
app.start();
```
---
## π§ ΠΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΈΡ
Π€ΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅Ρ ΡΠΈΠΏ ΡΡΡΡΠΎΠΉΡΡΠ²Π° (ΠΌΠΎΠ±ΠΈΠ»ΡΠ½ΠΎΠ΅ / Π΄Π΅ΡΠΊΡΠΎΠΏΠ½ΠΎΠ΅) ΠΈ Π·Π°Π³ΡΡΠΆΠ°Π΅Ρ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ:
```js
const routes = [
{
path: '/',
desktopComponent: DesktopHome,
mobileComponent: MobileHome
},
{
path: '/about',
desktopComponent: DesktopAbout,
mobileComponent: MobileAbout
}
];
const app = new App({ routes });
```
---
## π§© ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ
Π‘ΠΎΠ·Π΄Π°Π²Π°ΠΉΡΠ΅ Π°Π΄Π°ΠΏΡΠΈΠ²Π½ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ, Π½Π°ΡΠ»Π΅Π΄ΡΡΡΡ ΠΎΡ Π±Π°Π·ΠΎΠ²ΠΎΠ³ΠΎ ΠΊΠ»Π°ΡΡΠ°:
```js
import { Component } from '@dakir/simple-pwa';
class CustomComponent extends Component {
render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = `
:host { display: block; padding: 20px; }
h2 { color: #2c3e50; }
ΠΠΎΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
Π‘ΡΡΡΡΠΈΠΊ: ${this.state.count || 0}
+
`;
this.shadowRoot.getElementById('increment')?.addEventListener('click', () => {
this.setState({ count: (this.state.count || 0) + 1 });
});
}
}
}
customElements.define('custom-component', CustomComponent);
```
---
## ποΈ Π Π°Π±ΠΎΡΠ° Ρ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ΠΌ (Store)
ΠΠ»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Ρ ΡΠ΅Π°ΠΊΡΠΈΠ²Π½ΡΠΌΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡΠΌΠΈ:
```js
import { Store } from '@dakir/simple-pwa';
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠ³ΠΎ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°
const appStore = new Store({
user: null,
theme: 'light',
counter: 0
});
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ
appStore.setState({ theme: 'dark' });
// ΠΠΎΠ΄ΠΏΠΈΡΠΊΠ° Π½Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ
const unsubscribe = appStore.subscribe(state => {
console.log('Π‘ΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΎ:', state);
});
// ΠΡΠΏΠΈΡΠ°ΡΡΡΡ ΠΎΡ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ
unsubscribe();
```
---
## π Offline-First Ρ IndexedDB
SimplePWA Π²ΠΊΠ»ΡΡΠ°Π΅Ρ ΠΌΠΎΡΠ½ΡΠΉ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΠΈ Π΄Π»Ρ ΡΠ°Π±ΠΎΡΡ Ρ Π΄Π°Π½Π½ΡΠΌΠΈ Π² ΠΎΡΡΠ»Π°ΠΉΠ½-ΡΠ΅ΠΆΠΈΠΌΠ΅:
```js
import { App } from '@dakir/simple-pwa';
const app = App.create({
syncDataManagerOptions: {
dbName: 'my-app-db'
}
});
// ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΡ
ΠΈΠ· IndexedDB
const items = await app.getDBList();
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°
await app.addDBItem({
id: '123',
type: 'task',
data: JSON.stringify({ title: 'ΠΠ°Π΄Π°ΡΠ°', completed: false }),
lastModified: new Date().toISOString(),
lastSynced: new Date().toISOString()
});
// Π‘ΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ ΠΏΡΠΈ Π²ΠΎΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ ΡΠΎΠ΅Π΄ΠΈΠ½Π΅Π½ΠΈΡ
app.setupAutoSync({
tasks: () => fetch('/api/tasks').then(res => res.json()),
users: () => fetch('/api/users').then(res => res.json())
}, {
onNetworkRestore: true,
interval: 5 * 60 * 1000 // ΠΠ°ΠΆΠ΄ΡΠ΅ 5 ΠΌΠΈΠ½ΡΡ
});
```
---
## π οΈ Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ PWA ΡΠ΅ΡΠ΅Π· CLI
```bash
npx create-pwa my-project
```
CLI Π·Π°Π΄Π°ΡΡ Π²ΠΎΠΏΡΠΎΡΡ ΠΎ Π½Π°Π·Π²Π°Π½ΠΈΠΈ, ΠΎΠΏΠΈΡΠ°Π½ΠΈΠΈ, ΠΈΠΊΠΎΠ½ΠΊΠ°Ρ
ΠΈ Π΄ΡΡΠ³ΠΈΡ
ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠ°Ρ
. ΠΠΎΡΠ»Π΅ Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΡ Π²Ρ ΠΏΠΎΠ»ΡΡΠΈΡΠ΅ Π³ΠΎΡΠΎΠ²ΡΠΉ PWA ΠΏΡΠΎΠ΅ΠΊΡ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΠΎΠΆΠ½ΠΎ ΡΡΠ°Π·Ρ Π·Π°ΠΏΡΡΡΠΈΡΡ.
### π§βπ§ ΠΠ΅Π½Π΅ΡΠ°ΡΠΈΡ Service Worker
```bash
npx create-sw
```
CLI ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ Π½Π°ΡΡΡΠΎΠΈΡΡ ΡΡΡΠ°ΡΠ΅Π³ΠΈΠΈ ΠΊΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΈ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΡ ΠΎΡΡΠ»Π°ΠΉΠ½-ΡΠΎΡΡΠΎΡΠ½ΠΈΡ.
---
## π API Reference
### `App`
ΠΠ»Π°ΡΡ `App` ΠΈΠ½ΠΊΠ°ΠΏΡΡΠ»ΠΈΡΡΠ΅Ρ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΈΡ, Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅, ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ ΠΈ ΡΠ°Π±ΠΎΡΡ Ρ PWA.
#### π Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
```js
import { App } from '@dakir/simple-pwa';
const app = App.create({
rootElement: '#app', // ΠΈΠ»ΠΈ HTMLElement
routes: [ /* ... */ ],
pwa: { enabled: true },
syncDataManagerOptions: { dbName: 'my-db' }
});
```
#### π§ ΠΡΠ½ΠΎΠ²Π½ΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ
| ΠΠ΅ΡΠΎΠ΄ | ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ |
|------|----------|
| `start()` | ΠΠ°ΠΏΡΡΡΠΈΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ |
| `navigate(path)` | ΠΠ΅ΡΠ΅ΠΉΡΠΈ ΠΏΠΎ ΠΌΠ°ΡΡΡΡΡΡ |
| `getDeviceType()` | ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠ΅ΠΊΡΡΠΈΠΉ ΡΠΈΠΏ ΡΡΡΡΠΎΠΉΡΡΠ²Π° (`mobile` / `desktop`) |
| `getState()` | ΠΠΎΠ»ΡΡΠΈΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ store |
| `setState(patch)` | ΠΠ±Π½ΠΎΠ²ΠΈΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ store |
| `addEventListener()` | ΠΠΎΠ±Π°Π²ΠΈΡΡ Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΡΠΉ ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊ ΡΠΎΠ±ΡΡΠΈΠΉ |
---
### π§² Π Π°Π±ΠΎΡΠ° Ρ IndexedDB (SyncDataManager)
| ΠΠ΅ΡΠΎΠ΄ | ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ |
|------|----------|
| `getDBList(...)` | ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠΏΠΈΡΠΎΠΊ ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ² ΠΈΠ· IndexedDB |
| `getDBListByIndex(...)` | ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠΏΠΈΡΠΎΠΊ ΠΏΠΎ ΠΈΠ½Π΄Π΅ΠΊΡΡ |
| `getDBItem(id)` | ΠΠΎΠ»ΡΡΠΈΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΏΠΎ ID |
| `getDBItemByIndex(...)` | ΠΠΎΠ»ΡΡΠΈΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΏΠΎ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠΌΡ ΠΈΠ½Π΄Π΅ΠΊΡΡ |
| `addDBItem(obj)` | ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ |
| `updateDBItem(obj)` | ΠΠ±Π½ΠΎΠ²ΠΈΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ |
| `deleteDBItem(id)` | Π£Π΄Π°Π»ΠΈΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΏΠΎ ID |
| `syncDBList(fetchMethod)` | Π‘ΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ ΡΠΏΠΈΡΠΎΠΊ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ |
| `syncDBItem(fetchMethod)` | Π‘ΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ ΠΎΠ΄ΠΈΠ½ ΠΎΠ±ΡΠ΅ΠΊΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ |
| `isSyncNeeded(lastSynced, [timeDelay])` | ΠΡΠΎΠ²Π΅ΡΠΈΡΡ, ΡΡΠ΅Π±ΡΠ΅ΡΡΡ Π»ΠΈ ΠΏΠΎΠ²ΡΠΎΡΠ½Π°Ρ ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ |
| `deleteDB([nameDB])` | Π£Π΄Π°Π»ΠΈΡΡ Π±Π°Π·Ρ Π΄Π°Π½Π½ΡΡ
|
| `isOfflineMode()` | ΠΡΠΎΠ²Π΅ΡΠΈΡΡ, Π² ΠΎΡΡΠ»Π°ΠΉΠ½-ΡΠ΅ΠΆΠΈΠΌΠ΅ Π»ΠΈ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ |
| `isSyncing()` | ΠΡΠΎΠ²Π΅ΡΠΈΡΡ, ΠΈΠ΄ΡΡ Π»ΠΈ ΡΠ΅ΠΉΡΠ°Ρ ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ |
| `syncAllData(methods)` | ΠΠ°ΠΏΡΡΡΠΈΡΡ ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ Π²ΡΠ΅Ρ
ΠΊΠΎΠ»Π»Π΅ΠΊΡΠΈΠΉ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, Π·Π°Π΄Π°Ρ ΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ ΠΎΠ΄Π½ΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½ΠΎ) |
| `setupAutoSync(methods, options)` | ΠΠ°ΡΡΡΠΎΠΈΡΡ Π°Π²ΡΠΎΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ ΠΏΡΠΈ Π²ΠΎΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ ΡΠ΅ΡΠΈ, ΠΏΠΎ ΡΠ°ΠΉΠΌΠ΅ΡΡ, ΠΏΡΠΈ ΡΡΠ°ΡΡΠ΅ |
| `checkAndSync(id, method, [timeDelay])` | ΠΡΠΎΠ²Π΅ΡΠΈΡΡ ΠΈ ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ Π΄Π°Π½Π½ΡΠ΅ ΠΏΠΎ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ |
#### ΠΡΠΈΠΌΠ΅Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠ² ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΠΈ
```js
// ΠΠΎΠ»ΡΡΠΈΡΡ Π²ΡΠ΅ Π·Π°Π΄Π°ΡΠΈ
const tasks = await app.getDBList();
// ΠΠΎΠ±Π°Π²ΠΈΡΡ Π½ΠΎΠ²ΡΡ Π·Π°Π΄Π°ΡΡ
await app.addDBItem({ id: '1', type: 'task', ... });
// Π‘ΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ Π·Π°Π΄Π°ΡΠΈ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ
await app.syncDBList(() => fetch('/api/tasks').then(r => r.json()));
// ΠΠ²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠ°Ρ ΡΠΈΠ½Ρ
ΡΠΎΠ½ΠΈΠ·Π°ΡΠΈΡ ΠΏΡΠΈ Π²ΠΎΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ ΡΠ΅ΡΠΈ
app.setupAutoSync({
tasks: () => fetch('/api/tasks').then(r => r.json())
});
```
---
### π§ Store
Π Π΅Π°ΠΊΡΠΈΠ²Π½ΡΠΉ Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΡΠΉ ΡΡΠΎΡ Π΄Π»Ρ Ρ
ΡΠ°Π½Π΅Π½ΠΈΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
```js
import { Store } from '@dakir/simple-pwa';
// Π‘ΠΎΠ·Π΄Π°ΡΡ ΡΡΠΎΡ
const store = new Store({ theme: 'light', counter: 0 });
// ΠΠΎΠ΄ΠΏΠΈΡΠΊΠ° Π½Π° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ
const unsubscribe = store.subscribe(state => {
console.log('ΠΠΎΠ²ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅:', state);
});
// ΠΠ±Π½ΠΎΠ²ΠΈΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅
store.setState({ theme: 'dark' });
// ΠΡΠΏΠΈΡΠΊΠ°
unsubscribe();
```
---
### π§± ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ (Web Components)
ΠΠ°Π·ΠΎΠ²ΡΠΉ ΠΊΠ»Π°ΡΡ `Component` ΠΈ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄Π½ΡΠ΅ `DesktopComponent`, `MobileComponent`.
```js
import { Component } from '@dakir/simple-pwa';
class MyCounter extends Component {
render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = `
Count: ${this.state.count || 0}
+
`;
this.shadowRoot.getElementById('inc')?.addEventListener('click', () => {
this.setState({ count: (this.state.count || 0) + 1 });
});
}
}
}
customElements.define('my-counter', MyCounter);
```
---
### π§ ΠΠ΄Π°ΠΏΡΠΈΠ²Π½Π°Ρ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΈΡ
ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ ΠΌΠΎΠ±ΠΈΠ»ΡΠ½ΡΠ΅ ΠΈ Π΄Π΅ΡΠΊΡΠΎΠΏΠ½ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ Π΄Π»Ρ ΡΠ°Π·Π½ΡΡ
ΡΡΡΡΠΎΠΉΡΡΠ²:
```js
const routes = [
{
path: '/',
desktopComponent: DesktopHome,
mobileComponent: MobileHome
}
];
const app = new App({ routes });
```
---
## π ΠΠΈΡΠ΅Π½Π·ΠΈΡ
MIT
---
## π¬ ΠΠ±ΡΠ°ΡΠ½Π°Ρ ΡΠ²ΡΠ·Ρ ΠΈ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠ°
ΠΠ°ΡΠ»ΠΈ Π±Π°Π³ ΠΈΠ»ΠΈ Ρ
ΠΎΡΠΈΡΠ΅ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠΈΡΡ ΡΠ»ΡΡΡΠ΅Π½ΠΈΠ΅?
ΠΡΠΊΡΡΠ²Π°ΠΉΡΠ΅ [issue](https://github.com/Dakirchik/simplePWA/issues) ΠΈΠ»ΠΈ [PR](https://github.com/Dakirchik/simplePWA/pulls)!
---
π **SimplePWA β Π²Π°Ρ Π±ΡΡΡΡΡΠΉ ΠΏΡΡΡ ΠΊ production-ready PWA!**