Monthly Archives: September 2018

The Node.js Update - #Week 38 - 28 September, 2018

By Tamas Kadlecsik

The Node.js Update - #Week 38 - 28 September, 2018

Below you can find RisingStack‘s collection of the most important Node.js updates, tutorials & announcements from this week.

Rethinking JavaScript Test Coverage

TLDR: You can now expose coverage output through Node.js by setting the environment variable NODE_V8_COVERAGE to the directory you would like coverage data output in. The tool c8 can be used to output pretty reports based on this coverage information.

The Node.js Update - #Week 38 - 28 September, 2018

Understanding and building your own tiny blockchain in JavaScript

Despite blockchain tech usually having whitepapers freely available and more forums, discord and IRC channels, and developer groups than we know what to do with, the basic structure of a blockchain can be pretty opaque. Let’s take a look at how we’d build a very simple block blockchain to help visualize how transactions are added.

The Node.js Update - #Week 38 - 28 September, 2018

Getting to know Node’s child_process module

“Node.js is one of the most powerful platforms for managing resources in our computer and has became more and more popular over the years ever since it was released. As much as it’s great, and with all the love and respect that I have for it, Node.js alone is not enough.Accordingly, I’ve decided to write an article about Node’s child_process module — a utility module which provides you with functions that can create and manage other processes.”

The Node.js Update - #Week 38 - 28 September, 2018

Testing your Node.js API with Dredd

In this article, we are going to present a technology stack aimed to bridge the gap between front-end and back-end developers, enable us to document the API and continuously test it once it’s implemented.

The stack presented in this article consists of the following:

  • Dredd — API testing tool utilizing API Blueprint and Swagger API Description formats
  • API Blueprint — Specification language allowing us to document our APIs in Markdown-like syntax
  • Drakov — Tool that can use API Blueprint description of our API and set up a mock server to host the endpoints
  • Examples in this post will be shown using simple Node.js API with Express middleware on top of it.

Authentication Made Easy with Auth0: Part 1

Through example, we will demonstrate how to use Auth0 to secure a Node.js (Express) API that is accessed by a single page application.

The Node.js Update - #Week 38 - 28 September, 2018

Clustering in NodeJs — Performance Optimization

Node.js single-threaded nature is by default using a single core of a processor for code execution. Therefore NodeJs introduced a cluster module to spawn processes. “Cluster” was introduced to scale an application execution on multiple processor cores by creating worker processes. Worker processes share a single port, therefore, requests are routed through a shared port.

The Node.js Update - #Week 38 - 28 September, 2018

How to build a Facebook Messenger chatbot with Node.js and Dialogflow

Many chatbots leverage Natural Language Processing (NLP) to interpret the intent of a customer’s input, allowing the bot to give an accurate response. Implementing NLP in your bot can be pretty difficult, but there are several platforms that make it much easier.

The Node.js Update - #Week 38 - 28 September, 2018

Of these platforms, Dialogflow (formerly known as Api.ai) and Wit.ai are the most popular today. They both provide several NLP functions that parse user input and match them to the right response without any coding.

Announcing TypeScript 3.1

Let’s take a look at what this release of TypeScript brings us. Feel free to use the list below to jump around this post a bit.

  • Mappable tuple and array types
  • Easier properties on function declarations
  • Version redirects for TypeScript via typesVersions
  • Refactor from .then() to await

Meet Cogear.JS — a modern static website generator

Cogear.JS is a static websites generator built with Node.JS (9.x or higher) and based on Webpack (v4.6). It’s inspired by Jekyll and others, but built on the top of the latest frontend technologies.Provides awesome hot reloading experience in development mode.

The Top Ten Reasons to Attend Node+JS Interactive

Node+JS Interactive is less than a month away!

The Node.js Update - #Week 38 - 28 September, 2018

Workshops, more workshops, everyone likes workshops: Along with the amazing keynotes, panels and sessions, we will have hands-on workshops that cover everything from building effective communication techniques in open source community environments to mastering serverless with JS architecture.

How does your company use Node.js? (I’ll show you mine if you show me yours)

We had an interesting & insightful thread on r/node this week:

“Hi, long time lurker here. I personally find it difficult to get a read on patterns businesses use for node.js. Obviously it’s easy to find patterns that are common throughout open source projects, but these are mostly frameworks rather than large applications intended to be hosted and run somewhere

So, let’s compare notes! I’m going to give a short summary of the technical domain of the company I work for, how many employees and then an overview of patterns we use within the code and if I’m lucky some of you will too :)”

For more Node.js content, follow us on Twitter @RisingStack.

In case you need guidance with Docker, Kubernetes, Microservices or Node.js, feel free to ping us at info@risingstack.com!

Source:: risingstack.com

Setting Up Your PyMongo Environment

By Nolan Gallagher

Welcome to PyMongo Monday. This is the first in a series of regular blog posts that will introduce developers to programming MongoDB using the Python programming language. It’s called PyMongo Monday because PyMongo is the name of the client library (in MongoDB speak we refer to it as a “driver”) we use to interact with the MongoDB Server. Monday because we aim to release each new episode on Monday.

To get started we need to install the toolchain used by a typical MongoDB Python developer.

Installing m

First up is m. Hard to find online unless your search for “MongoDB m”, m is a tool to manage and use multiple installations of the MongoDB Server in parallel. It is an invaluable tool if you want to try out the latest and greatest beta version but still continue mainline development on our current stable release.

The easiest way to install m is with npm the Node.js package manager (which it turns out is not just for Node.js).

$ sudo npm install -g m
Password:******
/usr/local/bin/m -> /usr/local/lib/node_modules/m/bin/m
+ m@1.4.1
updated 1 package in 2.361s
$

If you can’t or don’t want to use npm you can download and install directly from the github repo. See the README there for details.

For today we will use m to install the current stable production version (4.0.2 at the time of writing).

We run the stable command to achieve this.

$ m stable
MongoDB version 4.0.2 is not installed.
Installation may take a while. Would you like to proceed? [y/n] y
... installing binary

######################################################################## 100.0%
/Users/jdrumgoole
... removing source
... installation complete
$

If you need to use the path directly in another program you can get that with m bin.

