You are on page 1of 20

1 ECMASCRIPT 2015 FEATURES

1.1 ARROWS AND LEXICAL THIS


Arrows are a function shorthand using the => syntax. They are syntactically similar to the related
feature in C#, Java 8 and CoffeeScript. They support both expression and statement bodies. Unlike
functions, arrows share the same lexical this as their surrounding code. If an arrow is inside another
function, it shares the "arguments" variable of its parent function.

// Expression bodies
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);

// Statement bodies
nums.forEach(v => {
  if (v % 5 === 0)
    fives.push(v);
});

// Lexical this
var bob = {
  _name: "Bob",
  _friends: [],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f));
  }
};

// Lexical arguments
function square() {
    let example = () => {
      let numbers = [];
      for (let number of arguments) {
        numbers.push(number * number);
      }
  
      return numbers;
    };
  
    return example();
  }
  
  console.log(square(2, 4, 7.5, 8, 11.5, 21)); // returns: [4, 16, 56.25, 64, 
132.25, 441]
  
1.2 CLASSES
ES2015 classes are a simple sugar over the prototype-based OO pattern. Having a single convenient
declarative form makes class patterns easier to use, and encourages interoperability. Classes support
prototype-based inheritance, super calls, instance and static methods and constructors.

class SkinnedMesh extends THREE.Mesh {
    constructor(geometry, materials) {
        super(geometry, materials);

        this.idMatrix = SkinnedMesh.defaultMatrix();
        this.bones = [];
        this.boneMatrices = [];
        //...
    }
    update(camera) {
        //...
        super.update();
    }
    static defaultMatrix() {
        return new THREE.Matrix4();
    }
}

1.3 ENHANCED OBJECT LITERALS


Object literals are extended to support setting the prototype at construction, shorthand for foo: foo
assignments, defining methods and making super calls. Together, these also bring object literals and
class declarations closer together, and let object-based design benefit from some of the same
conveniences.

class SkinnedMesh extends THREE.Mesh {
    constructor(geometry, materials) {
        super(geometry, materials);

        this.idMatrix = SkinnedMesh.defaultMatrix();
        this.bones = [];
        this.boneMatrices = [];
        //...
    }
    update(camera) {
        //...
        super.update();
    }
    static defaultMatrix() {
        return new THREE.Matrix4();
    }
}
The __proto__ property requires native support, and was deprecated in previous ECMAScript
versions. Most engines now support the property, but some do not. Also, note that only web
browsers are required to implement it, as it's in Annex B. It is available in Node.

1.4 TEMPLATE STRINGS


Template strings provide syntactic sugar for constructing strings. This is similar to string interpolation
features in Perl, Python and more. Optionally, a tag can be added to allow the string construction to
be customized, avoiding injection attacks or constructing higher level data structures from string
contents.

// Basic literal string creation
`This is a pretty little template string.`

    // Multiline strings
    `In ES5 this is
 not legal.`

// Interpolate variable bindings
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// Unescaped template strings
String.raw`In ES5 "\n" is a line-feed.`

// Construct an HTTP request prefix is used to interpret the replacements and 
construction
GET`http://foo.org/bar?a=${a}&b=${b}
    Content-Type: application/json
    X-Credentials: ${credentials}
    { "foo": ${foo},
      "bar": ${bar}}`(myOnReadyStateChangeHandler);s

1.5 DESTRUCTURING
Destructuring allows binding using pattern matching, with support for matching arrays and objects.
Destructuring is fail-soft, similar to standard object lookup foo["bar"], producing undefined values
when not found.

// list matching
var [a, ,b] = [1,2,3];
a === 1;
b === 3;

// object matching
var { op: a, lhs: { op: b }, rhs: c }
       = getASTNode()
// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
var {op, lhs, rhs} = getASTNode()

// Can be used in parameter position
function g({name: x}) {
  console.log(x);
}
g({name: 5})

// Fail-soft destructuring
var [a] = [];
a === undefined;

// Fail-soft destructuring with defaults
var [a = 1] = [];
a === 1;

// Destructuring + defaults arguments
function r({x, y, w = 10, h = 10}) {
  return x + y + w + h;
}
r({x:1, y:2}) === 23

1.6 DEFAULT + REST + SPREAD


