bigpipe-util 0.2.12 → 0.2.14

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/README.md CHANGED
@@ -1,124 +1,124 @@
1
- <img src="bigpipe.svg" alt="BigPipe logo">
2
-
3
- This library currently implements small part of [Facebook BigPipe][blog] 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
-
5
-
6
- ## 👀 Demo App
7
- Try the app with [live demo](http://bigpipe.xf.cz).
8
-
9
- ## 📕 Full documentation
10
- https://richarddobron.github.io/bigpipe-php/
11
-
12
- ## ℹ️ Requirements
13
- * PHP 7.1 or higher
14
- * Webpack
15
-
16
- ## 📦 Installation
17
- Follow these steps to install and set up:
18
-
19
- ### 1. Install composer package:
20
- ```shell
21
- $ composer require richarddobron/bigpipe
22
- ```
23
-
24
- ### 2. Install npm package:
25
- ```shell
26
- $ npm install bigpipe-util
27
- ```
28
-
29
- ### 3. Add the following to /path/to/resources/js/app.js:
30
- ```javascript
31
- import Primer from 'bigpipe-util/src/Primer';
32
-
33
- Primer();
34
-
35
- window.require = (modulePath) => {
36
- return modulePath.startsWith('bigpipe-util/')
37
- ? require('bigpipe-util/' + modulePath.substring(13) + '.js').default
38
- : require('./' + modulePath).default;
39
- };
40
- ```
41
-
42
- ### 4. Add these lines to the page footer:
43
- ```html
44
- <script>
45
- (new (require("bigpipe-util/src/ServerJS"))).handle(<?=json_encode(\dobron\BigPipe\BigPipe::jsmods())?>);
46
- </script>
47
- ```
48
-
49
- ## 🛜 Request API
50
-
51
- ```javascript
52
- import AsyncRequest from 'bigpipe-util/src/AsyncRequest';
53
-
54
- const request = (new AsyncRequest('/ajax/remove.php'))
55
- // or .setURI('/ajax/remove.php')
56
- .setMethod('POST')
57
- .setData({
58
- param: 'value',
59
- })
60
- .setInitialHandler(() => {
61
- // pre-request callback function
62
- })
63
- .setHandler((jsonResponse) => {
64
- // A function to be called if the request succeeds
65
- })
66
- .setErrorHandler((xhr) => {
67
- // A function to be called if the request fails
68
- })
69
- .setFinallyHandler((xhr) => {
70
- // after request callback function
71
- })
72
- .send();
73
-
74
- if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
75
- request.abort();
76
- }
77
- ```
78
-
79
- # ⚡️ What all can be Ajaxifed?
80
-
81
- ## 🔗 Links
82
- ```html
83
- <a href="#"
84
- ajaxify="/ajax/remove.php"
85
- rel="async">Remove Item</a>
86
- ```
87
-
88
- ## 📝 Forms
89
- ```html
90
- <form action="/submit.php"
91
- method="POST"
92
- rel="async">
93
- <input name="user">
94
- <input type="submit" name="Done">
95
- </form>
96
- ```
97
-
98
- ## 💬 Dialogs
99
- ```html
100
- <a href="#"
101
- ajaxify="/ajax/modal.php"
102
- rel="dialog">Open Modal</a>
103
- ```
104
-
105
- ## 🌟 Inspiration
106
-
107
- BigPipe is inspired by Facebook's BigPipe. For more details
108
- read their blog post: [Pipelining web pages for high performance][blog].
109
-
110
- ## 💡 Motivation
111
-
112
- There is a large number of PHP projects for which moving to modern frameworks like Laravel Livewire, React, Vue.js (and many more!) could be very challenging.
113
-
114
- The purpose of this library is to rapidly reduce the continuously repetitive code to work with the DOM and improve the communication barrier between PHP and JavaScript.
115
-
116
- ## 🤝 Contributing
117
-
118
- We welcome contributions! If you'd like to help improve this project, feel free to open an issue or submit a pull request.
119
-
120
- ## 📜 License
121
-
122
- The MIT License (MIT). Please see [License File](LICENSE) for more information.
123
-
124
- [blog]: https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
1
+ <img src="bigpipe.svg" alt="BigPipe logo">
2
+
3
+ This library currently implements small part of [Facebook BigPipe][blog] 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
+
5
+
6
+ ## 👀 Demo App
7
+ Try the app with [live demo](http://bigpipe.xf.cz).
8
+
9
+ ## 📕 Full documentation
10
+ https://richarddobron.github.io/bigpipe-php/
11
+
12
+ ## ℹ️ Requirements
13
+ * PHP 7.1 or higher
14
+ * Webpack
15
+
16
+ ## 📦 Installation
17
+ Follow these steps to install and set up:
18
+
19
+ ### 1. Install composer package:
20
+ ```shell
21
+ $ composer require richarddobron/bigpipe
22
+ ```
23
+
24
+ ### 2. Install npm package:
25
+ ```shell
26
+ $ npm install bigpipe-util
27
+ ```
28
+
29
+ ### 3. Add the following to /path/to/resources/js/app.js:
30
+ ```javascript
31
+ import Primer from 'bigpipe-util/dist/Primer';
32
+
33
+ Primer();
34
+
35
+ window.require = (modulePath) => {
36
+ return modulePath.startsWith('bigpipe-util/')
37
+ ? require('bigpipe-util/' + modulePath.substring(13) + '.js').default
38
+ : require('./' + modulePath).default;
39
+ };
40
+ ```
41
+
42
+ ### 4. Add these lines to the page footer:
43
+ ```html
44
+ <script>
45
+ (new (require("bigpipe-util/dist/ServerJS"))).handle(<?=json_encode(\dobron\BigPipe\BigPipe::jsmods())?>);
46
+ </script>
47
+ ```
48
+
49
+ ## 🛜 Request API
50
+
51
+ ```javascript
52
+ import AsyncRequest from 'bigpipe-util/dist/AsyncRequest';
53
+
54
+ const request = (new AsyncRequest('/ajax/remove.php'))
55
+ // or .setURI('/ajax/remove.php')
56
+ .setMethod('POST')
57
+ .setData({
58
+ param: 'value',
59
+ })
60
+ .setInitialHandler(() => {
61
+ // pre-request callback function
62
+ })
63
+ .setHandler((jsonResponse) => {
64
+ // A function to be called if the request succeeds
65
+ })
66
+ .setErrorHandler((xhr) => {
67
+ // A function to be called if the request fails
68
+ })
69
+ .setFinallyHandler((xhr) => {
70
+ // after request callback function
71
+ })
72
+ .send();
73
+
74
+ if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
75
+ request.abort();
76
+ }
77
+ ```
78
+
79
+ # ⚡️ What all can be Ajaxifed?
80
+
81
+ ## 🔗 Links
82
+ ```html
83
+ <a href="#"
84
+ ajaxify="/ajax/remove.php"
85
+ rel="async">Remove Item</a>
86
+ ```
87
+
88
+ ## 📝 Forms
89
+ ```html
90
+ <form action="/submit.php"
91
+ method="POST"
92
+ rel="async">
93
+ <input name="user">
94
+ <input type="submit" name="Done">
95
+ </form>
96
+ ```
97
+
98
+ ## 💬 Dialogs
99
+ ```html
100
+ <a href="#"
101
+ ajaxify="/ajax/modal.php"
102
+ rel="dialog">Open Modal</a>
103
+ ```
104
+
105
+ ## 🌟 Inspiration
106
+
107
+ BigPipe is inspired by Facebook's BigPipe. For more details
108
+ read their blog post: [Pipelining web pages for high performance][blog].
109
+
110
+ ## 💡 Motivation
111
+
112
+ There is a large number of PHP projects for which moving to modern frameworks like Laravel Livewire, React, Vue.js (and many more!) could be very challenging.
113
+
114
+ The purpose of this library is to rapidly reduce the continuously repetitive code to work with the DOM and improve the communication barrier between PHP and JavaScript.
115
+
116
+ ## 🤝 Contributing
117
+
118
+ We welcome contributions! If you'd like to help improve this project, feel free to open an issue or submit a pull request.
119
+
120
+ ## 📜 License
121
+
122
+ The MIT License (MIT). Please see [License File](LICENSE) for more information.
123
+
124
+ [blog]: https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
@@ -1,170 +1,148 @@
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
- }
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.RESOURCE_PHASE = exports.PAGELET_PHASE = exports.BIGPIPE_PHASE = void 0;
7
+ const BIGPIPE_PHASE = exports.BIGPIPE_PHASE = Object.freeze({
8
+ WAITING: 0,
9
+ READY_FOR_JS: 1,
10
+ LOADING_JS: 2,
11
+ COMPLETE: 3
12
+ });
13
+ const RESOURCE_PHASE = exports.RESOURCE_PHASE = Object.freeze({
14
+ PENDING: 0,
15
+ LOADING: 1,
16
+ LOADED: 2
17
+ });
18
+ const PAGELET_PHASE = exports.PAGELET_PHASE = Object.freeze({
19
+ ARRIVE_START: 0,
20
+ LOADING_CSS: 1,
21
+ DISPLAY_START: 2,
22
+ DISPLAY_END: 3,
23
+ ARRIVE_END: 4
24
+ });
25
+ let instance = null;
26
+ class BigPipe {
27
+ pageletResources = new Map();
28
+ pagelets = new Map();
29
+ phase = BIGPIPE_PHASE.WAITING;
30
+ constructor() {
31
+ if (instance) {
32
+ return instance;
33
+ }
34
+ instance = this;
35
+ }
36
+ onPageletArrive(pageletData) {
37
+ const Pagelet = require('./Pagelet').default;
38
+ if (pageletData.is_last) {
39
+ this.setPhase(BIGPIPE_PHASE.READY_FOR_JS);
40
+ }
41
+ const pagelet = new Pagelet(pageletData);
42
+ this.registerPagelet(pagelet);
43
+ pagelet.start();
44
+ }
45
+ fileLoaded(filename) {
46
+ const resource = this.pageletResources.get(filename);
47
+ if (resource) {
48
+ resource.onComplete();
49
+ }
50
+ }
51
+ registerPagelet(pagelet) {
52
+ this.pagelets.set(pagelet.id, pagelet);
53
+ }
54
+ areAllPageletsReady() {
55
+ if (this.pagelets.size === 0) {
56
+ return false;
57
+ }
58
+ return Array.from(this.pagelets.values()).every(pagelet => pagelet.phase >= PAGELET_PHASE.ARRIVE_END);
59
+ }
60
+ pageletDisplayed() {
61
+ if (!this.areAllPageletsReady()) {
62
+ return;
63
+ }
64
+ if (this.phase === BIGPIPE_PHASE.READY_FOR_JS) {
65
+ this.loadJSResources();
66
+ }
67
+ }
68
+ setPhase(newPhase) {
69
+ this.phase = newPhase;
70
+ }
71
+ loadJSResources() {
72
+ this.setPhase(BIGPIPE_PHASE.LOADING_JS);
73
+ const jsResources = this.getResourcesByType('js');
74
+ jsResources.forEach(resource => {
75
+ resource.startLoading();
76
+ });
77
+ this.notifyPageletsWithoutResources();
78
+ if (jsResources.length === 0) {
79
+ this.notifyAllPagelets();
80
+ }
81
+ }
82
+ getResourcesByType(type) {
83
+ return Array.from(this.pageletResources.values()).filter(resource => resource.type === type);
84
+ }
85
+ notifyPageletsWithoutResources() {
86
+ this.pagelets.forEach(pagelet => {
87
+ if (pagelet.jsResources.size === 0) {
88
+ pagelet.onJsFinished();
89
+ }
90
+ });
91
+ }
92
+ notifyAllPagelets() {
93
+ this.pagelets.forEach(pagelet => {
94
+ pagelet.onJsFinished();
95
+ });
96
+ }
97
+ extractFilename(filePath) {
98
+ const match = /([^/]+)$/.exec(filePath);
99
+ return match ? match[1] : filePath;
100
+ }
101
+ pageletResourceFactory(file, type) {
102
+ const name = this.extractFilename(file);
103
+ let resource = this.pageletResources.get(name);
104
+ if (!resource) {
105
+ resource = this.createResource(file, name, type);
106
+ this.pageletResources.set(name, resource);
107
+ }
108
+ return resource;
109
+ }
110
+ createResource(file, name, type) {
111
+ const self = this;
112
+ return {
113
+ file,
114
+ name,
115
+ type,
116
+ phase: RESOURCE_PHASE.PENDING,
117
+ callbacks: [],
118
+ startLoading() {
119
+ if (this.phase !== RESOURCE_PHASE.PENDING) {
120
+ return;
121
+ }
122
+ this.phase = RESOURCE_PHASE.LOADING;
123
+ if (type === 'css') {
124
+ const link = document.createElement('link');
125
+ link.rel = 'stylesheet';
126
+ link.href = file;
127
+ link.onload = () => self.fileLoaded(name);
128
+ link.onerror = () => self.fileLoaded(name);
129
+ document.head.appendChild(link);
130
+ } else if (type === 'js') {
131
+ const script = document.createElement('script');
132
+ script.src = file;
133
+ script.onload = () => self.fileLoaded(name);
134
+ script.onerror = () => self.fileLoaded(name);
135
+ document.body.appendChild(script);
136
+ }
137
+ },
138
+ onComplete() {
139
+ this.phase = RESOURCE_PHASE.LOADED;
140
+ this.callbacks.forEach(callback => callback(this));
141
+ },
142
+ attachToPagelet(callback) {
143
+ this.callbacks.push(callback);
144
+ }
145
+ };
146
+ }
147
+ }
148
+ exports.default = BigPipe;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "PAGELET_PHASE", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _BigPipe.PAGELET_PHASE;
10
+ }
11
+ });
12
+ exports.default = void 0;
13
+ var _BigPipe = _interopRequireWildcard(require("./BigPipe"));
14
+ var _ServerJS = _interopRequireDefault(require("./ServerJS"));
15
+ var _AsyncDOM = _interopRequireDefault(require("./async/AsyncDOM"));
16
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
17
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
18
+ const serverJS = new _ServerJS.default();
19
+ const asyncDOM = new _AsyncDOM.default();
20
+ const bigPipe = new _BigPipe.default();
21
+ function areAllResourcesLoaded(resources) {
22
+ if (resources.size === 0) {
23
+ return true;
24
+ }
25
+ return Array.from(resources.values()).every(resource => resource.phase === _BigPipe.RESOURCE_PHASE.LOADED);
26
+ }
27
+ class Pagelet {
28
+ cssResources = new Map();
29
+ jsResources = new Map();
30
+ id = '';
31
+ phase = _BigPipe.PAGELET_PHASE.ARRIVE_START;
32
+ constructor(pageletData) {
33
+ this.bigPipe = bigPipe;
34
+ this.id = pageletData.id;
35
+ this.phase = _BigPipe.PAGELET_PHASE.ARRIVE_START;
36
+ this.domops = pageletData.domops;
37
+ this.jsmods = pageletData.jsmods;
38
+ this.css = pageletData.css;
39
+ this.js = pageletData.js;
40
+ this.cssResources = new Map();
41
+ this.jsResources = new Map();
42
+ }
43
+ isComplete() {
44
+ return this.phase === _BigPipe.PAGELET_PHASE.ARRIVE_END;
45
+ }
46
+ setPhase(phase) {
47
+ this.phase = phase;
48
+ }
49
+ start() {
50
+ if (this.phase !== _BigPipe.PAGELET_PHASE.ARRIVE_START) {
51
+ return;
52
+ }
53
+ if (this.css.length > 0) {
54
+ this.css.forEach(cssFile => {
55
+ const cssResource = this.bigPipe.pageletResourceFactory(cssFile, 'css');
56
+ this.attachCssResource(cssResource);
57
+ });
58
+ }
59
+ if (this.js.length > 0) {
60
+ this.js.forEach(jsFile => {
61
+ const jsResource = this.bigPipe.pageletResourceFactory(jsFile, 'js');
62
+ this.attachJsResource(jsResource);
63
+ });
64
+ }
65
+ if (this.cssResources.size > 0) {
66
+ this.setPhase(_BigPipe.PAGELET_PHASE.LOADING_CSS);
67
+ this.cssResources.forEach(resource => {
68
+ resource.startLoading();
69
+ });
70
+ } else {
71
+ this.setContent();
72
+ this.onJsFinished();
73
+ }
74
+ }
75
+ attachCssResource(resource) {
76
+ resource.attachToPagelet(() => this.onCssFinished(resource));
77
+ this.cssResources.set(resource.file, resource);
78
+ }
79
+ attachJsResource(resource) {
80
+ resource.attachToPagelet(() => this.onJsFinished());
81
+ this.jsResources.set(resource.file, resource);
82
+ }
83
+ onJsFinished() {
84
+ if (this.isComplete()) {
85
+ return;
86
+ }
87
+ if (!areAllResourcesLoaded(this.jsResources)) {
88
+ return;
89
+ }
90
+ this.processJsMods();
91
+ this.setPhase(_BigPipe.PAGELET_PHASE.ARRIVE_END);
92
+ }
93
+ processJsMods() {
94
+ serverJS.handle(this.jsmods);
95
+ }
96
+ onCssFinished() {
97
+ if (!areAllResourcesLoaded(this.cssResources)) {
98
+ return;
99
+ }
100
+ this.setContent();
101
+ }
102
+ setContent() {
103
+ this.setPhase(_BigPipe.PAGELET_PHASE.DISPLAY_START);
104
+ if (this.domops) {
105
+ asyncDOM.invoke(this.domops);
106
+ }
107
+ this.setPhase(_BigPipe.PAGELET_PHASE.DISPLAY_END);
108
+ this.bigPipe.pageletDisplayed(this);
109
+ }
110
+ }
111
+ exports.default = Pagelet;