@webqit/webflo 0.20.4-next.2 → 0.20.4-next.4

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 (56) hide show
  1. package/package.json +13 -34
  2. package/site/docs/concepts/realtime.md +45 -44
  3. package/site/docs/getting-started.md +40 -40
  4. package/src/{Context.js → CLIContext.js} +9 -8
  5. package/src/build-pi/esbuild-plugin-uselive-transform.js +42 -0
  6. package/src/{runtime-pi/webflo-client/webflo-codegen.js → build-pi/index.js} +148 -142
  7. package/src/index.js +3 -1
  8. package/src/init-pi/index.js +7 -4
  9. package/src/init-pi/templates/pwa/.gitignore +6 -0
  10. package/src/init-pi/templates/pwa/.webqit/webflo/client.json +15 -0
  11. package/src/init-pi/templates/pwa/.webqit/webflo/layout.json +7 -0
  12. package/src/init-pi/templates/pwa/package.json +2 -2
  13. package/src/init-pi/templates/pwa/public/manifest.json +2 -2
  14. package/src/init-pi/templates/web/.gitignore +6 -0
  15. package/src/init-pi/templates/web/.webqit/webflo/client.json +12 -0
  16. package/src/init-pi/templates/web/.webqit/webflo/layout.json +7 -0
  17. package/src/init-pi/templates/web/package.json +2 -2
  18. package/src/runtime-pi/AppBootstrap.js +38 -0
  19. package/src/runtime-pi/WebfloRuntime.js +68 -56
  20. package/src/runtime-pi/apis.js +9 -0
  21. package/src/runtime-pi/index.js +2 -4
  22. package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
  23. package/src/runtime-pi/webflo-client/WebfloClient.js +33 -36
  24. package/src/runtime-pi/webflo-client/WebfloRootClient1.js +23 -17
  25. package/src/runtime-pi/webflo-client/WebfloRootClient2.js +1 -1
  26. package/src/runtime-pi/webflo-client/WebfloSubClient.js +14 -14
  27. package/src/runtime-pi/webflo-client/bootstrap.js +38 -0
  28. package/src/runtime-pi/webflo-client/index.js +2 -8
  29. package/src/runtime-pi/webflo-client/webflo-devmode.js +3 -3
  30. package/src/runtime-pi/webflo-fetch/LiveResponse.js +154 -116
  31. package/src/runtime-pi/webflo-fetch/index.js +436 -5
  32. package/src/runtime-pi/webflo-messaging/wq-message-port.js +1 -1
  33. package/src/runtime-pi/webflo-routing/HttpCookies.js +1 -1
  34. package/src/runtime-pi/webflo-routing/HttpEvent.js +12 -11
  35. package/src/runtime-pi/webflo-routing/HttpUser.js +7 -7
  36. package/src/runtime-pi/webflo-routing/WebfloRouter.js +12 -7
  37. package/src/runtime-pi/webflo-server/ServerSideCookies.js +3 -1
  38. package/src/runtime-pi/webflo-server/ServerSideSession.js +2 -1
  39. package/src/runtime-pi/webflo-server/WebfloServer.js +138 -200
  40. package/src/runtime-pi/webflo-server/bootstrap.js +59 -0
  41. package/src/runtime-pi/webflo-server/index.js +2 -6
  42. package/src/runtime-pi/webflo-server/webflo-devmode.js +24 -31
  43. package/src/runtime-pi/webflo-url/Url.js +1 -1
  44. package/src/runtime-pi/webflo-url/xURL.js +1 -1
  45. package/src/runtime-pi/webflo-worker/WebfloWorker.js +11 -15
  46. package/src/runtime-pi/webflo-worker/WorkerSideCookies.js +2 -1
  47. package/src/runtime-pi/webflo-worker/bootstrap.js +39 -0
  48. package/src/runtime-pi/webflo-worker/index.js +3 -7
  49. package/src/webflo-cli.js +1 -2
  50. package/src/runtime-pi/webflo-fetch/cookies.js +0 -10
  51. package/src/runtime-pi/webflo-fetch/fetch.js +0 -16
  52. package/src/runtime-pi/webflo-fetch/formdata.js +0 -54
  53. package/src/runtime-pi/webflo-fetch/headers.js +0 -151
  54. package/src/runtime-pi/webflo-fetch/message.js +0 -49
  55. package/src/runtime-pi/webflo-fetch/request.js +0 -62
  56. package/src/runtime-pi/webflo-fetch/response.js +0 -110
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "0.20.4-next.2",
15
+ "version": "0.20.4-next.4",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -33,31 +33,24 @@
33
33
  "site:build": "vitepress build site",