Callee-evaluated default parameter values. Turn an array into consecutive arguments in a function
call. Bind trailing parameters to an array. Rest replaces the need for arguments and addresses
common cases more directly.

function f(x, y = 12) {
    // y is 12 if not passed (or passed as undefined)
    return x + y;
}
f(3) == 15

function f(x, ...y) {
    // y is an Array
    return x * y.length;
}
f(3, "hello", true) == 6

function f(x, y, z) {
    return x + y + z;
}
// Pass each elem of array as argument
f(...[1, 2, 3]) == 6
1.7 LET + CONST
Block-scoped binding constructs. let is the new var. const is single-assignment. Static restrictions
prevent use before assignment.

function f() {
    {
        let x;
        {
            // this is ok since it's a block scoped name
            const x = "sneaky";
            // error, was just defined with `const` above
            x = "foo";
        }
        // this is ok since it was declared with `let`
        x = "bar";
        // error, already declared above in this block
        let x = "inner";
    }
}

1.8 ITERATORS + FOR..OF


Iterator objects enable custom iteration like CLR IEnumerable or Java Iterable. Generalize for..in to
custom iterator-based iteration with for..of. Don’t require realizing an array, enabling lazy design
patterns like LINQ.

let fibonacci = {
    [Symbol.iterator]() {
        let pre = 0, cur = 1;
        return {
            next() {
                [pre, cur] = [cur, pre + cur];
                return { done: false, value: cur }
            }
        }
    }
}

for (var n of fibonacci) {
    // truncate the sequence at 1000
    if (n > 1000)
        break;
    console.log(n);
}
Generators
Generators simplify iterator-authoring using function* and yield. A function declared as function*
returns a Generator instance. Generators are subtypes of iterators which include additional next and
throw. These enable values to flow back into the generator, so yield is an expression form which
returns a value (or throws).

Note: Can also be used to enable ‘await’-like async programming.

var fibonacci = {
    [Symbol.iterator]: function* () {
        var pre = 0, cur = 1;
        for (; ;) {
            var temp = pre;
            pre = cur;
            cur += temp;
            yield cur;
        }
    }
}

for (var n of fibonacci) {
    // truncate the sequence at 1000
    if (n > 1000)
        break;
    console.log(n);
}

1.9 MODULES
Language-level support for modules for component definition. Codifies patterns from popular
JavaScript module loaders (AMD, CommonJS). Runtime behaviour defined by a host-defined default
loader. Implicitly async model – no code executes until requested modules are available and
processed.

// lib/math.js
export function sum(x, y) {
    return x + y;
}
export var pi = 3.141593;

// app.js
import * as math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi));

// otherApp.js
import { sum, pi } from "lib/math";
console.log("2π = " + sum(pi, pi));
Some additional features include export default and export *:
export * from "lib/math";
export var e = 2.71828182846;
export default function (x) {
    return Math.exp(x);
}

// app.js
import exp, { pi, e } from "lib/mathplusplus";
console.log("e^π = " + exp(pi));

1.10 MAP + SET + WEAKMAP + WEAKSET


Efficient data structures for common algorithms. WeakMaps provides leak-free object-key’d side
tables.

// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;

// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;

// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined

// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in th
e set

1.11 SUBCLASSABLE BUILT-INS


In ES2015, built-ins like Array, Date and DOM Elements can be subclassed.

class MyArray extends Array {
    constructor(...args) { super(...args); }
}

var arr = new MyArray();
arr[1] = 12;
arr.length == 2

1.12 MATH + NUMBER + STRING + OBJECT APIS


Many new library additions, including core Math libraries, Array conversion helpers, and
Object.assign for copying.

Number.EPSILON
Number.isInteger(Infinity) // false
Number.isNaN("NaN") // false

Math.acosh(3) // 1.762747174039086
Math.hypot(3, 4) // 5
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2

"abcde".includes("cd") // true
"abc".repeat(3) // "abcabcabc"

Array.from(document.querySelectorAll("*")) // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg be
havior
[0, 0, 0].fill(7, 1) // [0,7,7]
[1, 2, 3].findIndex(x => x == 2) // 1
["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys() // iterator 0, 1, 2
["a", "b", "c"].values() // iterator "a", "b", "c"

Object.assign(Point, { origin: new Point(0, 0) })

1.13 PROMISES
Promises are a library for asynchronous programming. Promises are a first class representation of a
value that may be made available in the future. Promises are used in many existing JavaScript
libraries.

function timeout(duration = 0) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, duration);
    })
}