$ m bin 4.0.0
/usr/local/m/versions/4.0.1/bin
$

To run the corresponding binary do m use stable

$ m use stable
2018-08-28T11:41:48.157+0100 I CONTROL  [main] Automatically disabling TLS 1.0, to force-enable TLS 1.0 specify --sslDisabledProtocols 'none'
2018-08-28T11:41:48.171+0100 I CONTROL  [initandlisten] MongoDB starting : pid=38524 port=27017 dbpath=/data/db 64-bit host=JD10Gen.local
2018-08-28T11:41:48.171+0100 I CONTROL  [initandlisten] db version v4.0.2
2018-08-28T11:41:48.171+0100 I CONTROL  [initandlisten] git version: fc1573ba18aee42f97a3bb13b67af7d837826b47
< other server output >
...
2018-06-13T15:52:43.648+0100 I NETWORK  [initandlisten] waiting for connections on port 27017

Now that we have a server running we can confirm that it works by connecting via the mongo shell.

$ mongo
MongoDB shell version v4.0.0
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 4.0.0
Server has startup warnings:
2018-07-06T10:56:50.973+0100 I CONTROL  [initandlisten]
2018-07-06T10:56:50.973+0100 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2018-07-06T10:56:50.973+0100 I CONTROL  **[**initandlisten] **          Read and write access to data and configuration is unrestricted.
2018-07-06T10:56:50.973+0100 I CONTROL  [initandlisten] ** WARNING: You are running this process as the root user, which is not recommended.
2018-07-06T10:56:50.973+0100 I CONTROL  **[**initandlisten]
2018-07-06T10:56:50.973+0100 I CONTROL  **[**initandlisten] ** WARNING: This server is bound to localhost.
2018-07-06T10:56:50.973+0100 I CONTROL  [initandlisten] **          Remote systems will be unable to connect to this server.
2018-07-06T10:56:50.973+0100 I CONTROL  **[**initandlisten] **          Start the server with --bind_ip < address> to specify which IP
2018-07-06T10:56:50.973+0100 I CONTROL  [initandlisten] **          addresses it should serve responses from, or with --bind_ip_all to
2018-07-06T10:56:50.973+0100 I CONTROL  **[**initandlisten] **          bind to all interfaces. If this behavior is desired, start the
2018-07-06T10:56:50.973+0100 I CONTROL  [initandlisten] **          server with --bind_ip 127.0.0.1 to disable this warning.
2018-07-06T10:56:50.973+0100 I CONTROL  [initandlisten]

---
Enable MongoDB's free cloud-based monitoring service to collect and display
metrics about your deployment (disk utilization, CPU, operation statistics,
etc).

The monitoring data will be available on a MongoDB website with a unique
URL created for you. Anyone you share the URL with will also be able to
view this page. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command:
db.enableFreeMonitoring()
---

>

These warnings are standard. They flag that this database has no access controls setup by default and, that it is only listening to connections coming from the machine it is running on (localhost). We will learn how to setup access control and listen on a broader range of ports in later episodes.

Installing the PyMongo Driver

But this series is not about the MongoDB Shell, which uses JavaScript as its coin of the realm, it’s about Python. How do we connect to the database with Python?

First we need to install the MongoDB Python Driver, PyMongo. In MongoDB parlance a driver is a language-specific client library that allows developers to interact with the server in the idiom of their own programming language.

For Python that means installing the driver with pip. In node.js the driver is installed using npm and in Java you can use maven.

$ pip3 install pymongo
Collecting pymongo
  Downloading https://files.pythonhosted.org/packages/a1/e0/51df08036e04c1ddc985a2dceb008f2f21fc1d6de711bb6cee85785c1d78/pymongo-3.7.1-cp27-cp27m-macosx_10_13_intel.whl (333kB)
    100% |████████████████████████████████| 337kB 4.1MB/s
Installing collected packages: pymongo
Successfully installed pymongo-3.7.1
$

We recommend you use a virtual environment to isolate your PyMongo Monday code. This is not required but is very convenient for isolating different development streams.

Now we can connect to the database:

$ python
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 03:03:55)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pymongo                                                  (1)
>>> client = pymongo.MongoClient(host="mongodb://localhost:8000")   (2)
>>> result = client.admin.command("isMaster")                       (3)
>>> import pprint
>>> pprint.pprint(result)
{'ismaster': True,
 'localTime': datetime.datetime(2018, 6, 13, 21, 55, 2, 272000),
 'logicalSessionTimeoutMinutes': 30,
 'maxBsonObjectSize': 16777216,
 'maxMessageSizeBytes': 48000000,
 'maxWireVersion': 6,
 'maxWriteBatchSize': 100000,
 'minWireVersion': 0,
 'ok': 1.0,
 'readOnly': False}
>>>

First we import the PyMongo library (1). Then we create a local client object (2) that holds the connection pool and other status for this server. We generally don’t want more than one MongoClient object per program as it provides its own connection pool.

Now we are ready to issue a command to the server. In this case it’s the standard MongoDB server information command which is called rather anachronistically isMaster (3). This is a hangover from the very early versions of MongoDB. It appears in pre 1.0 versions of MongoDB (which is over ten years old at this stage). The isMaster command returns a dict which details a bunch of server information. In order to format this in a more readable way we import the pprint library.

Conclusion

That’s the end of episode one. We have installed MongoDB, installed the Python client library (aka driver), started a mongod server and established a connection between the client and server.

Next week we will introduce CRUD operations on MongoDB, starting with Create.

For direct feedback please pose your questions on twitter/jdrumgoole. That way everyone can see the answers.

The best way to try out MongoDB is via MongoDB Atlas our fully managed Database as a Service available on AWS, Google Cloud Platform (CGP) and Azure.

Source:: scotch.io

Using Parcel In A Vue.js App

By Chimezie Enyinnaya

When it comes to bundler, Webpack seems to be the de-facto bundler within the Vue.js community. In this tutorial, I will be showing you how to use Parcel in a Vue.js application completely from scratch.

What is Parcel

Parcel is a blazing fast, zero configuration web application bundler. It is a relatively new to the application bundler world. One of Parcel’s main selling point is it zero configuration as you don’t need have some kind of configurations to it. If you have ever used Webpack prior to version 4, then this will be a relief.

