Git Product home page Git Product logo

bigpipe's Introduction

BigPipe

Version npmBuild StatusDependenciesCoverage Status

BigPipe is a radical new web framework for Node.JS. The general idea is to decompose web pages into small re-usable chunks of functionality called Pagelets and pipeline them through several execution stages inside web servers and browsers. This allows progressive rendering at the front-end and results in exceptional front-end performance.

Most web frameworks are based on a request and response pattern, a request comes in, we process the data and output a template. But before we can output the template we have to wait until all data has been received in order for the template to be processed. This doesn't make any sense for Node.js applications where everything is done asynchronously. When receiving your first batch of data, why not send it directly to the browser so it can start downloading the required CSS, JavaScript and render it.

BigPipe is made up over 20 modules whose current status is available at: HEALTH.md

Installation

BigPipe is distributed through the node package manager (npm) and is written against Node.js 0.10.x.

npm install --save bigpipe

Versioning

To keep track of cross module compatibility, the imported components will be synced on minor releases. For example, [email protected] will always be compatible with [email protected] and [email protected].

Support

Got stuck? Or can't wrap your head around a concept or just want some feedback, we got a dedicated IRC channel for that on Freenode:

  • IRC Server: irc.freenode.net
  • IRC Room: #bigpipe

Still stuck? Create an issue. Every question you have is a bug in our documentation and that should be corrected. So please, don't hesitate to create issues, many of them.

Table of Contents

BigPipe

Getting started

In all of these example we assume that your file is setup as:

'use strict';

var BigPipe = require('bigpipe');

BigPipe.createServer()

public, returns BigPipe.

To create a BigPipe powered server can simply call the createServer method. This creates an HTTP or HTTPS server based on the options provided.

var bigpipe = BigPipe.createServer(8080, {
  pagelets: __dirname +'/pagelets',
  dist:  __dirname +'/dist'
});

The first argument in the function call is port number you want the server to listen on. The second argument is an object with the configuration/options of the BigPipe server. The following options are supported:

  • cache A cache which is used for storing URL lookups. This cache instance should have a .get(key) and .set(key, value) method. Defaults to false
  • dist The location of our folder where we can store our compiled CSS and JavaScript to disk. If the path or folder does not exist it will be automatically created. Defaults to working dir/dist.
  • pagelets A directory that contains your Pagelet definitions or an array of Pagelet constructors. Defaults to working dir/pagelets. If you don't provide Pages it will serve a 404 page for every request.
  • parser The message parser we should use for our real-time communication. See Primus for the available parsers. Defaults to JSON.
  • pathname The root path of an URL that we can use our real-time communication. This path should not be used by your Pages. Defaults to /pagelet
  • transformer The transformer or real-time framework we want to use for the real-time communication. We're bundling and using ws by default. See Primus for the supported transformers. Please note that you do need to add the transformer dependency to your package.json when you choose something other than ws.
  • redirect When creating a HTTPS server you could automatically start an HTTP server which redirects all traffic to the HTTPS equiv. The value is the port number on which this server should be started. Defaults to false.

In addition to the options above, all HTTPS server options are also supported. When you provide a server with cert and key files or set the port number to 443, it assumes you want to setup up a HTTPS server instead.

var bigpipe = BigPipe.createServer(443, {
  key: fs.readFileSync(__dirname +'/ssl.key', 'utf-8'),
  cert: fs.readFileSync(__dirname +'/ssl.cert', 'utf-8')
});

When you're creating an HTTPS server you got to option to also setup a simple HTTP server which redirects all content to HTTPS instead. This is done by supplying the redirect property in the options. The value of this property should be the port number you want this HTTP server to listen on:

var bigpipe = BigPipe.createServer(443, {
  ..

  key: fs.readFileSync(__dirname +'/ssl.key', 'utf-8'),
  cert: fs.readFileSync(__dirname +'/ssl.cert', 'utf-8'),
  redirect: 80
});

new BigPipe()

public, returns BigPipe.

If you want more control over the server creation process you can manually create a HTTP or HTTPS server and supply it to the BigPipe constructor.

'use strict';

var server = require('http').createServer()
  , BigPipe = require('bigpipe');

var bigpipe = new BigPipe(server, { options });

