@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.
Files changed (235) hide show
  1. package/.github/FUNDING.yml +12 -0
  2. package/.github/workflows/publish.yml +48 -0
  3. package/.gitignore +2 -0
  4. package/LICENSE +2 -2
  5. package/README.md +71 -2050
  6. package/package.json +28 -13
  7. package/site/-/_.md +139 -0
  8. package/site/-/docs.old.md +2010 -0
  9. package/site/.vitepress/cache/deps/@braintree_sanitize-url 2.js +93 -0
  10. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
  11. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js 2.map +7 -0
  12. package/site/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
  13. package/site/.vitepress/cache/deps/_metadata 2.json +85 -0
  14. package/site/.vitepress/cache/deps/_metadata.json +85 -0
  15. package/site/.vitepress/cache/deps/chunk-BUSYA2B4 2.js +9 -0
  16. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
  17. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js 2.map +7 -0
  18. package/site/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
  19. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK 2.js +9719 -0
  20. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js +9719 -0
  21. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js 2.map +7 -0
  22. package/site/.vitepress/cache/deps/chunk-Q2AYPHVK.js.map +7 -0
  23. package/site/.vitepress/cache/deps/chunk-QAXAIFA7 2.js +12705 -0
  24. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js +12705 -0
  25. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js 2.map +7 -0
  26. package/site/.vitepress/cache/deps/chunk-QAXAIFA7.js.map +7 -0
  27. package/site/.vitepress/cache/deps/cytoscape 2.js +30278 -0
  28. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent 2.js +4710 -0
  29. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
  30. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js 2.map +7 -0
  31. package/site/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
  32. package/site/.vitepress/cache/deps/cytoscape.js +30278 -0
  33. package/site/.vitepress/cache/deps/cytoscape.js 2.map +7 -0
  34. package/site/.vitepress/cache/deps/cytoscape.js.map +7 -0
  35. package/site/.vitepress/cache/deps/dayjs 2.js +285 -0
  36. package/site/.vitepress/cache/deps/dayjs.js +285 -0
  37. package/site/.vitepress/cache/deps/dayjs.js 2.map +7 -0
  38. package/site/.vitepress/cache/deps/dayjs.js.map +7 -0
  39. package/site/.vitepress/cache/deps/debug 2.js +453 -0
  40. package/site/.vitepress/cache/deps/debug.js +453 -0
  41. package/site/.vitepress/cache/deps/debug.js 2.map +7 -0
  42. package/site/.vitepress/cache/deps/debug.js.map +7 -0
  43. package/site/.vitepress/cache/deps/package 2.json +3 -0
  44. package/site/.vitepress/cache/deps/package.json +3 -0
  45. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api 2.js +4507 -0
  46. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
  47. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js 2.map +7 -0
  48. package/site/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  49. package/site/.vitepress/cache/deps/vitepress___@vueuse_core 2.js +584 -0
  50. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
  51. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js 2.map +7 -0
  52. package/site/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  53. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap 2.js +1166 -0
  54. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1166 -0
  55. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js 2.map +7 -0
  56. package/site/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  57. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js 2.js +1667 -0
  58. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
  59. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js 2.map +7 -0
  60. package/site/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  61. package/site/.vitepress/cache/deps/vitepress___minisearch 2.js +1815 -0
  62. package/site/.vitepress/cache/deps/vitepress___minisearch.js +1815 -0
  63. package/site/.vitepress/cache/deps/vitepress___minisearch.js 2.map +7 -0
  64. package/site/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  65. package/site/.vitepress/cache/deps/vue 2.js +344 -0
  66. package/site/.vitepress/cache/deps/vue.js +344 -0
  67. package/site/.vitepress/cache/deps/vue.js 2.map +7 -0
  68. package/site/.vitepress/cache/deps/vue.js.map +7 -0
  69. package/site/.vitepress/config.ts +147 -0
  70. package/site/.vitepress/theme/custom.css +50 -0
  71. package/site/.vitepress/theme/index.ts +6 -0
  72. package/site/api/webflo-fetch/FormData.md +0 -0
  73. package/site/api/webflo-fetch/Headers.md +0 -0
  74. package/site/api/webflo-fetch/LiveResponse.md +0 -0
  75. package/site/api/webflo-fetch/Request.md +0 -0
  76. package/site/api/webflo-fetch/Response.md +0 -0
  77. package/site/api/webflo-fetch/fetch.md +0 -0
  78. package/site/api/webflo-routing/HttpCookies.md +0 -0
  79. package/site/api/webflo-routing/HttpEvent/respondWith.md +1 -0
  80. package/site/api/webflo-routing/HttpEvent/waitUntil.md +1 -0
  81. package/site/api/webflo-routing/HttpEvent/waitUntilNavigate.md +1 -0
  82. package/site/api/webflo-routing/HttpEvent.md +30 -0
  83. package/site/api/webflo-routing/HttpSession.md +0 -0
  84. package/site/api/webflo-routing/HttpState.md +0 -0
  85. package/site/api/webflo-routing/HttpUser.md +0 -0
  86. package/site/api/webflo-routing/handler/fetch.md +42 -0
  87. package/site/api/webflo-routing/handler/next.md +54 -0
  88. package/site/api/webflo-routing/handler.md +119 -0
  89. package/site/api.md +26 -0
  90. package/site/contributing.md +16 -0
  91. package/site/docs/advanced/lifecycles.md +20 -0
  92. package/site/docs/advanced/redirects.md +0 -0
  93. package/site/docs/advanced/routing.md +1 -0
  94. package/site/docs/advanced.md +9 -0
  95. package/site/docs/concepts/realtime.md +637 -0
  96. package/site/docs/concepts/rendering.md +60 -0
  97. package/site/docs/concepts/request-response.md +47 -0
  98. package/site/docs/concepts/routing.md +656 -0
  99. package/site/docs/concepts/state.md +44 -0
  100. package/site/docs/concepts/templates.md +48 -0
  101. package/site/docs/concepts.md +97 -0
  102. package/site/docs/getting-started.md +378 -0
  103. package/site/docs/tech-stack.md +56 -0
  104. package/site/docs.md +100 -0
  105. package/site/examples/pwa.md +10 -0
  106. package/site/examples/web.md +11 -0
  107. package/site/examples.md +10 -0
  108. package/site/faq.md +13 -0
  109. package/site/guides/guide-auth.md +13 -0
  110. package/site/guides/guide-file-upload.md +11 -0
  111. package/site/guides/guide-service-worker.md +10 -0
  112. package/site/guides/tutorial-1-todo.md +24 -0
  113. package/site/guides.md +15 -0
  114. package/site/index.md +39 -0
  115. package/site/public/img/brand/logo-670x670.png +0 -0
  116. package/site/recipes/realtime.md +11 -0
  117. package/site/recipes/streaming.md +15 -0
  118. package/site/reference/cli.md +11 -0
  119. package/site/reference/config.md +13 -0
  120. package/site/reference/tools.md +9 -0
  121. package/src/Context.js +3 -11
  122. package/src/config-pi/deployment/Env.js +6 -19
  123. package/src/config-pi/deployment/Layout.js +11 -3
  124. package/src/config-pi/runtime/Client.js +40 -48
  125. package/src/config-pi/runtime/Server.js +52 -20
  126. package/src/config-pi/runtime/client/Worker.js +22 -20
  127. package/src/config-pi/static/Init.js +57 -0
  128. package/src/config-pi/static/index.js +2 -0
  129. package/src/deployment-pi/origins/index.js +1 -1
  130. package/src/deployment-pi/util.js +161 -0
  131. package/src/index.js +3 -9
  132. package/src/init-pi/index.js +117 -0
  133. package/src/init-pi/templates/pwa/app/handler.server.js +8 -0
  134. package/src/init-pi/templates/pwa/app/page.html +7 -0
  135. package/src/init-pi/templates/pwa/package.json +19 -0
  136. package/src/init-pi/templates/pwa/public/assets/app.css +16 -0
  137. package/src/init-pi/templates/pwa/public/index.html +39 -0
  138. package/src/init-pi/templates/pwa/public/manifest.json +29 -0
  139. package/src/init-pi/templates/web/app/handler.server.js +8 -0
  140. package/src/init-pi/templates/web/app/page.html +7 -0
  141. package/src/init-pi/templates/web/package.json +19 -0
  142. package/src/init-pi/templates/web/public/assets/app.css +16 -0
  143. package/src/init-pi/templates/web/public/index.html +39 -0
  144. package/src/runtime-pi/WebfloRuntime.js +350 -0
  145. package/src/runtime-pi/index.js +3 -10
  146. package/src/runtime-pi/webflo-client/ClientSideCookies.js +17 -0
  147. package/src/runtime-pi/webflo-client/ClientSideWorkport.js +63 -0
  148. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +213 -0
  149. package/src/runtime-pi/webflo-client/WebfloClient.js +500 -0
  150. package/src/runtime-pi/webflo-client/WebfloRootClient1.js +206 -0
  151. package/src/runtime-pi/webflo-client/WebfloRootClient2.js +113 -0
  152. package/src/runtime-pi/webflo-client/WebfloSubClient.js +118 -0
  153. package/src/runtime-pi/webflo-client/index.js +17 -0
  154. package/src/runtime-pi/webflo-client/webflo-codegen.js +469 -0
  155. package/src/runtime-pi/webflo-client/webflo-devmode.js +243 -0
  156. package/src/runtime-pi/webflo-client/webflo-embedded.js +50 -0
  157. package/src/runtime-pi/webflo-fetch/LiveResponse.js +437 -0
  158. package/src/runtime-pi/webflo-fetch/cookies.js +10 -0
  159. package/src/runtime-pi/webflo-fetch/fetch.js +16 -0
  160. package/src/runtime-pi/webflo-fetch/formdata.js +54 -0
  161. package/src/runtime-pi/webflo-fetch/headers.js +151 -0
  162. package/src/runtime-pi/webflo-fetch/index.js +5 -0
  163. package/src/runtime-pi/webflo-fetch/message.js +49 -0
  164. package/src/runtime-pi/webflo-fetch/request.js +62 -0
  165. package/src/runtime-pi/webflo-fetch/response.js +110 -0
  166. package/src/runtime-pi/webflo-fetch/util.js +28 -0
  167. package/src/runtime-pi/webflo-messaging/WQBroadcastChannel.js +10 -0
  168. package/src/runtime-pi/webflo-messaging/WQMessageChannel.js +26 -0
  169. package/src/runtime-pi/webflo-messaging/WQMessageEvent.js +87 -0
  170. package/src/runtime-pi/webflo-messaging/WQMessagePort.js +38 -0
  171. package/src/runtime-pi/webflo-messaging/WQRelayPort.js +47 -0
  172. package/src/runtime-pi/webflo-messaging/WQSockPort.js +113 -0
  173. package/src/runtime-pi/webflo-messaging/WQStarPort.js +104 -0
  174. package/src/runtime-pi/webflo-messaging/wq-message-port.js +404 -0
  175. package/src/runtime-pi/webflo-routing/HttpCookies.js +42 -0
  176. package/src/runtime-pi/webflo-routing/HttpEvent.js +112 -0
  177. package/src/runtime-pi/webflo-routing/HttpSession.js +11 -0
  178. package/src/runtime-pi/webflo-routing/HttpState.js +153 -0
  179. package/src/runtime-pi/webflo-routing/HttpUser.js +54 -0
  180. package/src/runtime-pi/webflo-routing/WebfloRouter.js +245 -0
  181. package/src/runtime-pi/webflo-server/ServerSideCookies.js +19 -0
  182. package/src/runtime-pi/webflo-server/ServerSideSession.js +38 -0
  183. package/src/runtime-pi/webflo-server/WebfloServer.js +937 -0
  184. package/src/runtime-pi/webflo-server/index.js +11 -0
  185. package/src/runtime-pi/webflo-server/messaging/Client.js +27 -0
  186. package/src/runtime-pi/webflo-server/messaging/ClientRequestRealtime.js +50 -0
  187. package/src/runtime-pi/webflo-server/messaging/Clients.js +25 -0
  188. package/src/runtime-pi/webflo-server/webflo-devmode.js +326 -0
  189. package/src/runtime-pi/{client → webflo-url}/Url.js +27 -76
  190. package/src/runtime-pi/webflo-url/index.js +1 -0
  191. package/src/runtime-pi/webflo-url/urlpattern.js +38 -0
  192. package/src/runtime-pi/{util-url.js → webflo-url/util.js} +5 -43
  193. package/src/runtime-pi/webflo-url/xURL.js +94 -0
  194. package/src/runtime-pi/webflo-worker/WebfloWorker.js +234 -0
  195. package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +19 -0
  196. package/src/runtime-pi/webflo-worker/WorkerSideWorkport.js +18 -0
  197. package/src/runtime-pi/webflo-worker/index.js +11 -0
  198. package/src/services-pi/index.js +2 -0
  199. package/src/services-pi/push/index.js +23 -0
  200. package/src/util.js +10 -0
  201. package/src/{webflo.js → webflo-cli.js} +4 -4
  202. package/src/runtime-pi/Application.js +0 -29
  203. package/src/runtime-pi/Cookies.js +0 -82
  204. package/src/runtime-pi/HttpEvent.js +0 -107
  205. package/src/runtime-pi/Router.js +0 -130
  206. package/src/runtime-pi/Runtime.js +0 -21
  207. package/src/runtime-pi/client/Application.js +0 -76
  208. package/src/runtime-pi/client/Context.js +0 -7
  209. package/src/runtime-pi/client/Router.js +0 -48
  210. package/src/runtime-pi/client/Runtime.js +0 -525
  211. package/src/runtime-pi/client/Workport.js +0 -190
  212. package/src/runtime-pi/client/createStorage.js +0 -58
  213. package/src/runtime-pi/client/generate.js +0 -481
  214. package/src/runtime-pi/client/index.js +0 -21
  215. package/src/runtime-pi/client/worker/Application.js +0 -44
  216. package/src/runtime-pi/client/worker/Context.js +0 -7
  217. package/src/runtime-pi/client/worker/Runtime.js +0 -275
  218. package/src/runtime-pi/client/worker/Workport.js +0 -78
  219. package/src/runtime-pi/client/worker/index.js +0 -21
  220. package/src/runtime-pi/server/Application.js +0 -101
  221. package/src/runtime-pi/server/Context.js +0 -16
  222. package/src/runtime-pi/server/Router.js +0 -159
  223. package/src/runtime-pi/server/Runtime.js +0 -558
  224. package/src/runtime-pi/server/index.js +0 -21
  225. package/src/runtime-pi/util-http.js +0 -86
  226. package/src/runtime-pi/xFormData.js +0 -24
  227. package/src/runtime-pi/xHeaders.js +0 -146
  228. package/src/runtime-pi/xRequest.js +0 -46
  229. package/src/runtime-pi/xRequestHeaders.js +0 -109
  230. package/src/runtime-pi/xResponse.js +0 -33
  231. package/src/runtime-pi/xResponseHeaders.js +0 -117
  232. package/src/runtime-pi/xURL.js +0 -105
  233. package/src/runtime-pi/xfetch.js +0 -23
  234. package/src/runtime-pi/xxHttpMessage.js +0 -102
  235. package/src/static-pi/index.js +0 -11