In addition to this, Parcel has out of the box support for JS, CSS, HTML, file assets etc, with no plugins needed, and it builds all these assets in a blazing fast bundle time.

Getting started

To get started using Parcel, we need to first install the Parcel bundler on our computer. We can do so by using the command below:

// using NPM
$ npm install -g parcel-bundler
// using Yarn
$ yarn global add parcel-bundler

Here, we install the Parcel bundler as a global dependence. We can also install the Parcel bundler per project:

// using NPM
$ npm install --save-dev parcel-bundler
// using Yarn
$ yarn add --dev parcel-bundler

Once that is installed, we can start using it by simply running the command below:

$ parcel index.html

Usage with Vue.js

Now let’s see how we can use Parcel in a Vue.js app. We’ll start by creating a new project:

$ mkdir vue-parcel
$ cd vue-parcel
$ npm init -y

We create a new directory (vue-parcel) that will hold our Vue.js app, then we initialize NPM, which will create a package.json with some default details.

Next, let’s install the dependences needed for our app:

$ npm install --save vue
$ npm install --save-dev parcel-bundler

We install Vue.js and the Parcel bundler.

Now, we can begin to flesh out the application. Within the project directory, create a new index.html file and paste the code below in it:

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Vue Parcel</title>
</head>
<body>
  <div id="app"></div>

  <!-- built files will be auto injected -->
  <script src="./src/main.js"></script>
</body>
</html>

Some pretty standard HTML. We add a div with an id of app and also a script tag that link to a JavaScript file, which we are yet to create. The main.js will serve as the main JavaScript file for our app and index.html file will be used as the entry point for Parcel.

Tip: Be sure to use a relative path when linking the main JavaScript file.

Next, let’s create the main.js file. Within the project’s root, create a new src directory. Then within the src directory, create a new main.js and paste the following code in it:

// src/main.js

import Vue from 'vue'
import App from './App'

new Vue({
  el: '#app',
  render: h => h(App)
})

First, we import Vue.js and App component (which we’ll create shortly). Then we create a new instance of Vue, passing to it the element we want to mount it on. Here, we are using a render function, and we pass the App component to it.

Next, let’s create the App component. Within src, create a new App.vue file and paste the code below in it:

// src/App.vue

<template>
  <div class="container">
    <h1>{{ message }}</h1>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      message: 'Using Parcel In A Vue.js App',
    };
  },
};
</script>

<style scoped>
  .container {
    width: 600px;
    margin: 50px auto;
    text-align: center;
  }
</style>

Here, we create a basic component that simply displays a message.

With our app complete, let’s run Parcel to compile and build our app. Before we do just that, let’s add a dev script to package.json:

// package.json

"scripts": {
  "dev": "parcel index.html"
}

We can now run Parcel with:

$ npm run dev

This will install the necessary dependences (@vue/component-compiler-utils and vue-template-compiler) it needs to build the app, then build up the app and start a dev server. The server will be running at http://localhost:1234, and you should get something similar to the image below:

If we want to use the full build (runtime + compiler) of Vue.js instead, as opposed to the runtime-only build used above, which might look like below:

// src/main.js

import Vue from 'vue';
import App from './App';

new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

Then we need to add the code below to package.json:

// package.json

"alias": {
  "vue": "./node_modules/vue/dist/vue.common.js"
}

Now, if we run Parcel, everything should work as expected.

In addition to the start script, we can also create scripts to watch and automatically rebuild as files changes while developing and bundle our application for production respectively:

// package.json

"scripts": {
  ...,
  "watch": "parcel watch index.html",
  "production": "parcel build index.html"
}

Note: watch mode doesn’t start a web server, so you need to have your own server.

Conclusion

That’s it! In this tutorial, we looked at what Parcel is and how we can use it in a Vue.js application. For more details about Parcel, kindly check their documentation.

Source:: scotch.io

Microservices Patterns: With Examples In Java By Chris Richardson

Ben Nadel reviews Microservices Patterns by Chris Richardson. This book provides a holistic and pragmatic view into the world of distributed systems architecture, covering a broad range of topics with a well managed degree of detail. Definitely a recommended read for anyone thinking about (or struggling to) move from a monolithic architecture into a distributed systems architecture….

Source:: bennadel.com

Build A Chat Widget with Python and JavaScript

By Neo Ighodaro

Building quality digital products is a requirement toward acquiring long-term customers, but inefficient communication is an efficient way to lose them just as quickly as you gain them. The internet is currently the world’s largest marketplace and everyone is building something for an online audience to consume, however, it would be a shame if there isn’t a way to receive feedback or interact with customers in realtime.

In this tutorial, we will look at how we can create a realtime chat widget using Pusher, Python, and JavaScript. When we are done building, the final application will look and work like this:

In the image above, we can see a digital product called “SPIN” and it has a chat widget option for visiting customers to interact with. On the left browser window, a customer visits this website and fills in his/her details before submitting the form.

There is an admin on the right browser window who can see all connected customers and respond to all their messages accordingly, providing effective and realtime support.

Prerequisites

To follow along with this tutorial, a basic knowledge of Python, Flask, JavaScript (ES6 syntax) and jQuery is required. You will also need the following installed:

Virtualenv is great for creating isolated Python environments, so we can install dependencies in an isolated environment, and not pollute our global packages directory.

Let’s install virtualenv with this command:

$ pip install virtualenv

⚠️ Virtualenv comes preinstalled with Python 3 so you may not need to install it if you are on this version.

Setting up the app environment

Let’s create our project folder, and activate a virtual environment within it:

$ mkdir python-pusher-chat-widget
$ cd python-pusher-chat-widget
$ virtualenv .venv
$ source .venv/bin/activate # Linux based systems
$ pathtoenvScriptsactivate # Windows users

Now that we have the virtual environment setup, we can install Flask and the remaining dependencies with this command:

$ pip install flask flask-cors simplejson

We need to install the Pusher library as we will need that for realtime updates.

Setting up Pusher

The first step here will be to get a Pusher Channels application. We need the application credentials for our realtime messaging to work.

