Home

Awesome


NOTICE:

This repository has been archived and is not supported.

No Maintenance Intended


NOTICE: SUPPORT FOR THIS PROJECT HAS ENDED

This projected was owned and maintained by Walmart. This project has reached its end of life and Walmart no longer supports this project.

We will no longer be monitoring the issues for this project or reviewing pull requests. You are free to continue using this project under the license terms or forks of this project at your own risk. This project is no longer subject to Walmart's bug bounty program or other security monitoring.

Actions you can take

We recommend you take the following action:

Forking and transition of ownership

For security reasons, Walmart does not transfer the ownership of our primary repos on Github or other platforms to other individuals/organizations. Further, we do not transfer ownership of packages for public package management systems.

If you would like to fork this package and continue development, you should choose a new name for the project and create your own packages, build automation, etc.

Please review the licensing terms of this project, which continue to be in effect even after decommission.

Fruit Loops

Provides a performant jQuery-like environment for rendering of client-side SPA application within node servers.

Example

FruitLoops.page({
  index: __dirname + '/index.html',

  host: 'www.github.com',
  path: '/foo/bar',

  resolver: function(href, page) {
    // Add -server suffix to load the server-specific build.
    if (href && !/-server\.js^/.test(href)) {
      href = href.replace(/\.js$/, '-server.js');
    }

    // Remap the external URL space to the local file system
    if (href) {
      href = path.relative('/r/phoenix/', href);
      href = __dirname + '/src/' + href;
    }

    return href;
  },

  callback: function(err, html) {
    if (err) {
      reply(err);
    } else {
      reply(html);
    }
  }
});

Page Lifecyle

For a given page request cycle a few different stages occur, approximating the browser's life cycle.

  1. Page created
  2. Initial DOM is loaded
  3. (optional) beforeExec callback is run allowing the host to modify the page environment.
  4. Embedded scripts are executed Scripts are executed sequentially and are blocking regardless of inlined or external. Currently async and defer attributes are ignored.
  5. (optional) loaded callback is executed
  6. Client code continues executing until emit occurs. See Emit Behaviors below for details on this behavior.

Emit Behaviors

Once the page has completed rendering it needs to notify the fruit-loops container that the response is ready for the user. This is done via the emit method.

emit supports one of three modes:

Both the immediate and ajax emit modes will wait for the next node event loop before emitting the page, allowing any pending operations to have a chance to complete. Note that these operations are not guaranteed to complete and critical behaviors generally should not rely on this timeout.

Note that Fruit loops will cancel pending async behaviors once the page emit's its contents. For ajax calls this means that the request will be aborted at whatever stage they are currently in. For setTimeout and setImmediate will be cleared by their respective clear API.

Once the emit process beings, the flow is as follows:

  1. All callbacks registered through onEmit are executed.
  2. All cancellable pending operations are canceled.
  3. (Optional) The finalize callback is called
  4. The current request's callback is called with the rendered HTML content

Public Only Rendering

One of the primary goals for Fruit Loops is to enable rendering of public only data. This allows for the server-side tier to handle the SEO concerns and fast load of common content and the client tier can handle augmenting the initial HTML payload with time or user specific data.

In many situations this architecture allows for the burden of rendering a page to be pushed out to the CDN and client tier rather than forcing the server to handle all pages.

With this goal in mind Fruit Loops does not currently support features like cookie propagation to the AJAX layer or persistence of the localStorage and sessionStorage shims. PRs are accepted for this of course.

Security

Like any other web server framework there are a variety of possible security concerns that might arise within a Fruit Loops environment. Where possible the framework attempts to fail safe but care needs to be taken, particularly when handling user input, to ensure application integrity.

Sandbox

All code for a given page is executed within a sandbox which isolates page code from node code. Things such as the host's require and other globals are not available to the page unless explicitly exposed through host code such as beforeExec.

Page lifecycle callbacks such as beforeExec, loaded, etc are not run in the sandbox.

Script Loader

Fruit Loop's default script loader is intended to be somewhat restrictive to limit risk. To this end it will only automatically load scripts:

No attempts will be made to load scripts that are injected at later stages in the page's life cycle. Any such scripts will be executed on the client side so standard XSS protections must be employed to avoid the creation of unauthorized script tags.

Should other scripts be loaded the loadInContext utility is available to client code. Even this still has the limitation of requiring that all files be loaded from the local file system.

