bigpipe-util 0.2.11 → 0.2.13

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/CHANGELOG.md CHANGED
@@ -4,17 +4,23 @@ All notable changes to `bigpipe-util` will be documented in this file.
4
4
 
5
5
  Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
6
6
 
7
+ ## v0.2.13 - 2026-04-05
8
+ ### Fixed
9
+ - submitter and \_\_DEV\_\_ checks
10
+
11
+ ## v0.2.12 - 2026-01-12
12
+ ### Added
13
+ - Added `Pagelet.js`, `BigPipe.js`
14
+
7
15
  ## v0.2.11 - 2025-12-27
16
+ ### Added
8
17
  - Added `incrementRequests` method to AsyncRequest.js
9
18
  - Added submitter to form data in onSubmit handler
10
19
 
11
- ### Added
12
- - Added `timeout` to open dialog with a delay in method `showFromModel`
13
-
14
20
  ## v0.2.10 - 2025-05-15
15
- - Added `onAfterLoad.js`, `waitForLoad.js`
16
21
 
17
22
  ### Added
23
+ - Added `onAfterLoad.js`, `waitForLoad.js`
18
24
  - Added `timeout` to open dialog with a delay in method `showFromModel`
19
25
 
20
26
  ## v0.2.8 - 2025-05-02
package/bigpipe.svg CHANGED
@@ -1,13 +1,13 @@
1
1
  <svg width="254.23999568394254" height="64.8" viewBox="0 0 254.23999568394254 64.8" xmlns="http://www.w3.org/2000/svg">
2
- <g transform="matrix(3.0856473041983374,0,0,3.0856473041983374,-6.171373325690784,-6.1698442238608315)" fill="#0C056D">
2
+ <g transform="matrix(3.0856473041983374,0,0,3.0856473041983374,-6.171373325690784,-6.1698442238608315)" fill="#478be6">
3
3
  <g xmlns="http://www.w3.org/2000/svg">
4
4
  <path d="M2.2427,8.9287a.5.5,0,0,1,0-.8574l10-6a.4971.4971,0,0,1,.5146,0l10,6a.5.5,0,0,1,0,.8574l-10,6a.5.5,0,0,1-.5146,0Zm20,3.1426L12.5,17.917,2.7573,12.0713a.5.5,0,0,0-.5146.8574l10,6a.5.5,0,0,0,.5146,0l10-6a.5.5,0,0,0-.5146-.8574Zm0,4L12.5,21.917,2.7573,16.0713a.5.5,0,0,0-.5146.8574l10,6a.5.5,0,0,0,.5146,0l10-6a.5.5,0,0,0-.5146-.8574Z"></path>
5
5
  </g>
6
6
  </g>
7
- <g transform="matrix(2.500000066227385,0,0,2.500000066227385,81.99999920527138,0.49999675485814166)" fill="#0C056D">
7
+ <g transform="matrix(2.500000066227385,0,0,2.500000066227385,81.99999920527138,0.49999675485814166)" fill="#478be6">
8
8
  <path d="M8.92 12.24 c1.48 0.4 2.88 1.72 2.88 3.66 c0 2.54 -1.6 4.1 -5.08 4.1 l-5.52 0 l0 -14 l5.22 0 c2.9 0 4.36 1.64 4.36 3.52 c0 1.48 -0.96 2.32 -1.86 2.72 z M6.14 8.56 l-2.02 0 l0 2.82 l2.02 0 c1.24 0 1.74 -0.58 1.74 -1.42 c0 -0.9 -0.58 -1.4 -1.74 -1.4 z M6.56 17.44 c1.6 0 2.32 -0.66 2.32 -1.9 c0 -1.06 -0.72 -1.8 -2.44 -1.8 l-2.32 0 l0 3.7 l2.44 0 z M16.62 6 l0 14 l-2.92 0 l0 -14 l2.92 0 z M32.2 12.559999999999999 c0.62 4.7 -2.44 7.64 -6.22 7.64 c-3.98 0 -7.26 -2.94 -7.26 -7.2 s3.36 -7.2 7.26 -7.2 c1.86 0 3.5 0.6 4.7 1.66 l-1.76 1.98 c-0.74 -0.52 -1.74 -0.9 -2.76 -0.9 c-2.54 0 -4.4 1.86 -4.4 4.46 s1.8 4.46 4.22 4.46 c1.84 0 3.12 -0.66 3.52 -2.42 l-3.3 0 l0 -2.48 l6 0 z"></path>
