richarddobron/bigpipe

BigPipe: Pipelining web pages for high performance built in PHP.
22,617 11
Install
composer require richarddobron/bigpipe
Latest Version:2.1.1
PHP:^8.0
License:MIT
Last Updated:Oct 18, 2026
Links: GitHub  ·  Packagist
Maintainer: dobron

A microframework for PHP and JavaScript. Send a page in independent parts (pagelets) that are rendered in parallel and reach the browser as soon as they are ready, update the page with DOM operations, open dialogs, load the next page into the layout and call JavaScript modules, all from PHP. The browser part is the npm package bigpipe-util.

👀 Demo App

Try the app with live demo or check how to install. It is a Laravel 13 application with:

  • Tutorials: a working example of every feature (streaming pagelets, lazy pagelets, poller, morph, Bootloader, events, DOM operations, dialogs, forms, payload, transport markers, configuration, DOM references, expired CSRF tokens and redirects), each with its code and a panel that shows what the server sent back.
  • A demo app: a blog, a shop, a dashboard of streamed pagelets, notifications and a profile, built the way an application is, from the Laravel recipes.

Every page of it is a page transition.

📕 Full documentation

https://bigpipe.etexweb.sk/

ℹ️ Requirements

📦 Installation

Follow these steps to install and set up:

1. Install composer package:

$ composer require richarddobron/bigpipe

2. Install npm package:

$ npm install bigpipe-util

3. Add the following to /path/to/resources/js/app.js:

import Primer from 'bigpipe-util/dist/Primer';
import { setModuleLoader } from 'bigpipe-util/dist/ModuleRegistry';

Primer();

Then tell BigPipe how to load your modules by name (PHP calls e.g. $response->call('UserLoggedInAlert')):

webpack

setModuleLoader(modulePath => require('./' + modulePath).default);

Vite

const modules = import.meta.glob(['./**/*.js', '!./app.js'], { eager: true });

setModuleLoader(modulePath => modules[`./${modulePath}.js`]?.default);

Vite needs an alias for the events module used by dialogs, see the documentation.

4. Add this line to the page footer:

<?= \dobron\BigPipe\BigPipe::render() ?>

It prints the script that sends the pagelets and the modules of the page to the browser. It queues them until the entrypoint has run, so the entrypoint can be a classic, async, defer or module script, before or after it. See the documentation for a Content Security Policy.

🚀 Quick start

Call a JavaScript module from PHP:

// resources/js/UserLoggedInAlert.js
export default function UserLoggedInAlert(username) {
    alert(`Welcome, ${username}!`);
}
$response = new \dobron\BigPipe\AsyncResponse();

$response->call('UserLoggedInAlert', null, ['Marvin']);
$response->send();

Update the page when a link is clicked or a form is submitted:

<a href="#" ajaxify="/ajax/remove.php?id=123" rel="async">Remove</a>
$response = new \dobron\BigPipe\AsyncResponse();

$response->remove('#item-' . (int) $_GET['id']);
$response->send();

Send a part of the page as soon as it is ready:

use dobron\BigPipe\BigPipe;
use dobron\BigPipe\Pagelet;

$feed = (new Pagelet('feed'))->defer(fn () => renderFeed(loadPosts()));
?>
<main><?= $feed ?></main>

<?php BigPipe::stream(); ?>

🧩 Pagelets

A page made of independent parts, each with its own content, CSS and JavaScript. The page is sent with their placeholders and the pagelets follow, one by one while they are rendered with BigPipe::stream(), or at the end of the page with BigPipe::render().

  • Pagelets: classes, deferred content, streaming, errors with fallbacks, display order and a version of the page without JavaScript.
  • Lazy pagelets: load a pagelet when it becomes visible, and infinite scroll.
  • Page transitions: load the next page into the layout instead of in full.
  • Bootloader: a resource map, and modules loaded only when they are called.
  • Poller: request a URL again and again, controlled by the server.
use dobron\BigPipe\Pagelet;

class FeedPagelet extends Pagelet
{
    protected array $css = ['/css/feed.css'];

    protected function content(): string
    {
        $this->call('Feed', 'init');

        return renderFeed(loadPosts());
    }
}

