Skip to content

Commit 41a4963

Browse files
authored
perf: Add config option enableResourceCache to cache dashboard resources locally for faster loading in additional browser tabs (#2920)
1 parent 13442c8 commit 41a4963

File tree

6 files changed

+149
-1
lines changed

6 files changed

+149
-1
lines changed

Parse-Dashboard/app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ module.exports = function(config, options) {
251251
<base href="${mountPath}"/>
252252
<script>
253253
PARSE_DASHBOARD_PATH = "${mountPath}";
254+
PARSE_DASHBOARD_ENABLE_RESOURCE_CACHE = ${config.enableResourceCache ? 'true' : 'false'};
254255
</script>
255256
<title>Parse Dashboard</title>
256257
</head>

Parse-Dashboard/parse-dashboard-config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
"secondaryBackgroundColor": ""
1111
}
1212
],
13-
"iconsFolder": "icons"
13+
"iconsFolder": "icons",
14+
"enableBrowserServiceWorker": false
1415
}

Parse-Dashboard/public/sw.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const CACHE_NAME = 'dashboard-cache-v1';
2+
3+
self.addEventListener('install', () => {
4+
self.skipWaiting();
5+
});
6+
7+
self.addEventListener('activate', event => {
8+
event.waitUntil(
9+
Promise.all([
10+
self.clients.claim(),
11+
caches.keys().then(cacheNames => {
12+
return Promise.all(
13+
cacheNames.map(cacheName => {
14+
if (cacheName !== CACHE_NAME) {
15+
return caches.delete(cacheName);
16+
}
17+
})
18+
);
19+
})
20+
])
21+
);
22+
});
23+
24+
self.addEventListener('fetch', event => {
25+
const req = event.request;
26+
if (req.destination === 'script' || req.destination === 'style' || req.url.includes('/bundles/')) {
27+
event.respondWith(
28+
caches.match(req).then(cached => {
29+
return (
30+
cached ||
31+
fetch(req).then(resp => {
32+
const resClone = resp.clone();
33+
caches.open(CACHE_NAME).then(cache => cache.put(req, resClone));
34+
return resp;
35+
})
36+
);
37+
})
38+
);
39+
}
40+
});
41+
42+
self.addEventListener('message', event => {
43+
if (event.data === 'unregister') {
44+
self.registration.unregister();
45+
}
46+
});

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
4343
- [Custom order in the filter popup](#custom-order-in-the-filter-popup)
4444
- [Persistent Filters](#persistent-filters)
4545
- [Scripts](#scripts)
46+
- [Resource Cache](#resource-cache)
4647
- [Running as Express Middleware](#running-as-express-middleware)
4748
- [Deploying Parse Dashboard](#deploying-parse-dashboard)
4849
- [Preparing for Deployment](#preparing-for-deployment)
@@ -510,6 +511,37 @@ Parse.Cloud.define('deleteAccount', async (req) => {
510511

511512
</details>
512513

514+
### Resource Cache
515+
516+
Parse Dashboard can cache its resources such as bundles in the browser, so that opening the dashboard in another tab does not reload the dashboard resources from the server but from the local browser cache. Caching only starts after login in the dashboard.
517+
518+
| Parameter | Type | Optional | Default | Example | Description |
519+
|-----------------------|---------|----------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|
520+
| `enableResourceCache` | Boolean | yes | `false` | `true` | Enables caching of dashboard resources in the browser for faster dashboard loading in additional browser tabs. |
521+
522+
523+
Example configuration:
524+
525+
```javascript
526+
const dashboard = new ParseDashboard({
527+
enableResourceCache: true,
528+
apps: [
529+
{
530+
serverURL: 'http://localhost:1337/parse',
531+
appId: 'myAppId',
532+
masterKey: 'myMasterKey',
533+
appName: 'MyApp'
534+
}
535+
]
536+
});
537+
```
538+
539+
> [!Warning]
540+
> This feature can make it more difficult to push dashboard updates to users. Enabling the resource cache will start a browser service worker that caches dashboard resources locally only once. As long as the service worker is running, it will prevent loading any dashboard updates from the server, even if the user reloads the browser tab. The service worker is automatically stopped, once the last dashboard browser tab is closed. On the opening of the first dashboard browser tab, a new service worker is started and the dashboard resources are loaded from the server.
541+
542+
> [!Note]
543+
> For developers: during dashboard development, the resource cache should be disabled to ensure reloading the dashboard tab in the browser loads the new dashboard bundle with any changes you made in the source code. You can inspect the service worker in the developer tools of most browsers. For example in Google Chrome, go to *Developer Tools > Application tab > Service workers* to see whether the dashboard service worker is currently running and to debug it.
544+
513545
# Running as Express Middleware
514546

515547
Instead of starting Parse Dashboard with the CLI, you can also run it as an [express](https://github.com/expressjs/express) middleware.

src/dashboard/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import installDevTools from 'immutable-devtools';
1212
import React from 'react';
1313
import ReactDOM from 'react-dom';
1414
import Dashboard from './Dashboard';
15+
import registerServiceWorker from '../registerServiceWorker';
1516

1617
require('stylesheets/fonts.scss');
1718
require('graphiql/graphiql.min.css');
1819
installDevTools(Immutable);
1920

2021
const path = window.PARSE_DASHBOARD_PATH || '/';
2122
ReactDOM.render(<Dashboard path={path} />, document.getElementById('browser_mount'));
23+
registerServiceWorker();

src/registerServiceWorker.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
function registerServiceWorker() {
2+
3+
if (!window.PARSE_DASHBOARD_ENABLE_RESOURCE_CACHE) {
4+
return;
5+
}
6+
7+
if (!('serviceWorker' in navigator)) {
8+
return;
9+
}
10+
11+
const mountPath = (window.PARSE_DASHBOARD_PATH || '/').replace(/\/?$/, '/');
12+
const swPath = `${mountPath}sw.js`;
13+
const countKey = `pd-sw-tabs:${mountPath}`;
14+
15+
/**
16+
* Registers the service worker at the specified path.
17+
*/
18+
const register = () => {
19+
navigator.serviceWorker.register(swPath).catch(() => {});
20+
};
21+
22+
/**
23+
* Increments the count of open dashboard tabs in localStorage.
24+
*/
25+
const increment = () => {
26+
let current = parseInt(localStorage.getItem(countKey) || '0', 10);
27+
if (!navigator.serviceWorker.controller && current > 0) {
28+
current = 0;
29+
}
30+
localStorage.setItem(countKey, String(current + 1));
31+
};
32+
33+
/**
34+
* Decrements the count of open dashboard tabs in localStorage.
35+
*/
36+
const decrement = () => {
37+
const current = parseInt(localStorage.getItem(countKey) || '0', 10);
38+
const next = Math.max(0, current - 1);
39+
localStorage.setItem(countKey, String(next));
40+
if (next === 0) {
41+
if (navigator.serviceWorker.controller) {
42+
navigator.serviceWorker.controller.postMessage('unregister');
43+
}
44+
navigator.serviceWorker.getRegistration(swPath).then(reg => {
45+
if (reg) {
46+
reg.unregister();
47+
}
48+
});
49+
caches.keys().then(keys => keys.forEach(k => caches.delete(k)));
50+
}
51+
};
52+
53+
increment();
54+
55+
window.addEventListener('load', () => {
56+
register();
57+
});
58+
window.addEventListener('beforeunload', () => {
59+
decrement();
60+
});
61+
window.addEventListener('pagehide', () => {
62+
decrement();
63+
});
64+
}
65+
66+
export default registerServiceWorker;

0 commit comments

Comments
 (0)