Skip to content

Latest commit

 

History

History
 
 

universal-react-node

Electrode Boilerplate Universal React Node

Dependency Status devDependency Status

Features

Electrode Boilerplate comes fully loaded with the best technologies available:

  • React - an awesome JavaScript library for building user interfaces, created by Facebook.

  • Redux - a predictable state container for JavaScript apps.

  • React Router - a powerful routing library built on top of React.

  • CSS Modules - a CSS file in which all class names and animation names are scoped locally by default. Fixes the problem of the global scope in CSS.

  • Universal rendering

  • Webpack - a powerful module bundler.

  • Offline-first The next frontier in performant progressive web apps: availability without a network connection from the instant your users load the app.

  • Webpack Isomorphic Loader - a powerful tool that makes NodeJS require understand files such as images for SSR.

  • Babel - a utility to transpile ES6 + 7.

  • ESLint - a pluggable linting utility for Javascript.

  • Mocha - a feature-rich Javascript testing framework.

  • Enzyme - a Javascript testing utility for React, created by airbnb.

  • TravisCI - a continuous integration service to build and test software projects.

  • Gulp - a Javascript build tool that lets us automate tasks.

  • Yeoman - a Scaffolding tool for modern webapps.

  • History - a Javascript library for managing session history.

  • Bluebird - a great Javascript promise library.

  • Electrode Confippet - a versatile and flexible utility for managing configurations of Node.js applications.

  • Electrode JWT CSRF - a module to enable stateless Cross-Site Request Forgery (CSRF) protection with JWT.

  • Electrode-Redux-Router-Engine - an Electrode routing and rendering engine using react-router and redux.

  • Component Caching - an optimizer to improve React Server Side Rendering speed

  • Electrode-Server - a configurable web server using Hapi.js on top of Node.js.

  • Electrify - a tool for analyzing the module tree of webpack projects.

  • Electrode-Docgen - a custom metadata extractor for the Electrode framework, automates component documentation. mentation.

This repo is a sample Electrode app with the following Electrode modules:

Install

git clone https://github.com/electrode-io/electrode.git
cd electrode-boilerplate-universal-react-node
npm install

Run

  • Start the electrode app in development environment:
$ NODE_ENV=development gulp hot

Running in development mode will also enable Redux Devtools so you can easily access the state of the Redux store. Please install the Redux Devtools extension in Chrome to enable this feature.

  • Start the electrode app in production environment:
$ gulp build
$ gulp server-prod
  • Running in the selected environment should load the appropriate configuration settings

  • Start the electrode app with service workers

$ gulp build
$ gulp server

Service worker currently does not work with webpack dev server. You need to build first and then run the server.

Instructions for boostrapping/Scaffolding new electrode application

You can bootstrap a new electrode web application from scratch by doing:

npm install -g yo generator-electrode gulp
yo electrode

This will set up an Electrode webapplication which will have 2 of the above 6 modules. The two modules that are available by default are:

Multiple Entry Points

The electrode-archetype-react-app module supports multiple entry points per app. In order to enable this feature:

  • Add an entry file in client/entry.config.js.
module.exports = {
  home: "./home.jsx",
  about: "./about.jsx"
};
  • Add a chunk selector to server/chunk-selector.js
"use strict";

const CHUNKS = {
  HOME: {
    css: "home",
    js: "home"
  },
  ABOUT: {
    css: "about",
    js: "about"
  }
};

const getChunks = (path) => {
  if (path.endsWith("/about")) {
    return CHUNKS.ABOUT;
  }

  return CHUNKS.HOME;
};

module.exports = (request) => {
  return getChunks(request.path);
};
  • Add a bundleChunkSelector option to plugins.webapp.options in config/default.json
{
  "plugins": {
    "webapp": {
      "module": "electrode-react-webapp",
      "options": {
        "bundleChunkSelector": "./server/chunk-selector.js",
        "pageTitle": "Electrode Boilerplate Universal React App",
        "paths": {
          "/{args*}": {
            "content": {
              "module": "./server/views/index-view"
            }
          }
        }
      }
    }
  }
}

Progressive Web App (PWA) features supported by the Electrode framework