34
34
  "site:preview": "vitepress preview site --port 4173"
35
35
  },
36
+ "exports": {
37
+ "./apis": "./src/runtime-pi/apis.js"
38
+ },
36
39
  "bin": {
37
40
  "webflo": "src/webflo-cli.js",
38
41
  "webflo-certbot-http-auth-hook": "src/services-pi/cert/http-auth-hook.js",
39
42
  "webflo-certbot-http-cleanup-hook": "src/services-pi/cert/http-cleanup-hook.js"
40
43
  },
41
44
  "dependencies": {
42
- "@linked-db/linked-ql": "^0.30.13",
43
45
  "@octokit/webhooks": "^7.15.1",
44
46
  "@webqit/backpack": "^0.1.12",
45
- "@webqit/oohtml-ssr": "^2.1.1",
46
- "@webqit/quantum-js": "^4.6.3",
47
+ "@webqit/oohtml-ssr": "^2.2.1",
48
+ "@webqit/use-live": "^0.5.41",
47
49
  "@webqit/util": "^0.8.11",
48
50
  "dotenv": "^16.4.7",
49
- "fs-extra": "^11.3.0",
50
- "i": "^0.3.7",
51
- "ioredis": "^5.5.0",
52
- "jsdom": "^21.1.1",
53
- "markdown-it-mathjax3": "^4.3.2",
54
51
  "mime-types": "^2.1.33",
55
- "npm": "^11.4.0",
56
- "pg": "^8.13.3",
57
52
  "simple-git": "^2.20.1",
58
- "urlpattern-polyfill": "^4.0.3",
59
- "vitepress-plugin-mermaid": "^2.0.17",
60
- "web-push": "^3.6.7"
53
+ "urlpattern-polyfill": "^4.0.3"
61
54
  },
62
55
  "devDependencies": {
63
56
  "chai": "^4.3.6",
@@ -65,31 +58,17 @@
65
58
  "coveralls": "^3.1.1",
66
59
  "esbuild": "^0.14.38",
67
60
  "fast-glob": "^3.3.3",
61
+ "jsdom": "^27.0.1",
62
+ "markdown-it-mathjax3": "^4.3.2",
68
63
  "mocha": "^10.0.0",
69
64
  "mocha-lcov-reporter": "^1.3.0",
70
- "vitepress": "^1.6.4"
65
+ "vitepress": "^1.6.4",
66
+ "vitepress-plugin-mermaid": "^2.0.17",
67
+ "web-push": "^3.6.7"
71
68
  },
72
69
  "author": "Oxford Harrison <oxharris.dev@gmail.com>",
73
70
  "maintainers": [
74
71
  "Oxford Harrison <oxharris.dev@gmail.com>"
75
72
  ],
76
- "contributors": [],
77
- "funding": {
78
- "type": "patreon",
79
- "url": "https://patreon.com/ox_harris"
80
- },
81
- "badges": {
82
- "list": [
83
- "npmversion",
84
- "npmdownloads",
85
- "patreon"
86
- ],
87
- "config": {
88
- "patreonUsername": "ox_harris",
89
- "githubUsername": "webqit",
90
- "githubRepository": "webflo",
91
- "githubSlug": "webqit/webflo",
92
- "npmPackageName": "@webqit/webflo"
93
- }
94
- }
73
+ "contributors": []
95
74
  }