Dynamic Scripts

In an effort to reduce possible attack vectors, the ability to execute dynamic code not loaded from the file system is disabled by default. This means that eval, Function() and setTimeout(string) will all explicitly throw if used. Should these behaviors be needed the evil flag may be set on the page's options. Enabling this should be done after thorough analysis of the codebase to ensure that there are no cases where arbitrary user input may be executed in an unsafe manner.

Some libraries, particularly templating libraries, will not operate properly without the evil flag. For Handlebars in particular, the recommendation is that build time precompilation be utilized as this removes the need for dynamic evaluation.

Shared State

If using the VM pooling functionality then the consequences of an XSS exploit could easily have a much larger impact as attacks can be crafted that will be persistent for the lifetime of the VM.

Supported Features

Due to differences in the goals of server vs. client rendering, Fruit Loops does not support the following behaviors that might be available within a full browser environment.

As such there are some jQuery APIs that are not implemented when running within a fruit-loops context. See Client APIs for the complete list of supported APIs.

There are three different methods generally available for handling the differences between the two tiers.

  1. Feature detection: Most of the unsupported features are simply not implemented vs. stubed for failure. Any such features can be omitted from the server execution flow simply by using standard feature detection practices.
  2. $serverSide global conditional: The global $serverSide is set to true on within Fruit Loops page environments and may be used for controlling conditional behavior. It's recommended that these be compiled out using a tool such a Uglify's conditional compilation to avoid overhead in one environment or the other.
  3. Server-specific build resolution: If using a tool such as Lumbar, a server-specific build may be created and loaded via a resolver that loads the server specific build.

It's highly recommended that a framework such as Thorax be used as this abstracts away many of the differences between the two environments but this is not required.

Performance

Even though the Fruit Loops strives for an environment with minimal differences between the client and server there are a number of performance concerns that are either specific to the server-side or exacerbated by execution on the server.

The two biggest performance concerns that have been seen are initialization time and overhead due to rendering otherwise hidden content on the server side.

Initialization Time

Creating the sandbox and initializing the client SPA infrastructure takes a bit of time and can also lead to confusion for the optimizer. Users that are rendering in a public only system and whose application support safely transitioning between pages via the navigate API may want to consider pooling and reusing page instances to avoid unnecessary overhead from repeated operations.

In one anecdote, an application pooling was able to reduce response times by a factor of 5 due to avoiding the context overhead and recreating the base application logic on each request. The impact of this will vary by application and should be examined in context.

Unnecessary Operations

Things like rendering menus and other initially hidden content all add to the CPU load necessary for parsing the content. While this is a concern for the client-side rendering as well this is much more noticeable when rendering on the server when all requests share the same event loop. It's recommended that any operations that won't generate meaningful content for the user on the initial load be setup so that the rendering is deferred until the point that it is needed. Generally this optimization should improve the initial load experience for both client and server environments.

Node APIs

#page(options)

Creates a new page object with the given options.

Available options:

The returned page instance consists of:

#pool(options)

Creates a pool of page objects.

Shares the same options as the page method with a few distinctions:

The returned pool instance consists of:

  var pool = FruitLoops.pool({
    poolSize: 2,
    index: __dirname + '/artifacts/pool-page.html',
    navigated: function(page, existingPage) {
      if (existingPage) {
        // Force backbone navigation if the page has been previously used.
        page.window.Backbone.history.loadUrl();
      }
    }
  });
  pool.navigate('/bar', function(err, html) {
    if (err) {
      reply(err);
    } else {
      reply(html);
    }
  });

page.$.ajax

There are a number of utility methods exposed on the node-side ajax instance including:

Client APIs

$ APIs

The following APIs are supported and should match the jQuery/Zepto implementation unless otherwise noted.

Constructors

Tree Traversal

Set Handling

Tree Manipulation

Node Manipulation

Not implemented:

Event APIs

Fruit loops implements stubs for:

Each of the above methods will perform no operations but may be chained.

Methods designed to trigger events are explicitly not implemented.

Detect

Fruit loop implements a direct port of Zepto's $.detect library.

AJAX

Not currently supported:

Form

Form handling methods are not supported at this time. This includes:

Effects

Effects APIs are generally not support in fruit loops. The exception being:

Static Methods

Not implement:

DOM APIs

In addition to the $ APIs, Fruit Loops implements a variety of DOM and global browser APIs.

Fruit Loops Extensions