Go to the Pusher website and create an account. After creating an account, you should create a new application. Follow the application creation wizard and then you should be given your application credentials, we will use this later in the article:


There’s one more thing we need to do here on this dashboard; because we will directly be triggering the message events on the client side of the application, we need to turn on a special feature that is turned off by default for security reasons. To learn more about triggering events on the client side, you can read the documentation here.

On the dashboard, click on App settings and scroll to the bottom of the page then select the option that says Enable client events:


Great, now let’s install the Pusher Python library, so that we can use Pusher in the application:

$ pip install pusher

File and folder structure

Here’s a representation of the file/folder structure for this app:

├── python-pusher-chat-widget
        ├── app.py
        ├── static
        └── templates

The static folder will contain the static files to be used as is defined by Flask standards. The templates folder will hold the HTML templates. In our application, app.py is the main entry point and will contain our server-side code.

Let’s create the app.py file and then the static and templates folders.

Building the backend

Before we start writing code to determine how the frontend of our application will be rendered, let’s fully develop the backend and all of its endpoints so that the frontend has something to communicate with when we build it.

Let’s open the app.py file and paste the following code:

// File: ./app.py

from flask import Flask, render_template, request, jsonify, makeresponse, json
from flaskcors import CORS
from pusher import pusher
import simplejson

app = Flask(_**name**_)
cors = CORS(app)
app.config_[_'CORSHEADERS'] = 'Content-Type'

# configure pusher object
pusher = pusher.Pusher(
app_id='PUSHER_APP_ID',
key='PUSHER_APP_KEY',
secret='PUSHER_APP_SECRET',
cluster='PUSHER_APP_CLUSTER',
ssl=True)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/admin')
def admin():
    return render_template('admin.html')

@app.route('/new/guest', methods=_[_'POST'])
def guestUser():
    data = request.json
    pusher.trigger(u'general-channel', u'new-guest-details', { 
        'name' : data_[_'name'], 
        'email' : data_[_'email']
        })
    return json.dumps(data)

@app.route("/pusher/auth", methods=_[_'POST'])
def pusher_authentication():
    auth = pusher.authenticate(channel=request.form_[_'channel_name'],socket_id=request.form_[_'socket_id'])
    return json.dumps(auth)

if _**name == '**_main_':
    app.run(host='0.0.0.0', port=5000, debug=True)

Replace the PUSHER_APP_* keys with the values on your Pusher dashboard.

The logic for this application is simple, we will require a Pusher public channel so that whenever a new customer connects with the chat widget, their details are sent over to the admin (using that public channel) and the admin can subscribe to a private channel (the customer will have to subscribe to this private channel too) using the customer’s email as a unique ID. The admin and that customer can further engage in one to one messaging over that private channel.

Let’s go over the code in the app.py file to see how it satisfies the logic we just discussed. We first imported all the required packages, then registered a new Pusher instance. Next, we declared four endpoints:

We used the trigger method on the Pusher instance here, the trigger method has the following syntax: pusher.trigger("a_channel", "an_event", {key: "data"}). You can find the docs for the Pusher Python library here to get more information on configuring and using Pusher in Python.

Building the frontend

In this section, we are going to do the following things:

We will be using Bootstrap as a base style for the application. We will also be using other third-party libraries so let’s fetch the source and place them in the appropriate directory inside the static directory.

Add these files in the static/js directory:

Add this file in the static/css directory:

The new folder structure should be:

├── python-pusher-chat-widget
    ├── app.py
    ├── static
    ├── css
        ├── admin.css
        ├── app.css
        ├── bootstrap.css
    ├── img
        ├── bg.jpg
    ├── js
        ├── admin.js
        ├── app.js
        ├── axios.js
        ├── bootstrap.js
        ├── jquery.js
        ├── popper.js
    ├── templates
    ├── admin.html
    ├── index.html

If you currently have this folder structure then you are good to go!

Setting up the homepage view

In the templates/index.html file, paste the following code:

<!-- File: ./templates/index.html -->
<!doctype html>
<html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Spin Spinner Spinnest!</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
    </head>
    <body>
    <div class="site-wrapper">
        <div class="site-wrapper-inner">
        <div class="cover-container">

            <header class="masthead clearfix">
            <div class="inner">
                <h3 class="masthead-brand">SPIN</h3>
                <nav class="nav nav-masthead">
                <a class="nav-link active" href="#">Home</a>
                <a class="nav-link" href="#">Features</a>
                <a class="nav-link" href="#">Contact</a>
                </nav>
            </div>
            </header>

            <main role="main" class="inner cover">
            <h1 class="cover-heading">SPIN</h1>
            <p class="lead">SPIN is a simple realtime chat widget powered by Pusher.</p>
            <p class="lead">
                <a href="#" class="btn btn-lg btn-secondary">GO for a SPIN?</a>
            </p>
            </main>

            <footer class="mastfoot">
            </footer>

        </div>
        </div>
    </div>

    <div class="chatbubble">
        <div class="unexpanded">
            <div class="title">Chat with Support</div>
        </div>
        <div class="expanded chat-window">
            <div class="login-screen container">
            <form id="loginScreenForm">
                <div class="form-group">
                <input type="text" class="form-control" id="fullname" placeholder="Name_" required>
                </div>
                <div class="form-group">
                <input type="email" class="form-control" id="email" placeholder="Email Address_" required>
                </div>
                <button type="submit" class="btn btn-block btn-primary">Start Chat</button>
            </form>
            </div>
            <div class="chats">
            <div class="loader-wrapper">
                <div class="loader">
                <span>{</span><span>}</span>
                </div>
            </div>
            <ul class="messages clearfix">
            </ul>
            <div class="input">
                <form class="form-inline" id="messageSupport">
                <div class="form-group">
                    <input type="text" autocomplete="off" class="form-control" id="newMessage" placeholder="Enter Message">
                </div>
                <button type="submit" class="btn btn-primary">Send</button>
                </form>
            </div>
            </div>
        </div>
    </div> 
    <script src="https://js.pusher.com/4.0/pusher.min.js"></script>
    <script src="{{ url_for('static', filename='js/jquery.js') }}"></script>
    <script src="{{ url_for('static', filename='js/popper.js') }}"></script>
    <script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>
    <script src="{{ url_for('static', filename='js/axios.js') }}"></script>
    <script src="{{ url_for('static', filename='js/app.js') }}"></script>
    </body>