1. Offline first

Offline first lets your app run without a network connection. At the same time it provides a great performance boost for repeat visit to your web site. This is done with a service worker and by pre-caching your static assets as well as runtime caching of dynamic server routes and external resources.
Learn More

2. Add To Homescreen

After visiting your website, users will get a prompt (if the user has visited your site at least twice, with at least five minutes between visits.) to add your application to their homescreen (web or mobile). Combined with offline caching, this means your web app can be used exactly like a native application.
Learn More

3. Push Notifications

Web push notifications allow users to opt-in to timely updates from sites they love and allow you to effectively re-engage them with customized, relevant content.
We will learn about Push Notifications in the next couple of sections.

Instructions for setting up Push Notifications

Web push is only supported on: Google Chrome 42+ (Desktop & Android)

The Push API requires a registered service worker so it can send notifications in the background when the web application isn't running. So we need to register our service worker first.
Check out this guideline to generate a service worker in an electrode app.
Also, check out the Adding Push Notifications to a Web App Codelab provided by Google for an in-depth guide on how push notifications and service workers work together.

Next we need to add a push event to this existing service worker for sending notifications to the client from a push server:

1. Creating a new file service worker file to listen to push events

In order to respond to push notifications events received from a remote server we need to listen for the push event on the active service worker. Since our service worker file is generated automatically we need to use the importScripts API, which lets us execute additional scripts in the context of the service worker.

self.addEventListener("push", (event) => {
  const title = "It worked!";
  const options = {
    body: "Great job sending that push notification!"
  };
  event.waitUntil(
    self.registration.showNotification(title, options)
  );
});

Sample file

2. Include this file in your webpack bundle by referencing it in sw-config.js

module.exports = {
  cache: {
    importScripts: ['./sw.js']
    }
}

Sample file

3. Register this service worker with the push event

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("sw.js", { scope: "./" })
  .then((registration) => {
    // Service worker registration was successful
  }
}

Sample file

The service worker is ready to accept push from the server. On receiving the push, it will provide the notification to the browser.

4. Setting up your API_KEY and GCM_SENDER_ID

We will be needing the API_KEY and GCM_ENDPOINT to send the messages from the server.
To generate these values, visit Firebase and create a new project.
Click on the setting icons and open Project settings.
Navigate to the CLOUD MESSAGING tab to view your Server key or Legacy Server key (API_KEY) and the Sender ID.
You need to update your manifest in sw-config.js to update the gcm_sender_id.

Instructions for sending a push notification

Now that we have our service worker up and running, we can send a push with the following steps:

1. Requesting Permission and Subscribing Users

The code for requesting permission and subscribing users in done in your app's code, rather than the service worker code.

navigator.serviceWorker.ready.then((registration) => {
  // Ask for user permission and subscribe
  registration.pushManager.subscribe({ userVisibleOnly: true })
    .then((subscription) => {
      // Successfully subscribed
      this.setState({
        subscription,
        subscribed: true
      });
    });
});

Sample subscription

Typically, after the user subscribes, we send the subscription information to the server and the server uses the subscriptionId to trigger a notification.

2. Sending Messages

