@webqit/webflo 0.11.61 → 0.20.2-next.0
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/.github/FUNDING.yml +12 -0
- package/.github/workflows/publish.yml +48 -0
- package/.gitignore +2 -0
- package/LICENSE +2 -2
- package/README.md +71 -2050
- package/package.json +28 -13
- package/site/-/_.md +139 -0
- package/site/-/docs.old.md +2010 -0
- package/site/.vitepress/cache/deps/@braintree_sanitize-url 2.js +93 -0
- package/site/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
- package/site/.vitepress/cache/deps/@braintree_sanitize-url.js 2.map +7 -0
- package/site/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
- package/site/.vitepress/cache/deps/_metadata 2.json +85 -0
- package/site/.vitepress/cache/deps/_metadata.json +85 -0
- package/site/.vitepress/cache/deps/chunk-BUSYA2B4 2.js +9 -0
- package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
- package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js 2.map +7 -0
- package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
- package/site/.vitepress/cache/deps/chunk-Q2AYPHVK 2.js +9719 -0
- package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js +9719 -0
- package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js 2.map +7 -0
- package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js.map +7 -0
- package/site/.vitepress/cache/deps/chunk-QAXAIFA7 2.js +12705 -0
- package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js +12705 -0
- package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js 2.map +7 -0
- package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js.map +7 -0
- package/site/.vitepress/cache/deps/cytoscape 2.js +30278 -0
- package/site/.vitepress/cache/deps/cytoscape-cose-bilkent 2.js +4710 -0
- package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
- package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js 2.map +7 -0
- package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
- package/site/.vitepress/cache/deps/cytoscape.js +30278 -0
- package/site/.vitepress/cache/deps/cytoscape.js 2.map +7 -0
- package/site/.vitepress/cache/deps/cytoscape.js.map +7 -0
- package/site/.vitepress/cache/deps/dayjs 2.js +285 -0
- package/site/.vitepress/cache/deps/dayjs.js +285 -0
- package/site/.vitepress/cache/deps/dayjs.js 2.map +7 -0
- package/site/.vitepress/cache/deps/dayjs.js.map +7 -0
- package/site/.vitepress/cache/deps/debug 2.js +453 -0
- package/site/.vitepress/cache/deps/debug.js +453 -0
- package/site/.vitepress/cache/deps/debug.js 2.map +7 -0
- package/site/.vitepress/cache/deps/debug.js.map +7 -0
- package/site/.vitepress/cache/deps/package 2.json +3 -0
- package/site/.vitepress/cache/deps/package.json +3 -0
- package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api 2.js +4507 -0
- package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
- package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js 2.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_core 2.js +584 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js 2.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap 2.js +1166 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1166 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js 2.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js 2.js +1667 -0
- package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
- package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js 2.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___minisearch 2.js +1815 -0
- package/site/.vitepress/cache/deps/vitepress___minisearch.js +1815 -0
- package/site/.vitepress/cache/deps/vitepress___minisearch.js 2.map +7 -0
- package/site/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/site/.vitepress/cache/deps/vue 2.js +344 -0
- package/site/.vitepress/cache/deps/vue.js +344 -0
- package/site/.vitepress/cache/deps/vue.js 2.map +7 -0
- package/site/.vitepress/cache/deps/vue.js.map +7 -0
- package/site/.vitepress/config.ts +147 -0
- package/site/.vitepress/theme/custom.css +50 -0
- package/site/.vitepress/theme/index.ts +6 -0
- package/site/api/webflo-fetch/FormData.md +0 -0
- package/site/api/webflo-fetch/Headers.md +0 -0
- package/site/api/webflo-fetch/LiveResponse.md +0 -0
- package/site/api/webflo-fetch/Request.md +0 -0
- package/site/api/webflo-fetch/Response.md +0 -0
- package/site/api/webflo-fetch/fetch.md +0 -0
- package/site/api/webflo-routing/HttpCookies.md +0 -0
- package/site/api/webflo-routing/HttpEvent/respondWith.md +1 -0
- package/site/api/webflo-routing/HttpEvent/waitUntil.md +1 -0
- package/site/api/webflo-routing/HttpEvent/waitUntilNavigate.md +1 -0
- package/site/api/webflo-routing/HttpEvent.md +30 -0
- package/site/api/webflo-routing/HttpSession.md +0 -0
- package/site/api/webflo-routing/HttpState.md +0 -0
- package/site/api/webflo-routing/HttpUser.md +0 -0
- package/site/api/webflo-routing/handler/fetch.md +42 -0
- package/site/api/webflo-routing/handler/next.md +54 -0
- package/site/api/webflo-routing/handler.md +119 -0
- package/site/api.md +26 -0
- package/site/contributing.md +16 -0
- package/site/docs/advanced/lifecycles.md +20 -0
- package/site/docs/advanced/redirects.md +0 -0
- package/site/docs/advanced/routing.md +1 -0
- package/site/docs/advanced.md +9 -0
- package/site/docs/concepts/realtime.md +637 -0
- package/site/docs/concepts/rendering.md +60 -0
- package/site/docs/concepts/request-response.md +47 -0
- package/site/docs/concepts/routing.md +656 -0
- package/site/docs/concepts/state.md +44 -0
- package/site/docs/concepts/templates.md +48 -0
- package/site/docs/concepts.md +97 -0
- package/site/docs/getting-started.md +378 -0
- package/site/docs/tech-stack.md +56 -0
- package/site/docs.md +100 -0
- package/site/examples/pwa.md +10 -0
- package/site/examples/web.md +11 -0
- package/site/examples.md +10 -0
- package/site/faq.md +13 -0
- package/site/guides/guide-auth.md +13 -0
- package/site/guides/guide-file-upload.md +11 -0
- package/site/guides/guide-service-worker.md +10 -0
- package/site/guides/tutorial-1-todo.md +24 -0
- package/site/guides.md +15 -0
- package/site/index.md +39 -0
- package/site/public/img/brand/logo-670x670.png +0 -0
- package/site/recipes/realtime.md +11 -0
- package/site/recipes/streaming.md +15 -0
- package/site/reference/cli.md +11 -0
- package/site/reference/config.md +13 -0
- package/site/reference/tools.md +9 -0
- package/src/Context.js +3 -11
- package/src/config-pi/deployment/Env.js +6 -19
- package/src/config-pi/deployment/Layout.js +11 -3
- package/src/config-pi/runtime/Client.js +40 -48
- package/src/config-pi/runtime/Server.js +52 -20
- package/src/config-pi/runtime/client/Worker.js +22 -20
- package/src/config-pi/static/Init.js +57 -0
- package/src/config-pi/static/index.js +2 -0
- package/src/deployment-pi/origins/index.js +1 -1
- package/src/deployment-pi/util.js +161 -0
- package/src/index.js +3 -9
- package/src/init-pi/index.js +117 -0
- package/src/init-pi/templates/pwa/app/handler.server.js +8 -0
- package/src/init-pi/templates/pwa/app/page.html +7 -0
- package/src/init-pi/templates/pwa/package.json +19 -0
- package/src/init-pi/templates/pwa/public/assets/app.css +16 -0
- package/src/init-pi/templates/pwa/public/index.html +39 -0
- package/src/init-pi/templates/pwa/public/manifest.json +29 -0
- package/src/init-pi/templates/web/app/handler.server.js +8 -0
- package/src/init-pi/templates/web/app/page.html +7 -0
- package/src/init-pi/templates/web/package.json +19 -0
- package/src/init-pi/templates/web/public/assets/app.css +16 -0
- package/src/init-pi/templates/web/public/index.html +39 -0
- package/src/runtime-pi/WebfloRuntime.js +350 -0
- package/src/runtime-pi/index.js +3 -10
- package/src/runtime-pi/webflo-client/ClientSideCookies.js +17 -0
- package/src/runtime-pi/webflo-client/ClientSideWorkport.js +63 -0
- package/src/runtime-pi/webflo-client/DeviceCapabilities.js +213 -0
- package/src/runtime-pi/webflo-client/WebfloClient.js +500 -0
- package/src/runtime-pi/webflo-client/WebfloRootClient1.js +206 -0
- package/src/runtime-pi/webflo-client/WebfloRootClient2.js +113 -0
- package/src/runtime-pi/webflo-client/WebfloSubClient.js +118 -0
- package/src/runtime-pi/webflo-client/index.js +17 -0
- package/src/runtime-pi/webflo-client/webflo-codegen.js +469 -0
- package/src/runtime-pi/webflo-client/webflo-devmode.js +243 -0
- package/src/runtime-pi/webflo-client/webflo-embedded.js +50 -0
- package/src/runtime-pi/webflo-fetch/LiveResponse.js +437 -0
- package/src/runtime-pi/webflo-fetch/cookies.js +10 -0
- package/src/runtime-pi/webflo-fetch/fetch.js +16 -0
- package/src/runtime-pi/webflo-fetch/formdata.js +54 -0
- package/src/runtime-pi/webflo-fetch/headers.js +151 -0
- package/src/runtime-pi/webflo-fetch/index.js +5 -0
- package/src/runtime-pi/webflo-fetch/message.js +49 -0
- package/src/runtime-pi/webflo-fetch/request.js +62 -0
- package/src/runtime-pi/webflo-fetch/response.js +110 -0
- package/src/runtime-pi/webflo-fetch/util.js +28 -0
- package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +10 -0
- package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +26 -0
- package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +87 -0
- package/src/runtime-pi/webflo-messaging/WQMessagePort.js +38 -0
- package/src/runtime-pi/webflo-messaging/WQRelayPort.js +47 -0
- package/src/runtime-pi/webflo-messaging/WQSockPort.js +113 -0
- package/src/runtime-pi/webflo-messaging/WQStarPort.js +104 -0
- package/src/runtime-pi/webflo-messaging/wq-message-port.js +404 -0
- package/src/runtime-pi/webflo-routing/HttpCookies.js +42 -0
- package/src/runtime-pi/webflo-routing/HttpEvent.js +112 -0
- package/src/runtime-pi/webflo-routing/HttpSession.js +11 -0
- package/src/runtime-pi/webflo-routing/HttpState.js +153 -0
- package/src/runtime-pi/webflo-routing/HttpUser.js +54 -0
- package/src/runtime-pi/webflo-routing/WebfloRouter.js +245 -0
- package/src/runtime-pi/webflo-server/ServerSideCookies.js +19 -0
- package/src/runtime-pi/webflo-server/ServerSideSession.js +38 -0
- package/src/runtime-pi/webflo-server/WebfloServer.js +937 -0
- package/src/runtime-pi/webflo-server/index.js +11 -0
- package/src/runtime-pi/webflo-server/messaging/Client.js +27 -0
- package/src/runtime-pi/webflo-server/messaging/ClientRequestRealtime.js +50 -0
- package/src/runtime-pi/webflo-server/messaging/Clients.js +25 -0
- package/src/runtime-pi/webflo-server/webflo-devmode.js +326 -0
- package/src/runtime-pi/{client → webflo-url}/Url.js +27 -76
- package/src/runtime-pi/webflo-url/index.js +1 -0
- package/src/runtime-pi/webflo-url/urlpattern.js +38 -0
- package/src/runtime-pi/{util-url.js → webflo-url/util.js} +5 -43
- package/src/runtime-pi/webflo-url/xURL.js +94 -0
- package/src/runtime-pi/webflo-worker/WebfloWorker.js +234 -0
- package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +19 -0
- package/src/runtime-pi/webflo-worker/WorkerSideWorkport.js +18 -0
- package/src/runtime-pi/webflo-worker/index.js +11 -0
- package/src/services-pi/index.js +2 -0
- package/src/services-pi/push/index.js +23 -0
- package/src/util.js +10 -0
- package/src/{webflo.js → webflo-cli.js} +4 -4
- package/src/runtime-pi/Application.js +0 -29
- package/src/runtime-pi/Cookies.js +0 -82
- package/src/runtime-pi/HttpEvent.js +0 -107
- package/src/runtime-pi/Router.js +0 -130
- package/src/runtime-pi/Runtime.js +0 -21
- package/src/runtime-pi/client/Application.js +0 -76
- package/src/runtime-pi/client/Context.js +0 -7
- package/src/runtime-pi/client/Router.js +0 -48
- package/src/runtime-pi/client/Runtime.js +0 -525
- package/src/runtime-pi/client/Workport.js +0 -190
- package/src/runtime-pi/client/createStorage.js +0 -58
- package/src/runtime-pi/client/generate.js +0 -481
- package/src/runtime-pi/client/index.js +0 -21
- package/src/runtime-pi/client/worker/Application.js +0 -44
- package/src/runtime-pi/client/worker/Context.js +0 -7
- package/src/runtime-pi/client/worker/Runtime.js +0 -275
- package/src/runtime-pi/client/worker/Workport.js +0 -78
- package/src/runtime-pi/client/worker/index.js +0 -21
- package/src/runtime-pi/server/Application.js +0 -101
- package/src/runtime-pi/server/Context.js +0 -16
- package/src/runtime-pi/server/Router.js +0 -159
- package/src/runtime-pi/server/Runtime.js +0 -558
- package/src/runtime-pi/server/index.js +0 -21
- package/src/runtime-pi/util-http.js +0 -86
- package/src/runtime-pi/xFormData.js +0 -24
- package/src/runtime-pi/xHeaders.js +0 -146
- package/src/runtime-pi/xRequest.js +0 -46
- package/src/runtime-pi/xRequestHeaders.js +0 -109
- package/src/runtime-pi/xResponse.js +0 -33
- package/src/runtime-pi/xResponseHeaders.js +0 -117
- package/src/runtime-pi/xURL.js +0 -105
- package/src/runtime-pi/xfetch.js +0 -23
- package/src/runtime-pi/xxHttpMessage.js +0 -102
- package/src/static-pi/index.js +0 -11
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { WQStarPort } from '../../webflo-messaging/WQStarPort.js';
|
|
2
|
+
import { ClientRequestRealtime } from './ClientRequestRealtime.js';
|
|
3
|
+
|
|
4
|
+
export class Client extends WQStarPort {
|
|
5
|
+
|
|
6
|
+
#clientID;
|
|
7
|
+
get clientID() { return this.#clientID; }
|
|
8
|
+
|
|
9
|
+
constructor(clientID) {
|
|
10
|
+
super();
|
|
11
|
+
this.#clientID = clientID;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getRequestRealtime(portID) {
|
|
15
|
+
return this.findPort((port) => port.portID === portID);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
createRequestRealtime(portID, url = null) {
|
|
19
|
+
const requestPort = new ClientRequestRealtime(portID, url);
|
|
20
|
+
const cleanup = this.addPort(requestPort);
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
if (requestPort.length || !this.findPort((port) => port === requestPort)) return;
|
|
23
|
+
cleanup();
|
|
24
|
+
}, 30000/*30sec*/);
|
|
25
|
+
return requestPort;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { WQStarPort } from '../../webflo-messaging/WQStarPort.js';
|
|
2
|
+
import { _wq } from '../../../util.js';
|
|
3
|
+
|
|
4
|
+
export class ClientRequestRealtime extends WQStarPort {
|
|
5
|
+
|
|
6
|
+
#portID;
|
|
7
|
+
get portID() { return this.#portID; }
|
|
8
|
+
|
|
9
|
+
#url;
|
|
10
|
+
get url() { return this.#url; }
|
|
11
|
+
|
|
12
|
+
#navigatedIn = false;
|
|
13
|
+
navigatedIn() { return this.#navigatedIn; }
|
|
14
|
+
|
|
15
|
+
#navigatedAway = false;
|
|
16
|
+
navigatedAway() { return this.#navigatedAway; }
|
|
17
|
+
|
|
18
|
+
constructor(portID, url) {
|
|
19
|
+
super();
|
|
20
|
+
this.#portID = portID;
|
|
21
|
+
this.#url = url;
|
|
22
|
+
const $url = new URL(url);
|
|
23
|
+
let lastNavigationEvent;
|
|
24
|
+
this.addEventListener('navigate', (e) => {
|
|
25
|
+
if (e.data.pathname === $url.pathname) {
|
|
26
|
+
this.#navigatedIn = true;
|
|
27
|
+
lastNavigationEvent = 'navigatein';
|
|
28
|
+
} else {
|
|
29
|
+
this.#navigatedAway = true;
|
|
30
|
+
lastNavigationEvent = 'navigateaway';
|
|
31
|
+
}
|
|
32
|
+
const event = new Event(lastNavigationEvent);
|
|
33
|
+
this.dispatchEvent(event);
|
|
34
|
+
if (event.defaultPrevented) {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
enterChannel(channelID, { resolveData = null } = {}) {
|
|
41
|
+
const client = _wq(this, 'meta').get('parentNode');
|
|
42
|
+
const clients = client && _wq(client, 'meta').get('parentNode');
|
|
43
|
+
if (!clients) {
|
|
44
|
+
throw new Error('Instance seem not connected to the messaging system.');
|
|
45
|
+
}
|
|
46
|
+
const channel = clients.getChannel(channelID, true);
|
|
47
|
+
const leave = channel.addPort(client, { resolveData });
|
|
48
|
+
return { channel, leave };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WQStarPort } from '../../webflo-messaging/WQStarPort.js';
|
|
2
|
+
import { WQRelayPort } from '../../webflo-messaging/WQRelayPort.js';
|
|
3
|
+
import { Client } from './Client.js';
|
|
4
|
+
|
|
5
|
+
export class Clients extends WQStarPort {
|
|
6
|
+
#channels = new Map;
|
|
7
|
+
|
|
8
|
+
getClient(clientID, autoCreate = false) {
|
|
9
|
+
if (autoCreate && !this.findPort((client) => client.clientID === clientID)) {
|
|
10
|
+
const client = new Client(clientID);
|
|
11
|
+
const cleanup = this.addPort(client);
|
|
12
|
+
client.wqLifecycle.close.then(cleanup);
|
|
13
|
+
}
|
|
14
|
+
return this.findPort((client) => client.clientID === clientID);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getChannel(channelName, autoCreate = false) {
|
|
18
|
+
if (!this.#channels.has(channelName) && autoCreate) {
|
|
19
|
+
const channel = new WQRelayPort(channelName);
|
|
20
|
+
this.#channels.set(channelName, channel);
|
|
21
|
+
channel.wqLifecycle.close.then(() => this.#channels.delete(channelName));
|
|
22
|
+
}
|
|
23
|
+
return this.#channels.get(channelName);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import Path from 'path';
|
|
2
|
+
import $glob from 'fast-glob';
|
|
3
|
+
import EsBuild from 'esbuild';
|
|
4
|
+
import chokidar from 'chokidar';
|
|
5
|
+
import { exec, spawn } from 'child_process';
|
|
6
|
+
import { platform } from 'os';
|
|
7
|
+
|
|
8
|
+
export class WebfloHMR {
|
|
9
|
+
|
|
10
|
+
static manage(app, options = {}) {
|
|
11
|
+
return new this(app, options);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#app;
|
|
15
|
+
|
|
16
|
+
#options;
|
|
17
|
+
get options() { return this.#options; }
|
|
18
|
+
|
|
19
|
+
#jsMeta = {
|
|
20
|
+
dependencyMap: null/*!IMPORTANT!*/,
|
|
21
|
+
mustRevalidate: true,
|
|
22
|
+
prevBuildResult: null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
#layoutDirsMatchMap;
|
|
26
|
+
#routeDirs;
|
|
27
|
+
#handlerMatch;
|
|
28
|
+
|
|
29
|
+
#clients = new Set;
|
|
30
|
+
get clients() { return this.#clients; }
|
|
31
|
+
|
|
32
|
+
#watcher;
|
|
33
|
+
get watcher() { return this.#watcher; }
|
|
34
|
+
|
|
35
|
+
#dirtiness = {
|
|
36
|
+
CSSAffected: false,
|
|
37
|
+
HTMLAffected: false,
|
|
38
|
+
clientRoutesAffected: new Set,
|
|
39
|
+
serviceWorkerAffected: false,
|
|
40
|
+
};
|
|
41
|
+
get dirtiness() { return this.#dirtiness; }
|
|
42
|
+
|
|
43
|
+
#ignoreList = new Set;
|
|
44
|
+
get ignoreList() { return this.#ignoreList; }
|
|
45
|
+
|
|
46
|
+
#eventQueue = [];
|
|
47
|
+
|
|
48
|
+
constructor(app, options = {}) {
|
|
49
|
+
this.#app = app;
|
|
50
|
+
this.#options = options;
|
|
51
|
+
// Filesytem matching
|
|
52
|
+
const _layoutDirPattern = (dirs) => `^(${[...dirs].map((d) => Path.relative(process.cwd(), d).replace(/^\.\//g, '').replace(/\./g, '\\.').replace(/\//g, '\\/')).join('|')})`;
|
|
53
|
+
this.#layoutDirsMatchMap = Object.fromEntries(['CLIENT_DIR', 'WORKER_DIR', 'SERVER_DIR', 'VIEWS_DIR', 'PUBLIC_DIR'].map((name) => {
|
|
54
|
+
return [name, new RegExp(_layoutDirPattern([app.config.LAYOUT[name]]))];
|
|
55
|
+
}));
|
|
56
|
+
this.#routeDirs = new Set([app.config.LAYOUT.CLIENT_DIR, app.config.LAYOUT.WORKER_DIR, app.config.LAYOUT.SERVER_DIR]);
|
|
57
|
+
this.#handlerMatch = new RegExp(`${_layoutDirPattern(this.#routeDirs)}(\\/.+)?\\/handler(?:\\.(client|worker|server))?\\.js$`);
|
|
58
|
+
// The watch and event ordering logic
|
|
59
|
+
const totalWatchDirs = new Set([...this.#routeDirs, app.config.LAYOUT.VIEWS_DIR, app.config.LAYOUT.PUBLIC_DIR]);
|
|
60
|
+
this.#watcher = chokidar.watch([...totalWatchDirs], { ignoreInitial: true });
|
|
61
|
+
let flushTimer;
|
|
62
|
+
const scheduleFlush = () => {
|
|
63
|
+
clearTimeout(flushTimer);
|
|
64
|
+
flushTimer = setTimeout(() => this.#flushEvents(), EVENT_FLUSH_DELAY);
|
|
65
|
+
};
|
|
66
|
+
const EVENT_FLUSH_DELAY = 200; // milliseconds
|
|
67
|
+
this.#watcher.on('all', async (type, $target) => {
|
|
68
|
+
if (!['unlink', 'add', 'change', 'addDir', 'unlinkDir'].includes(type)) return;
|
|
69
|
+
this.#eventQueue.push({ type, $target });
|
|
70
|
+
scheduleFlush();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async #flushEvents() {
|
|
75
|
+
// Sort by event priority and path depth (deepest files first for unlinks)
|
|
76
|
+
// The Dir events are for structuring purposes
|
|
77
|
+
const priority = {
|
|
78
|
+
unlink: 1,
|
|
79
|
+
unlinkDir: 2,
|
|
80
|
+
addDir: 3,
|
|
81
|
+
add: 4
|
|
82
|
+
};
|
|
83
|
+
const eventQueue = this.#eventQueue.splice(0).sort((a, b) => {
|
|
84
|
+
if (priority[a.type] !== priority[b.type]) {
|
|
85
|
+
return priority[a.type] - priority[b.type];
|
|
86
|
+
}
|
|
87
|
+
return b.$target.split('/').length - a.$target.split('/').length; // deeper first
|
|
88
|
+
});
|
|
89
|
+
const events = new Set;
|
|
90
|
+
let hasJustBeenRebuilt = false;
|
|
91
|
+
for (const { type, $target } of eventQueue) {
|
|
92
|
+
if (this.#ignoreList.has($target)) continue;
|
|
93
|
+
const target = Path.relative(process.cwd(), $target);
|
|
94
|
+
if (this.#layoutDirsMatchMap.PUBLIC_DIR.test(target)) { // Assets
|
|
95
|
+
if (target.endsWith('.css')) {
|
|
96
|
+
events.add({ type, target, fileType: 'css', kind: 'asset' });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (this.#layoutDirsMatchMap.VIEWS_DIR.test(target)) { // Views
|
|
100
|
+
if (/Dir$/.test(type)) { // (addDir | unlinkDir)
|
|
101
|
+
events.add({ type, target, fileType: null, kind: 'view' });
|
|
102
|
+
} else if (target.endsWith('.html')) {
|
|
103
|
+
events.add({ type, target, fileType: 'html', kind: 'view' });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (target.endsWith('.js')) {
|
|
107
|
+
if (/add|unlink/.test(type)) {
|
|
108
|
+
this.#jsMeta.mustRevalidate = true; // Invalidate graph
|
|
109
|
+
}
|
|
110
|
+
if ((!hasJustBeenRebuilt && type !== 'unlink') || !this.#jsMeta.dependencyMap) { // We need graph in place to process affected routes for an unlink event
|
|
111
|
+
await this.buildJS(this.#jsMeta.mustRevalidate/*fullBuild*/);
|
|
112
|
+
hasJustBeenRebuilt = true;
|
|
113
|
+
}
|
|
114
|
+
const affectedHandlers = this.#jsMeta.dependencyMap[target] || [];
|
|
115
|
+
for (const affectedHandler of affectedHandlers) {
|
|
116
|
+
const [, dir, affectedRoute = '/', realm] = this.#handlerMatch.exec(affectedHandler) || [];
|
|
117
|
+
for (const r of ['client', 'worker', 'server']) {
|
|
118
|
+
const scopeObj = {};
|
|
119
|
+
if (type === 'unlink' && realm === r && target === affectedHandler // Dedicated handlers directly removed. Calculate fallback!
|
|
120
|
+
&& (scopeObj.genericHandler = _toGeneric(target)) in this.#jsMeta.dependencyMap) {
|
|
121
|
+
// A fallback to generic handler happened
|
|
122
|
+
events.add({ type, target, fileType: 'js', affectedRoute, affectedHandler: scopeObj.genericHandler, realm: r, actionableEffect: 'change' });
|
|
123
|
+
} else if (realm === r || !realm/*generic handler*/ && (
|
|
124
|
+
this.#layoutDirsMatchMap[`${r.toUpperCase()}_DIR`].test(dir) && !(_toDedicated(target, r) in this.#jsMeta.dependencyMap)/*no dedicated handler exists*/
|
|
125
|
+
)) {
|
|
126
|
+
const actionableEffect = target === affectedHandler ? type : 'change';
|
|
127
|
+
events.add({ type, target, fileType: 'js', affectedRoute, affectedHandler, realm: r, actionableEffect });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (events.size) {
|
|
134
|
+
this.fire(events);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async fire(events) {
|
|
139
|
+
for (const event of events) {
|
|
140
|
+
if (event.realm === 'client') {
|
|
141
|
+
this.#dirtiness.clientRoutesAffected.add(event.affectedRoute);
|
|
142
|
+
} else if (event.realm === 'worker') {
|
|
143
|
+
this.#dirtiness.serviceWorkerAffected = true;
|
|
144
|
+
} else if (event.realm === 'server') {
|
|
145
|
+
if (/^unlink/.test(event.actionableEffect)) {
|
|
146
|
+
delete this.#app.routes[event.affectedRoute];
|
|
147
|
+
} else if (event.realm === 'server') {
|
|
148
|
+
this.#app.routes[event.affectedRoute] = `${Path.join(this.#app.config.DEV_DIR, event.affectedHandler)}?_webflohmrhash=${Date.now()}`;
|
|
149
|
+
}
|
|
150
|
+
} else if (event.fileType === 'css') {
|
|
151
|
+
this.#dirtiness.CSSAffected = true;
|
|
152
|
+
} else if (event.fileType === 'html' || !event.fileType) {
|
|
153
|
+
this.#dirtiness.HTMLAffected = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (this.#options.buildSensitivity === 1) {
|
|
157
|
+
await this.bundleAssetsIfPending();
|
|
158
|
+
}
|
|
159
|
+
// Broadcast to clients
|
|
160
|
+
const PUBLIC_DIR = Path.relative(process.cwd(), this.#app.config.LAYOUT.PUBLIC_DIR);
|
|
161
|
+
const $events = [...events].map((event) => {
|
|
162
|
+
const $event = { ...event };
|
|
163
|
+
$event.target = Path.relative(PUBLIC_DIR, event.target);
|
|
164
|
+
if (event.affectedHandler) {
|
|
165
|
+
$event.affectedHandler = Path.relative(PUBLIC_DIR, event.affectedHandler);
|
|
166
|
+
}
|
|
167
|
+
return $event;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
for (const client of this.#clients) {
|
|
171
|
+
client.send(JSON.stringify($events));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async buildJS(fullBuild = false) {
|
|
176
|
+
// 0. Generate graph
|
|
177
|
+
let buildResult;
|
|
178
|
+
try {
|
|
179
|
+
if (this.#jsMeta.prevBuildResult) {
|
|
180
|
+
if (fullBuild) await this.#jsMeta.prevBuildResult.rebuild.dispose();
|
|
181
|
+
else buildResult = await buildResult.rebuild();
|
|
182
|
+
}
|
|
183
|
+
if (!buildResult) {
|
|
184
|
+
const routeDirs = [...this.#routeDirs];
|
|
185
|
+
const entryPoints = await $glob(routeDirs.map((d) => `${d}/**/handler{,.client,.worker,.server}.js`), { absolute: true })
|
|
186
|
+
.then((files) => files.map((file) => file.replace(/\\/g, '/')));
|
|
187
|
+
const entryNames = routeDirs.length === 1 ? `${Path.relative(process.cwd(), routeDirs[0])}/[dir]/[name]` : `[dir]/[name]`;
|
|
188
|
+
const bundlingConfig = {
|
|
189
|
+
entryPoints,
|
|
190
|
+
outdir: this.#app.config.DEV_DIR,
|
|
191
|
+
entryNames,
|
|
192
|
+
bundle: true,
|
|
193
|
+
format: 'esm',
|
|
194
|
+
platform: 'browser', // optional but good for clarity
|
|
195
|
+
metafile: true, // This is key
|
|
196
|
+
treeShaking: true, // Optional optimization
|
|
197
|
+
logLevel: 'silent', // Suppress output
|
|
198
|
+
minify: false,
|
|
199
|
+
sourcemap: false,
|
|
200
|
+
incremental: true,
|
|
201
|
+
};
|
|
202
|
+
buildResult = await EsBuild.build(bundlingConfig);
|
|
203
|
+
}
|
|
204
|
+
} catch (e) { return false; }
|
|
205
|
+
|
|
206
|
+
// 1. Forward dependency graph (file -> [imported files])
|
|
207
|
+
const forward = {};
|
|
208
|
+
for (const [file, data] of Object.entries(buildResult.metafile.inputs)) {
|
|
209
|
+
forward[file] = data.imports?.map((imp) => Path.normalize(imp.path)) || [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 2. Reverse dependency graph (file -> [parents])
|
|
213
|
+
const reverse = {};
|
|
214
|
+
for (const [file, imports] of Object.entries(forward)) {
|
|
215
|
+
for (const dep of imports) {
|
|
216
|
+
if (!reverse[dep]) reverse[dep] = [];
|
|
217
|
+
reverse[dep].push(file);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 3. Trace from leaf file to roots (handler files)
|
|
222
|
+
const handlers = Object.keys(buildResult.metafile.inputs).filter((f) => this.#handlerMatch.test(f));
|
|
223
|
+
const handlerDepsMap = {};
|
|
224
|
+
|
|
225
|
+
for (const handler of handlers) {
|
|
226
|
+
const visited = new Set();
|
|
227
|
+
const stack = [handler];
|
|
228
|
+
while (stack.length) {
|
|
229
|
+
const current = stack.pop();
|
|
230
|
+
if (visited.has(current)) continue;
|
|
231
|
+
visited.add(current);
|
|
232
|
+
if (!handlerDepsMap[current]) handlerDepsMap[current] = [];
|
|
233
|
+
handlerDepsMap[current].push(handler);
|
|
234
|
+
const deps = forward[current] || [];
|
|
235
|
+
stack.push(...deps);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
this.#jsMeta.dependencyMap = handlerDepsMap;
|
|
239
|
+
this.#jsMeta.mustRevalidate = false;
|
|
240
|
+
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async bundleAssetsIfPending() {
|
|
245
|
+
const entries = {};
|
|
246
|
+
|
|
247
|
+
if (this.#dirtiness.clientRoutesAffected.size || this.#dirtiness.serviceWorkerAffected) {
|
|
248
|
+
entries.js = {};
|
|
249
|
+
entries.js.client = !!this.#dirtiness.clientRoutesAffected.size;
|
|
250
|
+
entries.js.worker = this.#dirtiness.serviceWorkerAffected;
|
|
251
|
+
// Clear state
|
|
252
|
+
this.#dirtiness.clientRoutesAffected.clear();
|
|
253
|
+
this.#dirtiness.serviceWorkerAffected = false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (this.#dirtiness.HTMLAffected) {
|
|
257
|
+
this.#dirtiness.HTMLAffected = false;
|
|
258
|
+
entries.html = {};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (this.#dirtiness.CSSAffected) {
|
|
262
|
+
this.#dirtiness.CSSAffected = false;
|
|
263
|
+
entries.css = {};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for (const e in entries) {
|
|
267
|
+
const buildKey = `build:${e}`;
|
|
268
|
+
let buildScript,
|
|
269
|
+
buildScriptName = this.#options.buildScripts?.[buildKey];
|
|
270
|
+
if (buildScriptName === true) {
|
|
271
|
+
buildScriptName = buildKey;
|
|
272
|
+
}
|
|
273
|
+
if (buildScriptName
|
|
274
|
+
&& (buildScript = this.#options.appMeta.scripts?.[buildScriptName])) {
|
|
275
|
+
await this.#spawnProcess(buildScript, entries[e]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async #spawnProcess(command, options = {}) {
|
|
281
|
+
const commandArr = [...new Set(
|
|
282
|
+
command.split(/\s+?/).concat(Object.keys(options)).filter((s) => !(s in options) || options[s])
|
|
283
|
+
)];
|
|
284
|
+
return await new Promise((resolve, reject) => {
|
|
285
|
+
const child = spawn(commandArr.shift(), commandArr, {
|
|
286
|
+
stdio: ['pipe', 'pipe', 'inherit', 'ipc'],
|
|
287
|
+
shell: true, // for Windows compatibility
|
|
288
|
+
});
|
|
289
|
+
child.on('message', (msg) => {
|
|
290
|
+
for (const file of msg?.outfiles || []) {
|
|
291
|
+
this.#ignoreList.add(file);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
child.on('exit', (code) => {
|
|
295
|
+
if (code === 0) resolve(child);
|
|
296
|
+
else reject(new Error(`Process exited with code ${code}`));
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const _toGeneric = (file) => {
|
|
303
|
+
return file.replace(/\.(client|worker|server)\.js$/, '.js');
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const _toDedicated = (file, suffix) => {
|
|
307
|
+
return file.replace(/\.js$/, `.${suffix}.js`);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export function openBrowser(url) {
|
|
311
|
+
const plat = platform();
|
|
312
|
+
let command;
|
|
313
|
+
if (plat === 'darwin') {
|
|
314
|
+
command = `open "${url}"`;
|
|
315
|
+
} else if (plat === 'win32') {
|
|
316
|
+
command = `start "" "${url}"`;
|
|
317
|
+
} else if (plat === 'linux') {
|
|
318
|
+
command = `xdg-open "${url}"`;
|
|
319
|
+
} else {
|
|
320
|
+
console.warn('🌐 Unable to auto-open browser on this platform.');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
exec(command, (err) => {
|
|
324
|
+
if (err) console.error('❌ Failed to open browser:', err);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
1
|
import { _with } from '@webqit/util/obj/index.js';
|
|
6
2
|
import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* ---------------------------
|
|
12
|
-
* The Url class
|
|
13
|
-
* ---------------------------
|
|
14
|
-
*/
|
|
3
|
+
import { DeepURLSearchParams } from './util.js';
|
|
4
|
+
import { Observer } from '@webqit/quantum-js';
|
|
15
5
|
|
|
16
|
-
export
|
|
6
|
+
export class Url {
|
|
17
7
|
|
|
18
|
-
/**
|
|
19
|
-
* Constructs a new Url instance.
|
|
20
|
-
*
|
|
21
|
-
* @param object input
|
|
22
|
-
* @param object pathMappingScheme
|
|
23
|
-
*
|
|
24
|
-
* @return void
|
|
25
|
-
*/
|
|
26
8
|
constructor(input) {
|
|
27
9
|
const Self = this.constructor;
|
|
28
10
|
// -----------------------
|
|
@@ -56,6 +38,9 @@ export default class Url {
|
|
|
56
38
|
// ----------
|
|
57
39
|
if (e.key === 'href' && e.related.length === 1) {
|
|
58
40
|
var urlObj = Self.parseUrl(e.value);
|
|
41
|
+
if (urlObj.pathname) {
|
|
42
|
+
urlObj.pathname = '/' + urlObj.pathname.split('/').filter(s => s.trim()).join('/');
|
|
43
|
+
}
|
|
59
44
|
delete urlObj.query;
|
|
60
45
|
delete urlObj.href;
|
|
61
46
|
onlyHrefChanged = true;
|
|
@@ -68,13 +53,27 @@ export default class Url {
|
|
|
68
53
|
urlObj.search = search;
|
|
69
54
|
}
|
|
70
55
|
}
|
|
71
|
-
if (e.key === 'search') {
|
|
56
|
+
if (e.key === 'search' && !e.related.includes('query')) {
|
|
72
57
|
// "search" was updated. So we update "query"
|
|
73
58
|
var query = Self.toQuery(urlObj.search || this.search); // Not e.value, as that might be a href value
|
|
74
59
|
if (!_strictEven(query, this.query)) {
|
|
75
60
|
urlObj.query = query;
|
|
76
61
|
}
|
|
77
62
|
}
|
|
63
|
+
if (e.key === 'pathname' && !e.related.includes('ancestorPathname')) {
|
|
64
|
+
// "pathname" was updated. So we update "ancestorPathname"
|
|
65
|
+
var ancestorPathname = (urlObj.pathname || this.pathname).replace(new RegExp('/[^/]+(?:/)?$'), '');
|
|
66
|
+
if (ancestorPathname !== this.ancestorPathname) {
|
|
67
|
+
urlObj.ancestorPathname = ancestorPathname;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (e.key === 'ancestorPathname' && !e.related.includes('pathname')) {
|
|
71
|
+
// "ancestorPathname" was updated. So we update "pathname"
|
|
72
|
+
var pathname = '/' + (urlObj.ancestorPathname || this.ancestorPathname).split('/').filter(s => s).concat((urlObj.pathname || this.pathname).split('/').filter(s => s).pop()).join('/');
|
|
73
|
+
if (pathname !== this.pathname) {
|
|
74
|
+
urlObj.pathname = pathname;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
78
77
|
}
|
|
79
78
|
if (!onlyHrefChanged) {
|
|
80
79
|
var fullOrigin = this.origin,
|
|
@@ -110,85 +109,37 @@ export default class Url {
|
|
|
110
109
|
Observer.set(this, _isString(input) ? Self.parseUrl(input) : Url.copy(input));
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
/**
|
|
114
|
-
* Converts the instance to string.
|
|
115
|
-
*
|
|
116
|
-
* @return string
|
|
117
|
-
*/
|
|
118
112
|
toString() {
|
|
119
113
|
return this.href;
|
|
120
114
|
}
|
|
121
115
|
|
|
122
|
-
/**
|
|
123
|
-
* Creates an instance from parsing an URL string
|
|
124
|
-
* or from a regular object.
|
|
125
|
-
*
|
|
126
|
-
* @param string|object href
|
|
127
|
-
*
|
|
128
|
-
* @return Url
|
|
129
|
-
*/
|
|
130
116
|
static from(href) {
|
|
131
117
|
return new this(_isObject(href) ? href : this.parseUrl(href));
|
|
132
118
|
}
|
|
133
119
|
|
|
134
|
-
/**
|
|
135
|
-
* Copies URL properties off
|
|
136
|
-
* the given object.
|
|
137
|
-
*
|
|
138
|
-
* @param object urlObj
|
|
139
|
-
*
|
|
140
|
-
* @return object
|
|
141
|
-
*/
|
|
142
120
|
static copy(urlObj) {
|
|
143
|
-
var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop]), {});
|
|
121
|
+
var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop] || ''), {});
|
|
144
122
|
if (!('query' in urlObj)) {
|
|
145
123
|
delete url.query;
|
|
146
124
|
}
|
|
147
125
|
return url;
|
|
148
126
|
}
|
|
149
127
|
|
|
150
|
-
/**
|
|
151
|
-
* Parses an URL and returns its properties
|
|
152
|
-
*
|
|
153
|
-
* @param string href
|
|
154
|
-
*
|
|
155
|
-
* @return object
|
|
156
|
-
*/
|
|
157
128
|
static parseUrl(href) {
|
|
158
|
-
var a =
|
|
159
|
-
a.href = href;
|
|
129
|
+
var a = new URL(href);
|
|
160
130
|
return this.copy(a);
|
|
161
131
|
}
|
|
162
132
|
|
|
163
|
-
/**
|
|
164
|
-
* Parses the input search string into a named map
|
|
165
|
-
*
|
|
166
|
-
* @param string search
|
|
167
|
-
*
|
|
168
|
-
* @return object
|
|
169
|
-
*/
|
|
170
133
|
static toQuery(search) {
|
|
171
|
-
return
|
|
134
|
+
return DeepURLSearchParams.eval({}, search);
|
|
172
135
|
}
|
|
173
136
|
|
|
174
|
-
/**
|
|
175
|
-
* Stringifies the input query to search string.
|
|
176
|
-
*
|
|
177
|
-
* @param object query
|
|
178
|
-
*
|
|
179
|
-
* @return string
|
|
180
|
-
*/
|
|
181
137
|
static toSearch(query) {
|
|
182
|
-
var search =
|
|
138
|
+
var search = DeepURLSearchParams.stringify(query);
|
|
183
139
|
return search ? '?' + search : '';
|
|
184
|
-
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
185
142
|
|
|
186
|
-
/**
|
|
187
|
-
* These are standard
|
|
188
|
-
* and shouldnt'/can't be modified
|
|
189
|
-
*
|
|
190
|
-
* @array
|
|
191
|
-
*/
|
|
192
143
|
const urlProperties = [
|
|
193
144
|
'protocol',
|
|
194
145
|
'username',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './urlpattern.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { _isNumeric } from '@webqit/util/js/index.js';
|
|
2
|
+
if (typeof URLPattern === 'undefined') {
|
|
3
|
+
await import('urlpattern-polyfill');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { exec: execMethod } = URLPattern.prototype;
|
|
7
|
+
const urlPatternMethods = {
|
|
8
|
+
isPattern: {
|
|
9
|
+
value: function () {
|
|
10
|
+
return Object.keys(this.keys || {}).some((compName) => this.keys[compName].length);
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
exec: {
|
|
14
|
+
value: function (...args) {
|
|
15
|
+
let components = execMethod.call(this, ...args);
|
|
16
|
+
if (!components) return;
|
|
17
|
+
components.vars = Object.keys(this.keys).reduce(({ named, unnamed }, compName) => {
|
|
18
|
+
this.keys[compName].forEach(key => {
|
|
19
|
+
let value = components[compName].groups[key.name];
|
|
20
|
+
if (typeof key.name === 'number') {
|
|
21
|
+
unnamed.push(value);
|
|
22
|
+
} else {
|
|
23
|
+
named[key.name] = value;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return { named, unnamed };
|
|
27
|
+
}, { named: {}, unnamed: [] });
|
|
28
|
+
components.render = (str) => {
|
|
29
|
+
return str.replace(/\$(\$|[0-9A-Z]+)/gi, (a, b) => {
|
|
30
|
+
return b === '$' ? '$' : (_isNumeric(b) ? components.vars.unnamed[b - 1] : components.vars.named[b]) || '';
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return components;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
Object.defineProperties(URLPattern.prototype, urlPatternMethods);
|