</html>

In this file, we have the HTML for the homepage. We also used Flask’s url_for function to dynamically link to all the local scripts and styles that we created.

Because we require our application to send and receive messages in realtime, we imported the official Pusher JavaScript library with this line of code:

    <script src="https://js.pusher.com/4.0/pusher.min.js"></script>

We included some custom classes within the HTML elements, however, these classes will be useless if we do not define them in the matching CSS file, open the static/css/app.css file and paste the following code:

/__ File: static/css/app.css /
a,
a:focus,
a:hover {
    color: #fff;
}

.btn-secondary,
.btn-secondary:hover,
.btn-secondary:focus {
    color: #333;
    text-shadow: none;
    background-color: #fff;
    border: .05rem solid #fff;
}

html,
body {
    height: 100%;
    background-color: #333;
}

body {
    color: #fff;
    text-align: center;
    text-shadow: 0 .05rem .1rem rgba(0,0,0,.5);
}

.site-wrapper {
    display: table;
    width: 100%;
    height: 100%; /_ For at least Firefox _/
    min-height: 100%;
    box-shadow: inset 0 0 5rem rgba(0,0,0,.5);
    background: url(../img/bg.jpg);
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
}

.site-wrapper-inner {
    display: table-cell;
    vertical-align: top;
}

.cover-container {
    margin-right: auto;
    margin-left: auto;
}

.inner {
    padding: 2rem;
}

.masthead {
    margin-bottom: 2rem;
}

.masthead-brand {
    margin-bottom: 0;
}

.nav-masthead .nav-link {
    padding: .25rem 0;
    font-weight: 700;
    color: rgba(255,255,255,.5);
    background-color: transparent;
    border-bottom: .25rem solid transparent;
}

.nav-masthead .nav-link:hover,
.nav-masthead .nav-link:focus {
    border-bottom-color: rgba(255,255,255,.25);
}

.nav-masthead .nav-link + .nav-link {
    margin-left: 1rem;
}

.nav-masthead .active {
    color: #fff;
    border-bottom-color: #fff;
}

@media (min-width: 48em) {
    .masthead-brand {
    float: left;
    }

    .nav-masthead {
    float: right;
    }
}

.cover {
    padding: 0 1.5rem;
}

.cover .btn-lg {
    padding: .75rem 1.25rem;
    font-weight: 700;
}

.mastfoot {
    color: rgba(255,255,255,.5);
}

@media (min-width: 40em) {
    .masthead {
    position: fixed;
    top: 0;
    }

    .mastfoot {
    position: fixed;
    bottom: 0;
    }

    .site-wrapper-inner {
    vertical-align: middle;
    }

    .masthead,
    .mastfoot,
    .cover-container {
    width: 100%;
    }
}

@media (min-width: 62em) {
    .masthead,
    .mastfoot,
    .cover-container {
    width: 42rem;
    }
}

.chatbubble {
    position: fixed;
    bottom: 0;
    right: 30px;
    transform: translateY(300px);
    transition: transform .3s ease-in-out;
}

.chatbubble.opened {
    transform: translateY(0)
}

.chatbubble .unexpanded {
    display: block;
    background-color: #e23e3e;
    padding: 10px 15px 10px;
    position: relative;
    cursor: pointer;
    width: 350px;
    border-radius: 10px 10px 0 0;
}

.chatbubble .expanded {
    height: 300px;
    width: 350px;
    background-color: #fff;
    text-align: left;
    padding: 10px;
    color: #333;
    text-shadow: none;
    font-size: 14px;
}

.chatbubble .chat-window {
    overflow: auto;
}

.chatbubble .loader-wrapper {
    margin-top: 50px;
    text-align: center;
}

.chatbubble .messages {
    display: none;
    list-style: none;
    margin: 0 0 50px;
    padding: 0;
}

.chatbubble .messages li {
    width: 85%;
    float: left;
    padding: 10px;
    border-radius: 5px 5px 5px 0;
    font-size: 14px;
    background: #c9f1e6;
    margin-bottom: 10px;
}

.chatbubble .messages li .sender {
    font-weight: 600;
}

.chatbubble .messages li.support {
    float: right;
    text-align: right;
    color: #fff;
    background-color: #e33d3d;
    border-radius: 5px 5px 0 5px;
}

.chatbubble .chats .input {
    position: absolute;
    bottom: 0;
    padding: 10px;
    left: 0;
    width: 100%;
    background: #f0f0f0;
    display: none;
}

.chatbubble .chats .input .form-group {
    width: 80%;
}

.chatbubble .chats .input input {
    width: 100%;
}

.chatbubble .chats .input button {
    width: 20%;
}

.chatbubble .chats {
    display: none;
}

.chatbubble .login-screen {
    margin-top: 20px;
    display: none;
}

.chatbubble .chats.active,
.chatbubble .login-screen.active {
    display: block;
}

/_ Loader Credit: https://codepen.io/ashmind/pen/zqaqpB _/
.chatbubble .loader {
    color: #e23e3e;
    font-family: Consolas, Menlo, Monaco, monospace;
    font-weight: bold;
    font-size: 10vh;
    opacity: 0.8;
}

.chatbubble .loader span {
    display: inline-block;
    -webkit-animation: pulse 0.4s alternate infinite ease-in-out;
            animation: pulse 0.4s alternate infinite ease-in-out;
}

.chatbubble .loader span:nth-child(odd) {
    -webkit-animation-delay: 0.4s;
            animation-delay: 0.4s;
}

@-webkit-keyframes pulse {
    to {
    -webkit-transform: scale(0.8);
            transform: scale(0.8);
    opacity: 0.5;
    }
}

@keyframes pulse {
    to {
    -webkit-transform: scale(0.8);
            transform: scale(0.8);
    opacity: 0.5;
    }
}

Setting up the admin dashboard view

In the templates/admin.html file, paste the following code:

<!-- File: templates/admin.html -->
<!doctype html>
<html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Admin</title>
    <link href="{{ url_for('static', filename='css/bootstrap.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='css/admin.css') }}" rel="stylesheet">
    </head>
    <body>
    <header>
        <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
            <a class="navbar-brand" href="#">Dashboard</a>
        </nav>
    </header>

    <div class="container-fluid">
        <div class="row" id="mainrow">
            <nav class="col-sm-3 col-md-2 d-none d-sm-block bg-light sidebar">
                <ul class="nav nav-pills flex-column" id="rooms">
                </ul>
            </nav>
            <main role="main" class="col-sm-9 ml-sm-auto col-md-10 pt-3" id="main">
                <h1>Chats</h1>
                <p>👈 Select a chat to load the messages</p>
                <p>&nbsp;</p>
                <div class="chat" style="margin-bottom:150px">
                    <h5 id="room-title"></h5>
                    <p>&nbsp;</p>
                    <div class="response">
                        <form id="replyMessage">
                            <div class="form-group">
                                <input type="text" placeholder="Enter Message" class="form-control" name="message" />
                            </div>
                        </form>
                    </div>
                    <div class="table-responsive">
                        <table class="table table-striped">
                        <tbody id="chat-msgs">
                        </tbody>
                    </table>
                </div>
            </main>
        </div>
    </div>

    <script src="https://js.pusher.com/4.0/pusher.min.js"></script>
    <script src="{{ url_for('static', filename='js/jquery.js') }}"></script>
    <script src="{{ url_for('static', filename='js/popper.js') }}"></script>
    <script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>
    <script src="{{ url_for('static', filename='js/axios.js') }}"></script>
    <script src="{{ url_for('static', filename='js/admin.js') }}"></script>
    </body>
</html>

Open the static/css/admin.css file and paste the following code:

/** File: static/css/admin.css **/
body {
    padding-top: 3.5rem;
}

h1 {
    padding-bottom: 9px;
    margin-bottom: 20px;
    border-bottom: 1px solid #eee;
}

.sidebar {
    position: fixed;
    top: 51px;
    bottom: 0;
    left: 0;
    z-index: 1000;
    padding: 20px 0;
    overflow-x: hidden;
    overflow-y: auto;
    border-right: 1px solid #eee;
}

.sidebar .nav {
    margin-bottom: 20px;
}

.sidebar .nav-item {
    width: 100%;
}

.sidebar .nav-item + .nav-item {
    margin-left: 0;
}

.sidebar .nav-link {
    border-radius: 0;
}

.placeholders {
    padding-bottom: 3rem;
}

.placeholder img {
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;
}

tr .sender {
    font-size: 12px;
    font-weight: 600;
}

tr .sender span {
    color: #676767;
}

.response {
    display: none;
}

Writing the app.js script

In this section, we will write the script that works with the homepage and supports the customers’ functions. This script will define the logic that will enable a customer to submit the form after filling in his/her details and everything else.

We will define some helper functions within an IIFE and these functions will run on the occurrence of several DOM events and possibly pass on the execution to other helper functions.

Open the app.js file and paste the following:

// File: static/js/app.js
(function() {
    'use strict';

    var pusher = new Pusher('PUSHER_APP_KEY', {
        authEndpoint: '/pusher/auth',
        cluster: 'PUSHER_APP_CLUSTER',
        encrypted: true
    });

    // ----------------------------------------------------
    // Chat Details
    // ----------------------------------------------------

    let chat = {
        name:  undefined,
        email: undefined,
        myChannel: undefined,
    }

    // ----------------------------------------------------
    // Targeted Elements
    // ----------------------------------------------------

    const chatPage   = $(document)
    const chatWindow = $('.chatbubble')
    const chatHeader = chatWindow.find('.unexpanded')
    const chatBody   = chatWindow.find('.chat-window')

    // ----------------------------------------------------
    // Register helpers
    // ----------------------------------------------------

    let helpers = {

        // ----------------------------------------------------
        // Toggles the display of the chat window.
        // ----------------------------------------------------

        ToggleChatWindow: function () {
            chatWindow.toggleClass('opened')
            chatHeader.find('.title').text(
                chatWindow.hasClass('opened') ? 'Minimize Chat Window' : 'Chat with Support'
            )
        },

        // --------------------------------------------------------------------
        // Show the appropriate display screen. Login screen or Chat screen.
        // --------------------------------------------------------------------

        ShowAppropriateChatDisplay: function () {
            (chat.name) ? helpers.ShowChatRoomDisplay() : helpers.ShowChatInitiationDisplay()
        },

        // ----------------------------------------------------
        // Show the enter details form.
        // ----------------------------------------------------

        ShowChatInitiationDisplay: function () {
            chatBody.find('.chats').removeClass('active')
            chatBody.find('.login-screen').addClass('active')
        },

        // ----------------------------------------------------
        // Show the chat room messages display.
        // ----------------------------------------------------

        ShowChatRoomDisplay: function () {
            chatBody.find('.chats').addClass('active')
            chatBody.find('.login-screen').removeClass('active')

            setTimeout(function(){
                chatBody.find('.loader-wrapper').hide()
                chatBody.find('.input, .messages').show()
            }, 2000)
        },

        // ----------------------------------------------------
        // Append a message to the chat messages UI.
        // ----------------------------------------------------

        NewChatMessage: function (message) {
            if (message !== undefined) {
                const messageClass = message.sender !== chat.email ? 'support' : 'user'

                chatBody.find('ul.messages').append(
                    `<li class="clearfix message ${messageClass}">
                        <div class="sender">${message.name}</div>
                        <div class="message">${message.text}</div>
                    </li>`
                )

                chatBody.scrollTop(chatBody[0].scrollHeight)
            }
        },

        // ----------------------------------------------------
        // Send a message to the chat channel.
        // ----------------------------------------------------

        SendMessageToSupport: function (evt) {

            evt.preventDefault()

            let createdAt = new Date()
            createdAt = createdAt.toLocaleString()

            const message = $('#newMessage').val().trim()

            chat.myChannel.trigger('client-guest-new-message', {
                'sender': chat.name,
                'email': chat.email,
                'text': message,
                'createdAt': createdAt 
            });

            helpers.NewChatMessage({
                'text': message,
                'name': chat.name,
                'sender': chat.email
            })

            console.log("Message added!")

            $('#newMessage').val('')
        },

        // ----------------------------------------------------
        // Logs user into a chat session.
        // ----------------------------------------------------

        LogIntoChatSession: function (evt) {
            const name  = $('#fullname').val().trim()
            const email = $('#email').val().trim().toLowerCase()

            // Disable the form
            chatBody.find('#loginScreenForm input, #loginScreenForm button').attr('disabled', true)

            if ((name !== '' && name.length >= 3) && (email !== '' && email.length >= 5)) {
                axios.post('/new/guest', {name, email}).then(response => {
                    chat.name = name
                    chat.email = email
                    chat.myChannel = pusher.subscribe('private-' + response.data.email);
                    helpers.ShowAppropriateChatDisplay()
                })
            } else {
                alert('Enter a valid name and email.')
            }

            evt.preventDefault()
        }
    }

    // ------------------------------------------------------------------
    // Listen for a new message event from the admin
    // ------------------------------------------------------------------

    pusher.bind('client-support-new-message', function(data){
        helpers.NewChatMessage(data)
    })

    // ----------------------------------------------------
    // Register page event listeners
    // ----------------------------------------------------

    chatPage.ready(helpers.ShowAppropriateChatDisplay)
    chatHeader.on('click', helpers.ToggleChatWindow)
    chatBody.find('#loginScreenForm').on('submit', helpers.LogIntoChatSession)
    chatBody.find('#messageSupport').on('submit', helpers.SendMessageToSupport)
}())