var p = timeout(1000).then(() => {
    return timeout(2000);
}).then(() => {
    throw new Error("hmm");
}).catch(err => {
    return Promise.all([timeout(100), timeout(200)]);
})
2 NODE & HAPI

2.1 INTRO
1. Create a new directory myproject, and from there:
2. Run: cd myproject, this goes into the created project folder.
3. Run: npm init and follow the prompts. This will generate a package.json file for you.
4. Run: npm install @hapi/hapi, this will install the latest version of hapi as a dependency in your
package.json.
5. Run: npm instal nodemon, this package will give us auto reload server on development.
6. Create index.js in root folder

/**
 * index.js
 */

const Hapi = require('@hapi/hapi');

const server = new Hapi.Server({
    host: 'localhost',
    port: 3101,
});

const main = async () => {
    await server.start()
    return server
};

main().then(server => {
    console.log('Server running at:', server.info.uri)
}).catch(err => {
    console.log(err)
    process.exit(1)
})

7. Now start the server using nodemon index.js.

Hapi has a built-in async method on the server called start(), and you’d think that’s all you need. But
soon, we’ll do a lot of things to our server before it actually starts, and it’ll be helpful to keep them
all in one place. It’s also going to handle any errors by printing them to the console and exiting out of
the node process.

After we define the function, we call it at the end of our file, which finally starts our server. Now,
let’s add some routes!

You add routes by using the server object’s route() method. The method takes either a config object
or an array of objects.
/**
 * index.js
 */

//  import hapi
const Hapi = require('@hapi/hapi');

// create a server with a host and port
const server = new Hapi.Server({
    host: 'localhost',
    port: 3101,
});

// add each route
server.route([
    {
        method: 'GET',
        path: '/',
        handler: (request, h) => {
            return 'I am root route';
        },
    },
    {
        method: 'GET',
        path: '/hello',
        handler: (request, h) => {
            return { msg: 'I am hello' };
        },
    }
]);

// define server start function
const main = async () => {
    await server.start()
    return server
};

// start server
main().then(server => {
    console.log('Server running at:', server.info.uri)
}).catch(err => {
    console.log(err)
    process.exit(1)
})
2.2 PLUGINS
hapi has an extensive and powerful plugin system that allows you to very easily break your
application up into isolated pieces of business logic, and reusable utilities. You can either add an
existing plugin to your application, or create your own.

Plugins are very simple to write. At their core they are an object with a register property, that is a
function with the signature async function (server, options). Additionally, the plugin object has a
required name property and several optional properties including version.

Let’s separate the route into plugin and use it in the Index.js.

First create new folder src/routes and add a users.js file.

/**
 * /src/routes/users.js
 */

'use strict';

exports.plugin = {
    pkg: require('../../package.json'),
    name: 'route-books',
    register: async (server, options, next) => {
        const basePath = '/api/v1/';
        server.route([
            {
                method: 'GET',
                path: basePath + 'users',
                handler: (request, h) => {
                    const data = { data: 'hello from users' }
                    return h.response(data).code(200)
                }
            },
            {
                method: 'GET',
                path: basePath + 'users/{id}',
                handler: (request, h) => {
                    return 'Hello from user ' + request.params.id;
                }
            }
        ]);
    }
};

Now, register the plugin before the server start

// index.js

'use strict';
const Hapi = require('@hapi/hapi');

const server = Hapi.server({
    port: 3000,
    host: 'localhost'
})

server.route({
    method: 'GET',
    path: '/hello',
    handler: (request, reply) => {
        return 'I am hello';
    }
});

const main = async () => {
    // register the plugin
    await server.register(require('./src/routes/users.js'));
    await server.start()
}

main().then(() => {
    console.log('Server running on %s', server.info.uri);
}).catch(err => {
    console.log(err)
    process.exit(1)
})

You can create plugins to modularize hapi as much as you like.

2.3 VALIDATION
In hapi, validation can be handled by Joi plugin to simplify things. To use it lets install Joi to our
project by registering it to our dependencies in package.json file:

"@hapi/joi": "15.1.1",
*note: for now, just use Joi 15.

And execute npm update to update our plugins.