@@ -20,17 +20,17 @@ event.waitUntilNavigate();
20
20
  ```
21
21
 
22
22
  ```js
23
- event.client.addEventListener('message', handle);
24
- event.client.postMessage({ message: 'You seem to be typing something.' });
23
+ event.client.addEventListener("message", handle);
24
+ event.client.postMessage({ message: "You seem to be typing something." });
25
25
  ```
26
26
 
27
27
  ```js
28
- const result = await event.user.confirm({ message: 'Should we proceed?' });
29
- const result = await event.user.prompt({ message: 'What is your name?' });
28
+ const result = await event.user.confirm({ message: "Should we proceed?" });
29
+ const result = await event.user.prompt({ message: "What is your name?" });
30
30
  ```
31
31
 
32
32
  ```js
33
- const { channel, leave } = event.client.enterChannel('test-channel');
33
+ const { channel, leave } = event.client.enterChannel("test-channel");
34
34
  ```
35
35
 
36
36
  ## The Traditional Setup
@@ -124,7 +124,7 @@ The **primary** realtime API here is `event.client` — a Webflo `MessagePort` s
124
124
 
125
125
  ```js
126
126
  export default async function (event) {
127
- event.client.postMessage({ hello: 'world' });
127
+ event.client.postMessage({ hello: "world" });
128
128
  }
129
129
  ```
130
130
 
@@ -138,10 +138,10 @@ These HTTP clients or handlers are the _Port B_.
138
138
  The primary realtime API here is `liveResponse.background` — the same Webflo `MessagePort` interface as `event.client`, supporting things like: `.postMessage()`, `.addEventListener()`, etc.
139
139
 
140
140
  ```js
141
- const response = await fetch('https://api.example.com/data');
142
- const liveResponse = LiveResponse.from(response);
141
+ const response = await fetch("https://api.example.com/data");
142
+ const liveResponse = await LiveResponse.from(response);
143
143
 