Above we have the JavaScript that powers the clients chat widget. In the code, we start by instantiating Pusher (remember to replace the PUSHER_* keys with the keys in your Pusher dashboard).

We have a helpers property that has a few functions attached to it. Each function has a comment explaining what it does right before it is defined. At the bottom of the script is where we register all the events and listeners that make the widget function as expected.

Writing the admin.js script
The code in the admin.js is similar to the app.js and functions in a similat manner. Open the admin.js add paste the following code:

// File: static/js/admin.js
(function () {
    'use strict';

    // ----------------------------------------------------
    // Configure Pusher instance
    // ----------------------------------------------------

    var pusher = new Pusher('PUSHER_APP_KEY', {
        authEndpoint: '/pusher/auth',
        cluster: 'PUSHER_APP_CLUSTER',
        encrypted: true
        });

    // ----------------------------------------------------
    // Chat Details
    // ----------------------------------------------------

    let chat = {
        messages: [],
        currentRoom: '',
        currentChannel: '',
        subscribedChannels: [],
        subscribedUsers: []
    }

    // ----------------------------------------------------
    // Subscribe to the generalChannel
    // ----------------------------------------------------

    var generalChannel = pusher.subscribe('general-channel');

    // ----------------------------------------------------
    // Targeted Elements
    // ----------------------------------------------------

    const chatBody = $(document)
    const chatRoomsList = $('#rooms')
    const chatReplyMessage = $('#replyMessage')

    // ----------------------------------------------------
    // Register helpers
    // ----------------------------------------------------

    const helpers = {

        // ------------------------------------------------------------------
        // Clear the chat messages UI
        // ------------------------------------------------------------------

        clearChatMessages: () => $('#chat-msgs').html(''),

        // ------------------------------------------------------------------
        // Add a new chat message to the chat window.
        // ------------------------------------------------------------------

        displayChatMessage: (message) => {
            if (message.email === chat.currentRoom) {

                $('#chat-msgs').prepend(
                    `<tr>
                        <td>
                            <div class="sender">${message.sender} @ <span class="date">${message.createdAt}</span></div>
                            <div class="message">${message.text}</div>
                        </td>
                    </tr>`
                )
            }
        },

        // ------------------------------------------------------------------
        // Select a new guest chatroom
        // ------------------------------------------------------------------

        loadChatRoom: evt => {
            chat.currentRoom = evt.target.dataset.roomId
            chat.currentChannel = evt.target.dataset.channelId

            if (chat.currentRoom !== undefined) {
                $('.response').show()
                $('#room-title').text(evt.target.dataset.roomId)
            }

            evt.preventDefault()
            helpers.clearChatMessages()
        },

        // ------------------------------------------------------------------
        // Reply a message
        // ------------------------------------------------------------------
        replyMessage: evt => {
            evt.preventDefault()

            let createdAt = new Date()
            createdAt = createdAt.toLocaleString()

            const message = $('#replyMessage input').val().trim()

            chat.subscribedChannels[chat.currentChannel].trigger('client-support-new-message', {
                'name': 'Admin',
                'email': chat.currentRoom,
                'text': message, 
                'createdAt': createdAt 
            });

            helpers.displayChatMessage({
                'email': chat.currentRoom,
                'sender': 'Support',
                'text': message, 
                'createdAt': createdAt
            })

            $('#replyMessage input').val('')
        },
    }

        // ------------------------------------------------------------------
        // Listen to the event that returns the details of a new guest user
        // ------------------------------------------------------------------

        generalChannel.bind('new-guest-details', function(data) {

        chat.subscribedChannels.push(pusher.subscribe('private-' + data.email));

        chat.subscribedUsers.push(data);

        // render the new list of subscribed users and clear the former
        $('#rooms').html("");
        chat.subscribedUsers.forEach(function (user, index) {

                $('#rooms').append(
                    `<li class="nav-item"><a data-room-id="${user.email}" data-channel-id="${index}" class="nav-link" href="#">${user.name}</a></li>`
                )
        })

        })

        // ------------------------------------------------------------------
        // Listen for a new message event from a guest
        // ------------------------------------------------------------------

        pusher.bind('client-guest-new-message', function(data){
            helpers.displayChatMessage(data)
        })

    // ----------------------------------------------------
    // Register page event listeners
    // ----------------------------------------------------

    chatReplyMessage.on('submit', helpers.replyMessage)
    chatRoomsList.on('click', 'li', helpers.loadChatRoom)
}())

Just like in the app.js we have the helpers object that holds the meat of the script and towards the bottom, the listeners and events are called and registered.