Next let’s edit our users.js file to this:

/**
 * /src/routes/users.js
 */

'use strict';
const Joi = require('@hapi/joi');

exports.plugin = {
    pkg: require('../../package.json'),
    name: 'route-books',
    register: async (server, options, next) => {
        const basePath = '/api/v1/';
        server.route([
            {
                method: 'GET',
                path: basePath + 'users',
                handler: (request, h) => {
                    const data = { data: 'hello from users' }
                    return h.response(data).code(200)
                }
            },
            {
                method: 'GET',
                path: basePath + 'users/{id}',
                options: {
                    validate: {
                        params: {
                            id: Joi.number().required().min(1)
                        },
                        query: {
                            page: Joi.number().min(0).default(1),
                        }
                    }
                },
                handler: (request, h) => {
                    return 'Hello from user ' + request.params.id;
                }
            }
        ]);
    }
};

We put options parameter in our route and validate it. Params equal request parameter, and query
is query parameter.

If we try to access the server like this:

http://localhost:3000/api/v1/users/a

or

http://localhost:3000/api/v1/users/9?page=a

Joi will validate it and return error.


2.4 AUTHENTICATION
Most modern web apps use some form of authentication. Authentication within hapi is based on the
concept of schemes and strategies. Schemes are a way of handling authentication within hapi. For
example, the @hapi/basic and @hapi/cookie plugins would be considered schemes. A strategy is a
pre-configured instance of a scheme. You use strategies to implement authentication schemes into
your application.

2.4.1 Schemes
A scheme is a method with the signature function (server, options). The server parameter is a
reference to the server the scheme is being added to, while the options parameter is the
configuration object provided when registering a strategy that uses this scheme.

This method must return an object with at least the key authenticate. Other optional methods that
can be used are payload and response.

You can either write your own authentication scheme, or use one of the many hapi auth plugins,
such as hapi-auth-basic or hapi-auth-cookie.

2.4.2 authenticate
The authenticate method has a signature of function (request, h), and is the only required method in
a scheme.

In this context, request is the request object created by the server. It is the same object that
becomes available in a route handler, and is documented in the API reference.

h is the standard hapi response toolkit.

When authentication is successful, you must call and return h.authenticated({ credentials,
artifacts }). credentials property is an object representing the authenticated user (or the credentials
the user attempted to authenticate with). Additionally, you may also have an artifacts key, which can
contain any authentication related data that is not part of the user's credentials.

The credentials and artifacts properties can be accessed later (in a route handler, for example) as
part of the request.auth object.

If authentication is unsuccessful, you can either throw an error or call and return
h.unauthenticated(error, [data]) where error is an authentication error and data is an optional object
containing credentials and artifacts. There's no difference between calling return
h.unauthenticated(error) or throwing an error if no data object is provided. The specifics of the error
passed will affect the behavior. More information can be found in the API documentation for
server.auth.scheme(name, scheme). It is recommended to use boom for errors.

2.4.3 payload
The payload method has the signature function (request, h).

Again, the standard hapi response toolkit is available here. To signal a failure throw an error, again
it's recommended to use boom for errors.

To signal a successful authentication, return h.continue.


2.4.4 response
The response method also has the signature function (request, h) and utilizes the standard response
toolkit. This method is intended to decorate the response object (request.response) with additional
headers, before the response is sent to the user.

Once any decoration is complete, you must return h.continue, and the response will be sent.

If an error occurs, you should instead throw an error where the error is recommended to be a boom.

2.4.5 cookie
@hapi/cookie is a plugin that will store a cookie in the users browser once they are authenticated.
This has the option of keeping the user logged in, even after they leave the site. Here is an example
of setting up @hapi/cookie. In this example, the home route, "/", is restricted and can only be
accessed once a user has authenticated themselves: (don’t forget to install bcrypt and
@hapi/cookie)

'use strict';

const Bcrypt = require('bcrypt');
const Hapi = require('@hapi/hapi');

const users = [
    {
        username: 'john',
        password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5T
m',   // 'secret'
        name: 'John Doe',
        id: '2133d32a'
    }
];

