@budarin/pluggable-serviceworker 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +3 -0
- package/.prettierrc.mjs +14 -0
- package/.vscode/extensions.json +26 -0
- package/.vscode/launch.json +20 -0
- package/.vscode/settings.json +164 -0
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
- package/src/index.ts +156 -0
- package/tsconfig.json +44 -0
package/.prettierignore
ADDED
package/.prettierrc.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @see https://prettier.io/docs/en/configuration.html
|
|
3
|
+
* @type {import("prettier").Config}
|
|
4
|
+
*/
|
|
5
|
+
const config = {
|
|
6
|
+
trailingComma: 'es5',
|
|
7
|
+
tabWidth: 4,
|
|
8
|
+
// semi: false,
|
|
9
|
+
singleQuote: true,
|
|
10
|
+
plugins: [],
|
|
11
|
+
htmlWhitespaceSensitivity: 'ignore',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default config;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"recommendations": [
|
|
3
|
+
// "codeium.codeium",
|
|
4
|
+
"dbaeumer.vscode-eslint",
|
|
5
|
+
"esbenp.prettier-vscode",
|
|
6
|
+
"stylelint.vscode-stylelint",
|
|
7
|
+
"dsznajder.es7-react-js-snippets",
|
|
8
|
+
"ecmel.vscode-html-css",
|
|
9
|
+
"vitest.explorer",
|
|
10
|
+
"vscode-icons-team.vscode-icons",
|
|
11
|
+
"wayou.vscode-todo-highlight",
|
|
12
|
+
"felixfbecker.css-stacking-contexts",
|
|
13
|
+
// "formulahendry.auto-rename-tag",
|
|
14
|
+
"mhutchie.git-graph",
|
|
15
|
+
"ms-azuretools.vscode-docker",
|
|
16
|
+
"ms-playwright.playwrigh",
|
|
17
|
+
"naumovs.color-highlight",
|
|
18
|
+
"planbcoding.vscode-react-refactor",
|
|
19
|
+
"svetlanakost.typograf",
|
|
20
|
+
"viktorzetterstrom.non-breaking-space-highlighter",
|
|
21
|
+
"wallabyjs.wallaby-vscode",
|
|
22
|
+
"yoavbls.pretty-ts-errors",
|
|
23
|
+
"donjayamanne.githistory",
|
|
24
|
+
"pranaygp.vscode-css-peek"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "node",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "Launch Program",
|
|
11
|
+
"skipFiles": [
|
|
12
|
+
"<node_internals>/**"
|
|
13
|
+
],
|
|
14
|
+
"program": "${workspaceFolder}\\src\\sw\\helpers\\handleFetch\\index.ts",
|
|
15
|
+
"outFiles": [
|
|
16
|
+
"${workspaceFolder}/**/*.js"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
{
|
|
2
|
+
// explorer
|
|
3
|
+
"explorer.autoReveal": true,
|
|
4
|
+
"explorer.incrementalNaming": "smart",
|
|
5
|
+
"debug.openExplorerOnEnd": true,
|
|
6
|
+
|
|
7
|
+
// editor
|
|
8
|
+
"editor.fontLigatures": true,
|
|
9
|
+
"editor.mouseWheelZoom": true,
|
|
10
|
+
"editor.smoothScrolling": true,
|
|
11
|
+
"editor.cursorSmoothCaretAnimation": "on",
|
|
12
|
+
"editor.minimap.enabled": false,
|
|
13
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
14
|
+
"editor.formatOnSave": true,
|
|
15
|
+
"editor.formatOnPaste": true,
|
|
16
|
+
"editor.language.colorizedBracketPairs": [
|
|
17
|
+
[
|
|
18
|
+
"(",
|
|
19
|
+
")"
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
"[",
|
|
23
|
+
"]"
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
"{",
|
|
27
|
+
"}"
|
|
28
|
+
]
|
|
29
|
+
],
|
|
30
|
+
"editor.suggestSelection": "first",
|
|
31
|
+
"editor.codeActionsOnSave": {
|
|
32
|
+
"source.fixAll": "explicit",
|
|
33
|
+
"source.organizeImports": "never"
|
|
34
|
+
},
|
|
35
|
+
"editor.guides.bracketPairs": true,
|
|
36
|
+
"editor.guides.highlightActiveBracketPair": false,
|
|
37
|
+
"editor.fontFamily": "Consolas, Monaco, Menlo,'Courier New', monospace",
|
|
38
|
+
|
|
39
|
+
// files
|
|
40
|
+
"files.autoSave": "onFocusChange",
|
|
41
|
+
"files.insertFinalNewline": true,
|
|
42
|
+
"files.trimFinalNewlines": true,
|
|
43
|
+
"files.trimTrailingWhitespace": true,
|
|
44
|
+
"files.eol": "\n",
|
|
45
|
+
|
|
46
|
+
// git
|
|
47
|
+
"git.enableSmartCommit": true,
|
|
48
|
+
"git.confirmSync": false,
|
|
49
|
+
"git.autofetch": true,
|
|
50
|
+
"git.autofetchPeriod": 60,
|
|
51
|
+
// "git.branchProtection": ["master"],
|
|
52
|
+
"git.checkoutType": [
|
|
53
|
+
"local",
|
|
54
|
+
"remote"
|
|
55
|
+
],
|
|
56
|
+
"git.ignoreRebaseWarning": true,
|
|
57
|
+
"git.inputValidationSubjectLength": 100,
|
|
58
|
+
"git.inputValidationLength": 200,
|
|
59
|
+
|
|
60
|
+
// ts/javascript
|
|
61
|
+
// "typescript.surveys.enabled": false,
|
|
62
|
+
"javascript.suggestionActions.enabled": false,
|
|
63
|
+
"typescript.tsdk": "./node_modules/typescript/lib",
|
|
64
|
+
"typescript.updateImportsOnFileMove.enabled": "always",
|
|
65
|
+
"typescript.preferences.importModuleSpecifier": "shortest",
|
|
66
|
+
"javascript.updateImportsOnFileMove.enabled": "always",
|
|
67
|
+
"typescript.format.semicolons": "insert",
|
|
68
|
+
"javascript.format.semicolons": "insert",
|
|
69
|
+
|
|
70
|
+
// prettier
|
|
71
|
+
"prettier.enable": true,
|
|
72
|
+
"json.format.enable": true,
|
|
73
|
+
"prettier.configPath": ".prettierrc.mjs",
|
|
74
|
+
"prettier.experimentalTernaries": true,
|
|
75
|
+
"prettier.enableDebugLogs": true,
|
|
76
|
+
"prettier.prettierPath": "./node_modules/prettier",
|
|
77
|
+
"prettier.htmlWhitespaceSensitivity": "strict",
|
|
78
|
+
"[json]": {
|
|
79
|
+
"editor.formatOnSave": true
|
|
80
|
+
},
|
|
81
|
+
"[css]": {
|
|
82
|
+
"editor.formatOnSave": true,
|
|
83
|
+
"editor.defaultFormatter": "vscode.css-language-features"
|
|
84
|
+
},
|
|
85
|
+
"[html]": {
|
|
86
|
+
"editor.formatOnSave": true,
|
|
87
|
+
"editor.defaultFormatter": "vscode.html-language-features"
|
|
88
|
+
},
|
|
89
|
+
"[jsonc]": {
|
|
90
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
91
|
+
},
|
|
92
|
+
"[xml]": {
|
|
93
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
94
|
+
},
|
|
95
|
+
"[svg]": {
|
|
96
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
97
|
+
},
|
|
98
|
+
"[typescriptreact]": {
|
|
99
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
100
|
+
},
|
|
101
|
+
"[typescript]": {
|
|
102
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
103
|
+
},
|
|
104
|
+
"[javascript]": {
|
|
105
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
106
|
+
},
|
|
107
|
+
"[javascriptreact]": {
|
|
108
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
109
|
+
},
|
|
110
|
+
"[markdown]": {
|
|
111
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// workbench
|
|
115
|
+
"workbench.iconTheme": "vscode-icons",
|
|
116
|
+
"workbench.colorTheme": "Default Dark+",
|
|
117
|
+
"workbench.externalBrowser": "chrome",
|
|
118
|
+
"workbench.colorCustomizations": {},
|
|
119
|
+
|
|
120
|
+
// terminal
|
|
121
|
+
"terminal.integrated.defaultProfile.windows": "Git Bash",
|
|
122
|
+
"terminal.integrated.cursorBlinking": true,
|
|
123
|
+
"terminal.integrated.fontFamily": "Consolas, 'Courier New', monospace",
|
|
124
|
+
"terminal.integrated.minimumContrastRatio": 4.5,
|
|
125
|
+
"terminal.integrated.smoothScrolling": true,
|
|
126
|
+
"terminal.integrated.profiles.windows": {
|
|
127
|
+
"Bash": {
|
|
128
|
+
"path": "C:\\Program Files\\Git\\bin\\bash.exe",
|
|
129
|
+
"args": []
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"terminal.integrated.scrollback": 500000,
|
|
133
|
+
"terminal.integrated.enablePersistentSessions": false,
|
|
134
|
+
"workbench.tree.enableStickyScroll": false,
|
|
135
|
+
"editor.stickyScroll.scrollWithEditor": false,
|
|
136
|
+
"editor.stickyScroll.enabled": false,
|
|
137
|
+
"accessibility.signals.terminalBell": {
|
|
138
|
+
"sound": "on"
|
|
139
|
+
},
|
|
140
|
+
"terminal.integrated.enableVisualBell": true,
|
|
141
|
+
|
|
142
|
+
// npm scripts
|
|
143
|
+
"npm.scriptExplorerAction": "run",
|
|
144
|
+
|
|
145
|
+
// emmet
|
|
146
|
+
"emmet.includeLanguages": {
|
|
147
|
+
"postcss": "css",
|
|
148
|
+
"javascript": "javascriptreact",
|
|
149
|
+
"typescript": "typescriptreact"
|
|
150
|
+
},
|
|
151
|
+
"emmet.showSuggestionsAsSnippets": true,
|
|
152
|
+
|
|
153
|
+
"notebook.defaultFormatter": "esbenp.prettier-vscode",
|
|
154
|
+
"window.zoomLevel": 0,
|
|
155
|
+
|
|
156
|
+
// extensions
|
|
157
|
+
"reactSnippets.settings.prettierEnabled": true,
|
|
158
|
+
|
|
159
|
+
"stylelint.configBasedir": "./",
|
|
160
|
+
"stylelint.configFile": "./stylelint.config.mjs",
|
|
161
|
+
"stylelint.packageManager": "pnpm",
|
|
162
|
+
|
|
163
|
+
"javascript.preferences.importModuleSpecifier": "non-relative",
|
|
164
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Вадим Бударин
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# @vadimbudarin/pluggable-serviceworker
|
|
2
|
+
|
|
3
|
+
🔌 Расширяемый через плагины Service Worker
|
|
4
|
+
|
|
5
|
+
Библиотека для создания модульных и расширяемых Service Worker'ов с помощью системы плагинов. Позволяет легко добавлять функциональность через плагины с поддержкой приоритетов и обработки ошибок.
|
|
6
|
+
|
|
7
|
+
## ✨ Особенности
|
|
8
|
+
|
|
9
|
+
- 🔌 **Система плагинов** - модульная архитектура для расширения функциональности
|
|
10
|
+
- 📊 **Приоритеты** - контроль порядка выполнения плагинов
|
|
11
|
+
- 🛡️ **Обработка ошибок** - централизованная обработка ошибок
|
|
12
|
+
- 🎯 **TypeScript** - полная поддержка типов
|
|
13
|
+
- 🚀 **Простота использования** - минимальная настройка
|
|
14
|
+
|
|
15
|
+
## 📦 Установка
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @vadimbudarin/pluggable-serviceworker
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
или
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @vadimbudarin/pluggable-serviceworker
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 🚀 Быстрый старт
|
|
28
|
+
|
|
29
|
+
### Базовое использование
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// sw.js
|
|
33
|
+
import { initializeServiceWorker } from '@vadimbudarin/pluggable-serviceworker';
|
|
34
|
+
|
|
35
|
+
// Простой плагин для кеширования
|
|
36
|
+
const cachePlugin = {
|
|
37
|
+
name: 'cache-plugin',
|
|
38
|
+
|
|
39
|
+
install: async (event) => {
|
|
40
|
+
const cache = await caches.open('my-cache-v1');
|
|
41
|
+
await cache.addAll(['/', '/styles.css', '/script.js']);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
fetch: async (event) => {
|
|
45
|
+
const cachedResponse = await caches.match(event.request);
|
|
46
|
+
return cachedResponse || fetch(event.request);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Инициализация Service Worker с плагинами
|
|
51
|
+
initializeServiceWorker([cachePlugin]);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Использование с приоритетами
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { initializeServiceWorker } from '@vadimbudarin/pluggable-serviceworker';
|
|
58
|
+
|
|
59
|
+
const authPlugin = {
|
|
60
|
+
name: 'auth-plugin',
|
|
61
|
+
priority: 1, // Выполняется первым
|
|
62
|
+
|
|
63
|
+
fetch: async (event) => {
|
|
64
|
+
// Проверка авторизации для API запросов
|
|
65
|
+
if (event.request.url.includes('/api/')) {
|
|
66
|
+
const token = await getAuthToken();
|
|
67
|
+
if (!token) {
|
|
68
|
+
return new Response('Unauthorized', { status: 401 });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null; // Передать обработку следующему плагину
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const cachePlugin = {
|
|
76
|
+
name: 'cache-plugin',
|
|
77
|
+
priority: 2, // Выполняется вторым
|
|
78
|
+
|
|
79
|
+
fetch: async (event) => {
|
|
80
|
+
const cachedResponse = await caches.match(event.request);
|
|
81
|
+
return cachedResponse || fetch(event.request);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const loggingPlugin = {
|
|
86
|
+
name: 'logging-plugin',
|
|
87
|
+
// Без приоритета - выполняется последним
|
|
88
|
+
|
|
89
|
+
fetch: async (event) => {
|
|
90
|
+
console.log('Запрос:', event.request.url);
|
|
91
|
+
return null;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
initializeServiceWorker([authPlugin, cachePlugin, loggingPlugin]);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Обработка ошибок
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { initializeServiceWorker } from '@vadimbudarin/pluggable-serviceworker';
|
|
102
|
+
|
|
103
|
+
const config = {
|
|
104
|
+
onError: (error, event) => {
|
|
105
|
+
console.error('Ошибка в Service Worker:', error);
|
|
106
|
+
|
|
107
|
+
// Отправка ошибки в аналитику
|
|
108
|
+
if ('fetch' in event) {
|
|
109
|
+
fetch('/api/errors', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
error: error.message,
|
|
113
|
+
url: event.request?.url,
|
|
114
|
+
timestamp: Date.now(),
|
|
115
|
+
}),
|
|
116
|
+
}).catch(() => {
|
|
117
|
+
// Игнорируем ошибки отправки логов
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
initializeServiceWorker(
|
|
124
|
+
[
|
|
125
|
+
/* ваши плагины */
|
|
126
|
+
],
|
|
127
|
+
config
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 🔧 API
|
|
132
|
+
|
|
133
|
+
### ServiceWorkerPlugin
|
|
134
|
+
|
|
135
|
+
Интерфейс плагина:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface ServiceWorkerPlugin {
|
|
139
|
+
name: string; // Уникальное имя плагина
|
|
140
|
+
priority?: number; // Приоритет выполнения (меньше = раньше)
|
|
141
|
+
install?: (event: ExtendableEvent) => void | Promise<void>;
|
|
142
|
+
activate?: (event: ExtendableEvent) => void | Promise<void>;
|
|
143
|
+
fetch?: (event: FetchEvent) => Promise<Response | null>;
|
|
144
|
+
message?: (event: MessageEvent) => void;
|
|
145
|
+
sync?: (event: SyncEvent) => void;
|
|
146
|
+
push?: (event: PushEvent) => void;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### initializeServiceWorker
|
|
151
|
+
|
|
152
|
+
Основная функция для инициализации Service Worker:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
function initializeServiceWorker(
|
|
156
|
+
plugins: ServiceWorkerPlugin[],
|
|
157
|
+
config?: ServiceWorkerConfig
|
|
158
|
+
): void;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### ServiceWorkerConfig
|
|
162
|
+
|
|
163
|
+
Конфигурация Service Worker:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
interface ServiceWorkerConfig {
|
|
167
|
+
plugins?: ServiceWorkerPlugin[];
|
|
168
|
+
onError?: (error: Error, event: Event) => void;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 📝 Примеры плагинов
|
|
173
|
+
|
|
174
|
+
### Плагин кеширования
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const cachePlugin = {
|
|
178
|
+
name: 'advanced-cache',
|
|
179
|
+
priority: 10,
|
|
180
|
+
|
|
181
|
+
install: async (event) => {
|
|
182
|
+
const cache = await caches.open('app-cache-v1');
|
|
183
|
+
await cache.addAll([
|
|
184
|
+
'/',
|
|
185
|
+
'/offline.html',
|
|
186
|
+
'/assets/app.css',
|
|
187
|
+
'/assets/app.js',
|
|
188
|
+
]);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
fetch: async (event) => {
|
|
192
|
+
// Стратегия "Cache First" для статических ресурсов
|
|
193
|
+
if (event.request.url.includes('/assets/')) {
|
|
194
|
+
const cachedResponse = await caches.match(event.request);
|
|
195
|
+
return cachedResponse || fetch(event.request);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Стратегия "Network First" для API
|
|
199
|
+
if (event.request.url.includes('/api/')) {
|
|
200
|
+
try {
|
|
201
|
+
const response = await fetch(event.request);
|
|
202
|
+
const cache = await caches.open('api-cache-v1');
|
|
203
|
+
cache.put(event.request, response.clone());
|
|
204
|
+
return response;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return caches.match(event.request);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Плагин уведомлений
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const notificationPlugin = {
|
|
219
|
+
name: 'notifications',
|
|
220
|
+
|
|
221
|
+
push: (event) => {
|
|
222
|
+
const data = event.data?.json() || {};
|
|
223
|
+
|
|
224
|
+
const options = {
|
|
225
|
+
body: data.body || 'Новое уведомление',
|
|
226
|
+
icon: data.icon || '/icon-192x192.png',
|
|
227
|
+
badge: '/badge-72x72.png',
|
|
228
|
+
tag: data.tag || 'default',
|
|
229
|
+
data: data.url ? { url: data.url } : undefined,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
self.registration.showNotification(
|
|
233
|
+
data.title || 'Уведомление',
|
|
234
|
+
options
|
|
235
|
+
);
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
message: (event) => {
|
|
239
|
+
if (event.data?.type === 'NOTIFICATION_CLICK') {
|
|
240
|
+
// Обработка клика по уведомлению
|
|
241
|
+
clients.openWindow(event.data.url);
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Плагин фоновой синхронизации
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const backgroundSyncPlugin = {
|
|
251
|
+
name: 'background-sync',
|
|
252
|
+
|
|
253
|
+
sync: (event) => {
|
|
254
|
+
if (event.tag === 'background-sync') {
|
|
255
|
+
event.waitUntil(doBackgroundSync());
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
async function doBackgroundSync() {
|
|
261
|
+
// Получение отложенных задач из IndexedDB
|
|
262
|
+
const tasks = await getPendingTasks();
|
|
263
|
+
|
|
264
|
+
for (const task of tasks) {
|
|
265
|
+
try {
|
|
266
|
+
await fetch(task.url, {
|
|
267
|
+
method: task.method,
|
|
268
|
+
body: task.body,
|
|
269
|
+
headers: task.headers,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await removeTask(task.id);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error('Ошибка синхронизации:', error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 🎯 Порядок выполнения
|
|
281
|
+
|
|
282
|
+
Плагины выполняются в следующем порядке:
|
|
283
|
+
|
|
284
|
+
1. **Плагины с приоритетом** - сортируются по возрастанию значения `priority`
|
|
285
|
+
2. **Плагины без приоритета** - выполняются в порядке добавления
|
|
286
|
+
|
|
287
|
+
### Пример:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
const plugins = [
|
|
291
|
+
{ name: 'third', priority: 30 },
|
|
292
|
+
{ name: 'first', priority: 10 },
|
|
293
|
+
{ name: 'fourth' }, // без приоритета
|
|
294
|
+
{ name: 'second', priority: 20 },
|
|
295
|
+
{ name: 'fifth' }, // без приоритета
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
// Порядок выполнения: first → second → third → fourth → fifth
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## 🛡️ Обработка ошибок
|
|
302
|
+
|
|
303
|
+
- Ошибки в плагинах автоматически перехватываются
|
|
304
|
+
- Можно настроить кастомный обработчик через `config.onError`
|
|
305
|
+
- Ошибка в одном плагине не останавливает выполнение других
|
|
306
|
+
|
|
307
|
+
## 📄 Лицензия
|
|
308
|
+
|
|
309
|
+
MIT © Vadim Budarin
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface SyncEvent extends ExtendableEvent {
|
|
2
|
+
readonly tag: string;
|
|
3
|
+
readonly lastChance: boolean;
|
|
4
|
+
}
|
|
5
|
+
declare global {
|
|
6
|
+
interface ServiceWorkerGlobalScopeEventMap {
|
|
7
|
+
sync: SyncEvent;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
interface ServiceWorkerEventHandlers {
|
|
11
|
+
install?: (event: ExtendableEvent) => void | Promise<void>;
|
|
12
|
+
activate?: (event: ExtendableEvent) => void | Promise<void>;
|
|
13
|
+
fetch?: (event: FetchEvent) => Promise<Response | null>;
|
|
14
|
+
message?: (event: MessageEvent) => void;
|
|
15
|
+
sync?: (event: SyncEvent) => void;
|
|
16
|
+
push?: (event: PushEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
interface ServiceWorkerPlugin extends ServiceWorkerEventHandlers {
|
|
19
|
+
name: string;
|
|
20
|
+
priority?: number;
|
|
21
|
+
}
|
|
22
|
+
interface ServiceWorkerConfig {
|
|
23
|
+
plugins?: ServiceWorkerPlugin[];
|
|
24
|
+
onError?: (error: Error, event: Event) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function createEventHandlers(plugins: ServiceWorkerPlugin[], config?: ServiceWorkerConfig): {
|
|
27
|
+
install: (event: ExtendableEvent) => void;
|
|
28
|
+
activate: (event: ExtendableEvent) => void;
|
|
29
|
+
fetch: (event: FetchEvent) => void;
|
|
30
|
+
message: (event: MessageEvent) => void;
|
|
31
|
+
sync: (event: SyncEvent) => void;
|
|
32
|
+
push: (event: PushEvent) => void;
|
|
33
|
+
};
|
|
34
|
+
export declare function initializeServiceWorker(plugins: ServiceWorkerPlugin[], config?: ServiceWorkerConfig): void;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,UAAU,SAAU,SAAQ,eAAe;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAChC;AAGD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,gCAAgC;QACtC,IAAI,EAAE,SAAS,CAAC;KACnB;CACJ;AAED,UAAU,0BAA0B;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACrC;AAED,UAAU,mBAAoB,SAAQ,0BAA0B;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,mBAAmB;IACzB,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClD;AAID,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,mBAAmB,EAAE,EAC9B,MAAM,GAAE,mBAAwB,GACjC;IACC,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC1C,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC3C,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACnC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACjC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACpC,CAkGA;AAED,wBAAgB,uBAAuB,CACnC,OAAO,EAAE,mBAAmB,EAAE,EAC9B,MAAM,CAAC,EAAE,mBAAmB,GAC7B,IAAI,CASN"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function createEventHandlers(plugins, config = {}) {
|
|
2
|
+
const handlers = {
|
|
3
|
+
install: [],
|
|
4
|
+
activate: [],
|
|
5
|
+
fetch: [],
|
|
6
|
+
message: [],
|
|
7
|
+
sync: [],
|
|
8
|
+
push: [],
|
|
9
|
+
};
|
|
10
|
+
const sortedPlugins = [
|
|
11
|
+
...plugins
|
|
12
|
+
.filter((plugin) => plugin.priority !== undefined)
|
|
13
|
+
.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0)),
|
|
14
|
+
...plugins.filter((plugin) => plugin.priority === undefined),
|
|
15
|
+
];
|
|
16
|
+
sortedPlugins.forEach((plugin) => {
|
|
17
|
+
if (plugin.install)
|
|
18
|
+
handlers.install.push(plugin.install);
|
|
19
|
+
if (plugin.activate)
|
|
20
|
+
handlers.activate.push(plugin.activate);
|
|
21
|
+
if (plugin.fetch)
|
|
22
|
+
handlers.fetch.push(plugin.fetch);
|
|
23
|
+
if (plugin.message)
|
|
24
|
+
handlers.message.push(plugin.message);
|
|
25
|
+
if (plugin.sync)
|
|
26
|
+
handlers.sync.push(plugin.sync);
|
|
27
|
+
if (plugin.push)
|
|
28
|
+
handlers.push.push(plugin.push);
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
install: (event) => {
|
|
32
|
+
event.waitUntil(Promise.all(handlers.install.map((handler) => Promise.resolve(handler(event)).catch((error) => config.onError?.(error, event)))));
|
|
33
|
+
},
|
|
34
|
+
activate: (event) => {
|
|
35
|
+
event.waitUntil(Promise.all(handlers.activate.map((handler) => Promise.resolve(handler(event)).catch((error) => config.onError?.(error, event)))));
|
|
36
|
+
},
|
|
37
|
+
fetch: (event) => {
|
|
38
|
+
event.respondWith((async () => {
|
|
39
|
+
for (const handler of handlers.fetch) {
|
|
40
|
+
try {
|
|
41
|
+
const result = await handler(event);
|
|
42
|
+
if (result) {
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
config.onError?.(error, event);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return fetch(event.request);
|
|
51
|
+
})());
|
|
52
|
+
},
|
|
53
|
+
message: (event) => {
|
|
54
|
+
handlers.message.forEach((handler) => {
|
|
55
|
+
try {
|
|
56
|
+
handler(event);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
config.onError?.(error, event);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
sync: (event) => {
|
|
64
|
+
handlers.sync.forEach((handler) => {
|
|
65
|
+
handler(event);
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
push: (event) => {
|
|
69
|
+
handlers.push.forEach((handler) => {
|
|
70
|
+
handler(event);
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function initializeServiceWorker(plugins, config) {
|
|
76
|
+
const handlers = createEventHandlers(plugins, config);
|
|
77
|
+
self.addEventListener('install', handlers.install);
|
|
78
|
+
self.addEventListener('activate', handlers.activate);
|
|
79
|
+
self.addEventListener('fetch', handlers.fetch);
|
|
80
|
+
self.addEventListener('message', handlers.message);
|
|
81
|
+
self.addEventListener('sync', handlers.sync);
|
|
82
|
+
self.addEventListener('push', handlers.push);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@budarin/pluggable-serviceworker",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Extensible via plugins service worker",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"serviceworker",
|
|
7
|
+
"plugins",
|
|
8
|
+
"extensible",
|
|
9
|
+
"serviceworker-plugins",
|
|
10
|
+
"serviceworker-plugins-extensible"
|
|
11
|
+
],
|
|
12
|
+
"author": "Vadim Budarin",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./dist/index.js",
|
|
19
|
+
"./types": "./dist/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^24.5.2",
|
|
23
|
+
"@types/serviceworker": "^0.0.153",
|
|
24
|
+
"@types/web": "^0.0.269",
|
|
25
|
+
"prettier": "^3.6.2",
|
|
26
|
+
"typescript": "^5.9.2"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"publish-package": "pnpm build && pnpm publish --access=public"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
interface SyncEvent extends ExtendableEvent {
|
|
2
|
+
readonly tag: string;
|
|
3
|
+
readonly lastChance: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Extend ServiceWorkerGlobalScope to include sync event
|
|
7
|
+
declare global {
|
|
8
|
+
interface ServiceWorkerGlobalScopeEventMap {
|
|
9
|
+
sync: SyncEvent;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ServiceWorkerEventHandlers {
|
|
14
|
+
install?: (event: ExtendableEvent) => void | Promise<void>;
|
|
15
|
+
activate?: (event: ExtendableEvent) => void | Promise<void>;
|
|
16
|
+
fetch?: (event: FetchEvent) => Promise<Response | null>;
|
|
17
|
+
message?: (event: MessageEvent) => void;
|
|
18
|
+
sync?: (event: SyncEvent) => void;
|
|
19
|
+
push?: (event: PushEvent) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ServiceWorkerPlugin extends ServiceWorkerEventHandlers {
|
|
23
|
+
name: string;
|
|
24
|
+
priority?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ServiceWorkerConfig {
|
|
28
|
+
plugins?: ServiceWorkerPlugin[];
|
|
29
|
+
onError?: (error: Error, event: Event) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type FetchResponse = Promise<Response | null>;
|
|
33
|
+
|
|
34
|
+
export function createEventHandlers(
|
|
35
|
+
plugins: ServiceWorkerPlugin[],
|
|
36
|
+
config: ServiceWorkerConfig = {}
|
|
37
|
+
): {
|
|
38
|
+
install: (event: ExtendableEvent) => void;
|
|
39
|
+
activate: (event: ExtendableEvent) => void;
|
|
40
|
+
fetch: (event: FetchEvent) => void;
|
|
41
|
+
message: (event: MessageEvent) => void;
|
|
42
|
+
sync: (event: SyncEvent) => void;
|
|
43
|
+
push: (event: PushEvent) => void;
|
|
44
|
+
} {
|
|
45
|
+
const handlers = {
|
|
46
|
+
install: [] as ((event: ExtendableEvent) => void | Promise<void>)[],
|
|
47
|
+
activate: [] as ((event: ExtendableEvent) => void | Promise<void>)[],
|
|
48
|
+
fetch: [] as ((event: FetchEvent) => FetchResponse)[],
|
|
49
|
+
message: [] as ((event: MessageEvent) => void)[],
|
|
50
|
+
sync: [] as ((event: SyncEvent) => void)[],
|
|
51
|
+
push: [] as ((event: PushEvent) => void)[],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Здесь происходит сортировка плагинов по их приоритету.
|
|
55
|
+
// Сначала выбираются плагины, у которых явно указан priority (plugin.priority !== undefined),
|
|
56
|
+
// затем они сортируются по возрастанию этого значения (меньший priority — выше в списке).
|
|
57
|
+
// После отсортированных по приоритету плагинов добавляются все остальные плагины,
|
|
58
|
+
// у которых priority не указан (plugin.priority === undefined), сохраняя их исходный порядок.
|
|
59
|
+
const sortedPlugins = [
|
|
60
|
+
...plugins
|
|
61
|
+
.filter((plugin) => plugin.priority !== undefined)
|
|
62
|
+
.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0)),
|
|
63
|
+
...plugins.filter((plugin) => plugin.priority === undefined),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
sortedPlugins.forEach((plugin) => {
|
|
67
|
+
if (plugin.install) handlers.install.push(plugin.install);
|
|
68
|
+
if (plugin.activate) handlers.activate.push(plugin.activate);
|
|
69
|
+
if (plugin.fetch) handlers.fetch.push(plugin.fetch);
|
|
70
|
+
if (plugin.message) handlers.message.push(plugin.message);
|
|
71
|
+
if (plugin.sync) handlers.sync.push(plugin.sync);
|
|
72
|
+
if (plugin.push) handlers.push.push(plugin.push);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
install: (event: ExtendableEvent): void => {
|
|
77
|
+
event.waitUntil(
|
|
78
|
+
Promise.all(
|
|
79
|
+
handlers.install.map((handler) =>
|
|
80
|
+
Promise.resolve(handler(event)).catch(
|
|
81
|
+
(error: unknown) =>
|
|
82
|
+
config.onError?.(error as Error, event)
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
activate: (event: ExtendableEvent): void => {
|
|
90
|
+
event.waitUntil(
|
|
91
|
+
Promise.all(
|
|
92
|
+
handlers.activate.map((handler) =>
|
|
93
|
+
Promise.resolve(handler(event)).catch(
|
|
94
|
+
(error: unknown) =>
|
|
95
|
+
config.onError?.(error as Error, event)
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
fetch: (event: FetchEvent): void => {
|
|
103
|
+
event.respondWith(
|
|
104
|
+
(async (): Promise<Response> => {
|
|
105
|
+
for (const handler of handlers.fetch) {
|
|
106
|
+
try {
|
|
107
|
+
const result = await handler(event);
|
|
108
|
+
if (result) {
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
config.onError?.(error as Error, event);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return fetch(event.request);
|
|
116
|
+
})()
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
message: (event: MessageEvent): void => {
|
|
121
|
+
handlers.message.forEach((handler) => {
|
|
122
|
+
try {
|
|
123
|
+
handler(event);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
config.onError?.(error as Error, event);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
sync: (event: SyncEvent): void => {
|
|
131
|
+
handlers.sync.forEach((handler) => {
|
|
132
|
+
handler(event);
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
push: (event: PushEvent): void => {
|
|
137
|
+
handlers.push.forEach((handler) => {
|
|
138
|
+
handler(event);
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function initializeServiceWorker(
|
|
145
|
+
plugins: ServiceWorkerPlugin[],
|
|
146
|
+
config?: ServiceWorkerConfig
|
|
147
|
+
): void {
|
|
148
|
+
const handlers = createEventHandlers(plugins, config);
|
|
149
|
+
|
|
150
|
+
self.addEventListener('install', handlers.install);
|
|
151
|
+
self.addEventListener('activate', handlers.activate);
|
|
152
|
+
self.addEventListener('fetch', handlers.fetch);
|
|
153
|
+
self.addEventListener('message', handlers.message);
|
|
154
|
+
self.addEventListener('sync', handlers.sync);
|
|
155
|
+
self.addEventListener('push', handlers.push);
|
|
156
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "nodenext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
// For nodejs:
|
|
13
|
+
"lib": ["esnext"],
|
|
14
|
+
"types": ["node", "serviceworker", "web"],
|
|
15
|
+
// and npm install -D @types/node
|
|
16
|
+
|
|
17
|
+
// Other Outputs
|
|
18
|
+
"sourceMap": true,
|
|
19
|
+
"declaration": true,
|
|
20
|
+
"declarationMap": true,
|
|
21
|
+
|
|
22
|
+
// Stricter Typechecking Options
|
|
23
|
+
"noUncheckedIndexedAccess": true,
|
|
24
|
+
"exactOptionalPropertyTypes": true,
|
|
25
|
+
|
|
26
|
+
// Style Options
|
|
27
|
+
"noImplicitReturns": true,
|
|
28
|
+
"noImplicitOverride": true,
|
|
29
|
+
"noUnusedLocals": true,
|
|
30
|
+
"noUnusedParameters": true,
|
|
31
|
+
"noFallthroughCasesInSwitch": true,
|
|
32
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
33
|
+
|
|
34
|
+
// Recommended Options
|
|
35
|
+
"strict": true,
|
|
36
|
+
"jsx": "react-jsx",
|
|
37
|
+
"verbatimModuleSyntax": true,
|
|
38
|
+
"isolatedModules": true,
|
|
39
|
+
"noUncheckedSideEffectImports": true,
|
|
40
|
+
"moduleDetection": "force",
|
|
41
|
+
"skipLibCheck": true,
|
|
42
|
+
"removeComments": true
|
|
43
|
+
}
|
|
44
|
+
}
|