diff options
-rwxr-xr-x | include/classes/class.bigpipe.php | 79 | ||||
-rwxr-xr-x | include/classes/class.pagelet.php | 84 | ||||
-rwxr-xr-x | include/functions.php | 12 | ||||
-rwxr-xr-x | index.php | 79 | ||||
-rwxr-xr-x | static/bigpipe-callbacks.js | 27 | ||||
-rwxr-xr-x | static/bigpipe.js | 274 | ||||
-rwxr-xr-x | static/blue.php | 17 | ||||
-rwxr-xr-x | static/delayJS.php | 16 | ||||
-rwxr-xr-x | static/green.php | 17 | ||||
-rwxr-xr-x | static/red.php | 17 |
10 files changed, 622 insertions, 0 deletions
diff --git a/include/classes/class.bigpipe.php b/include/classes/class.bigpipe.php new file mode 100755 index 0000000..1edefc8 --- /dev/null +++ b/include/classes/class.bigpipe.php @@ -0,0 +1,79 @@ +<?php +class BigPipe { + public static $enabled = TRUE; + private static $pagelets = []; + private static $count = 0; + + #==================================================================================================== + # Gibt TRUE zurück wenn BigPipe eingeschaltet ist + #==================================================================================================== + public static function isEnabled() { + return self::$enabled ? TRUE : FALSE; + } + + #==================================================================================================== + # Neues Pagelet zur Pipeline hinzufügen + #==================================================================================================== + public static function addPagelet(Pagelet $Pagelet, $priority) { + self::$pagelets[$priority][] = $Pagelet; + self::$count++; + } + + #==================================================================================================== + # Gibt einen einzelnen Pagelet-Response aus + #==================================================================================================== + private static function pageletResponse(Pagelet $Pagelet, $async = FALSE, $last = FALSE) { + $data = [ + 'ID' => $Pagelet->getID(), + 'RESOURCES' => ['CSS' => $Pagelet->getCSSFiles(), 'JS' => $Pagelet->getJSFiles(), 'JS_CODE' => removeLineBreaksAndTabs($Pagelet->getJSCode())] + ]; + + if($last) { + $data['IS_LAST'] = true; + } + + echo '<code class="hidden" id="_'.$data['ID'].'"><!-- '.str_replace('--', '--', removeLineBreaksAndTabs($Pagelet->getHTML())).' --></code>'."\n"; + echo '<script>BigPipe.onPageletArrive('.json_encode($data).($async ? ', document.getElementById("_'.$Pagelet->getID().'").innerHTML' : NULL).');</script>'."\n\n"; + } + + #==================================================================================================== + # Sendet den Output-Buffer so weit wie möglich in Richtung User + #==================================================================================================== + public static function flushOutputBuffer() { + ob_flush(); flush(); + } + + #==================================================================================================== + # Alle Pagelets an Client schicken + #==================================================================================================== + public static function render($async = FALSE) { + self::flushOutputBuffer(); + + $i = 0; + + ksort(self::$pagelets); + + foreach(array_reverse(self::$pagelets) as $priority => $pagelets) { + foreach($pagelets as $Pagelet) { + if(!self::isEnabled()) { + if($Pagelet->getJSCode()) { + echo '<script>'.$Pagelet->getJSCode().'</script>'."\n"; + } + + foreach($Pagelet->getCSSFiles() as $CSSFile) { + echo '<link href="'.$CSSFile.'" rel="stylesheet" />'."\n"; + } + + foreach($Pagelet->getJSFiles() as $JSFile) { + echo '<script src="'.$JSFile.'"></script>'."\n"; + } + } + + else { + self::pageletResponse($Pagelet, $async, (self::$count === ++$i)); + self::flushOutputBuffer(); + } + } + } + } +}
\ No newline at end of file diff --git a/include/classes/class.pagelet.php b/include/classes/class.pagelet.php new file mode 100755 index 0000000..c1f211a --- /dev/null +++ b/include/classes/class.pagelet.php @@ -0,0 +1,84 @@ +<?php +class Pagelet { + private $ID = NULL; + private $HTML = NULL; + private $JSCode = ""; + private $CSSFiles = []; + private $JSFiles = []; + private static $count = 0; + + public function __construct($priority = 50) { + $this->ID = 'P'.++self::$count; + BigPipe::addPagelet($this, $priority); + } + + #==================================================================================================== + # ID zurückgeben + #==================================================================================================== + public function getID() { + return $this->ID; + } + + #==================================================================================================== + # HTML-Code zurückgeben + #==================================================================================================== + public function getHTML() { + return $this->HTML; + } + + #==================================================================================================== + # CSS-Ressourcen zurückgeben + #==================================================================================================== + public function getCSSFiles() { + return $this->CSSFiles; + } + + #==================================================================================================== + # JS-Ressourcen zurückgeben + #==================================================================================================== + public function getJSFiles() { + return $this->JSFiles; + } + + #==================================================================================================== + # JS-Code zurückgeben + #==================================================================================================== + public function getJSCode() { + return $this->JSCode; + } + + #==================================================================================================== + # HTML-Code hinzufügen + #==================================================================================================== + public function addHTML($HTML) { + $this->HTML .= $HTML; + } + + #==================================================================================================== + # CSS-Ressource hinzufügen + #==================================================================================================== + public function addCSS($file) { + $this->CSSFiles[] = $file; + } + + #==================================================================================================== + # JS-Ressource hinzufügen + #==================================================================================================== + public function addJS($file) { + $this->JSFiles[] = $file; + } + + #==================================================================================================== + # JS-Code hinzufügen + #==================================================================================================== + public function addJSCode($code) { + $this->JSCode .= $code; + } + + #==================================================================================================== + # Magische Methode: __toString() + #==================================================================================================== + public function __toString() { + return '<div id="'.$this->getID().'">'.((!BigPipe::isEnabled()) ? $this->getHTML() : NULL).'</div>'; + } +}
\ No newline at end of file diff --git a/include/functions.php b/include/functions.php new file mode 100755 index 0000000..443d594 --- /dev/null +++ b/include/functions.php @@ -0,0 +1,12 @@ +<?php +#==================================================================================================== +# FUNCTION: Entfernt alle Zeilenumbrüche und Tabulatoren aus einem String +#==================================================================================================== +function removeLineBreaksAndTabs($mixed, $replace = NULL) { + if(is_array($mixed)) { + return array_map(__FUNCTION__, $mixed); + } + + return is_string($mixed) ? str_replace(["\r\n", "\r", "\n", "\t"], $replace, $mixed) : $mixed; +} +?>
\ No newline at end of file diff --git a/index.php b/index.php new file mode 100755 index 0000000..7f3de69 --- /dev/null +++ b/index.php @@ -0,0 +1,79 @@ +<?php +#==================================================================================================== +# Cache deaktivieren +#==================================================================================================== +header('Cache-Control: no-cache, no-store, must-revalidate'); + +#==================================================================================================== +# Klassen und Funktionen einbinden +#==================================================================================================== +require_once 'include/classes/class.bigpipe.php'; +require_once 'include/classes/class.pagelet.php'; +require_once 'include/functions.php'; + +#==================================================================================================== +# Pagelet mit rotem Hintergrund +#==================================================================================================== +$PageletRed = new Pagelet(); +$PageletRed->addHTML('<section id="red" class="text">I AM JUST A PAGELET WITH RED BACKGROUND</section>'); +$PageletRed->addCSS('static/red.php'); +$PageletRed->addJS('static/delayJS.php'); +$PageletRed->addJSCode("document.getElementById('red').innerHTML += ' [JS executed]';document.getElementById('red').style.borderRadius = '30px';"); + +#==================================================================================================== +# Pagelet mit blauem Hintergrund +#==================================================================================================== +$PageletBlue = new Pagelet(60); +$PageletBlue->addHTML('<section id="blue" class="text">I AM JUST A PAGELET WITH BLUE BACKGROUND</section>'); +$PageletBlue->addCSS('static/blue.php'); +$PageletBlue->addJS('static/delayJS.php'); +$PageletBlue->addJSCode("document.getElementById('blue').innerHTML += ' [JS executed]';document.getElementById('blue').style.borderRadius = '30px';"); + +#==================================================================================================== +# Pagelet mit grünem Hintergrund +#==================================================================================================== +$PageletGreen = new Pagelet(); +$PageletGreen->addHTML('<section id="green" class="text">I AM JUST A PAGELET WITH GREEN BACKGROUND</section>'); +$PageletGreen->addCSS('static/green.php'); +$PageletGreen->addJS('static/delayJS.php'); +$PageletGreen->addJSCode("document.getElementById('green').innerHTML += ' [JS executed]';document.getElementById('green').style.borderRadius = '30px';"); +?> +<!DOCTYPE html> +<html lang="de"> +<head> + <meta charset="UTF-8" /> + <meta name="robots" content="noindex, nofollow" /> + <style> + html{margin:0;padding:0;background:#B9C3D2;font-family:Calibri,Sans-Serif;} + body{max-width:1200px;margin:0 auto;} + .text{color:white;margin-bottom:30px;padding:40px;border-radius:4px;font-weight:600;text-align:center;border:4px solid black;} + </style> + <script> + var globalExecution = function globalExecution(code) { + window.execScript ? window.execScript(code) : window.eval.call(window, code); + }; + </script> + <script src="static/bigpipe.js"></script> + <script src="static/bigpipe-callbacks.js"></script> + <title>BigPipe Example</title> +</head> +<body> +<h1>BigPipe Example</h1> +<p>Auf dieser Beispielseite werden insgesamt 3 Pagelets gerendert von denen alle jeweils eine CSS- und eine JS-Ressource haben. Wobei jede der CSS-Ressourcen die Hintergrundfarbe des zugehörigen Pagelets ändert. +BigPipe wird hingehen und diese Pagelets der Reihe nach rendern. Dabei werden zuerst die zugehörigen CSS-Ressourcen geladen und dann der HTML-Code injiziert. Wenn dann von allen Pagelets die CSS-Ressourcen geladen +und der HTML-Code injiziert ist, dann wird BigPipe die JS-Ressourcen der Pagelets einbinden und den statischen Javascript-Code (falls vorhanden) ausführen. Damit man den Pipeline-Effekt auf dieser Beispielseite auch +sieht werden die CSS- und JS-Ressourcen über ein Delayscript geleitet. Debuginformationen findest du in der Javascript-Konsole.</p> + +<p><b>Weitere Informationen:</b> <a href="https://blackphantom.de/artikel/bigpipe-website-pipelining-und-schnellerer-aufbau-durch-einzelne-pagelets/" target="_blank">https://blackphantom.de/artikel/bigpipe-website-pipelining-und-schnellerer-aufbau-durch-einzelne-pagelets/</a></p> + +<?php +echo $PageletRed; +echo $PageletBlue; +echo $PageletGreen; +?> + +<?php +BigPipe::render(); +?> +</body> +</html>
\ No newline at end of file diff --git a/static/bigpipe-callbacks.js b/static/bigpipe-callbacks.js new file mode 100755 index 0000000..a9f48f1 --- /dev/null +++ b/static/bigpipe-callbacks.js @@ -0,0 +1,27 @@ +// Folgende Phasen stehen zur Auswahl: PAGELET_STARTED, RESOURCE_DONE, PAGELET_HTML_RENDERED, PAGELET_JS_EXECUTED und BIGPIPE_PAGELETS_RENDERED + +var debugLine = "------------------------------------------------------------------------------------------------------------------------"; + +BigPipe.registerPhaseDoneCallback('PAGELET_STARTED', function(Pagelet) { + console.log(debugLine); + console.log(Pagelet.pageletID + ":\t" + "Die Ausführung des Pagelets wurde gestartet."); +}); + +BigPipe.registerPhaseDoneCallback('RESOURCE_DONE', function(Resource) { + console.log(Resource.pageletID + ":\t" + 'Die Ressource ' + Resource.file + ' wurde geladen.'); +}); + +BigPipe.registerPhaseDoneCallback('PAGELET_HTML_RENDERED', function(Pagelet) { + console.log(Pagelet.pageletID + ":\t" + 'Die Platzhalter wurden mit HTML-Code befüllt.'); +}); + +BigPipe.registerPhaseDoneCallback('BIGPIPE_PAGELETS_RENDERED', function() { + console.log(debugLine); + console.log('BP' + ":\t" + 'Die Platzhalter von allen Pagelets wurden mit ihrem HTML-Code befüllt.'); + console.log(debugLine); +}); + +BigPipe.registerPhaseDoneCallback('PAGELET_JS_EXECUTED', function(Pagelet) { + console.log(Pagelet.pageletID + ":\t" + 'Der zugehörige Javascript-Code des Pagelets wurde ausgeführt.'); + console.log(debugLine); +});
\ No newline at end of file diff --git a/static/bigpipe.js b/static/bigpipe.js new file mode 100755 index 0000000..fcd35e9 --- /dev/null +++ b/static/bigpipe.js @@ -0,0 +1,274 @@ +//=================================================================================================== +// REVEALING MODLUE PATTERN: BigPipe +//=================================================================================================== +var BigPipe = (function() { + //=================================================================================================== + // PROTOTYPE: PageletResource-Konstruktor + //=================================================================================================== + function PageletResource(file, type, pageletID) { + this.pageletID = pageletID; + this.callbacks = []; + this.done = false; + this.file = file; + this.type = type; + } + + //=================================================================================================== + // PROTOTYPE: Startet den Ladevorgang der Ressource + //=================================================================================================== + PageletResource.prototype.start = function() { + if(this.type === 0) { + var element = document.createElement('link'); + element.setAttribute('rel', 'stylesheet'); + element.setAttribute('href', this.file); + } + + else { + var element = document.createElement('script'); + element.setAttribute('src', this.file); + element.async = true; + } + + document.head.appendChild(element); + + element.onload = function() { + BigPipe.executePhaseDoneCallbacks('RESOURCE_DONE', this); + this.executeCallbacks(); + }.bind(this); + + element.onerror = function() { + BigPipe.executePhaseDoneCallbacks('RESOURCE_DONE', this); + this.executeCallbacks(); + }.bind(this); + }; + + //=================================================================================================== + // PROTOTYPE: Registriert eine Callback-Funktion + //=================================================================================================== + PageletResource.prototype.registerCallback = function(callback) { + return this.callbacks.push(callback); + }; + + //=================================================================================================== + // PROTOTYPE: Führt alle registrierten Callback-Funktionen aus + //=================================================================================================== + PageletResource.prototype.executeCallbacks = function() { + if(!this.done) { + this.done = true; + + this.callbacks.forEach(function(callback) { + callback(); + }); + } + }; + + //=================================================================================================== + // PROTOTYPE: Pagelet-Konstruktor + //=================================================================================================== + function Pagelet(data) { + this.pageletID = data.ID; + this.HTML = data.HTML || ""; + this.CSSFiles = data.RESOURCES.CSS; + this.JSFiles = data.RESOURCES.JS; + this.JSCode = data.RESOURCES.JS_CODE; + + this.phase = 0; // 1 => Laden von CSS-Ressourcen, 2 => CSS-Ressourcen geladen, 3 => HTML wurde injiziert, 4 => JS-Ressourcen geladen und JS-Code ausgeführt + this.CSSResources = []; + this.JSResources = []; + } + + //=================================================================================================== + // PROTOTYPE: Startet die Initialisierung des Pagelets und startet die Pagelet-Ressourcen + //=================================================================================================== + Pagelet.prototype.start = function() { + BigPipe.executePhaseDoneCallbacks('PAGELET_STARTED', this); + this.CSSFiles.forEach(function(file) { + this.attachCSSResource(new PageletResource(file, 0, this.pageletID)); + }.bind(this)); + + this.JSFiles.forEach(function(file) { + this.attachJSResource(new PageletResource(file, 1, this.pageletID)); + }.bind(this)); + + this.CSSResources.forEach(function(resource) { + this.phase = 1; + resource.start(); + }.bind(this)); + + if(this.phase === 0) { + this.injectHTML(); + } + }; + + //=================================================================================================== + // PROTOTYPE: Fügt eine CSS-Ressource hinzu + //=================================================================================================== + Pagelet.prototype.attachCSSResource = function(resource) { + resource.registerCallback(this.onloadCSS.bind(this)); + return this.CSSResources.push(resource); + }; + + //=================================================================================================== + // PROTOTYPE: Fügt eine JS-Ressource hinzu + //=================================================================================================== + Pagelet.prototype.attachJSResource = function(resource) { + resource.registerCallback(this.onloadJS.bind(this)); + return this.JSResources.push(resource); + }; + + //=================================================================================================== + // PROTOTYPE: Führt den statischen JS-Code des Pagelets aus + //=================================================================================================== + Pagelet.prototype.executeJSCode = function() { + try { + globalExecution(this.JSCode); + BigPipe.executePhaseDoneCallbacks('PAGELET_JS_EXECUTED', this); + } catch(e) { + console.error(this.pageletID + ":\t" + e); + } + }; + + //=================================================================================================== + // PROTOTYPE: Pagelet-Methode + //=================================================================================================== + Pagelet.prototype.onloadJS = function() { + if(this.phase === 3 && this.JSResources.every(function(resource){ + return resource.done; + })) { + this.executeJSCode(); + this.phase = 4; + } + }; + + //=================================================================================================== + // PROTOTYPE: Pagelet-Methode + //=================================================================================================== + Pagelet.prototype.onloadCSS = function() { + if(this.CSSResources.every(function(resource){ + return resource.done; + })) { + this.injectHTML(); + } + }; + + //=================================================================================================== + // PROTOTYPE: Injiziert den HTML-Code des Pagelets in den DOM + //=================================================================================================== + Pagelet.prototype.injectHTML = function() { + this.phase = 2; + if(placeholder = document.getElementById(this.pageletID)) { + if(this.HTML) { + placeholder.innerHTML = this.HTML; + } + + else { + var content = document.getElementById('_' + this.pageletID); + placeholder.innerHTML = content.innerHTML.substring(5, content.innerHTML.length - 4); + document.body.removeChild(content); + } + } + + this.phase = 3; + + BigPipe.executePhaseDoneCallbacks('PAGELET_HTML_RENDERED', this); + BigPipe.executeNextPagelet(); + + if(BigPipe.phase === 2 && BigPipe.pagelets[BigPipe.pagelets.length - 1].pageletID === this.pageletID) { + BigPipe.executePhaseDoneCallbacks('BIGPIPE_PAGELETS_RENDERED'); + BigPipe.loadJSResources(); + } + }; + + //=================================================================================================== + // BigPipe-Hauptobjekt + //=================================================================================================== + var BigPipe = { + pagelets: [], + phase: 0, // 1 => Erstes Pagelet gestartet, 2 => Alle Pagelets angekommen, 3 => JS-Ressourcen geladen + JS-Code ausgeführt + offset: 0, + phaseDoneCallbacks: {}, + + executeNextPagelet: function() { + if(this.pagelets[this.offset]) { + this.pagelets[this.offset++].start(); + } + + else if(this.phase < 2) { + setTimeout(this.executeNextPagelet.bind(this), 30); + } + }, + + registerPhaseDoneCallback: function(phase, callback) { + if(!this.phaseDoneCallbacks[phase]) { + this.phaseDoneCallbacks[phase] = []; + } + return this.phaseDoneCallbacks[phase].push(callback); + }, + + executePhaseDoneCallbacks: function(phase, param) { + if(this.phaseDoneCallbacks[phase]) { + this.phaseDoneCallbacks[phase].forEach(function(callback) { + callback(param); + }); + } + }, + + onPageletArrive: function(data) { + if(this.pagelets.push(new Pagelet(data)) && this.phase === 0 && !data.IS_LAST) { + this.phase = 1; + this.executeNextPagelet(); + } + + else if(data.IS_LAST) { + this.phase = 2; + if(this.pagelets.length === 1) { + this.executeNextPagelet(); + } + } + }, + + loadJSResources: function() { + this.phase = 3; + var isLoading = false; + + this.pagelets.forEach(function(Pagelet) { + if(Pagelet.JSResources.length === 0) { + Pagelet.onloadJS(); + } + }); + + this.pagelets.forEach(function(Pagelet) { + Pagelet.JSResources.forEach(function(Resource) { + Resource.start(); + isLoading = true; + }); + }); + + if(!isLoading) { + this.pagelets.forEach(function(Pagelet) { + Pagelet.onloadJS(); + }); + } + } + }; + + //=================================================================================================== + // REVEALING MODULE PATTERN: Öffentliche API + //=================================================================================================== + return { + onPageletArrive: function(data) { + BigPipe.onPageletArrive(data); + }, + + registerPhaseDoneCallback: function(phase, callback) { + BigPipe.registerPhaseDoneCallback(phase, callback); + }, + + reset: function() { + BigPipe.phaseDoneCallbacks = {}; + BigPipe.pagelets = []; + BigPipe.offset = 0; + BigPipe.phase = 0; + } + }; +})();
\ No newline at end of file diff --git a/static/blue.php b/static/blue.php new file mode 100755 index 0000000..ddffcf6 --- /dev/null +++ b/static/blue.php @@ -0,0 +1,17 @@ +<?php +#==================================================================================================== +# Cache deaktivieren +#==================================================================================================== +header('Cache-Control: no-cache, no-store, must-revalidate'); + +#==================================================================================================== +# Content-Type setzen +#==================================================================================================== +header('Content-Type: text/css'); + +#==================================================================================================== +# Lange Ladezeit simulieren +#==================================================================================================== +usleep(intval(rand(60, 100).'0000')); +?> +#blue{background:blue;}
\ No newline at end of file diff --git a/static/delayJS.php b/static/delayJS.php new file mode 100755 index 0000000..1307ca0 --- /dev/null +++ b/static/delayJS.php @@ -0,0 +1,16 @@ +<?php +#==================================================================================================== +# Cache deaktivieren +#==================================================================================================== +header('Cache-Control: no-cache, no-store, must-revalidate'); + +#==================================================================================================== +# Content-Type setzen +#==================================================================================================== +header('Content-Type: text/javascript'); + +#==================================================================================================== +# Lange Ladezeit simulieren +#==================================================================================================== +usleep(intval(rand(10, 40).'0000')); +?>
\ No newline at end of file diff --git a/static/green.php b/static/green.php new file mode 100755 index 0000000..3e45fc6 --- /dev/null +++ b/static/green.php @@ -0,0 +1,17 @@ +<?php +#==================================================================================================== +# Cache deaktivieren +#==================================================================================================== +header('Cache-Control: no-cache, no-store, must-revalidate'); + +#==================================================================================================== +# Content-Type setzen +#==================================================================================================== +header('Content-Type: text/css'); + +#==================================================================================================== +# Lange Ladezeit simulieren +#==================================================================================================== +usleep(intval(rand(60, 100).'0000')); +?> +#green{background:green;}
\ No newline at end of file diff --git a/static/red.php b/static/red.php new file mode 100755 index 0000000..9ad993e --- /dev/null +++ b/static/red.php @@ -0,0 +1,17 @@ +<?php +#==================================================================================================== +# Cache deaktivieren +#==================================================================================================== +header('Cache-Control: no-cache, no-store, must-revalidate'); + +#==================================================================================================== +# Content-Type setzen +#==================================================================================================== +header('Content-Type: text/css'); + +#==================================================================================================== +# Lange Ladezeit simulieren +#==================================================================================================== +usleep(intval(rand(60, 100).'0000')); +?> +#red{background:red;}
\ No newline at end of file |