Sending message is as easy as executing a curl command. The curl command contains the subscriptionId, API_KEY (Your cloud messaging API key from firebase) and GCM_ENDPOINT (https://android.googleapis.com/gcm/send).

Alternatively, you can also send notifications from the service worker:

sendNotification() {
   const title = 'This Notification'
   const body = 'Is brought to you by your server worker!'
   const options = {body};
   navigator.serviceWorker.ready.then((registration) => {
     registration.showNotification(title, options);
   });
 }

Sample

navigator.serviceWorker.ready is a Promise that will resolve once a service worker is registered, and it returns a reference to the active ServiceWorkerRegistration. The showNotification() method of the ServiceWorkerRegistration interface creates a notification and returns a Promise that resolves to a NotificationEvent.


Instructions about standalone modules

Electrode Confippet

  • Confippet is a versatile utility for managing your NodeJS application configuration. Its goal is customization and extensibility, but offers a preset config out of the box.

Config

  • Once the bootstrapping is done using yo electrode, open the following config files:
config
|_ default.json
|_ development.json
|_ production.json
Development environment
  • Update the config/development.json to have the following settings:
{
  "server": {
    "connections": {
      "compression": false
    },
    "debug": {
      "log": ["error"],
      "request": ["error"]
    }
  },
  "connections": {
    "default": {
      "port": 3000
    }
  }
}
  • The above settings should show server log errors that may be beneficial for debugging, disable content encoding, and run the server in port 3000
Production environment
  • Update the config/production.json to have the following settings:
{
  "server": {
    "connections": {
      "compression": true
    },
    "debug": {
      "log": false,
      "request": false
    }
  },
  "connections": {
    "default": {
      "port": 8000
    }
  }
}
  • The above settings should disable server log errors, enable content encoding, and run the server in port 8000
  • The server key related configs are from hapi.js. More config options can be found here: http://hapijs.com/api
  • The connections key are electrode server specific: https://github.com/electrode-io/electrode-server/tree/master/lib/config
  • Keys that exist in the config/default.json that are also in the other environment configs will be replaced by the environment specific versions
Require
  • In Electrode, the configurations are loaded from server/index.js at this line:
const config = require("electrode-confippet").config;
const staticPathsDecor = require("electrode-static-paths");

require("electrode-server")(config, [staticPathsDecor()]);
Run
  • Start the electrode app in development environment:
$ NODE_ENV=development gulp hot
  • Start the electrode app in production environment:
$ gulp build
$ gulp server-prod
  • Running in the selected environment should load the appropriate configuration settings

Electrode CSRF-JWT

CSRF-JWT is an Electrode plugin that allows you to authenticate HTTP requests using JWT in your Electrode applications.

Install

  • Add the electrode-csrf-jwt component:
npm install electrode-csrf-jwt --save
  • Next, register the plugin with the Electrode server. Add the following configuration to the plugins section of config/default.json:
"electrode-csrf-jwt": {
  "options": {
    "secret": "test",
    "expiresIn": 60
  }
}

That's it! CSRF protection will be automatically enabled for endpoints added to the app. CSRF JWT tokens will be returned in the headers and set as cookies for every response and must be provided as both a header and a cookie in every POST request.

You can read more about options and usage details on the component's README page

CSRF-JWT Demonstration code

In addition to the above steps, the following modifications were made in order to demonstrate functionality:

  • A plugin with two endpoints was added as server/plugins/csrf.js and registered via config/default.json
  • AJAX testing logic was added to client/components/csrf.jsx

Electrode Electrify

An Electrode Javascript bundle viewer aptly named Electrify, this is a stunning visual tool that helps for analyzing the module tree of Webpack based projects. It's especially handy for catching large and/or duplicate modules which might be either bloating up your bundle or slowing down the build/install process.

Integration points in your app

Electrode-boilerplate-universal-react-node & electrode-scaffolder internally use electrode-archetype-react-app hence gulp electrify on your terminal will start the bundle viewer in the browser.

When you install Electrify globally using sudo npm install -g electrode-electrify, the Electrify command-line tool is made available as the quickest means of checking out your bundle. As of electrode-electrify v1.0.0, the tool takes any webpack-stats object as input and starts out a standalone HTML page as output in your browser, all you have to do is type electrify <path to stats.json> --open on your terminal.

Head over to the Electrify repository for a detailed view of the bundle viewer and checkout the source-code. electrify relies on webpack to generate the application modules/dependency tree and is independent of whichever server framework(hapijs, expressjs, etc.) you choose to use.


Electrode React SSR Caching

Electrode-react-ssr-caching module supports profiling React Server Side Rendering time and component caching to help you speed up SSR.

It supports 2 types of caching:

  • Simple - Component Props become the cache key. This is useful for use cases like Header and Footer, where the number of variations of props data is minimal which will make sure the cache size stays small.
  • Template - Components Props are first tokenized and then the generated template html is cached. The idea is akin to generating logic-less handlebars template from your React components and then use string replace to process the template with different props. This is useful for use cases like displaying Product information in a Carousel where you have millions of products in the repository and only cache one templatized html and then do a string replace to generate the final html

Install

$ npm install --save electrode-react-ssr-caching

Wiring

GOTCHA:
  • SSR caching of components only works in PRODUCTION mode, since the props(which are read only) are mutated for caching purposes and mutating of props is not allowed in development mode by react.

  • Make sure the electrode-react-ssr-caching module is imported first followed by the imports of react and react-dom module. SSR caching will not work if the ordering is changed since caching module has to have a chance to patch react's code first. Also if you are importing electrode-react-ssr-caching, react and react-dom in the same file , make sure you are using all require or all import. Found that SSR caching was NOT working if, electrode-react-ssr-caching is required first and then react and react-dom is imported.


To demonstrate functionality, we have added:

  • client/components/SSRCachingSimpleType.jsx for Simple strategy.
  • client/components/SSRCachingTemplateType.jsx for Template strategy.
  • To enable caching using electrode-react-ssr-caching, we need to do the below configuration:
  const cacheConfig = {
    components: {
      SSRCachingTemplateType: {
        strategy: "template",
        enable: true
      },
      SSRCachingSimpleType: {
        strategy: "simple",
        enable: true
      }
    }
  };

  SSRCaching.enableCaching();
  SSRCaching.setCachingConfig(cacheConfig);

The above configuration is done in server/index.js.

To read more, go to electrode-react-ssr-caching. The core implementation for caching is available here. You can also do Profiling of components


Electrode Redux Router Engine

Redux Router Engine handles async data for React Server Side Rendering using [react-router], Redux, and the [Redux Server Rendering] pattern.

Install

$ npm install --save electrode-redux-router-engine

Wiring

In this demo, the redux-router has been configured to work with the server/views/index-view.jsx component.createdReduxStore is used to perform async thunk actions to build the redux store and which gets wired into a new ReduxRouterEngine instance in the component's module.exports clause:

function createReduxStore(req, match) {
  const store = storeInitializer(req);
  return Promise.all([
      // DO ASYNC THUNK ACTIONS HERE : store.dispatch(boostrapApp())
      Promise.resolve({})
    ]).then(() => {
      return store;
  });
}

module.exports = (req) => {

  if (!req.server.app.routesEngine) {
    req.server.app.routesEngine = new ReduxRouterEngine({ routes, createReduxStore });
  }

  return req.server.app.routesEngine.render(req);
};

For more information on using this module, refer to the redux-router README.


Above The Fold Only Server Render

Above The Fold Only Server Render is a React component for optionally skipping server side rendering of components outside above-the-fold (or inside of the viewport). This component helps render your components on the server that are above the fold and the remaining components on the client.

above-the-fold-only-server-render helps increase performance both by decreasing the load on renderToString and sending the end user a smaller amount of markup.

By default, the above-the-fold-only-server-render component is an exercise in simplicity; it does nothing and only returns the child component.

Install

  • Add the above-the-fold-only-server-render component:
npm install above-the-fold-only-server-render --save

You can tell the component to skip server side rendering either by passing a prop skip={true} or by setting up skipServerRender in your app context and passing the component a contextKey prop.

Let's explore passing skip prop; there is an example in <your-electrode-app>/components/above-fold-simple.jsx. On the Home page, click the link to render the localhost:3000/above-the-fold page.

The best way to demo this existing component is actually going to be in your node_modules.

Navigate to <your-electrode-app>/node_modules/above-the-fold-only-server-render/lib/components/above-the-fold-only-server-render.js line 29:

  var SHOW_TIMEOUT = 50;

When we use this module at WalmartLabs, it's all about optimization. You are going to change line 29 to slow down the SHOW_TIMEOUT so you can see the component wrapper in action: Change this to:

  var SHOW_TIMEOUT = 3000;

Run the commands below and test it out in your app:

  gulp hot

The code in the <h3> tags that are above and below the <AboveTheFoldOnlyServerRender skip={true}> </AboveTheFoldOnlyServerRender> will render first:

  import React from "react";
  import {AboveTheFoldOnlyServerRender} from "above-the-fold-only-server-render";

  export class AboveFold extends React.Component {

    render() {
      return (
        <div>
          <h3>Above-the-fold-only-server-render: Increase Your Performance</h3>
          <AboveTheFoldOnlyServerRender skip={true}>
              <div className="renderMessage" style={{color: "blue"}}>
                <p>This will skip server rendering if the 'AboveTheFoldOnlyServerRender'
                  lines are present, or uncommented out.</p>
                <p>This will be rendered on the server and visible if the 'AboveTheFoldOnlyServerRender'
                  lines are commented out.</p>
                <p>Try manually toggling this component to see it in action</p>
                <p>
                  <a href="https://github.com/electrode-io/above-the-fold-only-server-render"
                    target="_blank">Read more about this module and see our live demo
                  </a>
                </p>
              </div>
          </AboveTheFoldOnlyServerRender>
          <h3>This is below the 'Above the fold closing tag'</h3>
        </div>
      );
    }
  }

You can also skip server side rendering by setting context in your app and passing a contextKey prop. Here is an example:

  import PropTypes from "prop-types";

  const YourComponent = () => {
      return (
        <AboveTheFoldOnlyServerRender contextKey="aboveTheFoldOnlyServerRender.SomeComponent">
          <div>This will not be server side rendered based on the context.</div>
        </AboveTheFoldOnlyServerRender>
      );
  };

  class YourApp extends React.Component {
    getChildContext() {
      return {
        aboveTheFoldOnlyServerRender: {
          YourComponent: true
        }
      };
    }

    render() {
      return (
        <YourComponent />
      );
    }
  }

  YourApp.childContextTypes = {
    aboveTheFoldOnlyServerRender: PropTypes.shape({
      AnotherComponent: PropTypes.bool
    })
  };

To learn more about this essential stand alone module visit the above-the-fold-only-server-render Github repo.

Electrode Bundle Analyzer

Bundle Analyzer is a webpack tool that gives you a detail list of all the files that went into your deduped and minified bundle JS file.

Install

sudo npm install -g electrode-bundle-analyzer

Configure

Bundle Analyzer expects a particular set of data for it to work.

Bundle Analyzer looks for the webpack module ID comment that normally looks something like this:

/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {

You can find more information about it in the Bundle Analyzer readme file.

Command-Line Interface

Usage: analyze-bundle --bundle [bundle.js] --stats [stats.json] --dir
[output_dir] --rewrite

Options:
  -b, --bundle   JS bundle file from webpack                          [required]
  -s, --stats    stats JSON file from webpack[default: "dist/server/stats.json"]
  -r, --rewrite  rewrite the bundle file with module ID comments removed
  -d, --dir      directory to write the analyze results       [default: ".etmp"]
  -h, --help     Show help                                             [boolean]

When you install Bundle Analyzer globally, analyze-bundle command-line tool is made available as the quickest means of checking out your bundle.

If you don't specify an output directory, a default one .etmp will be created and a .gitignore file is also added there to avoid git picking it up.

Two files will be written to the output directory:

  • bundle.analyze.json
  • bundle.analyze.tsv

The tsv file is a Tab Separated Values text file that you can easily import into a spreadsheet for viewing.

For example:

Module ID       Full Path       Identity Path   Size (bytes)
0       ./client/app.jsx        ./client/app.jsx  328
1       ./~/react/react.js      ~/react/react.js        46
2       ./~/react/lib/React.js  ~/react/lib/React.js    477
3       ./~/object-assign/index.js      ~/object-assign/index.js        984
4       ./~/react/lib/ReactChildren.js  ~/react/lib/ReactChildren.js    1344

You can view an example bundle.analyze.tsv output using the Electrode Boilerplate code.

Run

The Electrode Boilerplate's webpack config is already preconfigured to work with Bundle Analyzer, we just need to set the OPTIMIZE_STATS=true environment variable to generate the appropriate webpack build output:

NODE_ENV=development OPTIMIZE_STATS=true gulp build
analyze-bundle --bundle dist/js/bundle.42603ce3a63db995958f.js --stats dist/server/stats.json

Navigate to the .etmp folder to view the bundle.analyze.json or bundle.analyze.tsv output files.

Built with ❤️ by Team Electrode @WalmartLabs.