echo new FeedPagelet();          // <div id="pagelet_feed"></div>
echo FeedPagelet::lazy('/pagelets/feed'); // loaded when it becomes visible

⚡ Parallel rendering and fallbacks

Pagelets that wait for an API or a query do not have to wait for each other: with BigPipe::setParallel(true) they are rendered concurrently, so the page takes as long as the slowest pagelet, not as long as all of them. A pagelet that throws is replaced by its fallback, and the rest of the page is sent as usual.

use dobron\BigPipe\BigPipe;
use dobron\BigPipe\Pagelet;

BigPipe::setParallel(true);

class WeatherPagelet extends Pagelet
{
    protected mixed $fallback = '<p>The weather is not available right now.</p>';

    protected function content(): string
    {
        // Started without blocking (curl_multi, an async query…), awaited while the other pagelets are rendered.
        $forecast = Pagelet::await(fn () => $this->api->poll());

        return renderWeather($forecast);
    }
}

setPhase() and displayAfter() decide the order in which the browser shows the pagelets, and $response->refreshPagelet(new FeedPagelet()) renders a pagelet again and replaces it on the page. See Pagelets.

🧭 Page transitions

The links of the site load only the content of the next page into the layout, which stays with its scripts and state:

use dobron\BigPipe\AsyncResponse;
use dobron\BigPipe\Quickling;
?>
<main id="content"><?= $content ?></main>
<?php Quickling::init('content'); // in the layout: does nothing in a page transition ?>
// the controller answers a page transition with the content only
if (Quickling::isRequested()) {
    return (new AsyncResponse())->transition($content, 'Feed')->send();
}

🔁 Poller

Request a URL again and again; every response is applied like any other, and the server can slow the poller down or stop it:

use dobron\BigPipe\AsyncResponse;
use dobron\BigPipe\Poller;

(new Poller('/notifications/poll', 30000))->setMuteWhenIdle(5 * 60 * 1000)->start();

// the endpoint
$response = (new AsyncResponse())->setContent('#unread', (string) $unread);

if ($unread === 0) {
    $response->call(Poller::requestedId(), 'setInterval', [120000]); // or 'stop', 'mute'
}

$response->send();

🪄 DOMOPS API

  • setContent: Sets the content of an element.
  • appendContent: Insert content as the last child of specified element.
  • prependContent: Insert content as the first child of specified element.
  • insertAfter: Insert content after specified element.
  • insertBefore: Insert content before specified element.
  • remove: Remove specified element and its children.
  • replace: Replace specified element with content.
  • morph: Update specified element to match content, keeping focus and form values.
  • morphContent: Like morph, for the children of specified element only.
  • hide, show: Hide or show specified element.
$response = new \dobron\BigPipe\AsyncResponse();

$response->setContent('div#content', $newContent);
$response->send();

🔄 Refresh & Redirecting

$response = new \dobron\BigPipe\AsyncResponse();

$response->reload(250); // reload page with 250ms delay
// or
$response->redirect('/onboarding', 500); // redirect with 500ms delay

$response->send();

ℹ️ Payload & errors

$response = new \dobron\BigPipe\AsyncResponse();

$response->setPayload([
    'username' => $_POST['username'],
    'status' => 'unavailable',
    'message' => 'Username is unavailable.',
]);

$response->send();

setError() marks a response as failed, so the browser calls the error handler of the request instead of its handler:

$response = new \dobron\BigPipe\AsyncResponse();

$response
    ->setContent('#title-error', 'At most 80 characters.')
    ->setError('Could not save the post', 'The title is too long.');

$response->send();

🛠️ BigPipe API

  • call: Call JavaScript module method. You can call a specific class method or a regular function with the custom arguments.

Example PHP code:

$asyncResponse = new \dobron\BigPipe\AsyncResponse();

$asyncResponse->call('SecretModule', 'run', [
    'first argument',
    'second argument',
]);
// without a method, a class is created: $asyncResponse->call('UserLoggedInAlert', null, ['Marvin'])
$asyncResponse->send();

Example JavaScript code:

class SecretModule {
    run(first, second) {
        // ...
    }
}
  • instance: Keep one object in the browser and talk to it from more calls, also in the following responses.
  • define: Send data the browser can require as a module, e.g. the configuration of the page.
$chart = $asyncResponse->instance('ChartRenderer', [\dobron\BigPipe\TransportMarker::element('chart'), [10, 20, 30]]);
$chart->call('render');

$asyncResponse->define('SiteData', ['locale' => 'sk_SK']);
  • transport: Through transport markers you can send HTML content but also transform the content into JavaScript objects (such as Map, Set or Element).

Example PHP code:

$asyncResponse = new \dobron\BigPipe\AsyncResponse();
$asyncResponse->call('Chart', 'setup', [
    'element' => \dobron\BigPipe\TransportMarker::element('chart-div'),
    'dataPoints' => \dobron\BigPipe\TransportMarker::set([
        ['x' => 10, 'y' => 71],
        ['x' => 20, 'y' => 55],
        ['x' => 30, 'y' => 50],
        ['x' => 40, 'y' => 65],
    ]),
]);
$asyncResponse->send();

🔐 Expired CSRF tokens

A page that stays open after the session expired sends a token the server rejects. Give the browser the token, and a URL to get a new one: it sends the rejected request again, once, and the user does not lose what they typed.

\dobron\BigPipe\BigPipe::setCSRFToken($token, refreshUri: '/csrf-token');

// or answer the rejected request with the new token
$response->retryWithCSRFToken($newToken)->send();

⚡️ What can be ajaxified?

🔗 Links

<a href="#"
   ajaxify="/ajax/remove.php"
   rel="async">Remove Item</a>

📝 Forms

<form action="/submit.php"
      method="POST"
      rel="async">
    <input name="user">
    <input type="submit" name="Done">
</form>

💬 Dialogs

<a href="#"
   ajaxify="/ajax/modal.php"
   rel="dialog">Open Modal</a>
// /ajax/modal.php
$response = new \dobron\BigPipe\DialogResponse();

$response->setTitle('Delete the post?')
    ->setBody('<p>The post and its comments will be removed.</p>')
    ->setFooter('<button data-dismiss="modal">Cancel</button>')
    ->setBackdrop('static')
    ->dialog();

$response->send();

See Dialogs for their options, a React body and closing them from the server.

🧱 Integrations

  • Laravel: the setup, CSRF protection and long-running servers (Octane, FrankenPHP, RoadRunner, Swoole), and recipes for a real application.
  • React: render a React component with its props from PHP, e.g. in a dialog.

🌟 Inspiration

BigPipe is inspired by Facebook's BigPipe. For more details read their blog post: Pipelining web pages for high performance.

💡 Motivation

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.

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.

📑 Version Guidance

Version Released Status Repo PHP Version bigpipe-util
0.x 2022-03-27 Unsupported v0.x >=7.1 0.x
1.x 2022-07-29 Unsupported v1.x ^8.0 0.2.x
2.x 2026-10-10 Maintained main ^8.0 2.x

From 2.0, richarddobron/bigpipe and bigpipe-util share the major and minor version: a release that changes what the server sends comes out in both, e.g. 2.1.0 of this library works with 2.1.x of bigpipe-util. Fixes are released on their own as patch versions.

Deprecated methods keep working until the next major version and are marked with @deprecated.

🤝 Contributing

Please see CONTRIBUTING for details.

📜 License

This project is licensed under the MIT License. See the LICENSE file for details.

Related Packages

eelcol/laravel-html-dom

Laravel HTML Dom implementation

5,955 0
growthexponent/laravel-html-dom-parser

Laravel wrapper for the PHP HTML DOM Parser package.

261 4
insebre/ebre-escool-ng-installer

ebre-escool ng is an application management schools

13 0
unisharp/laravel-ckeditor

JavaScript WYSIWYG web text editor (for laravel).

772,669 373
techguy/laravel-ckeditor

JavaScript WYSIWYG web text editor (for laravel).

13,898 10

Version History

Version Released PHP License
2.1.1 ^8.0 MIT
2.1.0 ^8.0 MIT
2.0.0 ^8.0 MIT
1.2.1 ^8.0 MIT
1.2.0 ^8.0 MIT