@@ -0,0 +1,469 @@
1
+ import Fs from 'fs';
2
+ import Path from 'path';
3
+ import Jsdom from 'jsdom';
4
+ import EsBuild from 'esbuild';
5
+ import { gzipSync, brotliCompressSync } from 'zlib';
6
+ import { _afterLast, _beforeLast } from '@webqit/util/str/index.js';
7
+ import { _isObject, _isArray } from '@webqit/util/js/index.js';
8
+ import { jsFile } from '@webqit/backpack/src/dotfile/index.js';
9
+ import { Context } from '../../Context.js';
10
+ import {
11
+ readClientConfig,
12
+ readWorkerConfig,
13
+ readLayoutConfig,
14
+ readEnvConfig,
15
+ scanRoots,
16
+ scanRouteHandlers,
17
+ } from '../../deployment-pi/util.js';
18
+ import '../webflo-url/urlpattern.js';
19
+
20
+ function declareConfig({ $source, configExport, indentation = 0 }) {
21
+ const varName = 'config';
22
+ if (!indentation) {
23
+ $source.code.push(`const ${varName} = {`);
24
+ }
25
+ Object.keys(configExport).forEach((name) => {
26
+ const $name = ` ${' '.repeat(indentation)}${(_isArray(configExport) ? '' : (name.includes(' ') ? `'${name}'` : name) + ': ')}`;
27
+ if (['boolean', 'number'].includes(typeof configExport[name])) {
28
+ $source.code.push(`${$name}${configExport[name]},`);
29
+ } else if (_isArray(configExport[name])) {
30
+ $source.code.push(`${$name}[`);
31
+ declareConfig({
32
+ $source,
33
+ configExport: configExport[name],
34
+ indentation: indentation + 1
35
+ });
36
+ $source.code.push(` ${' '.repeat(indentation)}],`);
37
+ } else if (_isObject(configExport[name])) {
38
+ $source.code.push(`${$name}{`);
39
+ declareConfig({
40
+ $source,
41
+ configExport: configExport[name],
42
+ indentation: indentation + 1
43
+ });
44
+ $source.code.push(` ${' '.repeat(indentation)}},`);
45
+ } else {
46
+ $source.code.push(`${$name}'${configExport[name]}',`);
47
+ }
48
+ });
49
+ if (!indentation) {
50
+ $source.code.push(`};`);
51
+ }
52
+ }
53
+
54
+ function declareRoutes({ $context, $config, $source, which, offset, roots = [] }) {
55
+ const { flags: FLAGS, logger: LOGGER } = $context;
56
+ LOGGER?.log(LOGGER.style.keyword(`> `) + `Declaring routes...`);
57
+ // Define vars
58
+ const varName = 'routes';
59
+ const targetDir = Path.join(FLAGS.outdir || $config.LAYOUT.PUBLIC_DIR, offset);
60
+ // >> Routes mapping
61
+ $source.code.push(`const ${varName} = {};`);
62
+ // Route entries
63
+ let routeCount = 0;
64
+ scanRouteHandlers($config.LAYOUT, which, (file, route, filename, fstat) => {
65
+ // The "import" code
66
+ const routeId = 'route' + (++routeCount);
67
+ const importPath = Path.relative(targetDir, file);
68
+ $source.imports[importPath] = '* as ' + routeId;
69
+ // The route def
70
+ $source.code.push(`${varName}['${route}'] = ${routeId};`);
71
+ // Show
72
+ LOGGER?.log(
73
+ LOGGER.style.comment(` [${route}]: `) + LOGGER.style.url(importPath) + LOGGER.style.comment(` (${fstat.size / 1024} KB)`)
74
+ );
75
+ }, offset, roots);
76
+ // >> Specials
77
+ $source.code.push(`Object.defineProperty(${varName}, '$root', { value: '${offset}' });`);
78
+ $source.code.push(`Object.defineProperty(${varName}, '$sparoots', { value: ${JSON.stringify(roots)} });`);
79
+ if (!routeCount) {
80
+ LOGGER?.log(LOGGER.style.comment(` (none)`));
81
+ }
82
+ }
83
+
84
+ function writeImportWebflo($source, which) {
85
+ $source.imports[`@webqit/webflo/src/runtime-pi/webflo-${which}/index.js`] = `{ start }`;
86
+ }
87
+
88
+ function writeScriptBody({ $context, $config, $source, which, offset, roots, configExport }) {
89
+ // >> Config
90
+ $source.code.push(`// >> Config export`);
91
+ declareConfig({ $source, configExport });
92
+ $source.code.push(``);
93
+ // >> Routes mapping
94
+ $source.code.push(`// >> Routes`);
95
+ declareRoutes({ $context, $config, $source, which, offset, roots });
96
+ $source.code.push(``);
97
+ // >> Startup
98
+ $source.code.push(`// >> Startup`);
99
+ $source.code.push(`self.webqit = self.webqit || {};`);
100
+ $source.code.push(`self.webqit.app = await start.call({ config, routes })`);
101
+ }
102
+
103
+ async function bundleScript({ $context, $source, which, outfile, asModule = false, ...restParams }) {
104
+ const { flags: FLAGS, logger: LOGGER } = $context;
105
+ // >> Show banner...
106
+ LOGGER?.log(LOGGER.style.keyword(`---`));
107
+ LOGGER?.log(`Bundling ${which} build`);
108
+ LOGGER?.log(LOGGER.style.keyword(`---`));
109
+ // Apply compression?
110
+ const compression = !FLAGS.compression ? false : (
111
+ FLAGS.compression === true ? ['gz'] : FLAGS.compression.split(',').map(s => s.trim())
112
+ );
113
+ const moduleFile = `${_beforeLast(outfile, '.')}.esm.js`;
114
+ // >> Show waiting...
115
+ if (LOGGER) {
116
+ const waiting = LOGGER.waiting(
117
+ LOGGER.f`Writing the ES module file: ${moduleFile}`
118
+ );
119
+ waiting.start();
120
+ jsFile.write($source, moduleFile, 'ES Module file');
121
+ waiting.stop();
122
+ } else {
123
+ jsFile.write($source, moduleFile, 'ES Module file');
124
+ }
125
+ // >> esbuild config
126
+ const bundlingConfig = {
127
+ entryPoints: [moduleFile],
128
+ outfile,
129
+ bundle: true,
130
+ minify: true,
131
+ banner: { js: '/** @webqit/webflo */', },
132
+ footer: { js: '', },
133
+ format: 'esm',
134
+ ...(restParams.buildParams || {})
135
+ };
136
+ if (!asModule) {
137
+ // Support top-level await
138
+ // See: https://github.com/evanw/esbuild/issues/253#issuecomment-826147115
139
+ bundlingConfig.banner.js += '(async () => {';
140
+ bundlingConfig.footer.js += '})();';
141
+ }
142
+ // The bundling process
143
+ let waiting;
144
+ if (LOGGER) {
145
+ waiting = LOGGER.waiting(`Bundling...`);
146
+ LOGGER.log(
147
+ LOGGER.style.keyword(`> `) + 'Bundling...'
148
+ );
149
+ waiting.start();
150
+ }
151
+ // Main
152
+ await EsBuild.build(bundlingConfig);
153
+ // Compress...
154
+ const compressedFiles = [];
155
+ const removals = [];
156
+ if (compression) {
157
+ const contents = Fs.readFileSync(bundlingConfig.outfile);
158
+ if (compression.includes('gz')) {
159
+ const gzip = gzipSync(contents, {});
160
+ Fs.writeFileSync(bundlingConfig.outfile + '.gz', gzip);
161
+ compressedFiles.push(bundlingConfig.outfile + '.gz');
162
+ } else {
163
+ removals.push(bundlingConfig.outfile + '.gz');
164
+ }
165
+ if (compression.includes('br')) {
166
+ const brotli = brotliCompressSync(contents, {});
167
+ Fs.writeFileSync(bundlingConfig.outfile + '.br', brotli);
168
+ compressedFiles.push(bundlingConfig.outfile + '.br');
169
+ } else {
170
+ removals.push(bundlingConfig.outfile + '.br');
171
+ }
172
+ } else {
173
+ removals.push(bundlingConfig.outfile + '.gz');
174
+ removals.push(bundlingConfig.outfile + '.br');
175
+ }
176
+ // Remove moduleFile build
177
+ Fs.unlinkSync(bundlingConfig.entryPoints[0]);
178
+ removals.forEach((file) => Fs.existsSync(file) && Fs.unlinkSync(file));
179
+ if (waiting) waiting.stop();
180
+ // ----------------
181
+ // Stats
182
+ if (LOGGER) {
183
+ [bundlingConfig.outfile].concat(compressedFiles).forEach((file) => {
184
+ let ext = '.' + _afterLast(file, '.');
185
+ LOGGER.info(LOGGER.style.comment(` [${ext}]: `) + LOGGER.style.url(file) + LOGGER.style.comment(` (${Fs.statSync(file).size / 1024} KB)`));
186
+ });
187
+ LOGGER.log('');
188
+ }
189
+ return [bundlingConfig.outfile].concat(compressedFiles);
190
+ }
191
+
192
+ function handleEmbeds($context, embeds, targetDocumentFile) {
193
+ if (!Fs.existsSync(targetDocumentFile)) {
194
+ return 0;
195
+ }
196
+ const targetDocument = Fs.readFileSync(targetDocumentFile).toString();
197
+ if (!/\<\!DOCTYPE html/i.test(targetDocument.trim())) {
198
+ return 0;
199
+ }
200
+ const { logger: LOGGER } = $context;
201
+ let successLevel = 1, touched;
202
+ // >> Show banner...
203
+ LOGGER?.log(LOGGER.style.keyword(`---`));
204
+ LOGGER?.log(`Embedding client build`);
205
+ LOGGER?.log(LOGGER.style.keyword(`---`));
206
+ // Embed...
207
+ const dom = new Jsdom.JSDOM(targetDocument);
208
+ const by = 'webflo';
209
+ function embed(src, after) {
210
+ src = src.replace(/\\/g, '/');
211
+ let embedded = dom.window.document.querySelector(`script[src="${src}"]`);
212
+ if (!embedded) {
213
+ embedded = dom.window.document.createElement('script');
214
+ embedded.setAttribute('type', 'module');
215
+ embedded.setAttribute('src', src);
216
+ embedded.setAttribute('by', by);
217
+ if (after) {
218
+ after.after(embedded, `\n\t\t`);
219
+ } else {
220
+ dom.window.document.head.appendChild(embedded);
221
+ }
222
+ touched = true;
223
+ }
224
+ return embedded;
225
+ };
226
+ function unembed(src) {
227
+ src = Path.join('/', src);
228
+ src = src.replace(/\\/g, '/');
229
+ let embedded = dom.window.document.querySelector(`script[src="${src}"][by="${by}"]`);
230
+ if (embedded) {
231
+ embedded.remove();
232
+ touched = true;
233
+ }
234
+ };
235
+ embeds.all.forEach((src) => {
236
+ if (embeds.current.includes(src)) return;
237
+ unembed(src);
238
+ });
239
+ embeds.current.reduce((prev, src) => {
240
+ return embed(src, prev);
241
+ }, [...dom.window.document.head.querySelectorAll(`script[src]`)].pop() || dom.window.document.querySelector(`script`));
242
+ if (touched) {
243
+ Fs.writeFileSync(targetDocumentFile, dom.serialize());
244
+ successLevel = 2;
245
+ }
246
+ return successLevel;
247
+ }
248
+
249
+ // -------------
250
+
251
+ async function generateClientScript({ $context, $config, offset = '', roots = [], ...restParams }) {
252
+ const { flags: FLAGS, logger: LOGGER } = $context;
253
+ // -----------
254
+ const inSplitMode = !!roots.length || !!offset;
255
+ const targetDocumentFile = Path.join($config.LAYOUT.PUBLIC_DIR, offset, 'index.html');
256
+ // For when we're in split mode
257
+ const outfile_theWebfloClient = $config.CLIENT.filename.replace(/\.js$/, '.webflo.js');
258
+ const outfile_theWebfloClientPublic = Path.join($config.CLIENT.public_base_url, outfile_theWebfloClient);
259
+ // For when we're monolith mode
260
+ const outfile_mainBuild = Path.join(offset, $config.CLIENT.filename);
261
+ let publicBaseUrl = $config.CLIENT.public_base_url;
262
+ if (FLAGS.outdir) {
263
+ publicBaseUrl = '/' + Path.relative($config.LAYOUT.PUBLIC_DIR, FLAGS.outdir);
264
+ }
265
+ const outfile_mainBuildPublic = Path.join(publicBaseUrl, outfile_mainBuild);
266
+ // The source code
267
+ const $source = { imports: {}, code: [] };
268
+ const embeds = { all: [], current: [] };
269
+ // -----------
270
+ // 1. Derive params
271
+ const configExport = structuredClone({ CLIENT: $config.CLIENT, ENV: $config.ENV });
272
+ if ($config.CLIENT.capabilities?.service_worker === true) {
273
+ configExport.CLIENT.capabilities.service_worker = {
274
+ filename: Path.join(publicBaseUrl.replace(/^\//, ''), $config.WORKER.filename),
275
+ scope: $config.WORKER.scope
276
+ };
277
+ }
278
+ // 2. Add the Webflo Runtime
279
+ const outfiles = [];
280
+ if (inSplitMode) {
281
+ if (!offset) {
282
+ // We're building the Webflo client as a standalone script
283
+ LOGGER?.log(LOGGER.style.keyword(`---`));
284
+ LOGGER?.log(`[SPLIT_MODE] Base Build`);
285
+ LOGGER?.log(LOGGER.style.keyword(`---`));
286
+ // Write the import code and bundle
287
+ const $$source = { imports: {}, code: [], };
288
+ writeImportWebflo($$source, 'client');
289
+ const _outfiles = await bundleScript({
290
+ $context,
291
+ $source: $$source,
292
+ which: 'client',
293
+ outfile: Path.join($config.LAYOUT.PUBLIC_DIR, outfile_theWebfloClient),
294
+ asModule: true
295
+ });
296
+ outfiles.push(..._outfiles);
297
+ }
298
+ if (FLAGS['auto-embed']) {
299
+ embeds.current.push(outfile_theWebfloClientPublic);
300
+ embeds.current.push(outfile_mainBuildPublic);
301
+ }
302
+ } else {
303
+ // We're building the Webflo client as part of the main script
304
+ writeImportWebflo($source, 'client');
305
+ if (FLAGS['auto-embed']) {
306
+ embeds.current.push(outfile_mainBuildPublic);
307
+ }
308
+ }
309
+ // 3. Write the body and bundle
310
+ writeScriptBody({
311
+ $context,
312
+ $config,
313
+ $source,
314
+ which: 'client',
315
+ offset,
316
+ roots,
317
+ configExport,
318
+ ...restParams
319
+ });
320
+ // 4. Bundle
321
+ const _outfiles = await bundleScript({
322
+ $context,
323
+ $source,
324
+ which: 'client',
325
+ outfile: Path.join(FLAGS.outdir || $config.LAYOUT.PUBLIC_DIR, outfile_mainBuild),
326
+ asModule: true,
327
+ ...restParams
328
+ });
329
+ outfiles.push(..._outfiles);
330
+ // 4. Embed/unembed
331
+ embeds.all.push(outfile_theWebfloClientPublic);
332
+ embeds.all.push(outfile_mainBuildPublic);
333
+ handleEmbeds($context, embeds, targetDocumentFile);
334
+ // -----------
335
+ if (FLAGS.recursive && roots.length) {
336
+ const $roots = roots.slice(0);
337
+ const _outfiles = await generateClientScript({
338
+ $context,
339
+ $config,
340
+ offset: $roots.shift(),
341
+ roots: $roots,
342
+ ...restParams
343
+ });
344
+ return outfiles.concat(_outfiles);
345
+ }
346
+ return outfiles;
347
+ }
348
+
349
+ async function generateWorkerScript({ $context, $config, offset = '', roots = [], ...restParams }) {
350
+ const { flags: FLAGS } = $context;
351
+ // -----------
352
+ const outfile_mainBuild = Path.join(offset, $config.WORKER.filename);
353
+ const $source = { imports: {}, code: [] };
354
+ // -----------
355
+ // 1. Derive params
356
+ const configExport = structuredClone({ WORKER: $config.WORKER, ENV: $config.ENV });
357
+ if ($config.CLIENT.capabilities?.webpush === true) {
358
+ configExport.WORKER.capabilities = {
359
+ webpush: true
360
+ };
361
+ }
362
+ // Fetching strategies
363
+ for (const strategy of ['cache_first_urls', 'cache_only_urls']) {
364
+ if (configExport.WORKER[strategy].length) {
365
+ // Separate URLs from patterns
366
+ const [urls, patterns] = configExport.WORKER[strategy].reduce(([urls, patterns], url) => {
367
+ const patternInstance = new URLPattern(url, 'http://localhost');
368
+ const isPattern = patternInstance.isPattern();
369
+ if (isPattern && (patternInstance.pattern.hostname !== 'localhost' || patternInstance.pattern.port)) {
370
+ throw new Error(`Pattern URLs must have no origin part. Recieved "${url}".`);
371
+ }
372
+ return isPattern ? [urls, patterns.concat(patternInstance)] : [urls.concat(url), patterns];
373
+ }, [[], []]);
374
+ // Resolve patterns
375
+ if (patterns.length) {
376
+ // List all files
377
+ function scanDir(dir) {
378
+ Fs.readdirSync(dir).reduce((result, f) => {
379
+ const resource = Path.join(dir, f);
380
+ if (f.startsWith('.')) return result;
381
+ return result.concat(
382
+ Fs.statSync(resource).isDirectory() ? scanDir(resource) : '/' + Path.relative($config.LAYOUT.PUBLIC_DIR, resource)
383
+ );
384
+ }, []);
385
+ }
386
+ const files = scanDir($config.LAYOUT.PUBLIC_DIR);
387
+ // Resolve patterns from files
388
+ configExport.WORKER[strategy] = patterns.reduce((all, pattern) => {
389
+ const matchedFiles = files.filter((file) => pattern.test(file, 'http://localhost'));
390
+ if (matchedFiles.length) return all.concat(matchedFiles);
391
+ throw new Error(`The pattern "${pattern.pattern.pathname}" didn't match any files.`);
392
+ }, urls);
393
+ }
394
+ }
395
+ }
396
+ // 2. Add the Webflo Runtime
397
+ writeImportWebflo($source, 'worker');
398
+ // 3. Write the body and bundle
399
+ writeScriptBody({
400
+ $context,
401
+ $config,
402
+ $source,
403
+ which: 'worker',
404
+ offset,
405
+ roots,
406
+ configExport,
407
+ ...restParams
408
+ });
409
+ // 4. Bundle
410
+ const outfiles = await bundleScript({
411
+ $context,
412
+ $source,
413
+ which: 'worker',
414
+ outfile: Path.join(FLAGS.outdir || $config.LAYOUT.PUBLIC_DIR, outfile_mainBuild),
415
+ asModule: false,
416
+ ...restParams
417
+ });
418
+ // -----------
419
+ if (FLAGS.recursive && roots.length) {
420
+ const $roots = roots.slice(0);
421
+ const _outfiles = await generateWorkerScript({
422
+ $context,
423
+ $config,
424
+ offset: $roots.shift(),
425
+ roots: $roots,
426
+ ...restParams
427
+ });
428
+ return outfiles.concat(_outfiles);
429
+ }
430
+ return outfiles;
431
+ }
432
+
433
+ export async function generate({ client = true, worker = true, buildParams = {} } = {}) {
434
+ const $context = this;
435
+ if (!($context instanceof Context)) {
436
+ throw new Error(`The "this" context must be a Webflo Context object.`);
437
+ }
438
+ // Resolve common details
439
+ const $config = {
440
+ LAYOUT: await readLayoutConfig($context),
441
+ ENV: { ...await readEnvConfig($context), data: {} },
442
+ CLIENT: await readClientConfig($context),
443
+ WORKER: await readWorkerConfig($context),
444
+ };
445
+ if ($config.CLIENT.copy_public_variables) {
446
+ const publicEnvPattern = /(?:^|_)PUBLIC(?:_|$)/;
447
+ for (const key in process.env) {
448
+ if (publicEnvPattern.test(key)) {
449
+ $config.ENV.data[key] = process.env[key];
450
+ }
451
+ }
452
+ }
453
+ // Build
454
+ const outfiles = [];
455
+ if (client) {
456
+ const documentRoots = scanRoots($config.LAYOUT.PUBLIC_DIR, 'index.html');
457
+ const _outfiles = await generateClientScript({ $context, $config, roots: documentRoots, buildParams });
458
+ outfiles.push(..._outfiles);
459
+ }
460
+ if (worker) {
461
+ const applicationRoots = scanRoots($config.LAYOUT.PUBLIC_DIR, 'manifest.json');
462
+ const _outfiles = await generateWorkerScript({ $context, $config, roots: applicationRoots, buildParams });
463
+ outfiles.push(..._outfiles);
464
+ }
465
+ if (process.send) {
466
+ process.send({ outfiles });
467
+ }
468
+ return { outfiles };
469
+ }