9
9
  </g>
10
- <g transform="matrix(2.5714285714285716,0,0,2.5714285714285716,166.91428559167045,-0.4285714285714306)" fill="#0C056D">
10
+ <g transform="matrix(2.5714285714285716,0,0,2.5714285714285716,166.91428559167045,-0.4285714285714306)" fill="#478be6">
11
11
  <path d="M5.46 6 c3.14 0 4.8 1.94 4.8 4.3 c0 2.42 -1.66 4.34 -4.8 4.34 l-3.68 0 l0 5.36 l-0.58 0 l0 -14 l4.26 0 z M5.54 14.04 c2.52 0 4.14 -1.46 4.14 -3.74 c0 -2.32 -1.62 -3.72 -4.14 -3.72 l-3.76 0 l0 7.46 l3.76 0 z M12.74 6 l0 14 l-0.58 0 l0 -14 l0.58 0 z M19.400000000000002 6 c3.14 0 4.8 1.94 4.8 4.3 c0 2.42 -1.66 4.34 -4.8 4.34 l-3.68 0 l0 5.36 l-0.58 0 l0 -14 l4.26 0 z M19.48 14.04 c2.52 0 4.14 -1.46 4.14 -3.74 c0 -2.32 -1.62 -3.72 -4.14 -3.72 l-3.76 0 l0 7.46 l3.76 0 z M26.680000000000003 19.42 l7.28 0 l0 0.58 l-7.68 0 l-0.18 0 l0 -14 l0.58 0 l7.1 0 l0 0.58 l-7.1 0 l0 6.1 l5.66 0 l0 0.58 l-5.66 0 l0 6.16 z"></path>
12
12
  </g>
13
13
  </svg>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bigpipe-util",
3
3
  "description": "This library currently implements small part of Facebook BigPipe so far, but the advantage is to efficiently insert/replace content and work with the DOM. It is also possible to easily call JavaScript modules from PHP.",