Replace the PUSHER_APP_* keys with the keys on your Pusher dashboard.

Running the application

We can test the application using this command:

$ flask run

Now if we visit 127.0.0.1:5000 and 127.0.0.1:5000/admin we should test the application:

Conclusion

In this article, we have learned how we can leverage the power of Pusher in creating a chat widget powered by a Python backend. The entire code for this tutorial is available on GitHub.

Source:: scotch.io

Demystifying The Service Worker Lifecycle

By Chimezie Enyinnaya

The Service Worker Lifecycle

In an earlier article of mine, I talked about 4 essential things every PWA must have, which service worker happens to be part of. Service worker plays a very vital role when it’s comes to Progressive Web Apps (PWA), as it is responsible for offline caching, push notifications, background sync etc. In this article, we’ll be demystifying the service worker lifecycle and what can be done at each stage of the lifecycle.

For effective use of service worker, an understanding of the service lifecycle is essential. The service worker lifecycle consists of mainly 3 phases, which are:

  • Registration
  • Installation
  • Activation

Let’s go over each of them.

Registration

A service worker is basically a JavaScript file. One thing that differentiate a service worker file from a normal JavaScript file, is that service worker runs in the background. Before we can start using service worker, we must register it as a background process. This is the first phase of the phase of the lifecycle. Since service worker is not currently supported in all browsers yet. When registering a service worker, we must first check to make sure the browser supports service worker. Below is a code we can use to register a service worker:

// app.js

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
    .then(function (registration) {
        console.log('Service worker registered!');
    })
    .catch(function (err) {
        console.log('Registration failed!');
    })
}

First, we check if the browser support service worker, that is, if the navigator object has a serviceWorker property. Only when it’s supported that we would register the service worker. The register() takes the path to the service worker script and returns a promise.

At the point of registering a service worker, we can also define the scope of the service worker. The scope of a service worker determines the pages that the service worker can control. By default, the scope is defined by the location of the service worker script.

// app.js

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js', {
        scope: '/blog/'
    })
    .then(function (registration) {
        console.log('Service worker registered!');
    })
    .catch(function (err) {
        console.log('Registration failed!');
    })
}

In addition to accepting the path to service worker script, the register() can also accept an optional object, where we can define the scope of the service worker. Here, we define the scope of the service worker to /blog/, which will limit the service worker to only the blog directory.

Installation

The fact that a service worker has been successfully registered doesn’t mean it has been installed. That’s where the installation phase of the lifecycle comes into play. Upon successful registration of the service worker, the script is downloaded and then the browser will attempt to install the service worker. The service worker will only be installed in either of these cases:

  • The service worker hasn’t been registered before
  • The service worker script changes (even if it’s by one byte).

Once a service worker has been installed, an install event is fired. We can listen for this event and perform some application specific tasks. For example, we could cache our application static assets at this point:

// sw.js

const assetsToCache = [
    '/index.html',
    '/about.html',
    '/css/app.css',
    '/js/app.js',
]

self.addEventListener('install', function (event) {
    event.waitUntil(
        caches.open('staticAssetsCache').then(function (cache) {
              return cache.addAll(assetsToCache);
        })
      );
});

Here, we are using the open() of the Cache API, which accepts the name of the cache (staticAssetsCache in this case) to either open (if it already exists) or create and returns a promise. Once the promise is resolved, that is, inside the then(), we again make use of the addAll() of the Cache API, which accepts an array of URLs to cache. Since the open() wilol return a promise, we need wrap it inside event.waitUntil(), which will delay the installation of the service worker untill the promise is resolved. If the promise is rejected, the install event fails and the service worker will be discarded.

Activation

If the installation was successful, the service worker enters an installed state (though not yet active), during which it waits to take control of the page from the current service worker. It then moves on to the next phase in the lifecycle, which is the activation phase. A service worker is not immediately activated upon installation. A service worker will only be active (that is, be activated) in any of these cases:

  • If there is no service worker currently active
  • If the self.skipWaiting() is called in the install event handler of the service worker script
  • If the user refreshes the page

An example of using the skipWaiting() to active a service worker can look like below:

// sw.js

self.addEventListener('install', function (event) {
    self.skipWaiting();

    event.waitUntil(
           // static assets caching
      );
});

An activate event is fired upon a service worker being active. Like the install event, we could also listen for the activate event and perform some application specific tasks. For for example, clearing out the cache:

// sw.js

const cacheVersion = 'v1';

self.addEventListener('activate', function (event) { 
    event.waitUntil( 
        caches.keys().then(function (cacheNames) {
            cacheNames.map(function (cacheName) {
                if (cacheName.indexOf(cacheVersion) < 0) { 
                    return caches.delete(cacheName);
                   } 
                }); 
            });
        }) 
    ); 
});

The snippet above loops through all the named caches and deletes any existing if the cache does not belongs to the current service worker.

Once the service worker has been activated, it now has full control of the pages. With the service worker active, it can now handle events such as fetch, push and sync.

// sw.js

self.addEventListener('fetch', function (event) {
    event.respondWith(caches.match(event.request))
    .then(function (response) {
        return response || fetch(event.request);
    });
});

If the service worker after being active, does not receive any of the functional events mentioned above, it goes into an idle state. After being idle for some time, the service worker goes into a terminated state. This does not mean the service worker has been uninstalled or unregistered. In fact, the service worker will become idle again as soon as it begins to receive the fuctional events.

Below is a visual summary of the service worker lifecycle:

Conclusion

So in this article, we looked at the service worker lifecycle, the events that are emitted at end phase of the lifecycle. Also, we looked some possible things we could with a service worker by hooking into some of these events.

I hope you find this article helpful and let’s continue the conversation in the comments section below.

Source:: scotch.io

Experimenting With Lazy Loaded Modules, Ahead Of Time (AoT) Compiling, And Webpack 4 In Angular 6.1.7

Ben Nadel shares the approach to lazy loading modules that he’s finally got working with Angular 6.1.7, Webpack 4, and Ahead of Time (AoT) compiling. This approach uses symmetric loading semantics (between statically loaded and lazy loaded modules). And, allows each feature module to fully encapsulate its own router segment definitions….

Source:: bennadel.com