144
- liveResponse.background.addEventListener('message', (message) => {
144
+ liveResponse.background.addEventListener("message", (message) => {
145
145
  console.log(message);
146
146
  });
147
147
  ```
@@ -172,7 +172,7 @@ The `app.background` port is also a _multi-port_ hub housing all _active_ backgr
172
172
  Every interaction on this API is thus an interaction over all active realtime conversations.
173
173
 
174
174
  ```js
175
- app.background.addEventListener('message', (message) => {
175
+ app.background.addEventListener("message", (message) => {
176
176
  console.log(message);
177
177
  });
178
178
  ```
@@ -193,8 +193,8 @@ export default async function (event, next) {
193
193
  if (next.stepname) return await next(); // The conventional delegation line
194
194
 
195
195
  const response = await next();
196
- const liveResponse = LiveResponse.from(response);
197
- liveResponse.background.addEventListener('message', (message) => {
196
+ const liveResponse = await LiveResponse.from(response);
197
+ liveResponse.background.addEventListener("message", (message) => {
198
198
  console.log(message);
199
199
  });
200
200
 
@@ -209,7 +209,7 @@ The handler’s own `event.client` port remains its own _Port A_ for communicati
209
209
  ---
210
210
 
211
211
  ::: tip
212
- While not explicitly mentioned, external Webflo servers are just as accessible and interactive as the local server. A server-to-server interaction, for example, is just a matter of `LiveResponse.from(await fetch('https://api.example.com/data'))`.
212
+ While not explicitly mentioned, external Webflo servers are just as accessible and interactive as the local server. A server-to-server interaction, for example, is just a matter of `await LiveResponse.from(await fetch('https://api.example.com/data'))`.
213
213
  :::
214
214
 
215
215
  ## Entering Background Mode
@@ -230,7 +230,7 @@ event.client.postMessage({ progress: 10 });
230
230
  2. or achieving the same through higher-level APIs like `event.user.confirm()`:
231
231
 
232
232
  ```js
233
- const result = await event.user.confirm({ message: 'Are you sure?' });
233
+ const result = await event.user.confirm({ message: "Are you sure?" });
234
234
  ```
235
235
 
236
236
  ### _Handler returns a response and explicitly marks it **not done**_
@@ -278,9 +278,9 @@ event.respondWith(
278
278
  ```js
279
279
  event.respondWith(data, async ($data /* reactive copy of data */) => {
280
280
  await new Promise((resolve) => setTimeout(resolve, 1000));
281
- $data.someProp = 'someValue';
281
+ $data.someProp = "someValue";
282
282
  await new Promise((resolve) => setTimeout(resolve, 1000));
283
- $data.someProp = 'someOtherValue';
283
+ $data.someProp = "someOtherValue";
284
284
  });
285
285
  ```
286
286
 
@@ -294,7 +294,7 @@ A handler may return a `Generator` object by either:
294
294
  export default async function* (event) {
295
295
  yield data1;
296
296
  yield new Response(data2);
297
- yield new LiveResponse(null, { headers: { Location: '/' } });
297
+ yield new LiveResponse(null, { headers: { Location: "/" } });
298
298
  }
299
299
  ```
300
300
 
@@ -352,14 +352,14 @@ This handler returns a `LiveResponse` whose body starts as `{ step: 1 }` and is
352
352
  **_Result_:** The page first renders step: 1, then updates to step: 2 after ~200ms.
353
353
 
354
354
  ```js
355
- import { LiveResponse } from '@webqit/webflo';
355
+ import { LiveResponse } from '@webqit/webflo/apis';
356
356
  ```
357
357
 
358
358
  ```js
359
359
  export async function GET(event, next) {
360
360
  if (next.stepname) return await next(); // The conventional delegation line
361
361
 
362
- const res = LiveResponse.from({ step: 1 }, { done: false });
362
+ const res = await LiveResponse.from({ step: 1 }, { done: false });
363
363
  setTimeout(() => res.replaceWith({ step: 2 }), 200); // [!code highlight]
364
364
 
365
365
  return res;
@@ -373,7 +373,7 @@ This handler returns a skeleton object immediately for fast page load and then b
373
373
  **_Result_:** The UI renders a skeleton first, then progressively fills in as the object tree is built from the server.
374
374
 
375
375
  ```js
376
- import Observer from '@webqit/observer';
376
+ import { Observer } from '@webqit/webflo/apis';
377
377
  ```
378
378
 
379
379
  ```js
@@ -382,30 +382,30 @@ export async function GET(event, next) {
382
382
 
383
383
  const data = {
384
384
  menu: [
385
- { name: 'Home', href: '/' },
386
- { name: 'About', href: '/about' },
387
- { name: 'Contact', href: '/contact' },
385
+ { name: "Home", href: "/" },
386
+ { name: "About", href: "/about" },
387
+ { name: "Contact", href: "/contact" },
388
388
  ],
389
389
  content: {
390
- header: 'Loading...',
390
+ header: "Loading...",
391
391
  body: [],
392
392
  },
393
393
  };
394
394
 
395
395
  event.waitUntil(
396
396
  new Promise(async (resolve) => {
397
- const someData = await fetch('https://api.example.com/data');
398
- Observer.set(data.content, 'header', someData.header);
397
+ const someData = await fetch("https://api.example.com/data");
398
+ Observer.set(data.content, "header", someData.header);
399
399
 
400
400
  Observer.proxy(data.content.body).push(
401
- { text: 'Inventory 1:' + someData.items[0] },
402
- { text: 'Inventory 2:' + someData.items[1] }
401
+ { text: "Inventory 1:" + someData.items[0] },
402
+ { text: "Inventory 2:" + someData.items[1] }
403
403
  );
404
404
 
405
- const someData2 = await fetch('https://api.example.com/data2');
405
+ const someData2 = await fetch("https://api.example.com/data2");
406
406
  Observer.proxy(data.content.body).push(
407
- { text: 'Inventory 3:' + someData2.items[0] },
408
- { text: 'Inventory 4:' + someData2.items[1] }
407
+ { text: "Inventory 3:" + someData2.items[0] },
408
+ { text: "Inventory 4:" + someData2.items[1] }
409
409
  );
410
410
 
411
411
  resolve();
@@ -450,12 +450,12 @@ export async function POST(event, next) {
450
450
  // Initiate response
451
451
  event.respondWith({ step: 1 }, { done: false });
452
452
  // Make API call
453
- await fetch('https://api.example.com/data');
453
+ await fetch("https://api.example.com/data");
454
454
  // Update response
455
455
  event.respondWith({ step: 2 }, { done: false });
456
456
 
457
457
  // Redirect
458
- event.respondWith(null, { status: 302, headers: { Location: '/done' } }); // [!code highlight]
458
+ event.respondWith(null, { status: 302, headers: { Location: "/done" } }); // [!code highlight]
459
459
  }
460
460
  ```
461
461
 
@@ -471,7 +471,7 @@ This handler pauses request processing to interact with the user before proceedi
471
471
  export async function DELETE(event, next) {
472
472
  if (next.stepname) return await next(); // The conventional delegation line
473
473
 
474
- const message = 'This item will be permanently deleted. Are you sure?';
474
+ const message = "This item will be permanently deleted. Are you sure?";
475
475
  const answer = await event.user.confirm({ message }); // [!code highlight]
476
476
  if (answer?.data === true) {
477
477
  // proceed
@@ -487,7 +487,7 @@ Customization is as simple as intercepting these via `preventDefault()` to rende
487
487
 
488
488
  ```javascript
489
489
  // Intercept at window.webqit.app.background
490
- window.webqit.app.background.addEventListener('prompt', (e) => {
490
+ window.webqit.app.background.addEventListener("prompt", (e) => {
491
491
  e.preventDefault(); // [!code highlight]
492
492
  customDialog(e.data.message).then((result) => {
493
493
  // Reply to the request on the provided port
@@ -509,17 +509,17 @@ export async function GET(event, next) {
509
509
  if (next.stepname) return await next(); // The conventional delegation line
510
510
 
511
511
  // Channel ID - must tally with the client-side ID
512
- const channelID = 'test-channel';
512
+ const channelID = "test-channel";
513
513
 
514
514
  const { channel, leave } = event.client.enterChannel(channelID, {
515
515
  // Optionally annotate user's messages with user's name
516
- resolveData: (message) => ({ ...message, user: 'sample name' }),
516
+ resolveData: (message) => ({ ...message, user: "sample name" }),
517
517
  });
518
518
 
519
519
  event.waitUntilNavigate(); // keep open
520
- event.client.addEventListener('close', (e) => {
520
+ event.client.addEventListener("close", (e) => {
521
521
  //e.preventDefault();
522
- console.log('Chat closed');
522
+ console.log("Chat closed");
523
523
  });
524
524
  }
525
525
  ```
@@ -531,15 +531,15 @@ export default async function (event, next) {
531
531
  if (next.stepname) return await next(); // The conventional delegation line
532
532
 
533
533
  // Initialize states
534
- const data = { messageTrail: [{ message: 'Beginning of chat...' }] };
534
+ const data = { messageTrail: [{ message: "Beginning of chat..." }] };
535
535
  await event.respondWith(data, { done: false }); // [!code highlight]
536
536
 
537
537
  // The messaging part
538
538
  const response = await next(); // Call server
539
- const chatPort = LiveResponse.from(response).background;
539
+ const chatPort = (await LiveResponse.from(response)).background;
540
540
 
541
541
  // Channel ID - must tally with the server-side ID
542
- const channelID = 'test-channel';
542
+ const channelID = "test-channel";
543
543
 
544
544
  // Listen to upstream messages - from others in the same channel
545
545
  chatPort.addEventListener(`${channelID}:message`, (e) => {
@@ -599,9 +599,10 @@ export default async function (event, next) {
599
599
  :::
600
600
 
601
601
  ::: warning
602
+
602
603
  - Channel IDs must be unique and unguessable across the app.
603
604
  - Channels are not persistent and are only active for the duration of the request. A database is required to store channel data.
604
- :::
605
+ :::
605
606
 
606
607
  ## Appendix A – The Realtime Lifecycle
607
608
 
@@ -625,7 +626,7 @@ sequenceDiagram
625
626
  On entering background mode, Webflo initiates a handshake sequence as follows:
626
627
 
627
628
  1. Webflo gives the initial response a unique `X-Background-Messaging-Port` header that tells the client to connect in the background after rendering the initial response.
628
- > If the scenario is that the handler tiggers `event.client` messaging _before_ yielding a response, Webflo sends a temporary `202 Accepted` response to the client carrying this header.
629
+ > If the scenario is that the handler tiggers `event.client` messaging _before_ yielding a response, Webflo sends a temporary `202 Accepted` response to the client carrying this header.
629
630
  2. The client reads that header and opens the background channel.
630
631
  > If the client fails to connect within the handshake window, Webflo abandons the wait and concludes the request normally.
631
632
  3. On connecting, both sides resume the same request context — now in live mode.
@@ -22,23 +22,23 @@ This documentation is a work in progress. Please expect some rough edges, missin
22
22
 
23
23
  ## Installation
24
24
 
25
- ### Option 1: Global Installation (Recommended)
25
+ ### Option 1: Local Installation (Recommended)
26
26
 
27
- Install Webflo globally to use the CLI from anywhere:
27
+ Install Webflo as a dependency in your project:
28
28
 
29
29
  ```bash
30
- npm install -g @webqit/webflo
30
+ npm install @webqit/webflo
31
31
  ```
32
32
 
33
- ### Option 2: Local Installation
33
+ ### Option 2: Global Installation
34
34
 
35
- Install Webflo as a dependency in your project:
35
+ Install Webflo globally to use the CLI from anywhere:
36
36
 
37
37
  ```bash
38
- npm install @webqit/webflo
38
+ npm install -g @webqit/webflo
39
39
  ```
40
40
 
41
- The scope you choose will determine how you run Webflo commands. The Webflo commands in the rest of this page will show in both `global` and `local` styles.
41
+ The scope you choose will determine how you run Webflo commands. The Webflo commands in the rest of this page will show in both `local` and `global` styles.
42
42
 
43
43
  ## Creating a New Project
44
44
 
@@ -46,14 +46,14 @@ Webflo provides a CLI command to scaffold new projects:
46
46
 
47
47
  ::: code-group
48
48
 
49
- ```bash [global]
50
- webflo init my-webflo-app
51
- ```
52
-
53
49
  ```bash [local]
54
50
  npx webflo init my-webflo-app
55
51
  ```
56
52
 
53
+ ```bash [global]
54
+ webflo init my-webflo-app
55
+ ```
56
+
57
57
  :::
58
58
 
59
59
  This will create a new directory called `my-webflo-app` with a basic Webflo project structure.
@@ -70,28 +70,28 @@ The above command could take a project title and project description too:
70
70
 
71
71
  ::: code-group
72
72
 
73
- ```bash [global]
74
- webflo init my-webflo-app "My Webflo App" "My first ever Webflo app"
75
- ```
76
-
77
73
  ```bash [local]
78
74
  npx webflo init my-webflo-app "My Webflo App" "My first ever Webflo app"
79
75
  ```
80
76
 
77
+ ```bash [global]
78
+ webflo init my-webflo-app "My Webflo App" "My first ever Webflo app"
79
+ ```
80
+
81
81
  :::
82
82
 
83
83
  And you can specify a template to use:
84
84
 
85
85
  ::: code-group
86
86
 
87
- ```bash [global]
88
- webflo init my-webflo-app --template=web
89
- ```
90
-
91
87
  ```bash [local]
92
88
  npx webflo init my-webflo-app --template=web
93
89
  ```
94
90
 
91
+ ```bash [global]
92
+ webflo init my-webflo-app --template=web
93
+ ```
94
+
95
95
  :::
96
96
 
97
97
  The default is: `web`.
@@ -151,11 +151,11 @@ The generated `package.json` for your project will include scripts like `dev`, `
151
151
  "scripts": {
152
152
  // Builds HTML templates and client bundles into public/assets
153
153
  "build:html": "oohtml bundle --recursive --outdir=public/assets --auto-embed=app",
154
- "build:js": "webflo generate::client --recursive --outdir=public/assets --auto-embed",
154
+ "build:js": "webflo build --client --worker --server --recursive --outdir=public/assets --auto-embed",
155
155
  // Production build: runs both steps
156
156
  "build": "npm run build:html && npm run build:js",
157
157
  // Development server with auto-rebuilds and fine-grained HMR
158
- "dev": "webflo start --dev --build-sensitivity=2",
158
+ "dev": "webflo start --dev --build-sensitivity=1",
159
159
  // Production server
160
160
  "start": "webflo start"
161
161
  },
@@ -181,7 +181,7 @@ You can customize as necessary.
181
181
  ::: info Scripts & customization
182
182
  - You can customize `build:html` or `build:js` scripts if you need finer control
183
183
  - Add a `build:css` script if your CSS requires a build step; it'll be called in dev mode as necessary on CSS file changes
184
- - Adjust dev mode's rebuild frequency for assets with `--build-sensitivity` (e.g., `--build-sensitivity=2` — to defer rebuild until page reload; `0` — to turn off)
184
+ - Adjust dev mode's rebuild frequency for assets with `--build-sensitivity` (e.g., `--build-sensitivity=1` — to defer rebuild until page reload; `0` — to turn off)
185
185
  :::
186
186
 
187
187
  ## Running Your Application
@@ -194,14 +194,14 @@ Start the development server:
194
194
 
195
195
  ::: code-group
196
196
 
197
- ```bash [global]
198
- webflo start --dev
199
- ```
200
-
201
197
  ```bash [npm]
202
198
  npm run dev
203
199
  ```
204
200
 
201
+ ```bash [global]
202
+ webflo start --dev
203
+ ```
204
+
205
205
  :::
206
206
 
207
207
  The server starts on `http://localhost:3000` by default.
@@ -220,14 +220,14 @@ The server starts on `http://localhost:3000` by default.
220
220
 
221
221
  ::: code-group
222
222
 
223
- ```bash [global]
224
- webflo start --dev --open --port 8080
225
- ```
226
-
227
223
  ```bash [npm]
228
224
  npm run dev -- --open --port 8080
229
225
  ```
230
226
 
227
+ ```bash [global]
228
+ webflo start --dev --open --port 8080
229
+ ```
230
+
231
231
  :::
232
232
 
233
233
  ### Webflo Build
@@ -254,14 +254,14 @@ Start the production server when ready:
254
254
 
255
255
  ::: code-group
256
256
 
257
- ```bash [global]
258
- webflo start
259
- ```
260
-
261
257
  ```bash [npm]
262
258
  npm start
263
259
  ```
264
260
 
261
+ ```bash [global]
262
+ webflo start
263
+ ```
264
+
265
265
  Your app runs in production mode. Production can be in any filesystem-enabled JavaScript runtime.
266
266
 
267
267
  ::: warning Production differences
@@ -354,22 +354,22 @@ You’ve:
354
354
 
355
355
  ```bash
356
356
  # Initialize a new project
357
- webflo init <project-name>
357
+ npx webflo init <project-name>
358
358
 
359
359
  # Start development server
360
- webflo start --dev
360
+ npx webflo start --dev
361
361
 
362
362
  # Start production server
363
- webflo start
363
+ npx webflo start
364
364
 
365
365
  # Build for production
366
- webflo build
366
+ npx webflo build
367
367
 
368
368
  # Configure Webflo
369
- webflo config
369
+ npx webflo config
370
370
 
371
371
  # View help
372
- webflo --help
372
+ npx webflo --help
373
373
  ```
374
374
 
375
375
  </details>
@@ -1,4 +1,4 @@
1
- export class Context {
1
+ export class CLIContext {
2
2
 
3
3
  constructor(dict, CD = null) {
4
4
  // dict can be plain object or some Context instance itself
@@ -14,15 +14,16 @@ export class Context {
14
14
  }
15
15
  }
16
16
 
17
- get name() {
18
- return 'webflo';
19
- }
20
-
21
17
  // create
22
18
  static create(...args) {
23
19
  return new this(...args);
24
20
  }
25
21
 
22
+ // name
23
+ get name() {
24
+ return 'webflo';
25
+ }
26
+
26
27
  // CWD
27
28
  get CWD() {
28
29
  return this.dict.CWD || '';
@@ -33,7 +34,7 @@ export class Context {
33
34
  return this.dict.meta || {};
34
35
  }
35
36
 
36
- // app
37
+ // appMeta
37
38
  get appMeta() {
38
39
  return this.dict.appMeta || {};
39
40
  }
@@ -63,10 +64,10 @@ export class Context {
63
64
 
64
65
  // logger
65
66
  get logger() {
66
- return this.dict.logger;
67
+ return this.dict.logger || console;
67
68
  }
68
69
 
69
70
  set logger(value) {
70
71
  Object.defineProperty(this.dict, 'logger', { value } );
71
72
  }
72
- }
73
+ }
@@ -0,0 +1,42 @@
1
+ import Fs from 'fs/promises';
2
+ import { parse, compile, matchPrologDirective, serialize } from '@webqit/use-live';
3
+
4
+ export function UseLiveTransform() {
5
+ return {
6
+ name: 'uselive-transform',
7
+ setup(build) {
8
+ build.onLoad({ filter: /\.(js|mjs|ts|jsx|tsx)$/ }, async (args) => {
9
+ const code = await Fs.readFile(args.path, 'utf8');
10
+
11
+ // Super dirty detection
12
+ if (matchPrologDirective(code)) {
13
+ // Actual check...
14
+
15
+ let ast;
16
+ try { ast = parse(code, parserParams); } catch (e) { console.error(args.path, '\nUseLive transform error:', e); }
17
+
18
+ if (ast?.isLiveProgram || ast?.hasLiveFunctions) {
19
+ const result = await compile(parserParams.sourceType+'-file', ast, {
20
+ liveMode: ast.isLiveProgram, // Regarding top-level
21
+ fileName: args.path,
22
+ });
23
+ return { contents: serialize(result), loader: 'js' };
24
+ }
25
+ }
26
+
27
+ return { contents: code, loader: 'default' };
28
+ });
29
+ }
30
+ };
31
+ }
32
+
33
+ export const parserParams = {
34
+ ecmaVersion: 'latest',
35
+ sourceType: 'module',
36
+ executionMode: 'RegularProgram', // 'LiveProgram'
37
+ allowReturnOutsideFunction: true,
38
+ allowAwaitOutsideFunction: true,
39
+ allowSuperOutsideMethod: false,
40
+ preserveParens: false,
41
+ locations: true,
42
+ };