4
- "version": "0.2.11",
4
+ "version": "0.2.13",
5
5
  "keywords": [
6
6
  "bigpipe",
7
7
  "xhr",
package/src/BigPipe.js ADDED
@@ -0,0 +1,170 @@
1
+ export const BIGPIPE_PHASE = Object.freeze({
2
+ WAITING: 0,
3
+ READY_FOR_JS: 1,
4
+ LOADING_JS: 2,
5
+ COMPLETE: 3
6
+ });
7
+
8
+ export const RESOURCE_PHASE = Object.freeze({
9
+ PENDING: 0,
10
+ LOADING: 1,
11
+ LOADED: 2
12
+ });
13
+
14
+ let instance = null;
15
+
16
+ export default class BigPipe {
17
+ pageletResources = new Map();
18
+ pagelets = new Map();
19
+ phase = BIGPIPE_PHASE.WAITING;
20
+
21
+ constructor() {
22
+ if (instance) {
23
+ return instance;
24
+ }
25
+ instance = this;
26
+ }
27
+
28
+ onPageletArrive(pageletData) {
29
+ const Pagelet = require("./Pagelet").default;
30
+
31
+ if (pageletData.is_last) {
32
+ this.setPhase(BIGPIPE_PHASE.READY_FOR_JS);
33
+ }
34
+
35
+ const pagelet = new Pagelet(pageletData);
36
+ this.registerPagelet(pagelet);
37
+ pagelet.start();
38
+ }
39
+
40
+ fileLoaded(filename) {
41
+ const resource = this.pageletResources.get(filename);
42
+ if (resource) {
43
+ resource.onComplete();
44
+ }
45
+ }
46
+
47
+ registerPagelet(pagelet) {
48
+ this.pagelets.set(pagelet.id, pagelet);
49
+ }
50
+
51
+ areAllPageletsReady() {
52
+ if (this.pagelets.size === 0) {
53
+ return false;
54
+ }
55
+
56
+ return Array.from(this.pagelets.values()).every(
57
+ pagelet => pagelet.phase >= 3
58
+ );
59
+ }
60
+
61
+ pageletDisplayed() {
62
+ if (!this.areAllPageletsReady()) {
63
+ return;
64
+ }
65
+
66
+ if (this.phase === BIGPIPE_PHASE.READY_FOR_JS) {
67
+ this.loadJSResources();
68
+ }
69
+ }
70
+
71
+ setPhase(newPhase) {
72
+ this.phase = newPhase;
73
+ }
74
+
75
+ loadJSResources() {
76
+ this.setPhase(BIGPIPE_PHASE.LOADING_JS);
77
+
78
+ const jsResources = this.getResourcesByType('js');
79
+
80
+ jsResources.forEach(resource => {
81
+ resource.startLoading();
82
+ });
83
+
84
+ this.notifyPageletsWithoutResources();
85
+
86
+ if (jsResources.length === 0) {
87
+ this.notifyAllPagelets();
88
+ }
89
+ }
90
+
91
+ getResourcesByType(type) {
92
+ return Array.from(this.pageletResources.values())
93
+ .filter(resource => resource.type === type);
94
+ }
95
+
96
+ notifyPageletsWithoutResources() {
97
+ this.pagelets.forEach(pagelet => {
98
+ if (pagelet.jsResources.size === 0) {
99
+ pagelet.onJsFinished();
100
+ }
101
+ });
102
+ }
103
+
104
+ notifyAllPagelets() {
105
+ this.pagelets.forEach(pagelet => {
106
+ pagelet.onJsFinished();
107
+ });
108
+ }
109
+
110
+ extractFilename(filePath) {
111
+ const match = /([^/]+)$/.exec(filePath);
112
+ return match ? match[1] : filePath;
113
+ }
114
+
115
+ pageletResourceFactory(file, type) {
116
+ const name = this.extractFilename(file);
117
+
118
+ let resource = this.pageletResources.get(name);
119
+ if (!resource) {
120
+ resource = this.createResource(file, name, type);
121
+ this.pageletResources.set(name, resource);
122
+ }
123
+
124
+ return resource;
125
+ }
126
+
127
+ createResource(file, name, type) {
128
+ const self = this;
129
+
130
+ return {
131
+ file,
132
+ name,
133
+ type,
134
+ phase: RESOURCE_PHASE.PENDING,
135
+ callbacks: [],
136
+
137
+ startLoading() {
138
+ if (this.phase !== RESOURCE_PHASE.PENDING) {
139
+ return;
140
+ }
141
+
142
+ this.phase = RESOURCE_PHASE.LOADING;
143
+
144
+ if (type === 'css') {
145
+ const link = document.createElement('link');
146
+ link.rel = 'stylesheet';
147
+ link.href = file;
148
+ link.onload = () => self.fileLoaded(name);
149
+ link.onerror = () => self.fileLoaded(name);
150
+ document.head.appendChild(link);
151
+ } else if (type === 'js') {
152
+ const script = document.createElement('script');
153
+ script.src = file;
154
+ script.onload = () => self.fileLoaded(name);
155
+ script.onerror = () => self.fileLoaded(name);
156
+ document.body.appendChild(script);
157
+ }
158
+ },
159
+
160
+ onComplete() {
161
+ this.phase = RESOURCE_PHASE.LOADED;
162
+ this.callbacks.forEach(callback => callback(this));
163
+ },
164
+
165
+ attachToPagelet(callback) {
166
+ this.callbacks.push(callback);
167
+ }
168
+ };
169
+ }
170
+ }
package/src/Pagelet.js ADDED
@@ -0,0 +1,131 @@
1
+ import BigPipe, { RESOURCE_PHASE } from "./BigPipe";
2
+ import ServerJS from "./ServerJS";
3
+ import AsyncDOM from "./async/AsyncDOM";
4
+
5
+ const serverJS = new ServerJS();
6
+ const asyncDOM = new AsyncDOM();
7
+ const bigPipe = new BigPipe();
8
+
9
+ export const PAGELET_PHASE = Object.freeze({
10
+ ARRIVE_START: 0,
11
+ LOADING_CSS: 1,
12
+ DISPLAY_START: 2,
13
+ DISPLAY_END: 3,
14
+ ARRIVE_END: 4
15
+ });
16
+
17
+ function areAllResourcesLoaded(resources) {
18
+ if (resources.size === 0) {
19
+ return true;
20
+ }
21
+ return Array.from(resources.values()).every(
22
+ resource => resource.phase === RESOURCE_PHASE.LOADED
23
+ );
24
+ }
25
+
26
+ export default class Pagelet {
27
+ cssResources = new Map();
28
+
29
+ jsResources = new Map();
30
+
31
+ id = "";
32
+
33
+ phase = PAGELET_PHASE.ARRIVE_START;
34
+
35
+ constructor(pageletData) {
36
+ this.bigPipe = bigPipe;
37
+ this.id = pageletData.id;
38
+ this.phase = PAGELET_PHASE.ARRIVE_START;
39
+ this.domops = pageletData.domops;
40
+ this.jsmods = pageletData.jsmods;
41
+ this.css = pageletData.css;
42
+ this.js = pageletData.js;
43
+ this.cssResources = new Map();
44
+ this.jsResources = new Map();
45
+ }
46
+
47
+ isComplete() {
48
+ return this.phase === PAGELET_PHASE.ARRIVE_END;
49
+ }
50
+
51
+ setPhase(phase) {
52
+ this.phase = phase;
53
+ }
54
+
55
+ start() {
56
+ if (this.phase !== PAGELET_PHASE.ARRIVE_START) {
57
+ return;
58
+ }
59
+
60
+ if (this.css.length > 0) {
61
+ this.css.forEach((cssFile) => {
62
+ const cssResource = this.bigPipe.pageletResourceFactory(cssFile, 'css');
63
+ this.attachCssResource(cssResource);
64
+ });
65
+ }
66
+
67
+ if (this.js.length > 0) {
68
+ this.js.forEach((jsFile) => {
69
+ const jsResource = this.bigPipe.pageletResourceFactory(jsFile, 'js');
70
+ this.attachJsResource(jsResource);
71
+ });
72
+ }
73
+
74
+ if (this.cssResources.size > 0) {
75
+ this.setPhase(PAGELET_PHASE.LOADING_CSS);
76
+ this.cssResources.forEach((resource) => {
77
+ resource.startLoading();
78
+ });
79
+ } else {
80
+ this.setContent();
81
+ this.onJsFinished();
82
+ }
83
+ }
84
+
85
+ attachCssResource(resource) {
86
+ resource.attachToPagelet(() => this.onCssFinished(resource));
87
+ this.cssResources.set(resource.file, resource);
88
+ }
89
+
90
+ attachJsResource(resource) {
91
+ resource.attachToPagelet(() => this.onJsFinished());
92
+ this.jsResources.set(resource.file, resource);
93
+ }
94
+
95
+ onJsFinished() {
96
+ if (this.isComplete()) {
97
+ return;
98
+ }
99
+
100
+ if (!areAllResourcesLoaded(this.jsResources)) {
101
+ return;
102
+ }
103
+
104
+ this.processJsMods();
105
+
106
+ this.setPhase(PAGELET_PHASE.ARRIVE_END);
107
+ }
108
+
109
+ processJsMods() {
110
+ serverJS.handle(this.jsmods);
111
+ }
112
+
113
+ onCssFinished() {
114
+ if (!areAllResourcesLoaded(this.cssResources)) {
115
+ return;
116
+ }
117
+
118
+ this.setContent();
119
+ }
120
+
121
+ setContent() {
122
+ this.setPhase(PAGELET_PHASE.DISPLAY_START);
123
+
124
+ if (this.domops) {
125
+ asyncDOM.invoke(this.domops);
126
+ }
127
+
128
+ this.setPhase(PAGELET_PHASE.DISPLAY_END);
129
+ this.bigPipe.pageletDisplayed(this);
130
+ }
131
+ }
package/src/Primer.js CHANGED
@@ -87,7 +87,7 @@ export default function Primer() {
87
87
  const submitter = event.submitter || eventTarget.querySelector("button[type='submit']");
88
88
  const activeControls = eventTarget.querySelectorAll("input:not([readonly]),select:not([readonly]),textarea:not([readonly])");
89
89
 
90
- if (submitter.name) {
90
+ if (submitter && submitter.name) {
91
91
  formData.append(submitter.name, submitter.value);
92
92
  }
93
93
 
@@ -136,12 +136,16 @@ export default function Primer() {
136
136
  }
137
137
  })
138
138
  .setErrorHandler(function () {
139
- eventTarget.classList.add("async-saving");
139
+ eventTarget.classList.remove("async-saving");
140
140
 
141
141
  activeControls.forEach(function (control) {
142
142
  control.removeAttribute("readonly");
143
143
  });
144
144
 
145
+ if (submitter) {
146
+ submitter.disabled = false;
147
+ }
148
+
145
149
  if (eventTarget) {
146
150
  const loader = eventTarget.querySelector(".small-loader");
147
151
 
package/src/ServerJS.js CHANGED
@@ -2,7 +2,7 @@ import replaceTransportMarkers from "./core/replaceTransportMarkers";
2
2
 
3
3
  function handler(dependencies, guard, context) {
4
4
  return dependencies.map(function (args) {
5
- guard.apply(context, args);
5
+ return guard.apply(context, args);
6
6
  });
7
7
  }
8
8
 
@@ -11,8 +11,10 @@ export default class AsyncDOM {
11
11
  node = (node || document.documentElement).querySelector(selector);
12
12
  }
13
13
 
14
- if (__DEV__ && !node) {
15
- console.error(`Selector '${selector}' does not match anything!`)
14
+ if (!node) {
15
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
16
+ console.error(`Selector '${selector}' does not match anything!`)
17
+ }
16
18
  continue;
17
19
  }
18
20
 
@@ -46,6 +48,7 @@ export default class AsyncDOM {
46
48
  break;
47
49
  case "replace":
48
50
  DOM.replace(node, content.__html);
51
+ break;
49
52
  }
50
53
  }
51
54
  }
@@ -154,7 +154,7 @@ export default class AsyncRequest {
154
154
  const handler = this.getHandler();
155
155
  const errorHandler = this.getErrorHandler();
156
156
  const initialHandler = this.getInitialHandler();
157
- const getFinallyHandler = this.getFinallyHandler();
157
+ const finallyHandler = this.getFinallyHandler();
158
158
 
159
159
  initialHandler();
160
160
 
@@ -168,7 +168,7 @@ export default class AsyncRequest {
168
168
  let response;
169
169
  try {
170
170
  const safeJson = self._unshieldResponseText(this.responseText);
171
- response = eval("(" + safeJson + ")");
171
+ response = JSON.parse(safeJson);
172
172
  } catch (e) {
173
173
  throw new Error("Failed to handle response: " + e.message + "\n" + this.responseText);
174
174
  }
@@ -180,7 +180,7 @@ export default class AsyncRequest {
180
180
  errorHandler(this);
181
181
  }
182
182
 
183
- getFinallyHandler(this);
183
+ finallyHandler(this);
184
184
  }
185
185
 
186
186
  request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
@@ -18,7 +18,7 @@ export default function replaceTransportMarkers(markerValue, markers, key) {
18
18
  } else if (marker.__set) {
19
19
  markers[key] = new Set(marker.__set);
20
20
  } else {
21
- for (let value in marker) {
21
+ for (const value in marker) {
22
22
  replaceTransportMarkers(markerValue, marker, value);
23
23
  }
24
24
  }