const start = async () => {

    const server = Hapi.server({ port: 4000 });

    await server.register(require('@hapi/cookie'));

    server.auth.strategy('session', 'cookie', {
        cookie: {
            name: 'sid-example',
            password: '!wsYhFA*C2U6nz=Bu^%A@^F#SF3&kSR6',
            isSecure: false
        },
        redirectTo: '/login',
        validateFunc: async (request, session) => {

            const account = await users.find(
                (user) => (user.id === session.id)
            );
            if (!account) {

                return { valid: false };
            }

            return { valid: true, credentials: account };
        }
    });

    server.auth.default('session');

    server.route([
        {
            method: 'GET',
            path: '/',
            handler: function (request, h) {

                return 'Welcome to the restricted home page!';
            }
        },
        {
            method: 'GET',
            path: '/login',
            handler: function (request, h) {

                return ` <html>
                            <head>
                                <title>Login page</title>
                            </head>
                            <body>
                                <h3>Please Log In</h3>
                                <form method="post" action="/login">
                                    Username: <input type="text" name="userna
me"><br>
                                    Password: <input type="password" name="pa
ssword"><br/>
                                <input type="submit" value="Login"></form>
                            </body>
                        </html>`;
            },
            options: {
                auth: false
            }
        },
        {
            method: 'POST',
            path: '/login',
            handler: async (request, h) => {
                const { username, password } = request.payload;
                const account = users.find(
                    (user) => user.username === username
                );

                if (!account || !(await Bcrypt.compare(password, accoun
t.password))) {

                    return h.view('/login');
                }

                request.cookieAuth.set({ id: account.id });

                return h.redirect('/');
            },
            options: {
                auth: {
                    mode: 'try'
                }
            }
        }
    ]);

    await server.start();

    console.log('server running at: ' + server.info.uri);
};

start();

First, you need to do is register the @hapi/cookie plugin with server.register. Once the plugin is
registered, you configure your strategy by calling server.auth.strategy. server.auth.strategy takes
three parameters: name of the strategy, what scheme you are using, and an options object. For your
strategy, you name it session. For the scheme, you will be using the cookie scheme. If you were using
@hapi/basic, this parameter would be basic. The last parameter is an options object. This is how you
can customize your auth strategy to fit your needs.

The first property you configure is the cookie object. In your strategy, you will configure three
properties of the cookie object. First, you set the name of the cookie, in this case sid-example. Next,
you set the password that will be used to encrypt the cookie. This should be at least 32 characters
long. Last, you set isSecure to false. This is ok for development while working over HTTP. In
production, this should be switched back to true, which is the default setting.

The next property is redirectTo. This will tell the server where to redirect to if an unauthenticated
user tries to access a resource that requires authentication.
The last property is the validateFunc function. The validateFunc validates that a current cookie is still
valid. For example, if a user authenticates themselves successfully, receives a cookie, and then
leaves the site. Once they return, the validateFunc will check if their current cookie is still valid.

You setup the default strategy by calling server.auth.default('session'). This will set the default auth
strategy for all routes.

Once your strategy is set up, you need to set up route that will validate the provided username and
password. In this case, your POST route to '/login' will do just that. First, it will pull the username and
password from request.payload, which the user provided in the form from the '/login' 'GET' route.
Next, you find the user from the database by searching for their username:

const account = users.find(

(user) => user.username === username

);

If the user doesn't not exist, or if the provided password is wrong, you redirect the user back to the
login page. You use Bcrypt to compare the user provided password with the hashed password from
the database.

Lastly, if the user does exist, and the passwords match, the user is then redirected to the homepage.

Now try to merge the authentication with user.

2.5 SERVING STATIC FILES


In Hapi JS, all other tasks like serve the static file, add logger, use graphql or add authentication is
done by registering the plugin with the server for a specific task. Like that, if we want to serve the
static content in Hapi JS server, then we will register the inert plugin with server first. The inert
plugin is used to serve the static page in Hapi server.

1. First we add inert package in the package.json by below npm command:

npm i inert --save

2. After that we require inert in the server.js by require command like below:

const inert = require('inert');

3. After that we need to update the bootUpServer function with below line of code of snippet

await server.register(inert);

4. In the last step, we create a route for rendering the HTML file on browser. For creating HTML
file, we need to create a public folder in the root location of the project and creating HTML
inside the public folder.

            server.route({
                method: 'GET',
                path: '/staticpage',
                handler: (req, h) => {
                    return h.file('./public/static.html')
                }
            })

You might also like