neo.mjs 3.2.9 → 4.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/README.md +3 -1
- package/apps/ServiceWorker.mjs +7 -6
- package/apps/website/data/blog.json +26 -0
- package/buildScripts/createApp.mjs +16 -0
- package/buildScripts/webpack/development/webpack.config.worker.mjs +3 -1
- package/buildScripts/webpack/production/webpack.config.worker.mjs +3 -1
- package/examples/ServiceWorker.mjs +7 -6
- package/package.json +9 -9
- package/src/main/addon/ServiceWorker.mjs +15 -17
- package/src/worker/ServiceBase.mjs +82 -31
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ No need to take care of a workers setup, and the cross channel communication on
|
|
|
19
19
|
<a href="https://youtu.be/aEA5333WiWY"><img height="316px" width="400px" src="https://raw.githubusercontent.com/neomjs/pages/master/resources/images/neo-movie.png"></a>
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
|
+
<a href="https://tobiasuhlig.medium.com/predictive-offline-support-for-assets-you-have-not-used-yet-aeeccccd3754?source=friends_link&sk=e946e0f25f508e6a8cec4136400291a3">Version 4 release announcement</a>
|
|
23
|
+
|
|
22
24
|
## Content
|
|
23
25
|
1. <a href="#slack-channel">Slack Channel for questions & feedback</a>
|
|
24
26
|
2. <a href="#architectures">Scalable frontend architectures</a>
|
|
@@ -54,7 +56,7 @@ neo.mjs offers two different setups which follow the exact same API.
|
|
|
54
56
|
You can switch between <a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker">dedicated</a> and
|
|
55
57
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker">shared</a> workers at any point.
|
|
56
58
|
|
|
57
|
-
<img src="https://raw.githubusercontent.com/neomjs/pages/master/resources/images/workers-setup-
|
|
59
|
+
<img src="https://raw.githubusercontent.com/neomjs/pages/master/resources/images/workers-setup-v4.png">
|
|
58
60
|
|
|
59
61
|
The dedicated workers setup uses 4 threads (CPUs).
|
|
60
62
|
Most parts of the frameworks as well as your apps and components live within the app worker.
|
package/apps/ServiceWorker.mjs
CHANGED
|
@@ -8,6 +8,12 @@ import ServiceBase from '../src/worker/ServiceBase.mjs';
|
|
|
8
8
|
* @singleton
|
|
9
9
|
*/
|
|
10
10
|
class ServiceWorker extends ServiceBase {
|
|
11
|
+
/**
|
|
12
|
+
* @member {String} workerId='service'
|
|
13
|
+
* @protected
|
|
14
|
+
*/
|
|
15
|
+
workerId = 'service'
|
|
16
|
+
|
|
11
17
|
static getConfig() {return {
|
|
12
18
|
/**
|
|
13
19
|
* @member {String} className='Neo.ServiceWorker'
|
|
@@ -18,12 +24,7 @@ class ServiceWorker extends ServiceBase {
|
|
|
18
24
|
* @member {Boolean} singleton=true
|
|
19
25
|
* @protected
|
|
20
26
|
*/
|
|
21
|
-
singleton: true
|
|
22
|
-
/**
|
|
23
|
-
* @member {String} workerId='service'
|
|
24
|
-
* @protected
|
|
25
|
-
*/
|
|
26
|
-
workerId: 'service'
|
|
27
|
+
singleton: true
|
|
27
28
|
}}
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"author" : "Tobias Uhlig",
|
|
4
|
+
"authorImage" : "author_TobiasUhlig.jpeg",
|
|
5
|
+
"date" : "Mar 28, 2022",
|
|
6
|
+
"id" : 51,
|
|
7
|
+
"image" : "predictive-offline-support-for-assets-you-have-not-used-yet.png",
|
|
8
|
+
"name" : "Predictive offline support for assets you have not used yet",
|
|
9
|
+
"provider" : "Medium",
|
|
10
|
+
"publisher" : "ITNEXT",
|
|
11
|
+
"selectedInto": [],
|
|
12
|
+
"type" : "Blog Post",
|
|
13
|
+
"url" : "https://itnext.io/predictive-offline-support-for-assets-you-have-not-used-yet-aeeccccd3754?source=friends_link&sk=e946e0f25f508e6a8cec4136400291a3"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"author" : "Tobias Uhlig",
|
|
17
|
+
"authorImage" : "author_TobiasUhlig.jpeg",
|
|
18
|
+
"date" : "Jan 31, 2022",
|
|
19
|
+
"id" : 50,
|
|
20
|
+
"image" : "clean-architectures-your-benefits-of-using-view-controllers.png",
|
|
21
|
+
"name" : "Clean architectures: Your benefits of using view controllers",
|
|
22
|
+
"provider" : "Medium",
|
|
23
|
+
"publisher" : "ITNEXT",
|
|
24
|
+
"selectedInto": [],
|
|
25
|
+
"type" : "Blog Post",
|
|
26
|
+
"url" : "https://itnext.io/clean-architectures-your-benefits-of-using-view-controllers-7ce6b00f0ad5?source=friends_link&sk=ebef34d36d5d307c7985ba4dedb261f9"
|
|
27
|
+
},
|
|
2
28
|
{
|
|
3
29
|
"author" : "Tobias Uhlig",
|
|
4
30
|
"authorImage" : "author_TobiasUhlig.jpeg",
|
|
@@ -32,6 +32,7 @@ program
|
|
|
32
32
|
.option('-i, --info', 'print environment debug info')
|
|
33
33
|
.option('-a, --appName <value>')
|
|
34
34
|
.option('-m, --mainThreadAddons <value>', `Comma separated list of:\n${addonChoices.join(', ')}\nDefaults to DragDrop, Stylesheet`)
|
|
35
|
+
.option('-s, --useServiceWorker <value>', '"yes", "no"')
|
|
35
36
|
.option('-t, --themes <value>', ['all', ...themeFolders, 'none'].join(", "))
|
|
36
37
|
.option('-u, --useSharedWorkers <value>', '"yes", "no"')
|
|
37
38
|
.allowUnknownOption()
|
|
@@ -105,11 +106,22 @@ if (programOpts.info) {
|
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
if (!programOpts.useServiceWorker) {
|
|
110
|
+
questions.push({
|
|
111
|
+
type : 'list',
|
|
112
|
+
name : 'useServiceWorker',
|
|
113
|
+
message: 'Do you want to use a ServiceWorker for caching assets?',
|
|
114
|
+
choices: ['yes', 'no'],
|
|
115
|
+
default: 'no'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
inquirer.prompt(questions).then(answers => {
|
|
109
120
|
let appName = programOpts.appName || answers.appName,
|
|
110
121
|
mainThreadAddons = programOpts.mainThreadAddons || answers.mainThreadAddons,
|
|
111
122
|
themes = programOpts.themes || answers.themes,
|
|
112
123
|
useSharedWorkers = programOpts.useSharedWorkers || answers.useSharedWorkers,
|
|
124
|
+
useServiceWorker = programOpts.useServiceWorker || answers.useServiceWorker,
|
|
113
125
|
lAppName = appName.toLowerCase(),
|
|
114
126
|
appPath = 'apps/' + lAppName + '/',
|
|
115
127
|
dir = 'apps/' + lAppName,
|
|
@@ -181,6 +193,10 @@ if (programOpts.info) {
|
|
|
181
193
|
neoConfig.useSharedWorkers = true;
|
|
182
194
|
}
|
|
183
195
|
|
|
196
|
+
if (useServiceWorker !== 'no') {
|
|
197
|
+
neoConfig.useServiceWorker = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
184
200
|
if (!insideNeo) {
|
|
185
201
|
neoConfig.workerBasePath = '../../node_modules/neo.mjs/src/worker/';
|
|
186
202
|
}
|
|
@@ -15,8 +15,10 @@ export default env => {
|
|
|
15
15
|
|
|
16
16
|
if (filenameConfig.workers) {
|
|
17
17
|
Object.entries(filenameConfig.workers).forEach(([key, value]) => {
|
|
18
|
+
// Inside a neo workspace, we want to use the SW inside the top level apps folder,
|
|
19
|
+
// to allow overriding its logic
|
|
18
20
|
if (key === env.worker) {
|
|
19
|
-
entry[key] = path.resolve(neoPath, value.input);
|
|
21
|
+
entry[key] = path.resolve(key === 'service' && !insideNeo ? cwd : neoPath, value.input);
|
|
20
22
|
}
|
|
21
23
|
});
|
|
22
24
|
}
|
|
@@ -15,8 +15,10 @@ export default env => {
|
|
|
15
15
|
|
|
16
16
|
if (filenameConfig.workers) {
|
|
17
17
|
Object.entries(filenameConfig.workers).forEach(([key, value]) => {
|
|
18
|
+
// Inside a neo workspace, we want to use the SW inside the top level apps folder,
|
|
19
|
+
// to allow overriding its logic
|
|
18
20
|
if (key === env.worker) {
|
|
19
|
-
entry[key] = path.resolve(neoPath, value.input);
|
|
21
|
+
entry[key] = path.resolve(key === 'service' && !insideNeo ? cwd : neoPath, value.input);
|
|
20
22
|
}
|
|
21
23
|
});
|
|
22
24
|
}
|
|
@@ -8,6 +8,12 @@ import ServiceBase from '../src/worker/ServiceBase.mjs';
|
|
|
8
8
|
* @singleton
|
|
9
9
|
*/
|
|
10
10
|
class ServiceWorker extends ServiceBase {
|
|
11
|
+
/**
|
|
12
|
+
* @member {String} workerId='service'
|
|
13
|
+
* @protected
|
|
14
|
+
*/
|
|
15
|
+
workerId = 'service'
|
|
16
|
+
|
|
11
17
|
static getConfig() {return {
|
|
12
18
|
/**
|
|
13
19
|
* @member {String} className='Neo.ServiceWorker'
|
|
@@ -18,12 +24,7 @@ class ServiceWorker extends ServiceBase {
|
|
|
18
24
|
* @member {Boolean} singleton=true
|
|
19
25
|
* @protected
|
|
20
26
|
*/
|
|
21
|
-
singleton: true
|
|
22
|
-
/**
|
|
23
|
-
* @member {String} workerId='service'
|
|
24
|
-
* @protected
|
|
25
|
-
*/
|
|
26
|
-
workerId: 'service'
|
|
27
|
+
singleton: true
|
|
27
28
|
}}
|
|
28
29
|
}
|
|
29
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neo.mjs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "The webworkers driven UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -34,22 +34,22 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://neomjs.github.io/pages/",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@fortawesome/fontawesome-free": "^6.
|
|
37
|
+
"@fortawesome/fontawesome-free": "^6.1.1",
|
|
38
38
|
"@material/mwc-button": "^0.25.3",
|
|
39
39
|
"@material/mwc-textfield": "^0.25.3",
|
|
40
|
-
"autoprefixer": "^10.4.
|
|
41
|
-
"chalk": "^5.0.
|
|
40
|
+
"autoprefixer": "^10.4.4",
|
|
41
|
+
"chalk": "^5.0.1",
|
|
42
42
|
"clean-webpack-plugin": "^4.0.0",
|
|
43
|
-
"commander": "^9.
|
|
44
|
-
"cssnano": "^5.1.
|
|
43
|
+
"commander": "^9.1.0",
|
|
44
|
+
"cssnano": "^5.1.5",
|
|
45
45
|
"envinfo": "^7.8.1",
|
|
46
46
|
"fs-extra": "^10.0.1",
|
|
47
47
|
"highlightjs-line-numbers.js": "^2.8.0",
|
|
48
|
-
"inquirer": "^8.
|
|
48
|
+
"inquirer": "^8.2.2",
|
|
49
49
|
"neo-jsdoc": "^1.0.1",
|
|
50
50
|
"neo-jsdoc-x": "^1.0.4",
|
|
51
|
-
"postcss": "^8.4.
|
|
52
|
-
"sass": "^1.49.
|
|
51
|
+
"postcss": "^8.4.12",
|
|
52
|
+
"sass": "^1.49.10",
|
|
53
53
|
"webpack": "^5.70.0",
|
|
54
54
|
"webpack-cli": "^4.9.2",
|
|
55
55
|
"webpack-dev-server": "4.7.4",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import Base
|
|
2
|
-
import
|
|
3
|
-
import WorkerManager from '../../worker/Manager.mjs';
|
|
1
|
+
import Base from '../../core/Base.mjs';
|
|
2
|
+
import WorkerManager from '../../worker/Manager.mjs';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Creates a ServiceWorker instance, in case Neo.config.useServiceWorker is set to true
|
|
@@ -15,15 +14,6 @@ class ServiceWorker extends Base {
|
|
|
15
14
|
* @protected
|
|
16
15
|
*/
|
|
17
16
|
className: 'Neo.main.addon.ServiceWorker',
|
|
18
|
-
/**
|
|
19
|
-
* @member {String[]|Neo.core.Base[]|null} mixins=[RemoteMethodAccess]
|
|
20
|
-
*/
|
|
21
|
-
mixins: [RemoteMethodAccess],
|
|
22
|
-
/**
|
|
23
|
-
* @member {ServiceWorkerRegistration|null} registration=null
|
|
24
|
-
* @protected
|
|
25
|
-
*/
|
|
26
|
-
registration: null,
|
|
27
17
|
/**
|
|
28
18
|
* @member {Boolean} singleton=true
|
|
29
19
|
* @protected
|
|
@@ -45,22 +35,30 @@ class ServiceWorker extends Base {
|
|
|
45
35
|
path = (devMode ? config.basePath : config.workerBasePath) + (devMode ? folder : '') + fileName,
|
|
46
36
|
serviceWorker = navigator.serviceWorker;
|
|
47
37
|
|
|
38
|
+
window.addEventListener('beforeunload', me.onBeforeUnload.bind(me));
|
|
39
|
+
|
|
48
40
|
serviceWorker.register(path, opts)
|
|
49
41
|
.then(registration => {
|
|
50
|
-
me.registration = registration;
|
|
51
|
-
|
|
52
42
|
serviceWorker.ready.then(() => {
|
|
53
43
|
serviceWorker.onmessage = WorkerManager.onWorkerMessage.bind(WorkerManager);
|
|
54
44
|
|
|
55
45
|
WorkerManager.sendMessage('service', {
|
|
56
|
-
action
|
|
57
|
-
|
|
58
|
-
data : config
|
|
46
|
+
action: 'registerNeoConfig',
|
|
47
|
+
data : config
|
|
59
48
|
});
|
|
60
49
|
});
|
|
61
50
|
})
|
|
62
51
|
}
|
|
63
52
|
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
onBeforeUnload() {
|
|
58
|
+
WorkerManager.sendMessage('service', {
|
|
59
|
+
action: 'unregisterPort'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
Neo.applyClassConfig(ServiceWorker);
|
|
@@ -8,6 +8,19 @@ import RemoteMethodAccess from './mixin/RemoteMethodAccess.mjs';
|
|
|
8
8
|
* @abstract
|
|
9
9
|
*/
|
|
10
10
|
class ServiceBase extends Base {
|
|
11
|
+
/**
|
|
12
|
+
* @member {String} cacheName='neo-runtime'
|
|
13
|
+
*/
|
|
14
|
+
cacheName = 'neo-runtime'
|
|
15
|
+
/**
|
|
16
|
+
* @member {String[]} cachePaths
|
|
17
|
+
*/
|
|
18
|
+
cachePaths = [
|
|
19
|
+
'raw.githubusercontent.com/',
|
|
20
|
+
'/dist/production/',
|
|
21
|
+
'/fontawesome',
|
|
22
|
+
'/resources/'
|
|
23
|
+
]
|
|
11
24
|
/**
|
|
12
25
|
* @member {Object[]|null} channelPorts=null
|
|
13
26
|
* @protected
|
|
@@ -28,6 +41,11 @@ class ServiceBase extends Base {
|
|
|
28
41
|
* @protected
|
|
29
42
|
*/
|
|
30
43
|
remotes = []
|
|
44
|
+
/**
|
|
45
|
+
* @member {String|null} workerId=null
|
|
46
|
+
* @protected
|
|
47
|
+
*/
|
|
48
|
+
workerId = null
|
|
31
49
|
|
|
32
50
|
static getConfig() {return {
|
|
33
51
|
/**
|
|
@@ -35,19 +53,6 @@ class ServiceBase extends Base {
|
|
|
35
53
|
* @protected
|
|
36
54
|
*/
|
|
37
55
|
className: 'Neo.worker.ServiceBase',
|
|
38
|
-
/**
|
|
39
|
-
* @member {String} cacheName='neo-runtime'
|
|
40
|
-
*/
|
|
41
|
-
cacheName: 'neo-runtime',
|
|
42
|
-
/**
|
|
43
|
-
* @member {String[]|null} cachePaths
|
|
44
|
-
*/
|
|
45
|
-
cachePaths: [
|
|
46
|
-
'raw.githubusercontent.com/',
|
|
47
|
-
'/dist/production/',
|
|
48
|
-
'/fontawesome',
|
|
49
|
-
'/resources/'
|
|
50
|
-
],
|
|
51
56
|
/**
|
|
52
57
|
* @member {String[]|Neo.core.Base[]|null} mixins=[RemoteMethodAccess]
|
|
53
58
|
*/
|
|
@@ -61,14 +66,10 @@ class ServiceBase extends Base {
|
|
|
61
66
|
app: [
|
|
62
67
|
'clearCache',
|
|
63
68
|
'clearCaches',
|
|
64
|
-
'preloadAssets'
|
|
69
|
+
'preloadAssets',
|
|
70
|
+
'removeAssets'
|
|
65
71
|
]
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* @member {String|null} workerId=null
|
|
69
|
-
* @protected
|
|
70
|
-
*/
|
|
71
|
-
workerId: null
|
|
72
|
+
}
|
|
72
73
|
}}
|
|
73
74
|
|
|
74
75
|
/**
|
|
@@ -95,19 +96,20 @@ class ServiceBase extends Base {
|
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* @param {String} name=this.cacheName
|
|
99
|
+
* @returns {Object}
|
|
98
100
|
*/
|
|
99
|
-
clearCache(name=this.cacheName) {
|
|
100
|
-
caches.
|
|
101
|
-
|
|
102
|
-
.then(cachesToDelete => Promise.all(cachesToDelete.map(cacheToDelete => caches.delete(cacheToDelete))))
|
|
101
|
+
async clearCache(name=this.cacheName) {
|
|
102
|
+
await caches.delete(name);
|
|
103
|
+
return {success: true}
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/**
|
|
106
|
-
*
|
|
107
|
+
* @returns {Object}
|
|
107
108
|
*/
|
|
108
|
-
clearCaches() {
|
|
109
|
-
caches.keys()
|
|
110
|
-
|
|
109
|
+
async clearCaches() {
|
|
110
|
+
let keys = await caches.keys();
|
|
111
|
+
await Promise.all(keys.map(name => caches.delete(name)));
|
|
112
|
+
return {success: true}
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
/**
|
|
@@ -255,6 +257,19 @@ class ServiceBase extends Base {
|
|
|
255
257
|
this.onConnect(event.source);
|
|
256
258
|
}
|
|
257
259
|
|
|
260
|
+
/**
|
|
261
|
+
* @param {Object} msg
|
|
262
|
+
* @param {ExtendableMessageEvent} event
|
|
263
|
+
*/
|
|
264
|
+
onUnregisterPort(msg, event) {
|
|
265
|
+
for (let [index, value] of this.channelPorts.entries()) {
|
|
266
|
+
if (value.clientId === event.source.id) {
|
|
267
|
+
this.channelPorts.splice(index, 1);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
258
273
|
/**
|
|
259
274
|
* @param {Object} data
|
|
260
275
|
* @param {String} [data.cacheName=this.cacheName]
|
|
@@ -280,9 +295,7 @@ class ServiceBase extends Base {
|
|
|
280
295
|
hasMatch = !!asset;
|
|
281
296
|
}
|
|
282
297
|
|
|
283
|
-
|
|
284
|
-
items.push(item);
|
|
285
|
-
}
|
|
298
|
+
!hasMatch && items.push(item);
|
|
286
299
|
}
|
|
287
300
|
|
|
288
301
|
if (items.length > 0) {
|
|
@@ -311,6 +324,44 @@ class ServiceBase extends Base {
|
|
|
311
324
|
});
|
|
312
325
|
}
|
|
313
326
|
|
|
327
|
+
/**
|
|
328
|
+
* You can either pass an url, an array of urls or an object with additional options
|
|
329
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete
|
|
330
|
+
* @param {String|String[]|Object} data
|
|
331
|
+
* @param {String|String[]} data.assets
|
|
332
|
+
* @param {String} data.cacheName=this.cacheName
|
|
333
|
+
* @param {Object} data.options
|
|
334
|
+
* @param {Boolean} data.options.ignoreMethod=false
|
|
335
|
+
* @param {Boolean} data.options.ignoreSearch=false
|
|
336
|
+
* @param {Boolean} data.options.ignoreVary=false
|
|
337
|
+
* @returns {Object}
|
|
338
|
+
*/
|
|
339
|
+
async removeAssets(data) {
|
|
340
|
+
if (!Neo.isObject(data)) {
|
|
341
|
+
data = {
|
|
342
|
+
assets: data
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let assets = data.assets,
|
|
347
|
+
cacheName = data.cacheName || this.cacheName,
|
|
348
|
+
options = data.options || {},
|
|
349
|
+
cache = await caches.open(cacheName),
|
|
350
|
+
promises = [];
|
|
351
|
+
|
|
352
|
+
if (!Array.isArray(assets)) {
|
|
353
|
+
assets = [assets];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
assets.forEach(asset => {
|
|
357
|
+
promises.push(cache.delete(asset, options));
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
await Promise.all(promises);
|
|
361
|
+
|
|
362
|
+
return {success: true};
|
|
363
|
+
}
|
|
364
|
+
|
|
314
365
|
/**
|
|
315
366
|
* @param {String} dest app, data, main or vdom (excluding the current worker)
|
|
316
367
|
* @param {Object} opts configs for Neo.worker.Message
|