aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xasync.php25
-rwxr-xr-xinclude/classes/BigPipe/BigPipe.php105
-rwxr-xr-xinclude/classes/BigPipe/DemoPagelet.php17
-rwxr-xr-xinclude/classes/BigPipe/Pagelet.php74
-rwxr-xr-xinclude/classes/BigPipe/Resource.php74
-rw-r--r--include/classes/BigPipe/Resource/CSS.php20
-rw-r--r--include/classes/BigPipe/Resource/JS.php20
-rw-r--r--include/pagelets.php13
-rwxr-xr-xindex.php14
-rw-r--r--readme.md11
-rwxr-xr-xstatic/bigpipe.js244
11 files changed, 432 insertions, 185 deletions
diff --git a/async.php b/async.php
index bd67865..453b27f 100755
--- a/async.php
+++ b/async.php
@@ -18,17 +18,22 @@ header('Cache-Control: no-cache, no-store, must-revalidate');
#===============================================================================
# Include classes and functions
#===============================================================================
-require_once 'include/classes/BigPipe/BigPipe.php';
-require_once 'include/classes/BigPipe/Pagelet.php';
-require_once 'include/classes/BigPipe/DemoPagelet.php';
+spl_autoload_register(function($classname) {
+ $classpath = 'include/classes/%s.php';
+ $classname = str_replace('\\', '/', $classname);
+
+ require_once sprintf($classpath, $classname);
+});
+
require_once 'include/functions.php';
#===============================================================================
# Check if BigPipe should be disabled
#===============================================================================
-if(isset($_GET['bigpipe']) AND (int) $_GET['bigpipe'] === 0) {
- // You can also check for search spiders and disable the pipeline
- BigPipe\BigPipe::enablePipeline(FALSE);
+if(isset($_GET['bigpipe'])) {
+
+ # You can use this method also to disable pipeline for Googlebot or something.
+ BigPipe\BigPipe::enabled($_GET['bigpipe']);
}
// Outsourced to avoid duplicate code in index.php and async.php
@@ -56,13 +61,21 @@ require_once 'include/pagelets.php';
<!-- >>> [Additional code for the async function] -->
<script>
var Application = {
+ bigPipeEnabled: <?=json_encode(BigPipe\BigPipe::enabled())?>,
+
placeholderHTML: function(HTML) {
document.getElementById('placeholder_container').innerHTML = HTML;
}
};
function fireAsyncRequest(href) {
+ if(Application.bigPipeEnabled === false) {
+ alert("Note: Pipelining is disabled and page will be loaded quite normal.");
+ return;
+ }
+
console.info('ASYNC REQUEST FIRED!');
+
Application.placeholderHTML("");
BigPipe.reset();
var transport_frame;
diff --git a/include/classes/BigPipe/BigPipe.php b/include/classes/BigPipe/BigPipe.php
index 1af4d8a..082ca14 100755
--- a/include/classes/BigPipe/BigPipe.php
+++ b/include/classes/BigPipe/BigPipe.php
@@ -2,23 +2,31 @@
namespace BigPipe;
class BigPipe {
- private static $enabled = TRUE;
- private static $debug = TRUE;
- private static $pagelets = [];
- private static $count = 0;
+ private static $debugging = FALSE;
+ private static $enabled = TRUE;
+ private static $pagelets = [];
+ private static $count = 0;
#===============================================================================
- # Return TRUE if the pipeline is enabled
+ # Enable or disable the pipeline mode
#===============================================================================
- public static function isEnabled() {
+ public static function enabled($change = NULL) {
+ if($change !== NULL) {
+ self::$enabled = (bool) $change;
+ }
+
return self::$enabled;
}
#===============================================================================
- # Enable or disable the pipeline mode
+ # Return if debugging is enabled or change
#===============================================================================
- public static function enablePipeline($enabled = TRUE) {
- return self::$enabled = (bool) $enabled;
+ public static function debugging($change = NULL): bool {
+ if($change !== NULL) {
+ self::$debugging = (bool) $change;
+ }
+
+ return self::$debugging;
}
#===============================================================================
@@ -33,20 +41,45 @@ class BigPipe {
# Prints a single pagelet response
#===============================================================================
private static function singleResponse(Pagelet $Pagelet, $last = FALSE) {
+ if(self::debugging()) {
+ self::addDebugPhaseDoneJS($Pagelet);
+
+ array_map('self::addDebugPhaseDoneJS', $Pagelet->getCSSResources());
+ array_map('self::addDebugPhaseDoneJS', $Pagelet->getJSResources());
+
+ usleep(rand(125, 175) * 2000);
+ }
+
+ $stylesheets = [];
+ $javascripts = [];
+
+ foreach($Pagelet->getCSSResources() as $Resource) {
+ $stylesheets[$Resource->getURL()] = $Resource->getPhaseDoneJS();
+ }
+
+ foreach($Pagelet->getJSResources() as $Resource) {
+ $javascripts[$Resource->getURL()] = $Resource->getPhaseDoneJS();
+ }
+
$pageletJSON = [
- 'ID' => $Pagelet->getID(), 'NEED' => $Pagelet->getDependencies(),
- 'RESOURCES' => ['CSS' => $Pagelet->getCSSFiles(), 'JS' => $Pagelet->getJSFiles(), 'JS_CODE' => removeLineBreaksAndTabs($Pagelet->getJSCode())],
- 'PHASES' => (object) $Pagelet->getPhaseDoneJS()
+ 'ID' => $Pagelet->getID(),
+ 'NEED' => $Pagelet->getDependencies(),
+ 'RSRC' => (object) [
+ Resource::TYPE_STYLESHEET => (object) $stylesheets,
+ Resource::TYPE_JAVASCRIPT => (object) $javascripts,
+ ],
+ 'CODE' => removeLineBreaksAndTabs($Pagelet->getJSCode()),
+ 'PHASE' => $Pagelet->getPhaseDoneJS()
];
if($last) {
- $pageletJSON['IS_LAST'] = true;
+ $pageletJSON['IS_LAST'] = TRUE;
}
$pageletHTML = removeLineBreaksAndTabs($Pagelet->getHTML());
$pageletHTML = str_replace('--', '&#45;&#45;', $pageletHTML);
- $pageletJSON = json_encode($pageletJSON, (self::$debug ? JSON_PRETTY_PRINT : NULL));
+ $pageletJSON = json_encode($pageletJSON, (self::debugging() ? JSON_PRETTY_PRINT : NULL));
echo "<code class=\"hidden\" id=\"_{$Pagelet->getID()}\"><!-- {$pageletHTML} --></code>\n";
echo "<script>BigPipe.onPageletArrive({$pageletJSON}, document.getElementById(\"_{$Pagelet->getID()}\"));</script>\n\n";
@@ -71,13 +104,13 @@ class BigPipe {
foreach(array_reverse(self::$pagelets) as $priority => $pagelets) {
foreach($pagelets as $Pagelet) {
- if(!self::isEnabled()) {
- foreach($Pagelet->getCSSFiles() as $CSSFile) {
- echo "<link href=\"{$CSSFile}\" rel=\"stylesheet\" />\n";
+ if(!self::enabled()) {
+ foreach($Pagelet->getCSSResources() as $Resource) {
+ echo $Resource->renderHTML()."\n";
}
- foreach($Pagelet->getJSFiles() as $JSFile) {
- echo "<script src=\"{$JSFile}\"></script>\n";
+ foreach($Pagelet->getJSResources() as $Resource) {
+ echo $Resource->renderHTML()."\n";
}
foreach($Pagelet->getJSCode() as $JSCode) {
@@ -88,10 +121,38 @@ class BigPipe {
else {
self::singleResponse($Pagelet, (self::$count === ++$i));
self::flushOutputBuffer();
-
- self::$debug AND usleep((rand(250, 1000) * 1000));
}
}
}
}
-} \ No newline at end of file
+
+ #===============================================================================
+ # Add PhaseDoneJS for debugging Pagelet and Resource
+ #===============================================================================
+ private static function addDebugPhaseDoneJS($Instance) {
+ $objpath = str_replace('\\', '|', get_class($Instance));
+
+ if($Instance instanceof Pagelet) {
+ $message = "console.log(\"%%c[{$objpath}]%%c#(%%c%s%%c): PhaseDoneJS for phase: %s\", \"font-weight:bold\", \"color:#666\", \"color:#008B45\", \"color:#666\")";
+
+ $Instance->addPhaseDoneJS($Instance::PHASE_INIT, sprintf($message, $Instance->getID(), 'INIT'));
+ $Instance->addPhaseDoneJS($Instance::PHASE_LOADCSS, sprintf($message, $Instance->getID(), 'LOADCSS'));
+ $Instance->addPhaseDoneJS($Instance::PHASE_HTML, sprintf($message, $Instance->getID(), 'HTML'));
+ $Instance->addPhaseDoneJS($Instance::PHASE_LOADJS, sprintf($message, $Instance->getID(), 'LOADJS'));
+ $Instance->addPhaseDoneJS($Instance::PHASE_DONE, sprintf($message, $Instance->getID(), 'DONE'));
+
+ return $Instance;
+ }
+
+ if($Instance instanceof Resource) {
+ $message = "console.log(\"[{$objpath}]%%c#(%%c%s%%c): PhaseDoneJS for phase: %s\", \"color:#666\", \"color:#008B45\", \"color:#666\")";
+
+ $Instance->addPhaseDoneJS($Instance::PHASE_INIT, sprintf($message, $Instance->getID(), 'INIT'));
+ $Instance->addPhaseDoneJS($Instance::PHASE_LOAD, sprintf($message, $Instance->getID(), 'LOAD'));
+ $Instance->addPhaseDoneJS($Instance::PHASE_DONE, sprintf($message, $Instance->getID(), 'DONE'));
+
+ return $Instance;
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/include/classes/BigPipe/DemoPagelet.php b/include/classes/BigPipe/DemoPagelet.php
deleted file mode 100755
index adb9f89..0000000
--- a/include/classes/BigPipe/DemoPagelet.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-namespace BigPipe;
-
-class DemoPagelet extends Pagelet {
-
- public function __construct($customID = NULL, $priority = Pagelet::PRIORITY_NORMAL, array $dependencies = []) {
- parent::__construct($customID, $priority, $dependencies);
-
- $message = '%s: PhaseDoneJS for phase %s';
-
- $this->addPhaseDoneJS(self::PHASE_ARRIVE, 'console.log("'.sprintf($message, $this->getID(), 'ARRIVE').'")');
- $this->addPhaseDoneJS(self::PHASE_LOADCSS, 'console.log("'.sprintf($message, $this->getID(), 'LOADCSS').'")');
- $this->addPhaseDoneJS(self::PHASE_PUTHTML, 'console.log("'.sprintf($message, $this->getID(), 'PUTHTML').'")');
- $this->addPhaseDoneJS(self::PHASE_LOADJS, 'console.log("'.sprintf($message, $this->getID(), 'LOADJS').'")');
- $this->addPhaseDoneJS(self::PHASE_EXECJS, 'console.log("'.sprintf($message, $this->getID(), 'EXECJS').'")');
- }
-} \ No newline at end of file
diff --git a/include/classes/BigPipe/Pagelet.php b/include/classes/BigPipe/Pagelet.php
index 436e12a..7502a02 100755
--- a/include/classes/BigPipe/Pagelet.php
+++ b/include/classes/BigPipe/Pagelet.php
@@ -2,11 +2,11 @@
namespace BigPipe;
class Pagelet {
- private $ID = NULL;
+ private $ID = '';
private $HTML = '';
private $JSCode = [];
- private $JSFiles = [];
- private $CSSFiles = [];
+ private $JSResources = [];
+ private $CSSResources = [];
private $phaseDoneJS = [];
private $dependencies = [];
private $tagname = 'div';
@@ -24,11 +24,11 @@ class Pagelet {
#===============================================================================
# Phase numbers for PhaseDoneJS
#===============================================================================
- const PHASE_ARRIVE = 0; # After the pagelet reached BigPipe
+ const PHASE_INIT = 0; # After the pagelet object was initialized
const PHASE_LOADCSS = 1; # After all the CSS resources have been loaded
- const PHASE_PUTHTML = 2; # After the HTML content has been injected into the placeholders
+ const PHASE_HTML = 2; # After the placeholder HTML was replaced
const PHASE_LOADJS = 3; # After all the JS resources have been loaded
- const PHASE_EXECJS = 4; # After the static JS code has been executed
+ const PHASE_DONE = 4; # After the static JS code has been executed
public function __construct($customID = NULL, $priority = self::PRIORITY_NORMAL, array $dependencies = []) {
$this->phaseDoneJS = array_pad($this->phaseDoneJS, 5, []);
@@ -53,20 +53,6 @@ class Pagelet {
}
#===============================================================================
- # Return the CSS resources
- #===============================================================================
- public function getCSSFiles() {
- return $this->CSSFiles;
- }
-
- #===============================================================================
- # Return the JS resources
- #===============================================================================
- public function getJSFiles() {
- return $this->JSFiles;
- }
-
- #===============================================================================
# Return the main JS code
#===============================================================================
public function getJSCode() {
@@ -81,17 +67,35 @@ class Pagelet {
}
#===============================================================================
- # Attach a CSS resource
+ # Add resource
+ #===============================================================================
+ public function addResource(Resource $Resource): Resource {
+ switch($Resource->getType()) {
+ case Resource::TYPE_STYLESHEET:
+ return $this->CSSResources[] = $Resource;
+ break;
+
+ case Resource::TYPE_JAVASCRIPT:
+ return $this->JSResources[] = $Resource;
+ break;
+
+ default:
+ return $Resource;
+ }
+ }
+
+ #===============================================================================
+ # Short: Add CSS resource by URL
#===============================================================================
- public function addCSS($href) {
- return $this->CSSFiles[] = $href;
+ public function addCSS($resourceURL): Resource {
+ return $this->addResource(new Resource\CSS($resourceURL));
}
#===============================================================================
- # Attach a JS resource
+ # Short: Add JS resource by URL
#===============================================================================
- public function addJS($href) {
- return $this->JSFiles[] = $href;
+ public function addJS($resourceURL): Resource {
+ return $this->addResource(new Resource\JS($resourceURL));
}
#===============================================================================
@@ -111,11 +115,25 @@ class Pagelet {
#===============================================================================
# Return all registered PhaseDoneJS callbacks
#===============================================================================
- public function getPhaseDoneJS() {
+ public function getPhaseDoneJS(): array {
return $this->phaseDoneJS;
}
#===============================================================================
+ # Return the attached CSS resources
+ #===============================================================================
+ public function getCSSResources(): array {
+ return $this->CSSResources;
+ }
+
+ #===============================================================================
+ # Return the attached JS resources
+ #===============================================================================
+ public function getJSResources(): array {
+ return $this->JSResources;
+ }
+
+ #===============================================================================
# Return all display dependencies
#===============================================================================
public function getDependencies(): array {
@@ -134,7 +152,7 @@ class Pagelet {
#===============================================================================
public function __toString() {
$pageletHTML = "<{$this->tagname} id=\"{$this->getID()}\">";
- $pageletHTML .= !BigPipe::isEnabled() ? $this->getHTML() : NULL;
+ $pageletHTML .= !BigPipe::enabled() ? $this->getHTML() : NULL;
$pageletHTML .= "</{$this->tagname}>";
return $pageletHTML;
diff --git a/include/classes/BigPipe/Resource.php b/include/classes/BigPipe/Resource.php
new file mode 100755
index 0000000..d93ad16
--- /dev/null
+++ b/include/classes/BigPipe/Resource.php
@@ -0,0 +1,74 @@
+<?php
+namespace BigPipe;
+
+abstract class Resource {
+ private $ID = '';
+ private $type = '';
+ private $resourceURL = '';
+ private $phaseDoneJS = [];
+ private static $count = 0;
+
+ #===============================================================================
+ # Render resource HTML for disabled pipeline
+ #===============================================================================
+ abstract public function renderHTML();
+
+ #===============================================================================
+ # Resource types
+ #===============================================================================
+ const TYPE_STYLESHEET = 0;
+ const TYPE_JAVASCRIPT = 1;
+
+ #===============================================================================
+ # Phase numbers for PhaseDoneJS
+ #===============================================================================
+ const PHASE_INIT = 0; # Resource object has been initialized
+ const PHASE_LOAD = 1; # Loading of resource has been started
+ const PHASE_DONE = 2; # Loading of resource is done.
+
+ #===============================================================================
+ # Build resource
+ #===============================================================================
+ public function __construct($type, $resourceURL) {
+ $this->phaseDoneJS = array_pad($this->phaseDoneJS, 3, []);
+ $this->ID = 'R'.++self::$count;
+ $this->type = $type;
+ $this->resourceURL = $resourceURL;
+ }
+
+ #===============================================================================
+ # Return the unique ID
+ #===============================================================================
+ public function getID() {
+ return $this->ID;
+ }
+
+ #===============================================================================
+ # Return the resource type
+ #===============================================================================
+ public function getType() {
+ return $this->type;
+ }
+
+ #===============================================================================
+ # Return the resource URL
+ #===============================================================================
+ public function getURL() {
+ return $this->resourceURL;
+ }
+
+ #===============================================================================
+ # Attach a PhaseDoneJS callback
+ #===============================================================================
+ public function addPhaseDoneJS($phase, $callback) {
+ return $this->phaseDoneJS[$phase][] = removeLineBreaksAndTabs($callback);
+ }
+
+ #===============================================================================
+ # Return all registered PhaseDoneJS callbacks
+ #===============================================================================
+ public function getPhaseDoneJS() {
+ return $this->phaseDoneJS;
+ }
+}
+?> \ No newline at end of file
diff --git a/include/classes/BigPipe/Resource/CSS.php b/include/classes/BigPipe/Resource/CSS.php
new file mode 100644
index 0000000..71d2244
--- /dev/null
+++ b/include/classes/BigPipe/Resource/CSS.php
@@ -0,0 +1,20 @@
+<?php
+namespace BigPipe\Resource;
+
+class CSS extends \BigPipe\Resource {
+
+ #===============================================================================
+ # Build resource
+ #===============================================================================
+ public function __construct($resourceURL) {
+ parent::__construct(parent::TYPE_STYLESHEET, $resourceURL);
+ }
+
+ #===============================================================================
+ # Render resource HTML
+ #===============================================================================
+ public function renderHTML() {
+ return sprintf('<link data-id="%s" href="%s" rel="stylesheet" />', $this->getID(), $this->getURL());
+ }
+}
+?> \ No newline at end of file
diff --git a/include/classes/BigPipe/Resource/JS.php b/include/classes/BigPipe/Resource/JS.php
new file mode 100644
index 0000000..f9e109b
--- /dev/null
+++ b/include/classes/BigPipe/Resource/JS.php
@@ -0,0 +1,20 @@
+<?php
+namespace BigPipe\Resource;
+
+class JS extends \BigPipe\Resource {
+
+ #===============================================================================
+ # Build resource
+ #===============================================================================
+ public function __construct($resourceURL) {
+ parent::__construct(parent::TYPE_JAVASCRIPT, $resourceURL);
+ }
+
+ #===============================================================================
+ # Render resource HTML
+ #===============================================================================
+ public function renderHTML() {
+ return sprintf('<script data-id="%s" src="%s"></script>', $this->getID(), $this->getURL());
+ }
+}
+?> \ No newline at end of file
diff --git a/include/pagelets.php b/include/pagelets.php
index 176bc74..9725879 100644
--- a/include/pagelets.php
+++ b/include/pagelets.php
@@ -1,8 +1,13 @@
<?php
#===============================================================================
+# Enable debugging mode
+#===============================================================================
+BigPipe\BigPipe::debugging(TRUE);
+
+#===============================================================================
# Pagelet with red background color
#===============================================================================
-$PageletRed = new BigPipe\DemoPagelet('redPL');
+$PageletRed = new BigPipe\Pagelet('redPL');
$PageletRed->addHTML('<section id="red" class="text">I AM A PAGELET WITH RED BACKGROUND</section>');
$PageletRed->addCSS('static/red.php');
$PageletRed->addCSS('static/red.php');
@@ -12,7 +17,7 @@ $PageletRed->addJSCode("document.getElementById('red').innerHTML += ' [JS execut
#===============================================================================
# Pagelet with blue background color
#===============================================================================
-$PageletBlue = new BigPipe\DemoPagelet('bluePL', BigPipe\Pagelet::PRIORITY_HIGH);
+$PageletBlue = new BigPipe\Pagelet('bluePL', BigPipe\Pagelet::PRIORITY_HIGH);
$PageletBlue->addHTML('<section id="blue" class="text">I AM A PAGELET WITH BLUE BACKGROUND</section>');
$PageletBlue->addCSS('static/blue.php');
$PageletRed->addCSS('static/red.php');
@@ -22,7 +27,7 @@ $PageletBlue->addJSCode("document.getElementById('blue').innerHTML += ' [JS exec
#===============================================================================
# Pagelet with green background color
#===============================================================================
-$PageletGreen = new BigPipe\DemoPagelet('greenPL');
+$PageletGreen = new BigPipe\Pagelet('greenPL');
{
#===============================================================================
@@ -39,7 +44,7 @@ $PageletGreen = new BigPipe\DemoPagelet('greenPL');
// the first which arrives, but it will first be displayed if his dependency
// pagelets are already displayed.
- $InnerPagelet = new BigPipe\DemoPagelet('innerPL', BigPipe\Pagelet::PRIORITY_HIGHEST, [$PageletGreen->getID()]);
+ $InnerPagelet = new BigPipe\Pagelet('innerPL', BigPipe\Pagelet::PRIORITY_HIGHEST, [$PageletGreen->getID()]);
$InnerPagelet->addHTML('<section sytle="background:#FFF;padding:5px;">Inner Pagelet \(o_o)/</section>');
}
diff --git a/index.php b/index.php
index 5f12fd6..2a1b1fd 100755
--- a/index.php
+++ b/index.php
@@ -7,9 +7,13 @@ header('Cache-Control: no-cache, no-store, must-revalidate');
#===============================================================================
# Include classes and functions
#===============================================================================
-require_once 'include/classes/BigPipe/BigPipe.php';
-require_once 'include/classes/BigPipe/Pagelet.php';
-require_once 'include/classes/BigPipe/DemoPagelet.php';
+spl_autoload_register(function($classname) {
+ $classpath = 'include/classes/%s.php';
+ $classname = str_replace('\\', '/', $classname);
+
+ require_once sprintf($classpath, $classname);
+});
+
require_once 'include/functions.php';
#===============================================================================
@@ -44,8 +48,8 @@ require_once 'include/pagelets.php';
</head>
<body>
<h1>BigPipe Demo</h1>
-<p>You see on this page 3 pagelets are getting rendered. Each pagelet has his own CSS and JS resources. The CSS resources change the background color of the pagelet (so you can see the effect when the CSS is loaded). The next step is to load the JS resources and they change the border-radius of the pagelet. After the loading of CSS and JS resources the static JS callback get executed. Additionally, the PhaseDoneJS callbacks performed for each pagelet phase. See the javascript console for more debug informations.</p>
-<p>PhaseDoneJS is a new feature of BigPipe which can execute JS callbacks for each pagelet phase. Each pagelet can have multiple PhaseDoneJS callbacks for each phase. The difference between a PhaseDoneJS callback and a static JS callback ("JS_CODE") is the following: The static JS callback always get executed (regardless of whether the pipeline is enabled or disabled) and can be a main part of the JS from the pagelet. But the PhaseDoneJS callbacks are only executed if the pipeline is enabled. They are suitable for application-specific stuff.</p>
+<p>You see on this page 3 pagelets are getting rendered. Each pagelet has his own CSS and JS resources. The CSS resources change the background color of the pagelet (so you can see the effect when the CSS is loaded). The next step is to replace the content on the placeholder HTML with the Pagelet HTML load the JS resources and they change the border-radius of the pagelet. After the loading of CSS and JS resources the static JS callback get executed. Additionally, the PhaseDoneJS callbacks performed for each pagelet phase. See the javascript console for more debug informations.</p>
+<p>PhaseDoneJS is a new feature of BigPipe which can execute JS callbacks for each pagelet phase. Each pagelet can have multiple PhaseDoneJS callbacks for each phase. The difference between a PhaseDoneJS callback and a static JS callback ("CODE") is the following: The static JS callback always get executed (regardless of whether the pipeline is enabled or disabled) and can be a main part of the JS from the pagelet. But the PhaseDoneJS callbacks are only executed if the pipeline is enabled. They are suitable for application-specific stuff.</p>
<p><strong>Notice:</strong> BigPipe may support the features of the new <em>PHP 7</em> in the future and you may also benefit from the performance boost with <em>PHP 7</em>.</p>
diff --git a/readme.md b/readme.md
index 5c06f8d..8a861d7 100644
--- a/readme.md
+++ b/readme.md
@@ -1,8 +1,5 @@
-## Problembeschreibung
-Beim Seitenaufbau einer Website mit vielen Javascript- und CSS-Dateien kommt es oft dazu, dass man als Besucher beobachten kann wie der Browser das Layout darstellt. Das ist besonders dann der Fall, wenn eingebundene Javascript-Dateien das Rendering blockieren weil sie noch am laden sind. Aber auch wenn für das Layout (oder Teile davon) mehrere einzelne CSS-Dateien eingebunden werden und der Besucher zum Beispiel eine langsame Internetverbindung hat. An dieser Stelle kommt [Facebook's Pipeline-Technik "BigPipe"](https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919) in's Spiel. Mit BigPipe kann man eine Website in einzelne Fragmente zerlegen die Pagelets genannt werden. Jedes Pagelet besteht mindestens aus seinem HTML-Code und optional aus zugehörigen Javascript- und CSS-Dateien.
+# BigPipe: Short description
+> BigPipe is a fundamental redesign of the dynamic web page serving system. The general idea is to decompose web pages into small chunks called pagelets, and pipeline them through several execution stages inside web servers and browsers. This is similar to the pipelining performed by most modern microprocessors: multiple instructions are pipelined through different execution units of the processor to achieve the best performance. Although BigPipe is a fundamental redesign of the existing web serving process, it does not require changing existing web browsers or servers; it is implemented entirely in PHP and JavaScript.
-## Website mit BigPipe in einzelne Fragmente aufteilen
-Wenn das HTML auf dem Server erzeugt wird dann wird es nicht direkt an der eigentlichen Stelle im HTML-Code ausgegeben sondern an der Stelle wird nur ein Platzhalter-Element reingeschrieben welches leer ist. Dieses Platzhalter-Element bekommt dann eine eindeutige ID damit BigPipe das Element identifizieren und den echten HTML-Code injizieren kann. Bevor das passiert werden allerdings erst einmal die zum Pagelet zugehörigen CSS-Ressourcen eingebunden und der HTML-Code wird erst dann in das Platzhalter-Element injiziert wenn die CSS-Ressource vollständig geladen ist. Wenn von allen Pagelets die CSS-Ressourcen geladen sind und der HTML-Code in die Platzhalter-Element injiziert wurde, dann geht BigPipe hin und bindet von jedem Pagelet die JS-Ressourcen ein. Jedes einzelne Pagelet wird also gepipelinet und nacheinander gerendert.
-
-## Priorisieren von Pagelets: Die wichtigsten Pagelets zuerst
-Der Besucher will beim Besuch einer Seite möglichst schnell die Informationen bekommen, die er haben will. Komplett sinnfrei wäre es also wenn sich bei einer langsamen Internetverbindung auf einmal zuerst der Footer der Seite, dann die Sidebar, dann die Navigation und dann erst die eigentlichen Inhalte aufbauen würden. Da der eigentliche HTML-Code eins Pagelets jetzt aber nicht mehr direkt an die entsprechende Stelle geschrieben wird, sondern dort nur Platzhalter eingesetzt werden, ist es möglich, die einzelnen Pagelets serverseitig zu priorisieren. So kann man zum Beispiel dem Pagelet für den Hauptinhalt eine höhere Priorität geben als dem Pagelet für die Sidebar. Und BigPipe würde tatsächlich zuerst den Hauptinhalt rendern und dann erst die Sidebar (ganz egal ob die Endposition der Sidebar im HTML-Code noch vor dem Hauptinhalt liegt). \ No newline at end of file
+**More information from *Changhao Jiang* at Facebook Engineering:**
+<https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919> \ No newline at end of file
diff --git a/static/bigpipe.js b/static/bigpipe.js
index 5139483..3bf5809 100755
--- a/static/bigpipe.js
+++ b/static/bigpipe.js
@@ -2,18 +2,67 @@
// Revealing Module Pattern
//==============================================================================
var BigPipe = (function() {
+
+ //==============================================================================
+ // PhaseDoneJS object; responsible for Pagelet and Resource
+ //==============================================================================
+ var PhaseDoneJS = {
+ //==============================================================================
+ // PhaseDoneJS: Increases phase and executes callbacks
+ //==============================================================================
+ handler: function(context, phase) {
+ for(var currentPhase = context.phase; currentPhase <= phase; ++currentPhase) {
+ this.execute(context, currentPhase);
+ }
+
+ return context.phase = ++phase;
+ },
+
+ //==============================================================================
+ // PhaseDoneJS: Executes the callbacks of the given phase
+ //==============================================================================
+ execute: function(context, phase) {
+ context.phaseDoneJS[phase].forEach(function(code) {
+ try {
+ globalExecution(code);
+ } catch(e) {
+ console.error("PhaseDoneJS: " + e);
+ }
+ });
+ }
+ };
+
//==============================================================================
// Resource: Represents a single CSS or JS resource
//==============================================================================
- function Resource(resourceURL, type) {
+ function Resource(resourceURL, type, pageletID, phaseDoneJS) {
+ this.pageletID = pageletID;
this.resourceURL = resourceURL;
this.callbacks = [];
this.node = false;
this.done = false;
this.type = type;
+
+ this.phaseDoneJS = phaseDoneJS;
+ this.phase = 0;
+
+ PhaseDoneJS.handler(this, Resource.PHASE_INIT);
}
//==============================================================================
+ // Resource: Resource types
+ //==============================================================================
+ Resource.TYPE_STYLESHEET = 0;
+ Resource.TYPE_JAVASCRIPT = 1;
+
+ //==============================================================================
+ // Resource: Phase numbers for PhaseDoneJS
+ //==============================================================================
+ Resource.PHASE_INIT = 0;
+ Resource.PHASE_LOAD = 1;
+ Resource.PHASE_DONE = 2;
+
+ //==============================================================================
// Resource: Register a new callback
//==============================================================================
Resource.prototype.registerCallback = function(callback) {
@@ -24,9 +73,7 @@ var BigPipe = (function() {
// Resource: Executes all registered callbacks
//==============================================================================
Resource.prototype.executeCallbacks = function() {
- if(!this.done) {
- this.done = true;
-
+ if(!this.done && (this.done = true)) {
this.callbacks.forEach(function(callback) {
callback();
});
@@ -37,29 +84,32 @@ var BigPipe = (function() {
// Resource: Loading the resource
//==============================================================================
Resource.prototype.execute = function() {
- if(this.type === 0) {
- this.node = document.createElement('link');
- this.node.setAttribute('rel', 'stylesheet');
- this.node.setAttribute('href', this.resourceURL);
+ switch(this.type) {
+ case Resource.TYPE_STYLESHEET:
+ this.node = document.createElement('link');
+ this.node.setAttribute('rel', 'stylesheet');
+ this.node.setAttribute('href', this.resourceURL);
+ break;
+ case Resource.TYPE_JAVASCRIPT:
+ this.node = document.createElement('script');
+ this.node.setAttribute('src', this.resourceURL);
+ this.node.setAttribute('async', true);
+ break;
+ default:
+ return false;
}
- else {
- this.node = document.createElement('script');
- this.node.setAttribute('src', this.resourceURL);
- this.node.async = true;
- }
+ var callback = function() {
+ PhaseDoneJS.handler(this, Resource.PHASE_DONE);
+ this.executeCallbacks();
+ }.bind(this);
- this.node.setAttribute('class', 'bigpipe');
+ this.node.onload = callback;
+ this.node.onerror = callback;
document.head.appendChild(this.node);
- this.node.onload = function() {
- this.executeCallbacks();
- }.bind(this);
-
- this.node.onerror = function() {
- this.executeCallbacks();
- }.bind(this);
+ PhaseDoneJS.handler(this, Resource.PHASE_LOAD);
};
//==============================================================================
@@ -67,8 +117,8 @@ var BigPipe = (function() {
//==============================================================================
Resource.prototype.abortLoading = function() {
if(this.node) {
- this.node.onload = function(){};
- this.node.onerror = function(){};
+ this.node.onload = null;
+ this.node.onerror = null;
// Remove element from DOM
var parentNode = this.node.parentNode;
@@ -80,84 +130,88 @@ var BigPipe = (function() {
// Pagelet: Represents a single pagelet
//==============================================================================
function Pagelet(data, HTML) {
- this.pageletID = data.ID;
- this.HTML = HTML;
- this.CSSFiles = data.RESOURCES.CSS;
- this.JSFiles = data.RESOURCES.JS;
- this.JSCode = data.RESOURCES.JS_CODE;
- this.NEED = data.NEED;
+ this.ID = data.ID;
+ this.NEED = data.NEED;
+ this.HTML = HTML;
+ this.JSCode = data.CODE;
+ this.phaseDoneJS = data.PHASE;
+ this.stylesheets = data.RSRC[Resource.TYPE_STYLESHEET];
+ this.javascripts = data.RSRC[Resource.TYPE_JAVASCRIPT];
this.phase = 0;
- this.CSSResources = [];
- this.JSResources = [];
+ this.resources = [[], []];
- this.phaseDoneJS = data.PHASES;
-
- this.phaseDoneHandler(0);
+ PhaseDoneJS.handler(this, Pagelet.PHASE_INIT);
}
//==============================================================================
- // Pagelet: Increases phase and executes PhaseDoneJS
+ // Pagelet: Phase numbers for PhaseDoneJS
//==============================================================================
- Pagelet.prototype.phaseDoneHandler = function(phase) {
- for(var currentPhase = this.phase; currentPhase <= phase; ++currentPhase) {
- this.executePhaseDoneJS(currentPhase);
- }
-
- return (this.phase = ++phase);
- };
+ Pagelet.PHASE_INIT = 0;
+ Pagelet.PHASE_LOADCSS = 1;
+ Pagelet.PHASE_HTML = 2;
+ Pagelet.PHASE_LOADJS = 3;
+ Pagelet.PHASE_DONE = 4;
//==============================================================================
- // Pagelet: Executes the callbacks of the specific phase
+ // Pagelet: Initialize the pagelet resources
//==============================================================================
- Pagelet.prototype.executePhaseDoneJS = function(phase) {
- this.phaseDoneJS[phase].forEach(function(code) {
- try {
- globalExecution(code);
- } catch(e) {
- console.error("PhaseDoneJS: " + e);
- }
- });
+ Pagelet.prototype.initializeResources = function() {
+ var resourceURL;
+ var phaseDoneJS;
+
+ for(resourceURL in this.stylesheets) {
+ phaseDoneJS = this.stylesheets[resourceURL];
+ this.attachResource(new Resource(resourceURL, Resource.TYPE_STYLESHEET, this.ID, phaseDoneJS));
+ }
+
+ for(resourceURL in this.javascripts) {
+ phaseDoneJS = this.javascripts[resourceURL];
+ this.attachResource(new Resource(resourceURL, Resource.TYPE_JAVASCRIPT, this.ID, phaseDoneJS));
+ }
};
//==============================================================================
- // Pagelet: Initialize and execute the CSS resources
+ // Pagelet: Executes all resources of the specific type
//==============================================================================
- Pagelet.prototype.execute = function() {
- var isStarted = false;
-
- this.CSSFiles.forEach(function(resourceURL) {
- this.attachCSSResource(new Resource(resourceURL, 0));
- }.bind(this));
-
- this.JSFiles.forEach(function(resourceURL) {
- this.attachJSResource(new Resource(resourceURL, 1));
- }.bind(this));
+ Pagelet.prototype.executeResources = function(type) {
+ var somethingExecuted = false;
- this.CSSResources.forEach(function(resource) {
- isStarted = true;
+ this.resources[type].forEach(function(resource) {
+ somethingExecuted = true;
resource.execute();
}.bind(this));
- // If no CSS resource was started (= no external CSS resources exists), then begin to inject the HTML
- !isStarted && this.replaceHTML();
+ return somethingExecuted;
};
//==============================================================================
- // Pagelet: Attach a new CSS resource to the pagelet
+ // Pagelet: Initialize and execute the CSS resources
//==============================================================================
- Pagelet.prototype.attachCSSResource = function(resource) {
- resource.registerCallback(this.onloadCSS.bind(this));
- return this.CSSResources.push(resource);
+ Pagelet.prototype.execute = function() {
+ this.initializeResources();
+
+ if(!this.executeResources(Resource.TYPE_STYLESHEET)) {
+ this.replaceHTML();
+ }
};
//==============================================================================
- // Pagelet: Attach a new JS resource to the pagelet
+ // Pagelet: Attach a new resource to the pagelet
//==============================================================================
- Pagelet.prototype.attachJSResource = function(resource) {
- resource.registerCallback(this.onloadJS.bind(this));
- return this.JSResources.push(resource);
- };
+ Pagelet.prototype.attachResource = function(resource) {
+ switch(resource.type) {
+ case Resource.TYPE_STYLESHEET:
+ resource.registerCallback(this.onloadCSS.bind(this));
+ break;
+
+ case Resource.TYPE_JAVASCRIPT:
+ resource.registerCallback(this.onloadJS.bind(this));
+ break;
+ }
+
+ return this.resources[resource.type].push(resource);
+ }
//==============================================================================
// Pagelet: Executes the main JS code of the pagelet
@@ -167,20 +221,20 @@ var BigPipe = (function() {
try {
globalExecution(code);
} catch(e) {
- console.error(this.pageletID + ": " + e);
+ console.error(this.ID + ": " + e);
}
});
- this.phaseDoneHandler(4);
+ PhaseDoneJS.handler(this, Pagelet.PHASE_DONE);
};
//==============================================================================
// Pagelet: Get each time called if a single JS resource has been loaded
//==============================================================================
Pagelet.prototype.onloadJS = function() {
- if(this.phase === 3 && this.JSResources.every(function(resource){
+ if(this.phase === 3 && this.resources[Resource.TYPE_JAVASCRIPT].every(function(resource){
return resource.done;
})) {
- this.phaseDoneHandler(3);
+ PhaseDoneJS.handler(this, Pagelet.PHASE_LOADJS);
this.executeJSCode();
}
};
@@ -189,30 +243,30 @@ var BigPipe = (function() {
// Pagelet: Get each time called if a single CSS resource has been loaded
//==============================================================================
Pagelet.prototype.onloadCSS = function() {
- if(this.CSSResources.every(function(resource){
+ if(this.resources[Resource.TYPE_STYLESHEET].every(function(resource){
return resource.done;
})) {
- this.phaseDoneHandler(1);
+ PhaseDoneJS.handler(this, Pagelet.PHASE_LOADCSS);
this.replaceHTML();
}
};
//==============================================================================
- // Pagelet: Injects the HTML content into the DOM
+ // Pagelet: Replaces the placeholder node HTML
//==============================================================================
Pagelet.prototype.replaceHTML = function() {
- document.getElementById(this.pageletID).innerHTML = this.HTML;
+ document.getElementById(this.ID).innerHTML = this.HTML;
- this.phaseDoneHandler(2);
+ PhaseDoneJS.handler(this, Pagelet.PHASE_HTML);
- BigPipe.pageletHTMLreplaced(this.pageletID);
+ BigPipe.pageletHTMLreplaced(this.ID);
};
//==============================================================================
// BigPipe
//==============================================================================
var BigPipe = {
- pagelets: [],
+ pagelets: [],
phase: 0,
done: [],
wait: [],
@@ -226,7 +280,7 @@ var BigPipe = (function() {
this.pagelets.push(pagelet);
- if(this.phase = 0) {
+ if(this.phase === 0) {
this.phase = 1;
}
@@ -262,7 +316,7 @@ var BigPipe = (function() {
}
// Check if this was the last pagelet and then execute loading of the external JS resources
- if(BigPipe.phase === 2 && BigPipe.done.length === BigPipe.pagelets.length ) {
+ if(BigPipe.phase === 2 && BigPipe.done.length === BigPipe.pagelets.length) {
BigPipe.executeJSResources();
}
},
@@ -271,15 +325,9 @@ var BigPipe = (function() {
this.phase = 3;
this.pagelets.forEach(function(pagelet) {
- if(pagelet.JSResources.length === 0) {
+ if(!pagelet.executeResources(Resource.TYPE_JAVASCRIPT)) {
pagelet.onloadJS();
}
-
- else {
- pagelet.JSResources.forEach(function(resource) {
- resource.execute();
- });
- }
});
}
};
@@ -294,7 +342,11 @@ var BigPipe = (function() {
reset: function() {
BigPipe.pagelets.forEach(function(pagelet) {
- pagelet.CSSResources.concat(pagelet.JSResources).forEach(function(resource) {
+ pagelet.resources[Resource.TYPE_STYLESHEET].forEach(function(resource) {
+ resource.abortLoading();
+ });
+
+ pagelet.resources[Resource.TYPE_JAVASCRIPT].forEach(function(resource) {
resource.abortLoading();
});
});