If you are using this pattern to create a BigPipe server instance you need to use the bigpipe.listen method to listen to the server. When this is called, BigPipe starts compiling all assets, attach the correct listeners to the supplied server, attach event listeners and finally listen on the server. The first argument of this method is the port number you want to listen on, the second argument is an optional callback function that should be called when server starts listening for requests.

bigpipe.listen(8080, function listening() {
  console.log('hurray, we are listening on port 8080');
});

BigPipe.version

public, returns string.

bigpipe.version;

The current version of the BigPipe framework that is running.

BigPipe.define()

public, returns BigPipe.

bigpipe.define(pagelets, callback);

Merge pagelet(s) in the collection of existing pagelets. If given a string it will search that directory for the available Pagelet files. After all dependencies have been compiled the supplied, the callback is called.

bigpipe.define('../pagelets', function done(err) {

});

bigpipe.define([Pagelet, Pagelet, Pagelet], function done(err) {

}).define('../more/pagelets', function done(err) {

});

BigPipe.before()

public, returns BigPipe.

bigpipe.before(name, fn, options);

BigPipe has two ways of extending it's build-in functionality, we have plugins but also middleware layers. The important difference between these is that middleware layers allow you to modify the incoming requests before they reach BigPipe.

There are 2 different kinds of middleware layers, async and sync. The main difference is that the sync middleware doesn't require a callback. It's completely optional and ideal for just introducing or modifying the properties on a request or response object.

All middleware layers need to be named, this allows you to enable, disable or remove the middleware layers. The supplied middleware function can either be a pre-configured function that is ready to modify the request and responses:

bigpipe.before('foo', function (req, res) {
  req.foo = 'bar';
});

Or an unconfigured function. We assume that a function is unconfigured if the supplied function has less than 2 arguments. When we detect such a function we automatically call it with the context that is set to BigPipe and the supplied options object and assume that it returns a configured middleware layer.

bigpipe.before('foo', function (configure) {
  return function (req, res) {
    res.foo = configure.foo;
  };
}, { foo: 'bar' });

If you're building async middleware layers, you simply need to make sure that your function accepts 3 arguments:

  • req The incoming HTTP request.
  • res The outgoing HTTP response.
  • next The continuation callback function. This function follows the error first callback pattern.
bigpipe.before('foo', function (req, res, next) {
  asyncthings(function (err, data) {
    req.foo = data;
    next(err);
  });
});

BigPipe.remove()

public, returns BigPipe.

bigpipe.remove(name);

Removes a middleware layer from the stack based on the given name.

bigpipe.before('layer', function () {});
bigpipe.remove('layer');

BigPipe.disable()

public, returns BigPipe.

bigpipe.disable(name);

Temporarily disables a middleware layer. It's not removed from the stack but it's just skipped when we iterate over the middleware layers. A disabled middleware layer can be re-enabled.

bigpipe.before('layer', function () {});
bigpipe.disable('layer');

BigPipe.enable()

public, returns BigPipe.

bigpipe.enable(name);

Re-enable a previously disabled module.

bigpipe.disable('layer');
bigpipe.enable('layer');

BigPipe.use()

public, returns BigPipe.

bigpipe.use(name, plugin);

Plugins can be used to extend the functionality of BigPipe itself. You can control the client code as well as the server side code of BigPipe using the plugin interface.

bigpipe.use('ack', {
  //
  // Only run on the server.
  //
  server: function (bigpipe, options) {
     // do stuff
  },

  //
  // Runs on the client, it's automatically bundled.
  //
  client: function (bigpipe, options) {
     // do client stuff
  },

  //
  // Optional library that needs to be bundled on the client (should be a string)
  //
  library: '',

  //
  // Optional plugin specific options, will be merged with Bigpipe.options
  //
  options: {}
});

Pagelets

Pagelets are part of the bigpipe/pagelet module and more information is available at: https://github.com/bigpipe/pagelet

Events

Everything in BigPipe is build upon the EventEmitter interface. It's either a plain EventEmitter or a proper stream. This a summary of the events we emit:

Event Usage Location Description
log public server A new log message
transform::pagelet public server Transform a Pagelet
listening public server The server is listening
error public server The HTTP server received an error
pagelet::configure public server A new pagelet has been configured

Debugging

The library makes use of the diagnostics module and has all it's internals namespaced to bigpipe:. These debug messages can be trigged by starting your application with the DEBUG= env variable. In order to filter out all messages except BigPipe's message run your server with the following command:

DEBUG=bigpipe:* node <server.js>

The following DEBUG namespaces are available:

  • bigpipe:server The part that handles the request dispatching, page / pagelet transformation and more.
  • bigpipe:pagelet Pagelet generation.
  • bigpipe:compiler Asset compilation.
  • bigpipe:primus BigPipe Primus setup.
  • pagelet:primus Pagelet and Primus interactions
  • pagelet Pagelet interactions

Testing

Tests are automatically run on Travis CI to ensure that everything is functioning as intended. For local development we automatically install a pre-commit hook that runs the npm test command every time you commit changes. This ensures that we don't push any broken code into this project.

Inspiration

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

License

BigPipe is released under MIT.

bigpipe's People

Contributors

3rd-eden avatar adrian-chang avatar benjaminparnell avatar damonoehlman avatar jaredmdobson avatar jcrugzz avatar lholmquist avatar mbonaci avatar seanewest avatar sequoia avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bigpipe's Issues

Passing socket ids (or other data) between pagelets

I've been poking around trying to figure out how to build a basic authentication system using bigpipe. The problem I'm having is how to get a unique identifier for each logged-in user (I'm assuming it is best to use the primus.spark.id), and where to retrieve that data, either in a pagelet, or from the page and pass it to a pagelet.

Can I get some input on best practices for managing this with bigpipe?
I looked at the pagelet.substream.id, but I'm concerned that won't be available to other pagelets, and I'll have to find another value to figure out how to maintain user data across multiple pagelets.

Taking a basic example of a chat app, I have a user who is logged in "new_user",(the login form is a pagelet). I also have a pagelet of logged-in users, but I don't want to show the "new_user" in the list of logged-in users. If I want the pagelets to manage what data gets returned to the client, how can I share data from one pagelet to another? And is it possible to access the primus socket directly? Or what is the best way to do that?

Incorrect stylus leads to race condition on startup

https://github.com/bigpipe/bigpipe/blob/master/lib/compiler.js#L329 will be called with the error from the smithy compiler, e.g. stylus. Below is some console.log output related to the race condition. Shortcircuiting the fn(error) with done(error) will work, but that is completely illogical. Most likely related to wacky listener code in stylus

// initial assemble values that go through async.each
[ '/home/swaagie/projects/contour/assets/nodejitsu/navigation/base.styl',
  '/home/swaagie/projects/browsenpm.org/node_modules/packages-pagelet/package.js',
  '/home/swaagie/projects/browsenpm.org/node_modules/packages-pagelet/css.styl',
  'http://code.jquery.com/jquery-2.1.0.min.js',
  '/home/swaagie/projects/bigpipe/pagelets/diagnostics/diagnostic.styl',
  '/home/swaagie/projects/bigpipe/pagelets/diagnostics/diagnostic.styl' ]

// Content length, listener length in stylus and file the compiler was called with
3995 1
/home/swaagie/projects/contour/assets/nodejitsu/navigation/base.styl
/home/swaagie/projects/browsenpm.org/node_modules/packages-pagelet/package.js
3535 1
/home/swaagie/projects/browsenpm.org/node_modules/packages-pagelet/css.styl
58 1
/home/swaagie/projects/bigpipe/pagelets/diagnostics/diagnostic.styl
58 1
/home/swaagie/projects/bigpipe/pagelets/diagnostics/diagnostic.styl
3995 1
/home/swaagie/projects/contour/assets/nodejitsu/navigation/base.styl

I would like to contribute documentation and examples.

Hello there BigPipe Team,

I would like to contribute documentation and examples. First I am trying to learn as much as I can from your repositories and issues. As far as I can tell from using it:

Still have a lot to learn, but would love to help anywhere I can and hopefully you guys can point me in the right direction.

Also logo, is it a big tobacco pipe? https://openclipart.org/detail/183205/Pipe or a big pipe: https://openclipart.org/detail/204553/Isometric%20pipes

Thanks,
Jared

trailing headers

One of the biggest problems that we have to overcome is that we are sending the headers before all pagelets have been processed. It could be that one of the pagelets need to modify headers of the request to for example add or update a cookie.

To make this work a seamless as possible we should probably override the setHeader method once we've written our initial template and have it do addTrailers call instead so these changed headers can be written once we've processed the pagelet.

Consistency

This issue tracks and discusses the various of inconsistencies between the various of projects in the BigPipe organization.

  1. namespacing: We're currently namespacing all our debug statements with one single :. But out event namespacing is using :: as a delimiter.
  2. british/english/old english: This is a mistake I make often, there are different ways of writing initialise/initialize. We should probably be using initialize, but who says that our users aren't making the same mistakes as living a multicultural open source world.. I don't really want to force a language on to people. We've already started with normalization in certain projects: https://github.com/bigpipe/pagelet/blob/master/index.js#L655-L657 but this can probably be done a better way.
    Affected:

Request based resource caching

A Page defines which resources are available for the Pagelets as multiple pagelets can access the same resource we want to make sure that we don't retrieve the data twice. So we should cache the responses from the resources until our request has been written. While we are fetching data, all other requests should be queued and flushed with the response when we receive our data.

This should only happen on the initial and first request. When we are using a real-time connection, it should bypass the cache.

Safer flushing

There are a lot of cross browser bugs when flushing data to the browser. Some of them require at least an x amount of bytes before they render anything. We should make sure that our chunks either queue on the server side until we have enough bytes or provide padding.

Some of these bugs are mentioned in the http://nl1.php.net/flush documentation and I highly doubt that these are fixed in node it self.

See also: http://www.stevesouders.com/blog/2009/05/18/flushing-the-document-early/ and this books.

RPC events leak through the substream

The RPC messages that we send between the server and client are leaking to the data event of the substream. This makes it nearly impossible for hackers to communicate in realtime using our substream. We should capture the data packets before they are emitted as data events.

The problem with this is that we need message transformers of Primus. These transformers are only applied the main connection and not the substreams. Work has been done make message transforming more robust and re-usable. We will be allowed to use the message interception/transformation when a new primus and substream has been released.

Local site dependencies clog the pipe

After taking a look at how the npm-package-json-pagelet specified an external clientside JS dependency, I tried doing something similar with a local JS file:

require('bigpipe').Pagelet.extend({
  name: 'localvideo',
  view: __dirname + '/view.jade',
  js: 'client.js',
  css: 'client.styl',

  dependencies: [
    '/app.js'
  ]
}).on(module);

It would seem though that this stops bigpipe serving any content. Just wondering what the appropriate way to instruct a pagelet to require non-cdn hosted JS content is? For the moment, I've worked around it by having the file included in the layout.

Quickling/pjax through real-time

Quickling allows you to remove redundant work by only updating specific sections of your page when you advance to a new page. Facebook describes quickling as way to transparently ajaxify the whole website. It's a technique that's also deployed at @github in the form of @defunkt's jquery-pjax. Both of these implementations are using AJAX requests to update small regions of the page. When load a page we immediately establish a real-time connection to the back-end. This connection should be leveraged instead. The flow for this would be:

  1. User click a link or forward/back button
  2. Quickling sends a request to the server for the content area
  3. Quickling blanks the content area to give the users a feel of loading activity
  4. Downloads the JavaScript and CSS required for the page
  5. Show the new content.

Under the hood it needs:

  • Link controller, for attaching the quickline to links
  • History manager, for reacting to prev/next and manipulating the history's pushState
  • Bootloader from pipe to load the assets.
  • Unload active pagelets and it's resources.

The only obvious downside of this would be that the real-time connection isn't caching the page lookups. If you we're to do an AJAX request using GET you could leverage the browser cache to minimize the load time even further.

References:

https://github.com/defunkt/jquery-pjax
https://github.com/blog/831-issues-2-0-the-next-generation
http://www.slideshare.net/ajaxexperience2009/chanhao-jiang-and-david-wei-presentation-quickling-pagecache

Create Data class for handling POST/PUT data

Create a basic class that is capable of handling the different data types send from browser/form, e.g. json/multipart/encoded. Connect middleware would also be an option, however it would require parsing and fetching data upfront before any bootstrapping or rendering is done. Thus middleware would delay the whole page. A data class would allow processing to be deferred until right before rendering.

Client side progress

When we add the initial bootstrap to the page, it might be useful to know how many pagelets we are going to receive so we can do some progress indication.

Pagelet based CSRF

Web security is something that a lot of frameworks ignore and defer to third party developers through a plugin/middleware system. With Bigpipe I would love to see this build in by default in order to protect developers and it's users for possible attacks. One of these attacks is CSRF.

When we add a pagelet to the page we could automatically add a CSRF id to the it's configuration so it's readable from the client side as well as introduce it in the template engine though primus.crsf.

Not yet sure what the best approach would be for this, but that it should be included is something that I'm certain about.

Example application

It would be great to have an example hello world application, to see the benefits and simple end to end code.

CLI application

Getting started with an initial Project structure when you are starting with bigpipe is quite intensive.

You've got alot of directory to create:

  1. A directory for pages
  2. Directories for pagelets
    1. a index.js file
    2. a css, javascript and view for the paglet
  3. Directories for views

All this should ideally be done through a small cli application that creates some initial project structure like:

bigpipe init

And for adding new pagelets

bigpipe pagelet <name>

And the same for pages:

bigpipe page <name>

Plugin interface

Having a well architected plugin system for this framework is one of the top priorities. We cannot depend on any existing "middleware" plugins as we do not have the notion of a request/response pattern. We have a page, with pagelets. In addition to that, I want to keep the framework as light weight as possible.

There are a couple of awesome things I would love to build upon this framework but it's usage would be to much of an edge case that it should be implemented in a form of a plugin. Which is good as we can start building some of the requirements of the plugin interface based on the needs of these plugins.

A plugin should have control over the pagelet selection process. With this control it would be easy to create an A/B testing plugin on top of the pagelets that would automatically switch between a couple of pre-defined pagets. But not only A/B tests would leverage from this, it would also make it possible to build mobile interface from the same page by just serving a different set of pagelets that have reduced set of functionality but still use the same data/resources as the "regular" pagelets.

Would love to have input on this matter.

Make page fully restful

Basically this is pretty much done already, POST/PUT are handled correctly against page/pagelet. Render is basically equal to GET. We could create a more clear API, by defining the specific HTTP verbs on the page/pagelet, e.g. include DELETE, HEAD.

CSS post processing

The CSS retrieved once the pagelet has arrived on the page. This CSS is retrieved asynchronous but due to browser bugs it needs poll the page for stylesheet changes. So in order for this to work we should add a single CSS rule to the end of pagelet's CSS file:

#pagelet_<filename> { height: 45px }

As the filename of the CSS should be an MD5/SHA of the content, we need to add this after we've generated the filename for the file.

Symlinked main pagelet module leads to race condition

Easily replicated with tests, simply symlink the pagelet module, it seems a require race condition related to symlinking will lead to an undefined pagelet when Pagelet.optimize is called.

     TypeError: Cannot call method 'init' of undefined
      at allocate (/home/swaagie/projects/bigpipe/page.js:256:37)
      at Array.map (native)
      at discover (/home/swaagie/projects/bigpipe/page.js:255:28)
      at Context.<anonymous> (/home/swaagie/projects/bigpipe/test/page.test.js:129:13)
      at Test.Runnable.run (/home/swaagie/projects/bigpipe/node_modules/mocha/lib/runnable.js:204:15)
      at Runner.runTest (/home/swaagie/projects/bigpipe/node_modules/mocha/lib/runner.js:374:10)
      at /home/swaagie/projects/bigpipe/node_modules/mocha/lib/runner.js:452:12
      at next (/home/swaagie/projects/bigpipe/node_modules/mocha/lib/runner.js:299:14)
      at /home/swaagie/projects/bigpipe/node_modules/mocha/lib/runner.js:309:7
      at next (/home/swaagie/projects/bigpipe/node_modules/mocha/lib/runner.js:247:23)
      at Object._onImmediate (/home/swaagie/projects/bigpipe/node_modules/mocha/lib/runner.js:276:5)
      at processImmediate [as _immediateCallback] (timers.js:330:15)

.status max callstack

Will cause callstack loops as the same response and request will be used again and will walk the same chain. Capture/status should just write the 500-pagelet to the stream and end it

Extending Client Functionality through Plugins

I notice that the use section of the docs shows how to write a plugin. I've had some success writing the server handler but so far attempting to implement a client handler has yielded no results.

Just wondering if there is some trick to this that I'm missing...

Client side CSS/JS caching

Ideally we would like to have the browser cache each CSS/JS resource. This could be done by just supplying a CSS/JS file, but this is suboptimal as each file will trigger request overhead.

Bundling script or style tags on the other hand will prevent any caching. Resulting in new

As pagelets will require html5 anyway, localStorage seems like an ideal candidate, but there are some caveats, like:

  • how to ensure the latest version of css/js is sent/available
  • add fallback if localStorage has no assets
  • pagelets should be aware of what is in localStorage by cookies

Routing should discover child pagelets

Child pagelets should be checked and added if they have routes as well, this will prevent redefinition of the same child pagelet as parent just to get the route registered.

Error: Callback was already called. -- Caused by broken stylus files.

I have a pagelet that uses uses stylus for the CSS. At the top of this stylus file there was an incorrect @import statement:

@import ./tooltip.styl

This caused the server to crash with the following message:

/Users/V1/Projects/3rd-Eden/bigpipe/node_modules/async/lib/async.js:22
            if (called) throw new Error("Callback was already called.");
                              ^
Error: Callback was already called.
    at /Users/V1/Projects/3rd-Eden/bigpipe/node_modules/async/lib/async.js:22:31
    at store (/Users/V1/Projects/3rd-Eden/bigpipe/lib/compiler.js:317:25)
    at Renderer.render (/Users/V1/.smithy/node_modules/stylus/lib/renderer.js:106:5)
    at requiredNib (/Users/V1/Projects/3rd-Eden/bigpipe/node_modules/smithy/lib/stylus.js:60:16)
    at EventEmitter.installed (/Users/V1/Projects/3rd-Eden/bigpipe/node_modules/smithy/node_modules/canihaz/index.js:204:11)
    at EventEmitter.g (events.js:175:14)
    at EventEmitter.emit (events.js:98:17)
    at done (/Users/V1/Projects/3rd-Eden/bigpipe/node_modules/smithy/node_modules/canihaz/index.js:348:15)
    at ChildProcess.exithandler (child_process.js:635:7)
    at ChildProcess.EventEmitter.emit (events.js:98:17)

So it seems that the @import paths might be incorrectly given to or by smith

Ability to customize the view used based on request parameters (or similar)

I'd really like to be able to customise the view used for a page, so that something like the following page configuration would work:

require('../basepage').extend({
  path: '/:org/:role/session/:sessionid',
  view: function() {
    return '../views/session.jade';
  }
}).on(module);

In my case, once I had a function that could return which view to use then I would like to configure the view based on the organisation :org that was specified.

NOTE: If there is an alternative way of achieving the same outcome, then I'll happily use that instead.

Forego pages, only use pagelets

During my last presentation @amsterdamjs this was suggested by a listener. Based on the frameworks we showed of pages entirely consisting out of pagelets. The driving force behind this change is the ability to have recursive pagelets. If pagelets can act as container, why would pages be required?

This issue could have been created on the pagelet repo as the biggest changes will be made to that code/repo, however this is more about the general pattern behind bigpipe.

Currently pages are responsible for the following. The question/discussion is where should each part of the logic go? Either shift it to bigpipe server or the pagelet.

Pagelet discovery
The pages that are provided to the Pipe instance will automatically discover their pagelets on initialization. Pagelets already contain the traverse function to discover recursive pagelets, in theory this could be swapped 1:1.

Rendering
Pages have logic to accommodate each different rendering mode, e.g. async/pipeline/sync. https://github.com/bigpipe/bigpipe/blob/master/page.js#L297-L396. This logic can be shifted to pagelets.

Configure/bootstrap html response
Renders the initial headers and writes the HTML to the client as fast as possible. This is external to pagelets and should move to the bigpipe server. To accommodate request dispatching, the request handling should ideally also be done by the bigpipe server.

Request handling
Pipe.dispatch is responsible finding the Page with the correct route and invoking the Page.configure. This logic could be shifted to the Pagelet.configure method, as long as Pagelets have routing. The write, flush and end can be shifted to the bigpipe server. Each pagelet has a reference to the pipe instance and should be able to call these methods with the correct data (in stead of referencing this).

Routing
Any request is dispatched from bigpipe to responding routes. In stead of the page having a router the pagelet can have a Router instance. The tricky part is to ensure this is only done for parent pagelets. When the bigpipe instance calls optimize on parent pagelets this should be handled. The parent pagelet will also have to maintain a params property.

View and dependencies
Pages and pagelets already both have a view template, this should not matter. In fact by not using a templater (but client side JS, if async render) the amount of template code in templates should be reduced. Since pagelets can simply be included using <element data-pagelet="name"></element>

Charset, status code and content-type
Should just as easily be contained by the pagelet, the parent pagelet will then determine these, even if a child pagelet has different values. Those child pagelet values shoud be ignored.

where did the "Page" object come form?

When I was using bigpipe,I wrote my code just like bellow

var BigPipe = require('bigpipe');
var page=BigPipe.Page;
page.extend({
    path: '/',
    view:'index.html'
}).on(module);

But when I ran my application,I got an error like this:

TypeError: Cannot call method 'extend' of undefined

It said that the Page is undefined.
And I can't find anything about initialization of the Page objcet on http://bigpipe.io/, someone help me,plz.

Roadmap

Please note that this roadmap is work in progress. 
Do not assume that this is final until this message 
has been removed by the topic creator.

0.8

  • Conditional Pagelets #56

0.9

  • Prepend _ to each private function in pagelet to prevent collisions with extended functions
  • Realtime dashboard example app (demonstration purposes only)
  • Rewrite of the internal compiler
  • Merge Page with Pagelet to create a uniform interface #55

0.10

  • Realtime dashboard example app (documentation en tutorial around it)
  • TodoMVC sample application
  • Enable sandboxing for client code.
  • Refactor of client code loading and removing the dependence of knowing the pagelet names.

0.11

  • Quicklining/pjax #7
  • more documentation
  • more tests

pagelet states

The pagelets are added dynamically to the page. When we've received the initial template from the server we could scan the document and add special .loading classes to the pagelets. These classes could be leveraged by a global CSS file to improve the user experiance while we are lazy loading the pagelets.

This allows developers to add spinners and what more to the page. In addition to that it could be used for styling error states and complete states.

Error reporting

The pipe.js client framework should come with build-in error tracking. We can track when a pagelet is having errors as their client-side JavaScript will be loaded in to an iframe. In addition to that we can capture the window.error and register those errors.

As we've established a connection with the server, we can easily transfer this back to the backend and emit a client error event with the stack, message, line number and where the error was thrown.

var Pipe = require('pagelet');

pipe = new Pipe(server, { opts });
pipe.on('client error', function (error) {
  console.log(error instanceof Error); // true
});

This allows us to hook in a custom report and deal with them.

Continuous, learning code generation.

The initial core library is generated from frequently used libraries inside the Pagelets instances and it's dependencies. But just because these libraries are frequently used in these Pagelets it doesn't mean that the pages were these pagelets are used on are frequently visited. There for we should continuously monitor incoming requests and make adjustments to our code generation pattern. Code generation is however an expensive operation as it needs to be minified and should be stalled until we've received a confidence level of 95%.

Once we've established a stable pattern we can store this on disk to prevent to much overhead.

deprecate: the support of multiple css processors

We're currently trying to be the jack of all trades. This is hurting the innovation and future of this project. In order to move faster with a smaller and more flexible codebase I suggest we deprecate all the CSS pre-processors like stylus, less and sass and just go with one system which will be rework.

Rework is flexible enough to replace all those languages without having to rewrite much of your css. It's plugin architecture makes it ideal for building flexible pagelets. One use case could be defining default styling that should be applied to all pagelets, adding css colors and what more..

Example:

var bigpipe = require('bigpipe').createServer();

bigpipe.design
.use(require('rework-plugin'))
.use(require('another-rework-plugin'));

WebSocket multiplexing

Each pagelet should have a real-time connection with the backend so they can be continuously updated. These connections need to be namespaced or multiplexed in to one single real-time connection that the pipe.js front-end library establishes.

As we are using Primus for handling all the real-time connections, we can create a primus plugin that will deal with this connection abstraction.

The plugin should satisfy the following hard requirements.

  • It should inherit the changes that other primus plugins have made.
  • There should be no need to pre-establish a namespaced channel on the server side.
  • There should be an event emitted when a new namespace is requested by the client.
  • There should be no limit on the amount of namespaces that a client can create.
  • No information should be leaked through the multiplex.

Multiplexing in socket.io is done through:

socket.of('/channel').on('connection', function () {
  // code
});

While it might look nice from a syntax perspective, it can be confusing and overly verbose internally. The only thing that multiplixing requires is providing a direction of the message. So instead of creating new instances we could adopt:

primus.at('namespace').write(data);

primus.from('namespace', function (data) {
  // code
});

POST

Critical

This is a critical issue and needs to be resolved as soon as possible as it's blocking the use of bigpipe at Nodejitsu.

We've been through 2 revisions of post handling in BigPipe. Both were okay, both had serious flaws. This post tries to lay the foundation on how POST and PUT requests need to be handled in bigpipe. There are a lot of factors in play here in order to make this operate as fluently as possible. These are the requirements:

  1. POST and PUT should be handled asynchronously
  2. At least one Pagelet needs to be able to handle the post request.
    • The pagelet must be allowed (authorization)
  3. The Page needs to be handle the post request.
  4. The request should limited to a maximum amount of bytes to prevent DDOS
  5. The request should be loaded dynamically, so that pagelets that don't require post can still be rendered.
  6. The pagelet should be able to redirect the page if needed and cancel the writing of all other headers / data.

Are there any other case's i'm missing here?

Allow custom client bigpipe.js scripts

We currently force our bigpipe.js upon our users. People might not want to follow our idea's on sandboxing, client-side execution of code and html updating. By refactoring this in to a separate component we can have frameworks like emberjs, react, angular and buzzword take care of this logic. Making this whole thing framework independend which would be freaking awesome.

Area's that it needs to touch:

  • bigpipe compiler: It needs to be able to read/include a custom library instead of bigpipe.js
  • bootstrap: It needs to be hooked by the bootstrap module so it can generate it's own {bootstrap} logic.
  • pagelet: It needs to provide it's own pagelet fragment.

The wrapper, framework, insert name suggestion here should be easily extendable like all our other parts are. Example codes:

var BigPipe = require('bigpipe');

var bigpipe.createServer(function () {
   engine: require('bigpipe-engine').exend({
      bootstrap: function () {}, // return string
      fragment: function () { }, // return string with {bigpipe::tags}
      library: require.resolve('what-ever.js')
   })
});

//
// Or programatically:
//
bigpipe.engine(require('bigpipe-engine').extend({
  // same props as above.
}));

BOOM.

llbd9f2

Assets with no content are created as null.js

When you have a pagelet with a completely empty js or css file they will be send as {"css":["/null.js"],"js":["/null.js"] } to the client. These files will also be loading forever since they are not captured by the compiler and for some odd reason the 404 page isn't triggered.

To reproduce simply load a new Pagelet on an empty page. Set the css and js to an empty file that you've created using touch <filename>

View rendering

Each pagelet have their own template. This template needs to be able to compile to a client-side working template and render on the server side. The server side rendering is important when you want to have your page render fully for SEO reasons.

The client side rending is needed so we can automatically update the view client side when we receive new data.

Ideally we shouldn't be bound to a template language, most template engines like ejs and jade can compile to a client side compatible function. We should probably write a simple separate view engine that can handle this. Good prior art for inspiration would be the viewful engine of Flatiron: https://github.com/flatiron/viewful

Remove abstractions

Resources

Resources are to opinionated for this project. Resource management should be done by plugins or third party code.

ACL

ACL this is nice, but again, too opinionated.

Extract primus, use as default plugin.

The real-time functionality isn't something that everybody needs, it makes the project extra awesome, but I feel that it should be extracted out and moved as default enabled plugin.

This will make the code easier to test and maintain as it's currently spread out through 3 different projects:

  • bigpipe: The server integration
  • bigpipe.js: The client integration
  • pagelet: The RPC and async submitting.

Having it all as a self contained plugin would make adding new features a lot easier.

Allow posting/putting straight to page

Allow developers to force a POST/PUT inside a pagelet to go to the page, when a method is aimed at the POST defer rendering of the pagelets until that POST/PUT is processed. That way the application won't end up in an awkward state.

Flush res.write() initial request

The initial request should be responded to asap with res.write(). Flushing an initial part of the page (like nav, header, hero element and footer) should give the visitor the idea the page is highly responsive. Additional segments of the page, e.g. pagelets can then be piped to complete the page.

This solves the problem of multi-assets, as each pagelets can be accompanied by its own assets. If stuff has been send before it should not be send again.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.