Notes on Solidity in Depth

Layout of a Solidity Source File

Version Pragma

1
pragma solidity ^0.4.11;

Importing other Source File

1
2
3
4
5
6
7
import "filename";

import * as symbolName from "filename";

import { symbol as alias, symbol2 } from "filename";

import "filename" as symbolName;

In the above, filename is always treated as a path with / as directory seperator.

All path names are treated as absolute paths unless thay start with . or ...

Remapping

For example, remap github.com/ethereum/dapp-bin/library to /usr/local/dapp-bin/library

solc

1
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol` as it_mapping;
1
solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol

Comments

Single Line Comments: //

Multi-line Comments: /* ... */

Natspec Comments: /// or /** ... */

Natspec should be used directly above function declarations or statements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.4.11;

/** @title Shape calculator */
contract shapeCaculator {
/** @dev Calculates a rectangle's surface and perimeter.
* @param w Width of the rectangle.
* @param h Height of the rectangle.
* @param s The calculated surface.
* @param p The calculated perimeter.
*/
function rectangle(uint w, uint h) returns (uint s, uint p) {
s = w * h;
p = 2 * (w + h);
}
}

Structure of Contract

Contracts in Solidity are similar to classes in object-oriented languages. Each contract can contain declarations of State Variables, Functions, Function Modifiers, Events, Struct Types and Enum Types.

State Variables

State variables are values which are permanently stored in contract storage.

1
2
3
4
5
pragma solidity ^0.4.11;

contract SimpleStorage {
uint storedData; // state variable
}

Functions

Functions are executable units of code within a contract.

1
2
3
4
5
6
7
pragma solidity ^0.4.11;

contract SimpleAuction {
function bid() payable {
// ...
}
}

Function Modifiers

Function modifiers can be used to amend the semantics of functions in a declarative way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.11;

contract Purchase {
address public seller;

modifier onlySeller () { // Modifier
require(msg.sender == seller);
_;
}

function abort() onlySeller { // Modifier usage
//...
}
}

Events

Events are convenience interfaces with the EVM logging facilities.

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.4.11;

contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount);

function bid() payable {
// ...
HighestBidIncreased(msg.sender, msg.value);
}
}

Struct Types

Structs are custom defined types that can group several variables.

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.4.11;

contract Ballot {
struct Voter {
uint weight;
bool voted;
address delegate;
uint vote;
}
}

Enum Types

1
2
3
4
5
6
7
8
9
pragma solidity ^0.4.0;

contract Purchase {
enum State {
Created,
Locked,
Inactive
}
}

Types

  • bool: true or false

    • ! logical negation

    • && logical and

    • || logical or

    • == logical equality

    • != logical inequality

  • int/uint: uint8 to uint256, int8 to int256

    • <=, >=, ==, !=, <, >

    • &, |, ^, ~, bit operators

    • +, -, *, /, %, **, <<, >>

  • address: holds a 20 bytes value(size of an Ethereum address)

    • <=, =>, ==, !=, <, >

Starting with version 0.5.0 contracts do not derive from the address type, but can still be explicitly converted to address.

Members of Address

  • balance

  • transfer, address.transfer(value), send value Wei to the address.

  • send: Send is the low-level counterpart of transfer. If the execution fails, the current contract will not stop with an exception, but send will return false

There are some dangers in using send: The transfer fails if the call stack depth is 1024 and it also fails if the recipient runs out of gas. So in order to make safe Ether transfers, always check the return value of send, use transfer or even better.

  • call, callcode, and delegatecall

Furthermore, to interface with contracts that do not adhere to the ABI, the function call is provided which takes an arbitrary number of arguments of any type. The arguments are padding to 32 bytes and concatenated. One exception is the case where the first argument is encoded to exactly four bytes. In this case, it is not padding to allow the use of function signatures here.

1
2
3
address nameReg = '0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2';
nameReg.call('register', 'MyName');
nameReg.call(bytes4(keccak256("fun(uint256))), );

call returns a boolean indicating whether the invoked function terminated (true) or caused an EVM exception (false). It is not possible to access the actual data returned.

It is possible to adjust the supplied gas with the .gas() modifier:

1
nameReg.call.gas(1000000000)('register', 'myName');

and supplied Ether value can be controlled too:

1
nameReg.call.value(1 ether)('regiter', 'myName');

Chained

1
nameReg.call.value(1 ether).gas(100000000)('register', 'myName');

In a similar way, the function delegatecall can be used: the difference is that only the code of the given address is used, all other aspects(storage, balance, …) are taken from the current contract. The purpose of delegatecall is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called callcode was available that did not provide access to the original msg.sender and msg.value.

All three functions call, delegatecall, and callcode are very low-level functions and should only be used as a last resort as they break the type-safety of Solidity.

The .gas() option is available on all three methods, while the .value() option is not supported for delegatecall.

All Contracts inherit the members of address, so it is possible to query the balance of the current contract using this.balance.

The use of callcode is discouraged and will be removed in the future.

Fixed-size byte array

  • .length yields the fixed length of the byte array(read-only)

It is possible to use an array of bytes as byte[], but it is wasting a lot of space, 21 bytes every element, to be exact, when passing in calls. It is better to use bytes.

Dynamically-sized byte array

bytes: dynamically-sized array, see Array, not a value-type.

string: dynamically-sized UTF-8-encoded string, see Array, not a value-type.

Address Liberals

Hexadecimal literals that pass the address checksum test, for example 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF are of address type. Hexadecimal literals that are between 39 and 41 digits long and do not pass the checksum test produce a warning and are treated as regular rational number literals

Rational and Integer Literals

Integer literal are formed form a sequence of numbers in the range 0-9. They are interpreted as decimals, for example, 69 means sixty nine. Octal literals do not exist in Solidity and leading zeros are invalid.

Decimal fraction literals are formed by a . with at least one number on one side(1., .2, 1.3)

Scientific notation is also supported, where the base can have fractions, white the exponent cannot(2e10, -2e10, 2e-10, 2.5e10)

Number literal expressions retains arbitrary precision until they are converted to a non-literal type.

For example (2**800 + 1) - 2**800 result in the constant 1, .5 * 8 results in the integer 4

Number literal expressions are converted into a non-literal type as soon as they are used with non-literal expressions. Even though we know that the value of the expression assigned to b in the following example evaluates to an integer, but the partial expression 2.5 + a does not type check so the code does not compile.

1
2
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;

String Literals

String literals are written with either double or sinlge-quotes. They do not imply trailing zeros as in C, "foo" represents three bytes not four.

String Literal supports escape characters, such as \n, \xNN and \uNNNN.

Hexadecimal Literals

hex"001122FF"

Enums

Enums are one way to create a user-defined type in Solidity. They are explicitly convertable to and from all integer types but implicit conversion is not allowed. The explicit conversions check the value ranges at runtime, and a failure causes an exception.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pragma solidity ^0.4.11;

contract test {
enum ActionChoices {
GoLeft,
GoRight,
GoStraight,
GoStill
}

ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;

function setGoStraight() {
choice = ActionChoices.GoStraight;
}

function getChoice() returns (ActionChoices) {
return choice;
}
function getDefaultChoice() returns (uint) {
return uint(defaultChoice);
}
}

Function Types

Function types are notated as follows:

1
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]

In contrast to the parameter types, the return types cannot be empty - if the function type should not return anything, the whole returns (<return type>) part has to be omitted.

Public (or external) functions have a special member called selector, which returns the ABI function selector:

Reference Types

Complex types, i.e. types which do not always fit into 256 bits have to be handled more carefully than than the value-types we have already seen. Since copying them can be quite expensive, we have to think about whether we want them to be store in memory(which is not persisting) or storage(where the state variables are held).

Data location

Every complex type, i.e. arrays and structs has an additional annotation, the “data location” about where it is stored in memory or in storage. Depending on the context, there is always a default, but it can be overrideen by appeding either storage or memory to the type. The default for function parameters(including return parameters) is memory the default for local variable is storage and the location is forced to storage for state variables

There is also a third data location, calldata, which is a non-modificable, non-persistent area where function arguments are stored. Function parameter(not return parameters) of external functions are forced to calldata and behave mostly like memory.

Data locations are imortant because they change how assignment behave: assignments between storage and memory and also to a state variable always create an independent copy. Assignments to local storage variables only assign a reference though, and this reference always points to the state variable even if the latter is changed in the meantime. On the other hand, assignments from a memory stoted reference type to another memory-stored reference type do not create a copy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pragma solidity ^0.4.11;

contract C {
uint[] x; // the data location of x is storage

// the data location of memoryArray is memory
function f(uint[] memoryArray) {
x = memoryArray; // works, copy the whole array to storage
var y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.length = 2; // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// the following does not work; it would need to create a new temporary / unamed array in storage, but storage is 'statically' allocated
// y = memoryArray;
// This does not work either, since it would 'reset' the pointer, but there is not sensible location it could point to.
// delete y;
}
}
Summary
  • Forced data location:

    • parameters(not return) of external functions: calldata

    • state variables: storage

  • Default data location:

    • parameters(also return) of functions: memory

    • all other local variables: storage

Arrays

An array of fixed size k and element type T is written as T[k]

An array of dynamic size as T[]

An array of 5 dynamic arrays of uint is uint[][5]. (note that the notation is reversed when compared to some other language). To access the second uint in the third dynamic array, you use x[2][1]

Variables of type bytes and string are special arrays. A bytes is similar to byte[], but it is packed tightly in calldata. string is equal to bytes but does not allow length or index access(for now).

So bytes should always be preferred over byte[] because it is cheaper.

Structs

Solidity provides a way to define new types in the form of structs, which is shown in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.4.11;

contract CrowdFunding {
struct Funder {
address addr;
uint amount;
}

struct Campaign {
address beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping(uint => Funder) funders;
}
}

It is not possible for a struct to contain a member of its own type, although the struct itself can be the value type of a mapping member. This restriction is necessary, as the size of the struct has to be finite.

Mappings

Mapping types are declared as mapping(_keyType => _valueType). Here _keyType can be almost any type except for a mapping, a dynamically sized array, a contract, an enum, and a struct. _valueType can actually be any type, including mappings.

Mappings can be seen as hash tables which are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is all zeros: a type’s default value.

Mappings are only allowed for state variables( or as storage reference types in internal function).

It is possible to mark mapping public and have Solidity create a getter. The _keyType will become a required parameter for the getter and it will return _valueType.

Mappings are not iterable, but it is possible to implement a data structure on top of them.

Operators Involving LValues

LValue, i.e. a variable or something that can be assigned to.

delete

delete a assigns the initial value for the type to a. I.e., for integers, it is equivalent to a = 0.

delete has no effect on whole mappints. So if you delete a struct, it will reset all members that are not mappings and also recurse into the memebers unless they are mappings. However, individual keys and what they map to can be deleted.

It’s really important to note that delete a really behaves like an assignment to a, it stores a new object in a.

Conversions between Elementary Types.

Implicit Conversions

In general, an implicit conversion between value-types is possible if it makes sense semantically and no information is lost: uint8 is convertible to uint16, but uint16 is not convertible to uint8. And int8 is not convertible to uint256(because uint256 cannot hold hold e.g. -1).

Explicit Conversions

If the compiler does not allow implicit conversion but you know what you are doing, an explicit type conversion is sometimes possible.

1
2
int8 y = -3;
uint x = uint(y);

Type Deduction

For convenience, it is not always necessary to explicitly specify the type of a variable, the compiler automatically infers it from the type of the first expression that is assigned to the variable:

1
2
uint24 x = 0x123;
var y = x;

Units and Globally Available Variables

Ether Units

A literal number can take a suffix of wei, finney, szabo, or ether to convert between the subdenominations of Ether, where Ether currency numbers without a postfix are assumed to be Wei.

2 ether === 2000 finney evaluates to true

Time Units

Suffixes like seconds, minutes, hours, days, weeks and years after literal numbers can be used to convert between units of time where seconds are the base unit.

Take care if you perform calendar calculations using these units, because not every year equals 365 days and not even every day has 24 hours because of leap seconds. Due to the fact that leap seconds cannot be predicted, an exact calendar library has to be updated by an external oracle.

1
2
3
4
5
function f(unit start, unit dayAfter) {
if (now >= start + dayAfter + 1 days) {
// ...
}
}

Other Global Variables and Functions:

Post

Solidity CheatSheet

Global Variables

Block

  • block.coinbase: address - current block miner’s address

  • block.difficulty: uint - current block difficulty

  • block.gaslimit: uint - current block gaslimit

  • block.number: uint - current block number

  • block.timestamp: uint - current block timestamp

Msg

  • msg.data: bytes - complete calldata

  • msg.gas: uint - remaining gas

  • msg.sender: address - sender of the message(current call)

  • msg.value: uint - number of wei sent with the message

Now

  • now: uint - current block timestamp(alias for block.timestamp)

Tx

  • tx.gasprice: uint - gas price of the transaction

  • tx.origin: address - sender of the transaction (full call chain)

Global Functions

  • assert(bool condition): abort execution and revert state changes if condition is false(use for internal error)

  • require(bool condition): abort execution and revert state changes if condition is false(use for malformed input or error in external component)

  • revert(): abort execution and revert state changes

  • block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks

  • sha256(…) returns (bytes32): compute the SHA-256 hash of the (tightly packed) arguments

  • keccak256(…) returns (bytes32): compute the Ethereum-SHA-3(Keccak-256) hash of the (tightly packed) arguments

  • sha3(…) returns (bytes32): alias for keccak256

  • ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address): recover address associated with the public key from elliptic curve signature

  • addmod(uint x, uint y, uint k) returns (uint): compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2 ** 256

  • mulmod(uint x, uint y, uint k) returns (uint): compute (x * y) % k where the addition is performed with arbitrary precision and does not wrap around 2 ** 256

  • selfdestruct(address recipient): destroy the current contract, sending its funds to the given address

  • suicide(address recipient): alias for selfdestruct

  • .balance: uint256: balance of the Address in Wei

  • .send(uint256 amount) returns (bool): send given amount of Wei to Address, returns false on failure

  • .transfer(uint256 amount): send given amount of Wei to Address, throws on failure.

Scope Variables

  • this: current contract’s type - the current contract, explicitly convertable to address

  • super: the contract one level higher in the inheritance hierarchy

Function Visibility Specifier

  • public: visible externally and internally(creates accessor function for storage/state variables)

  • private: only visible in the current contract

  • external: only visible externally(only for functions) - i.e. can only be message-called)

  • internal: only visible internally

Modifiers

  • pure: for function - disallows modification or access of state - this is not enforced yet

  • view: for function - disallows modifiction of state - this is not enforced yet

  • payable: for function - allows them to receive Ether together with a call

  • constant: for state variable - disallow assignment(except initialization), does not occupy storage slot

  • contant: for function - same as view

  • anonymous: for events - does not store event signature as topic

  • indexed: for event parameters - store the parameter as topic

HTTP/2 by Node.js

Original

Websites delivered using HTTP/2 enjoys a wide range of new features including -

  • fully multiplexed connections: all requests and responses to a domain are fully multiplexed via a single connection, making best use of available bandwidth.

  • header compression: repeated headers are compressed with HPACK compression so that they are not resent with every request and response.

  • PUSH: resources can be pre-emptively pushed by the server to the client, speeding up page load times.

Node.js just launched support(v8.8.1) for HTTP/2 as part of their core. In this post, we will create a simple HTTP/2 server to serve static files and then demonstrate some cool features like HTTP/2 PUSH.

Get an SSL certificate

Even though the HTTP/2 spec does not mandate HTTPS, browsers have decided that they will only support HTTP/2 on a HTTPS connection. This would also mitigate interference from older proxies which may not understand newer protocol.

For your local server, a self-signed certificate will work. You can find how to setup a self-signed certificate here

Building a Static File Server

Let us start with a simple server which just serves static files. Note that if you are using node.js 8.7.0, you need to run node with the --expose-http2 flag.

We will be listening to the stream event and responding to it with the corresponding file from the server root(public, in this case) using the respondWithFile API. We are using the mime-type module to look up the correct mime type to send along with the response.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const http2 = require('http2')
const fs = require('fs')
const path = require('path')
const mime = require('mime-types')

cosnt {
HTTP2_HEADER_PATH,
HTTP2_HEADER_METHOD,
HTTP_STATUS_NOT_FOUND,
HTTP_STATUS_INTERNAL_SERVER_ERROR
} = http2.constants

const options = {
key: fs.readFileSync('./selfsigned.key'),
cert: fs.readFileSync('./selfsigned.crt'),
}

const server = http2.createSecureServer(options)

const serverRoot = './public'

function respondToStreamError(err, stream) {
console.log(err)
if (err.code === 'ENOENT') {
stream.respond({
':stats': HTTPS_STATUS_NOT_FOUND,
})
} else {
stream.respond({
':status': HTTP_STATUS_INTERNAL_SERVER_ERROR,
})
}
stream.end()
}

server.on('stream', (stream, headers) => {
const reqPath = headers[HTTP2_HEADER_PATH]
const reqMethod = headers[HTTP2_METHOD]

const fullPath = path.join(serverRoot, reqPath)
const responseMimeType = mime.lookup(fullPath)

stream.respondWithFile(fullPath, {
'content-type': responseMimeType
}, {
onError: err => respondToStreamError(err, stream)
})
})

server.listen(443)

Server PUSH Example

Now we have simpel HTTP/2 server running, lets try to use one of the new featues in HTTP/2 - HTTP/2 PUSH. This can lead to significant performance improvements in high latency environments, if done correctly.

We are loading a simple HTML file pointing to style.css which references to our font file. The request to the font file is only made after css file is discovered in the HTML, downloaded and then parsed. This is how the waterfall would have usually looked like:

You can initate a new PUSH with the pushStream API. Since we know that the browser is going to be requesting the font file in the future, we can PUSH the font file as soon as the server receives the request for the HTML file.

When the actual request for the font file takes place, it is claimed from the PUSH cache, instead of making a network request then.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
const http2 = require('http2')
const fs = require('fs')
const path = require('path')
const mime = require('mime-types')

const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_METHOD,
HTTP_STATUS_NOT_FOUND,
HTTP_STATUS_INTERNAL_SERVER_ERROR,
} = http2.constants

const options = {
key: fs.readFileSync('./selfsigned.key'),
cert: fs.readFileSync('./selfsigned.crt'),
}

const server = http2.createSecureServer(options)

const serverRoot = './public'

function respondToStreamError (err, stream) {
console.log(err)
if (err.code === 'ENOENT') {
stream.respond({
':status': HTTP_STATUS_NOT_FOUND,
})
} else {
stream.respond({
':status': HTTP_STATUS_INTERNAL_SERVER_ERROR,
})
}
stream.end()
}

server.on('stream', (stream, headers) => {
const reqPath = headers[HTTP2_HEADER_PATH]
const reqMethod = headers[HTTP2_HEADER_METHOD]
const fullPath = path.join(serverRoot, reqPath)
const responseMimeType = mime.lookup(fullPath)

if (fullPath.endsWith('.html')) {
console.log('html')
// handle the html file
stream.respondWithFile(fullPath, {
'content-type': 'text/html'
}, {
onError: (err) => {
respondToStreamError(err, stream)
}
})

stream.pushStream({
':path': '/font.woff'
}, {
parent: stream.id,
}, pushStream => {
console.log('pushing')
pushStream.respondWithFile(path.join(serverRoot, '/font.woff'), {
'content-type': 'text/css',
}, {
onError: err => {
respondToStreamError(err, pushStream)
}
})
})
} else {
// handle the static file
console.log(fullPath)
stream.respondWithFile(fullPath, {
'content-type': responseMimeType,
}, {
onError: err => respondToStreamError(err, stream)
})
}
})

server.listen(443)

Rocket of Rust

Introduction

Rocket’s design is centered around three core philosophies:

  • Function declaration and parameter type should contain all necessary information to validate and process a request. This immediately prohibits APIs where request state is retrieved from a global context. As a result, request handling is self-contained in Rocket: handlers are regular functions with regular arguments.

  • All request handling information should be typed. Because the web and HTTP are themselves untyped(or stringly typed, as some call it), this means that something or someone has to convert strings to native types. Rocket does this for you with zero programming overhead.

  • Decisions should not be forced. Templates, serialization, sessions and just about everything else are all pluggable, optional components. While Rocket has official support and libraries for each of these, they are completely optional and swappable.

Getting Started

1
cargo new hello-rocket --bin

Add Dependencies

1
2
3
[dependencies]
rocket = "0.3.3"
rocket_codegen = "0.3.3"
1
2
3
4
5
6
7
8
9
10
11
12
13
#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/")]
fn index() -> &'static str {
"Hello World"
}

fn main () {
rocket::ignite().mount("/", routes![index]).launch();
}

It creates an index route, mount the route at the / path, and launches the application. Compile and run the program with cargo run, you should see the following:

1
2
3
4
5
6
7
8
9
10
11
🔧  Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: [core count * 2]
=> secret key: generated
=> limits: forms = 32KiB
=> tls: disabled
🛰 Mounting '/':
=> GET /
🚀 Rocket has launched from http://localhost:8000

Overview

Rocket provides primitives to build web servers and applications with Rust: the rest is up to you. In short, Rocket provides routing, pre-processing of requests and psot-processing of response. Your application code instructs Rocket what to pre-process and post-process and fills the gaps between pre-processing and post-processing.

LifeCycle

Rocket’s main task is to listen for incoming web prequests, dispatch the request to the application code, and retur a response to the client. We call the process that goes form request to response the ‘lifecycle’. The lifecycle can be summarized as following sequence of steps:

  • Routing

    Rocket parse an incoming HTTP request into native structure that your code operates on indirectly. Rocket determines which request handler to invoke by matching against route attributes declared in your application.

  • Validation

    Rocket validates the incoming request against types and guards present in the matched route. If validation fails, Rocket forwards the request to the next matching route or calls an error handler.

  • Processing

    The request handler associated with the route is invoked with validated arguments. This is the main business logic of an application. Processing completes by returning a Response.

  • Response

    The returned Response is processed. Rocket generates the appropriate HTTP response and sends it to the client. This completes the lifecycle. Rocket continues listening for requesting, restarting the lifecycle for each incoming request.

Routing

Rocket applications are centered around routes and handlers. A route is a combination of:

  • A set of parameters to match an incoming request against.

  • A handler to process the request and return a response.

A handler is simply a function that takes an arbirary number of arguments and returns any arbitrary type.

The parameters to match against include static paths, dynamic paths, path segments, forms, query string, request format specifiers and body data. Rocket uses attributes, which look like function decoration in other languages to make declaring routes easy. Routes are declared by annotating a function, the handler, with a set of parameters to match against. A complete route declaration looks like this:

1
2
3
4
#[get("/world")]
fn world() -> &'static str {
"Hello World"
}

This declare the world route to match against the static path /world on incoming GET requests. The world route is simple, but additional route parameters are necessary when building more interesting application.

Mounting

Before Rocket can dispatch requests to a route, the route needs to be mounted. Mounting a route is like namespacing it. Routes are mounted via the mount method on a Rocket instance. A Rocket instance is typically created with the racket::ignite() static method.

The mount method takes:

  • A path to namespace a list of routes under.

  • A list of route handlers through the routes! macro, typing Rocket’s code generation to your application.

For instance, to mount the world route we declared above, we can write the following:

1
rocket::ignite().mount("/hello", routes![world]);

This create a new Rocket instance via the ignite function and mounts the world route to the /hello path. As a result, GET requests to the /hello/world path will be directed to the world function.

Namespacing

When a route is declared inside a module other than the root, you may find yourself with unexpected errors when mounting:

1
2
3
4
5
6
7
8
9
mod other {
#[get("/world")]
pub fn world() -> String {
"Hello World"
}
}
fn main () {
rocket::ignite().mount("/hello", routes![world]);
}

This occurs because the routes! macro implicitly converts the route’s name into the name of a structure generated by Rocket’s code generation. The solution is to name the route by a module path instead:

1
rocket::ignite().mount("/hello", routes![other::world]);

Launching

Now that Rocket knows about the route, you can tell Rocket to start accepting requests via the launch method. The method starts up the server and wait for incoming requests. When a request arrives, Rocket finds the matching route and dispatches the requests to the route’s handler.

We typically call launch from the main function. Our complete Hello World application thus looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/world")]
fn world() -> String {
"Hello World"
}

fn main () {
rocket::ignite().mount("/hello", routes![world]).launch();
}

Fastify Plugin Guide

In Fastify everything is a plugin, your routes, your utilities and so on are all plugins. And to add a new plugin, whatever its functionality is, in Fastify

Registers

1
fastify.register(require('./my-plugin', opts, callback))

Register creates a new Fastify context, this means that if you do any change to the Fastify instance, that change will not be reflected into the context’s ancestors. In other words, encapsulated!

Required Plugins must expose a single function with the following signature:

1
module.exports = function (instance, opts, next) {}

The Fastify’s plugin module is fully reentrant and graph-based, it handles without any kind of problem asynchronous code and it guarantees the load order of the plugins, even the close order.

Decorators

Fastify offers you a way nice and elegant to wrote an utility: decorator.

1
fastify.decorate('util', (a, b) => a + b)

Now you can access your utility by doing fastify.util.

1
2
3
4
fastify.register((instance, opts, next)=> {
instance.decorate('util', (a, b) => a + b)
next()
})

Hooks

Execute your utility.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fastify.register((instance, opts, next) => {
instance.decorate('util', (req, key, value) => { req.key = value })

instance.addHook('preHandler', (req, reply, done) => {
instance.util(req, 'timestamp', new Date())
done()
})

instance.get('/plugin1', (req, reply) => {
reply.send(req)
})

next()
})

fastify.get('/plugin2', (req, reply) => {
reply.send(req)
})

Time to Start Fastify

Getting Started

Install

1
yarn add fastify

First Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Require the framework and instantiate it
const fastify = require('fastify')()

// Declare a route
fastify.get('/', (req, reply) => {
reply.send({
hello: 'world',
})
})

// Run the server
fastify.listen(3000, (err) => {
if (err) throw err
console.log(`Server is running on ${fastify.server.address().port}`)
})

Schema Serialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const fastify = require('fastify')()

const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}

// Declare a route with an output shcema
fastify.get('/', opts, (req, reply) => {
replysend({ hello: 'world' })
})

fastify.listen(3000, (err) => {
if (err) throw err
console.log(`Server is running on ${fastify.server.address().port}`)
})

Register

Register routes in seperate files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// server.js
const fastify = require('fastify')()

fastify.register(require('./route'))

const opts = {
hello: 'world',
something: true,
}

fastify.register([
require('./another-route'),
require('./yet-another-route'),
], opts, (err) => {
if (err) throw err
})

fastify.listen(3000, (err) => {
if (err) throw err
console.log(`Server is running on ${fastify.server.address().port}`)
})
1
2
3
4
5
6
7
// route.js
module.exports = (fastify, options, next) {
fastify.get('/', (req, reply) => {
replysend({ hello: 'world' })
})
next()
}

or async/await:

1
2
3
4
5
module.exports = async (fastify, options) => {
fastify.get('/', (req, reply) => {
replysend({ hello: 'world' })
})
}

Server Methods

server

fastify.server: The Node Core server object.

ready

Function called when all the plugins has been loaded. It takes an error parameter if something went wrong.

1
fastify.ready(err => { if (err) throw err })

listen

Starts the server on the given port after all the plugins loaded, internally waits for the .ready() event. The callback is the same as the Node Core.

1
2
3
fastify.listen(3000, err => {
if (err) throw err
})

Specifying an address is also supported:

1
2
3
fastify.listen(3000, '127.0.0.1', err => {
if (err) throw err
})

route

Method to add routes to the server, it also have shorthands.

routes iterator

The fastify instance is an Iterable object with all the registered routes. The route properties are the same the developer has declared.

close

fastify.close(callback), call this function to close the server instance and run the onClose hook.

decorate*

Function useful if you need to decorate the fastify instance, Reply or Request.

register

Fastify allows the user to extends its functionalities with plugins. A plugin can be a set of routes, a server decorator or whatever.

use

Function to add middlewares to Fastify.

addHook

Function to add a specific hook in the lifecycle of Fastify.

logger

The logger instance.

Inject

Fake http injection(for testing purpose).

setSchemaCompiler

Set the schema compiler for all routes.

setNotFoundHandler

fastify.setNotFoundHandler(handler(req, reply)): set the 404 handler. This call is fully encapsulated, so different plugins can set different not found handlers.

setErrorHandler

fastify.setErrorHandler(handler(err, reply)): set a function that will be called whenever an error happens. The handler is fully encapsulated, so different plugins can set different error handlers.

Routes

1
fastify.route(options)
  • method: currently it supports DELETE, GET, HEAD, PATCH, POST, PUT, and OPTIONS, it could be an array of methods.

  • url: the path of the url to match this route (alias: path)

  • schema: an object containing the schemas for the request and response. They need to be in JSON Schema format.

    • body: validates the body of the request if it is a POST or PUT.

    • querystring: validates the querystring. This can be a complete JSON Schema Object, with the property type of object and properties object of parameters, or simply the values of what would be contained in the properties object as shown below.

    • params: validates the params.

    • response: fitler and generate a schema for the response, setting a schema allows us to have 10-20% more throughput.

  • beforeHandler(req, res, done): a function called just before the request handler, useful if you need to perform authentication at route level for example, it could also be and array of functions.

  • handler(req, reply): the function that will handle this request.

  • request if defined in Request.

  • reply is defined in Reply.

Exmaple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' },
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
},
},
handler: function (req, reply) {
reply.send({
hello: 'world',
})
}
})

fastify.route({
method: 'GET',
url: '/',
schema: {/*..*/},
beforeHandler: function (req, reply, done) {
// auth
done()
},
handler: function(req, reply) {
reply.send({
hello: 'world',
})
}
})

ShortHands

fastify.method(path, [options], handler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const opts = {
schema: {
response: {
200: {
type: 'object',
properites: {
hello: { type: 'string' }
}
}
}
}
}

fastify.get('/', opts, (req, reply) => {
reply.send({
hello: 'world',
})
})

fastify.all(path, [options], handler) will add the same handler to all the supported methods.

Async/Await

1
2
3
4
5
fastify.get('/', options, async (req, reply) => {
var data = await getData()
var processed = await processData(data)
return processed
})

Route Prefixing

1
2
3
4
5
6
7
8
9
10
11
12
// server.js
const fastify = require('fastify')()

fastify.register(require('./routes/v1/users'), {
prefix: '/v1',
})

// routes/v1/users
module.exports = function (fastify, opts, next) {
fastify.get('/user', handler_v1)
next()
}

Now your client can access to /v1/user.

Be aware that if you use fastify-plugin this option won’t work.

Logging

Logging is disabled by default, and you can enable it by passing { logger: true } or { logger: { level: 'info' } } when you create the fastify instance.

1
2
3
4
5
6
7
8
9
10
const fastify = require('fastify')({
logger: true,
})

fastify.get('/', options, (req, reply) => {
req.log.info('Some info about the current request')
reply.send({
hello: 'world',
})
})

Middlewares

Fastify provides out of the box an asynchronous middleare engine compatible with Express and Restify middlewares.

Fastify middleware don’t support the full syntax middleware(err, req, res, next) because error handling is done inside Fastify.

Also if you are using a middleware taht bundles different, smaller middlewares such as helmet, we recommand to use the single module to get better performances.

1
2
3
4
5
6
7
fastify.use(require('cors')())
fastify.use(require('dns-prefetch-control')())
fastify.use(require('frameguard')())
fastify.use(require('hide-powered-by')())
fastify.use(require('hsts')())
fastify.use(require('ienoopen')())
fastify.use(require('x-xss-protection')())

If you need to run a middleware only under certain path, just pass the path as first parameter to use and you are done.

Note that this does not support routes with parameters, (eg: /users/:id/comments) and wildcard is not supported in multiple paths.

1
2
3
4
5
6
7
8
const serveStatic = require('serve-static')

// single path
fastify.use('/css', servveStatic('/asserts'))
// wildcard path
fastify.use('/css/*', servveStatic('/asserts'))
// multiple paths
fastify.use(['/css', '/js'], servveStatic('/asserts'))

Hook

By using the hook you can interact directly inside the lifecycle of Fastify, there are three different Hooks that you can use(in order of execution):

  • onRequest

  • preHandler

  • onResponse

  • onClose

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fastify.addHook('onRequest', (req, res, next) => {
// some code
next()
})

fastify.addHook('preHandler', (req, res, next) => {
// some code
next()
})

fastify.addHook('onResponse', (res, next) => {
// some code
next()
})

If you want to pass a custom error code to the user, just use the reply.code():

1
2
3
4
5
fastify.addHook('onRequest', (req, res, next) => {
// some code
reply.code(400)
next(new Error('some error'))
})

The error will be handled by Reply

The unique hook that is not inside the lifecycle is 'onClose', this one is triggered when you call fastify.close() to stop the server, and it is useful if you have some plugins that need a ‘shutdown’ part, such as a connection to database.

Only for this hook the parameters of the functions changes.

1
2
3
4
fastify.addHook('onClose', (instance, done) => {
// some code
done()
})

Scope

Except for onClose all the hooks are encapsulated this means that you can decide where your hooks should run by using register.

beforeHandler

beforeHandler is not a standard hook like preHandler, but is a function that your register right in the route option that will be executed only in the specified route.

beforeHandler is executed always after the preHandler hook.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fastify.addHook('preHander', (req, reply, done) => {
// your code
done()
})

fastify.route({
method: 'GET',
url: '/',
schema: {/*...*/},
beforeHandler: (req, reply, done) => {/*...*/ ;done()}
handler: (req, reply) => { reply.send({ hello: 'world' }) }
})

fastify.route({
method: 'GET',
url: '/',
shcema: {/*...*/},
beforeHandler: [
function first (req, reply, done) {
done()
},
function second (req, reply, done) {
done()
}
],
handler: function (req, reply) {
reply.send({ hello: 'world' })
}
})

Decorators

If you need to add functionalities to the Fastify instance, the decorator api is what you need.

This api allows you to add new properties to the Fastify instance, a property value is not restricted to be a function, could also be an object or a string for example.

Usage

decorate

Just call the decorate api and pass the name of the new property and its value.

1
2
3
fastify.decorate('utility', () => {
// something very useful
})

As said above, you can decorate the instance with other values and not only functions:

1
2
3
4
fastify.decorate('conf', {
db: 'some db',
port: 3000,
})

Once you decorate the instance you can access the value every time you need by using the name you passed as parameter:

1
2
fastify.utility()
console.log(fastify.conf.db)

Decorators are not overwritable, if you try to declare a decorator already declared, decorate will throw an exception.

decorateReply

As the name suggest, this api is needed if you want to add new methods to the Reply core object. Just call the decorateReply api and pass the name of the new property and its value.

1
2
3
fastify.decorateReply('utility', function () {
// something very useful
})

Note: using an arrow function will break the binding of this to the Fastify reply instance.

decorateRequest
1
2
3
fastify.decorateRequest('utility', function () {
// something very useful
})

Note: using an arrow function will break the binding of this to the Fastify request instance.

extendServerError

If you need to extend the standard server error, this api is what you need.

You must pass a function that returns an Object, Fastify will extend the server error with the returned object of your function. The function will receive the original error object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fastify.extendServerError(err => {
return {
timestamp: new Date(),
}
})

/*
The resulting object will be: {
error: string,
message: string,
statusCode: number,
timestamp: date,
}
*/

Sync and Async

decorate is synchronous API, if you need to add a decorator that has an asynchronous bootstrap, could happen that Fastify boots up before your decorator is ready. To avoid this issue you must use register api in combination with fastify-plugin.

Dependencies

If your decorator depends on another decorator, you can declare the dependencies of your function, it’s pretty easy, you just need to add an array of strings(representing the names of the decorators your are depending on) as third parameter.

1
fastify.decorate('utility', fn, ['greet', 'log'])

If a dependency is not satisfied, decorate will throw an exception.

hasDecorator

1
fastify.hasDecorator('utility')

Validation and Serialize

Fastify uses a schema based approach and even if it is not mandatory we recommend to use JSON Schema to validate you routes and serialize your output, internally Fastify compiles the schema in a highly performance function.

Validation

The route validation internally uses Ajv, which is highly performant JSON validator. Validate the input is very easy, just add the fields that you need inside the route schema and you are done.

The supported validations are

  • body: Validates the body of the request if it is a POST or PUT

  • querystring: Validates the querystring. This can be a complete JSON Schema object, with the property type of object and properties object of parameters, or simply the values of what would be contained in the properties object as shown below.

  • params: Validates teh route params.

  • headers: Validates teh request headers.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const schema = {
body: {
type: 'object',
properties: {
someKey: { type: 'string'},
someOtherKey: { type: 'number' },
}
},
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' },
},
params: {
type: 'object',
properties: {
par1: { type: 'string' },
par2: { type: 'number' },
},
},
headers: {
type: 'object',
properties: {
'x-foo': { type: 'string' },
},
required: ['x-foo'],
}
}

Schema Compiler

The schemaCompiler is a function that returns a function that validates the body, url parameters, headers and query string.

The default schemaCompiler returns a function that implements the ajv validation interface. fastify use it internally to speed the valiation up.

Serialize

Usually you will send your data to the clients via JSON, and Fastify has a powerful tools to help you: fast-json-stringify, which is used if you have provided an output schema in the route options.

1
2
3
4
5
6
7
8
9
10
11
const schema = {
response: {
200: {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' },
}
}
}
}

As you can see the response schema is based on the status code, if you want to use the same schema for mutliple status code, you can use 2xx.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const schema = {
response: {
'2xx': {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' },
}
},
201: {
type: 'object',
properties: {
value: { type: 'string' }
}
}
}
}

Lifecycle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Incoming Request

└─▶ Instance Logger

└─▶ Routing

404 ◀─┴─▶ onRequest Hook

4**/5** ◀─┴─▶ run Middlewares

4**/5** ◀─┴─▶ Parsing

415 ◀─┴─▶ Validation

400 ◀─┴─▶ preHandler Hook

4**/5** ◀─┴─▶ beforeHandler

4**/5** ◀─┴─▶ User Handler

└─▶ Reply
│ │
│ └─▶ Outgoing Response

└─▶ onResponse Hook

Reply

The second parameter of the handler function is Reply.

Reply is a core Fastify object that exposes the following functions:

  • .code(statusCode)

  • .header(name, value)

  • .type(value): Set the header Content-Type

  • .redirect([code, ] url): default code 302

  • .serializer(function): Set a custom serializer for the payload

  • .send(payload)

  • .sent: A boolean value that you can use if you need to know it send has already been called.

1
2
3
4
5
6
fastify.get('/', options, (request, reply) => {
reply
.code(200)
.type('application/json')
.send({ hello: 'world' })
})

Code

If not set via reply.code, the resulting statusCode will be 200

Sets a custom header to the response.

If you not set a Content-Type header, Fastify assumes that you are using application/json, unless you are send a stream, in that case Fastify recognize it and sets the Content-Type at application/octet-stream.

Redirect

Redirects a request to the specified url, the status code is optional, default to 302.

1
reply.redirect('/home')

Type

Sets the content type for the response.

It’s the shortcut for reply.header('Content-Type', 'application/json')

Serializer

Send

Objects

If you are sending JSON Objects, send will serialize the object with fast-json-stringify if you setted an output schema, otherwise fast-safe-stringify

Promises

Send handle natively the promsies and supports out of the box async-await:

1
2
3
4
5
6
7
8
9
10
11
12
13
fastify.get('/promises', options, (req, reply) => {
const promise = new Prmise(/*...*/)
reply
.code(200)
.send(promise)
})

fastify.get('/async-await', options, async (req, reply) => {
let res = await new Promise(resolve => {
setTimeout(resolve, 200, { hello: 'world' })
})
return res
})

Streams

Send can also handle streams out of box, internally uses pump to avoid leaks of file description. If you are sneding a stream and you have not setted a Content-Type header, send will set it at application/octet-stream.

Errors

If you pass to send an object that is an instance of Error, Fastify will automatically create error structured as the following:

1
2
3
4
5
{
error: string, // the http error message
message: string, // the user error message
statusCode: number, // the http status code
}

Request

The first parameter of the handler function is Request.

Request is a core Fastify object containing the following fields:

  • query

  • body

  • params: the params matching the URL

  • headers

  • req: the incoming HTTP request from Node core

  • log

1
2
3
4
5
6
7
8
fastify.post('/:params', options, (req, reply) => {
console.log(request.body)
console.log(request.query)
console.log(request.params)
console.log(request.headers)
console.log(request.req)
request.log.info('some info')
})

Content Type Parser

Natively Fastify supports only application/json content-type. If you need to support different content types you can use the addContentTypeParser api.

Catch All

1
2
3
4
5
fastify.addContentTypeParser('*', function (req, done) {
var data = ''
req.on('data', chunk => {data += chunk})
req.on('end', () => done(data))
})

Plugins

Fastify allows the user to extend its functionalities with plugins. A plugin can be a set of routes, a server decorator or whatever.

The API you will need for one or more plugins is register.

By default, register creates a new scope, this means that if you do some changes to the Fastify instance(via decorate), this change will not be reflected to the current context ancestors, but only to its sons.

1
2
3
4
5
6
fastify.register(plugin, [options])

fastify.register([
require('./another-route'),
require('./yet-another-route'),
], opts)

Craete a Plugin

Create a plugin is very easy, you just need to create a function that takes three paremters, the fastify instance, an options object and the next callback.

1
2
3
4
5
module.exports = (fastify, opts, next) => {
fastify.decorate('utility', () => {})
fastify.get('/', handler)
next()
}

Handle the Scope

You have two ways to tell Fastify to avoid the creation of a new context:

  • Use the fastify-plugin module

  • Use the skip-override hidden property

We recomended to use the fastify-plugin module, because it solves this problem for you, and you can pass as parameter a version range of Fastify taht your plguin support.

1
2
3
4
5
6
const fp = require('fastify-plguin')

module.exports = fp(function(fastify, opts, next) {
fastify.decorate('utility', () => {})
next()
}, '0.x')

Apollo With TypeScript

Original

Start with a simple React Component wrapped with the graphql higher order component from React Apollo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React from 'react'
import gql from 'graphql-tag'
import { graphql } from 'react-apollo'

export const HERO_QUERY = gql`
query GetCharacter($episode: Episodi!) {
hero(episode: $episode) {
name
id
friends {
name
id
appearsIn
}
}
}
`

export const withCharacter = graphql(HERO_QUER, {
options: () => ({
variables: { episode: 'JEDI' }
})
})

export const withCharacter(({ data: { loading, hero, error }}) => {
if (loading) {
return <div>Loading</div>
}
if (error) {
return <div>Error</div>
}
return (
<div>
{hero &&
<div>
<h3>{hero.name}</h3>
{hero.friends.map(friend => <h6 key={friend.i}>{friend.name}</h6>}></h6>)}
</div>
}
</div>
)
})

The above code pulls some data from a GraphQL API using a query and includes lifecycle information, such as loading and error information.

With a few minor changes, we can tell TypeScript how to support us in writing code within this render function.

  • We need to tell TS what the shape of our data from our graphql server will look like. We manually write the types for our response data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export type Hero {
name: string
id: string
appearsIn: string[]
friends: Hero[]
}

export type Response {
hero: Hero
}

export const withCharacter = graphql<Response>(HERO_QUERY, {
options: ({episode}) => ({
variables: { episode }
})
})
  • The last line is where the magic happens. We tell TS what the shape of the result will look like from the server when the graphql enhancer wraps a component.

If you already had your project set up with TS, and have already typed your response data, all you have to do is add the type to the graphql HOC and you are off!

Take Control of Your Tree

Wrapped components are almost always exported and used by a component somewhere else in your tree, so if your exported component has prop requirements, we need to tell TS so it can help prevent errors elsewhere in our tree. Since the graphql wrapper suppors polymorphic types, we can use the second type parameter of it to do just that.

1
2
3
4
5
6
7
8
9
export type InputProps = {
episode: string
}

export const withCharacter = graphql<Response, InputProps>(HERO_QUERY, {
options: ({ episode }) => ({
variables: { episode },
})
})

Then the code looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'
import ApolloClient, { createNetworkInterface } from 'apollo-client'
import { ApolloProvider } from 'react-apollo'

import Character from './Character'

export const networkInterface = createNetworkInterface({
uri: 'https://mpjk0plp9.lp.gql,zone/graphql',
})
export const client = new ApolloClient({
networkInterface,
})
export default () => (
<ApolloProvider client={client}>
<Character />
</ApolloProvider>
)

Source Code of Koa-Static

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
'use strict'

/**
* Module Dependencies
*/

const debug = require('debug')('koa-static')
const { resolve } = require('path')
const assert = require('assert')
const send = require('koa-send')

/**
* Expose `serve()`
*/

module.exports = serve

/**
* Serve static files from `root`
*
* @param {String} root
* @param {Object} [opts]
* @return {Function}
* @api public
*/

function serve (root, opts) {
opts = opts || {}

// ensure `root`
assert(root, 'root directory is required to serve files')

// options
debug('static %s %j', root, opts)
// set opts.root to absolute one
opts.root = resolve(root)

// set default static file
if (opts.index !== false) opts.index = opts.index || 'index.html'

// if defer is supported
if (!opts.defer) {
return async function serve (ctx, next) {
let done = false

if (ctx.method === 'HEAD' || ctx.method === 'GET') {
try {
done = await send(ctx, ctx.path, opts)
} catch (err) {
if (err.status !== 404) {
throw err
}
}
}

if (!done) {
await next()
}
}
}

retrun async function serve (ctx, next) {
await next()
if(ctx.method !== 'HEAD' && ctx.method !== 'GET') return
// response is already handled
if (ctx.body != null || ctx.status !== 404) return

try {
await send(ctx, ctx.path, opts)
} catch (err) {
if (err.status !== 404) {
throw err
}
}
}
}

Usage

1
2
3
const Koa = require('koa')
const app = new Koa()
app.use(require('koa-static')(root, opts))

Options

  • maxage: default to 0.

  • hidden: Allow transfer of hidden files, detault to false.

  • index: Default file name, detault to ‘index.html’.

  • defer: If true, serves after return next().

  • gzip: default to true.

  • extensions: default to false.

React-Hot-Loader in Webpack With Ts-Loader

It’s very easy to integrate react-hot-loader into webpack with ts-loader.

Config the webpack

Add Hot Module Replacement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// webpack.entry
app: [
'webpack/hot/only-dev-server',
'webpack-dev-server/client?http://localhost:8080',
path.resolve(__dirname, './src'),
],

plugins: [
new webpack.HotModuleReplacementPlugin()
],

devServer: {
hot: true,
}

Add React-Hot-Loader into webpack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// add react-hot-loader to entry point

entry: [
'webpack/hot/only-dev-server',
'webpack-dev-server/client?http://localhost:8080',
'react-hot-loader/patch',
path.resolve(__dirname, './src/'),
],
// add loader
module: {
rules: [
{
test: /\.tsx$/,
use: ['react-hot-loader/webpack/', 'ts-loader'],
exclude: /node_modules/,
include: path.resolve(__dirname, './src'),
}
]
}

Wrap Root Container into module.hot.accept

1
2
3
4
5
6
if (module.hot) {
module.hot.accept('./router', () => {
const Root = require('./router').default
render(<Root />, htmlDOM)
})
}

Note, if you are using ts with babel-preset-es2015, you can set babel to ignore es2015 module transform by

1
2
3
{
"presets": [["es2015", {"modules": false}]]
}

because webpack 3 has built-in support for ES2015 modules. You won’t need to re-require you root module.

1
2
3
4
5
if (module.hot) {
module.hot.accept('./router', () => {
render(<Root />, htmlDOM)
})
}

Same treatment in ts if your target is es6

Methods of Observable

Original
Playground

Observable

Direct Subclass
  • ConnectableObservable

  • GroupedObservable

  • Subject

Indirect Subclass
  • AnonymousSubject

  • AsyncSubject

  • BehaviorSubject

  • ReplaySubject

Static Public Methods

  • bindCallback

public static bindCallback(func: function, selector: function, schedular: Schedular): function(...params: *): Observable

Convert a callback API to a function that returns an Observable

Give it a function f of type f(x, callback) and it will return a function g taht when called as g(x) will output an Observable.

bindCallback is not an operator because its input and output are not Observables.

The input is a function with some parameters, but the last parameter must be a callback function.

The output of the bindCallback is a function that takes the same parameters as original function takes, except the last one(the callback). When the output function is called with arguments, it will return an Observable.

If the original function’s callback takes one argument, the Observable will emit that value. If on the other hand callback is called with multiple values, resulting Observable will emit an array with these argumetns.

More in Origin Article
  • bindNodeCallback

public static bindNodeCallback(func: function, scheduler: Schedular): function(...param: *): Observable

  • combineLatest

public static combineLatest(observable1: ObservableInput, observable2: ObservableInput, project: function, schedular: Schedular): Observable

Combines multiple Observables to create an Observable whose values are calculated from the latest values of each of its input Observables.

Whenever any input Observable emits a value, it computes a formula using the latest values from all the inputs, then emits the output of the formula.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// combine two timer Observable
const firstTimer = Rx.Observable.timer(0, 1000)
const secondTimer = Rx.Observable.timer(500, 1000)
const combineTimers = Rx.Observable.combineLatest(firstTimer, secondTimer)
combineTimers.subscribe(console.log)
// logs
// [0, 0] after 0.5s, triggered by secondTimer
// [1, 0] after 1s, triggered by firstTimer
// [1, 1.5] after 1.5s, triggered by secondTimer


// combine an array of observable
const observables = [1, 2, 4].map(n => Rx.Observable.of(n).delay(n * 1000).startWith(0))
const combined = Rx.Observable.combineLatest(observables)
combined.subscribe(console.log)
// logs
// [0, 0, 0] immediately
// [1, 0, 0] after 1s triggered by observables[0]
// [1, 2, 0] after 2s triggered by observables[1]
// [1, 2, 4] after 4s triggered by observables[2]

// use project function to dynamically calculate the body-mass index
const weight = Rx.Observable.of(70, 72, 76, 79, 75)
const height = Rx.Observable.of(1.76, 1.77, 1.78)
const bmi = Rx.Observable.combineLatest(weight, height, (w, h) => w/(h*h))
bmi.subscribe(console.log)
// With output to console:
// BMI is 24.212293388429753
// BMI is 23.93948099205209
// BMI is 23.671253629592222
  • concat

public static concat(input1: ObservableInput, input2: ObservableInput, scheduler: Schedular): Observable

Creates an output Observable which sequentially emits all values from given Observable and then moves on to the next.

1
2
3
4
5
6
7
8
9
10
// Concatenate a timer counting form 0 to 3 with a synchronous sequence from 1 to 10
var timer = Rx.Observable.interval(1000).take(4)
var sequence = Rx.Observable.range(1, 10)
var result = Rx.Observable.concat(timer, sequence)

// concatenate an array of 3 Observables, different from the one above
var timer1 = Rx.Observable.interval(1000).take(10)
var timer2 = Rx.Observable.interval(1000).take(6)
var timer3 = Rx.Observable.interval(1000).take(10)
var result = Rx.Observable.concat([timer1, timer2, timer3])
  • create

public static create(onSubscription: function(observer: Observer): TearDownLogic): Observable

Creates a new Observable, that will execute the specified function when an Observer subscribes to it.

1
2
3
4
5
var observable = Rx.Observable.create(function(observer) {
observer.next(1)
observer.next(2)
observer.next(3)
})
  • defer

public static defer(observableFactory: function(): SubscribableOrPromise): Observable

Creates an Observable that, on subscribe, calls an Observable factory to make an Observable for each new Observer

Creates the Observable lazily, that is, only when it is subscribed.

1
2
3
4
5
6
7
8
// when defer is called, it returns an Observable
var clicksOrInterval = Rx.Observable.defer(function(){
if (Math.random() > 0.5) {
return Rx.Observable.fromEvent(document, 'click')
} else {
return Rx.Observable.interval(1000)
}
})
  • empty

public static empty(scheduler: Scheduler): Observable

Creates an Observable that emits no items to the Observer and immediately emits a completion notification.

Just emits ‘complete’ and nothing else

1
var result = Rx.Observable.empty().startWith(8)
  • from

public static from(ish: ObservableInput<T>, scheduler: Scheduler): Observable<T>

Creates an Observable from an Array, an array-like Object, a Promise, an iterable object, or an Observable-like Object

Convert almost anything to an Observable

  • fromEvent

public static fromEvent(target: EventTargetLike, eventName: string, options: EventListenerOptions, selector: SelectorMethodSignature<T>): Observable<T>

Creates an Observable from DOM events, or Node EventEmitter events or others

1
var clicks = Rx.Observable.fromEvent(document, 'click')
  • fromEventPattern

public static fromEventPattern(addHandler: function(handler: Function): any, removeHandler: function(handler: Function, signal?: any): void, selector: function(...args: any): T): Observable<T>

Converts any addHandler/removeHandler API to an Observable

1
2
3
4
5
6
7
8
9
10
11
12
function addClickHandler(handler) {
document.addEventListener('click', handler)
}

function removeClick(handler) {
document.removeEventListener('click', handler)
}

var clicks = Rx.Observable.fromEventPattern(
addClickHandler,
removeClickHandler,
)
  • fromPromise

public static fromPromise(promise: Promise<T>, scheduler: Scheduler): Observable<T>

Returns an Observable that just emit the Promise’s resolved value, then complete

1
var result = Rx.Observable.fromPromise(fetch('reqres.in/api/users'))
  • interval

public static interval(period: number, scheduler: Scheduler): Observable

Emits incremental numbers periodically in time

  • merge

public static merge(observables: ...ObservableInput, concurrent: number, scheduler: Scheduler): Observable

Creates an output Observable which concurrently emits all values from every given input Observable

Flatten multiple Observables together by blending their values into one Observable

1
2
3
var clicks = Rx.Observable.fromEvent(document, 'click')
var timer = Rx.Observable.interval(1000)
var clicksOrTimer = Rx.Observable.merge(clicks, timer)
  • never

public static never(): Observable

Creates an Observable that emits no items to the Observer

An Observable that never emits anything

1
2
Rx.Observable.never().startWith(7).subscribe(console.log)
// won't console 7
  • of

public static of(values: ...T, scheduler: Scheduler): Observable<T>

Creates an Observbale that emits some values you specify as arguments, immediately one after the other, and then emits a complete notification.

Emits the arguments you provide, then complete.

  • range

public static range(start: number, count: number, scheduler: Scheduler): Observable

Creates an Observable that emits a sequence of numbers within a specified range.

Emits a sequence of numbers in a range

  • throw

public static throw(error: any, scheduler: Scheduler): Observable

Just emit ‘error’ and nothing else.

  • timer

public static timer(initialDelay: number|Date, period: number, scheduler: Scheduler): Observable

Creates an Observable that starts emitting after an initialDelay and emits ever increasing numbers after each period of time thereafter.

It’s like interval, but you can specify when should the emissions start.

  • webSocket

public static webSocket(urlConfigOrSource: string | WebSocketSubjectConfig): Observable

1
let subject = Rx.Observable.webSocket('ws://localhost:8001')
  • zip

public static zip(observables: *): Observable<R>

Combines multiple Observables to create an Observable whose values are calculated from the valeus, in order, of each of its input Observables.

1
2
3
4
5
6
7
8
9
let age$ = Rx.Observable.of(27, 25, 29)
let name$ = Rx.Observable.of('Foo', 'Bar', 'Beer')
let isDev$ = Rx.Observable.of(true, true, false)

Rx.Observable.zip(age$, name$, isDev$, (age, name, isDev) => ({age, name, isDev}))
// outputs
// { age: 27, name: 'Foo', isDev: true }
// { age: 25, name: 'Bar', isDev: true }
// { age: 29, name: 'Beer', isDev: false }
  • constructor

public constructor (subsribe: Function)

  • audit

public audit(durationSelector: function(value: T): SubscribableOrPromise): Observable<T>

Ignores source value for a duration determined by another Observable, then emis the most recent value from the source Observable, then repeats this process.

It’s like auditTime, but the silencing duration is determined by a second Observable.

audit is similar to throttle, but emits the last value from the silenced time window.

1
2
var clicks = Rx.Observable.fromEvent(document, 'click')
var result = clicks.audit(ev => Rx.Observable.interval(1000))
  • auditTime

public auditTime(duration: number, scheduler: Scheduler): Observable<T>

Ignores source values for duration milliseconds, then emits the most recent value from the source Observable, then repeats this process.

Similar to throttleTime

  • buffer

public buffer(closingNotifier: Observable<any>): Observable<T>

Buffers the source Observable values until closingNotifier emits.

Collect values from the past as an array, and emits that array when another Observable emits.

  • bufferCount

public bufferCount(bufferSize: number, startBufferEvery: number): Observable<T[]>

1
2
var interval = Rx.Observable.interval(1000)
interval.bufferCount(3, 2)
  • bufferTime

public bufferTime(bufferTimeSpan: number, bufferCreationIntervel: number, maxBufferSize: number, scheduler: Scheduler): Observable<T[]>

Buffers the source Observable values for a specific time period.

Collect values from the past as an array, and emits those array periodically in time.

  • bufferToggle

public bufferToggle(openings: SubscribableOrPromise<O>, closingSelector: function(value: O): SubscribableOrPromise): Observable<T[]>

  • bufferWhen

public bufferWhen(closingSelector: function(): Obversable): Obversable<T[]>

Buffers the source Observable values, using a factory function of closing Observables to determine when to close, emit and reset the buffer.

  • catch

public catch(selector: function): Obversable

Catches errors on the observable to be handled by returning a new obversable or throwing an error.

1
2
3
4
5
Rx.Observable.of(1, 2, 3, 4, 5)
.map(n => {
if (n === 4) throw 'four'
return n
}).catch(err => Rx.Observable.of('I' , 'II', 'III', 'IV', 'V'))
  • combineAll

public combineAll(project: function): Observable

Convert a higher-order Observable into a first-order Observable by waiting for the outer Observable to complete, then applying combineLatest

Similar to CombineLatest

Flatten an Observable-of-Observables by applying combineLatest when the Observable-of-Observable completes

1
2
3
4
5
var clicks = Rx.Observable.fromEvent(document, 'click');
var higherOrder = clicks.map(ev =>
Rx.Observable.interval(Math.random()*2000).take(13)
).take(3);
var result = higherOrder.combineAll();
  • combineLatest

public combineLatest(other: ObservableInput, project: function): Observable

Combines multiple Observables to create an Observable whose values are calculated from the latest values of each of its input Observable.

  • concat

public concat(other: ObservableInput, scheduler: Scheduler): Observable

  • concatAll

public concatAll(): Observable

Converts a higher-order Observable into a first-order Observable by concatenating the inner Observable in order.

Flatten an Observable-of-Observable by putting one inner Observable after the other

  • concatMap

public concatMap(project: function(value: T, ?index: number): ObservableInput, resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any): Observable

Projects each source value to an Observable which is merged in the output Observable, in a serialized fashion waiting for each one to complete before merging the next.

Map => Concat

Map each value to an Observable, then flatten all of these inner Observables using concatAll

1
2
3
4
var clicks = Rx.Observable.fromEvent(document, 'click')
var result = clicks.concatMap(ev => {
return Rx.Observable.interval(1000).take(4)
})
  • concatMaoTo

public concatMapTo(innerObservable: ObservableInput, resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any): Observer

It’s like concatMap, but maps each value always to the same inner Observable

  • count

public count(predicate: function(value: T, i: number, source: Observable<T>): boolean): Observable

Counts the number of emissions on the source and emits the number when the source completes.

1
2
3
4
5
6
7
8
9
// count how many seconds have passed before the first click
var seconds = Rx.Observable.interval(1000)
var clicks = Rx.Observable.fromEvent(document, 'click')
var secondsBeforeClick = seconds.takeUntil(clicks)
secondsBeforeClick.count()

// count how many odd numbers are there between 1 and 7
var numbers = Rx.Observable.range(1, 7)
numbers.count(i => i % 2)
  • debounce

public debounce(durationSelector: function(value: T): SubscribableOrPromise): Observable

Emits a value from the source Observable only after a particular time span determined by another Observable has passes without another source emission.

It’s like debounceTime, but the time span of emission silence is determined by a second Observable.

1
2
var clicks = Rx.Observable.fromEvent(document, 'click')
var result = clicks.debounce(() => Rx.Observable.interval(1000))
  • debounceTime

public debounceTime(dueTime: number, scheduler: Scheduler): Observable

Emits a value from the source Observable only after a particular time span has passed without another source emission.

It’s like delay, but passes only the most recent value from each burst of emissions.

  • defaultEmpty

public defaultIfEmpty(defaultValue: any): Observable

Emits a given value if the source Observable completes without emitting any next value, otherwise mirrors the source Observable.

If the source Observable turns out to be empty, then this operator will emit a default value.

1
2
3
var clicks = Rx.Observable.fromEvent(document, 'click')
var clicksBeforeFive = clicks.takeUntil(Rx.Observable.interval(5000))
clicksBeforeFive.defaultIfEmpty('no click')
  • delay

public delay(delay: number|Date, scheduler: Scheduler): Observable

Delays the emission of items from the source Observable by a given timeout or until a given Date.

  • delayWhen

public delayWhen(delayDurationSelector: function(value: T): Observable): Observable

It’s like delay, but the time span of the delay duration is determined by a second Observable.

  • dematerialize

public dematerialize(): Observable

Convert an Observable of Notification objects into the emissions that they represent.

Unwraps Notification objects as actual next, error, and complete emissions. The opposite of materialize.

dematerialize is assumed to operate an Observable that only emits Notification objects as next emission, and does not emit any error. Such Observable is the output of a materialize operation. Those notifications are then unwrapped using the metadata they contain, and emitted as next, errorand complete on the output Observable.

1
2
3
4
5
var notiA = new Rx.Notification('N', 'A')
var notiB = new Rx.Notification('N', 'B')
var notiE = new Rx.Notification('E', void 0, new TypeError('x.toUpperCase is not a function'))
var materialized = Rx.Observable.of(notiA, notiB, notiE)
var upperCase = materialized.dematerialize()
  • distinct

public distinct(keySelector: function, flushes: Observable): Observable

Returns an Observable that emits all items emitted by the source Observable that are distinct by comparison from previous items.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Rx.Observable.of(1,1,2,2,1,1,3,4,5,67,6,6,).distinct()
// 1,2,3,4,5,67,6
// repeated 1 omitted

// keySelector
interface Person {
age: number
name: string
}

Rx.Observable.of<Person>(
{ age: 4, name: 'Foo' },
{ age: 7, name: 'Bar' },
{ age: 5, name: 'Foo' },
).distinct((p: Person) => p.name)

Note: ‘1’ is different from 1

  • distinctUntilChanged

public distinctUntilChanged(compare: function): Observable

Returns an Observable that emits all items emitted by the source Observable that are distinct by comparison from the previous item. (not items)

  • distinctUntilKeyChanged

public distinctUntilKeyChanged(key: string, compare: function): Observable

1
2
3
4
5
6
7
8
9
10
interface Person {
age: number
name: string
}

Rx.Observable.of<Person>(
{ age: 4, name: 'Foo' },
{ age: 5, name: 'Bar' },
{ age: 3, naem: 'For' },
).distinctUntilKeyChanged('name', (x: string, y: string) => x.substring(0, 2) === y.substring(0, 2))
  • do

public do(nextOrObserver: Observer|function, error: function, complete: function): Observable

Perform a side effect for every emission on the source Observable, but return an Observable that is identical to the source.

Intercepts each emission on the source and runs a function, but returns an output which is identical to the source.

  • elemetnAt

public elementAt(index: number, defaultValue: T): Observable

Emits the single value at the specified index in a sequence of emissions from the source Observable.

Emits only the i-th value , then completes.

1
2
var clicks = Rx.Observable.fromEvent(document, 'click')
clicks.elementAt(2)
  • every

public every(predicate: function, thisArg: any): Observable

Returns an Observable that emits whether or not every item of the source satisfies the condition specified.

1
2
3
4
// all elements are less than 5, otherwise false
Observable.of(1, 2, 3, 4, 5, 6)
.every(x => x < 5)
.subscribe(x => console.log(x)); // -> false
  • exhaust

public exhaust(): Observable

Convert a higher-order Observable into a first-order Observable by dropping inner Observables while the previoud inner Observable has not yet completed.

Flatten an Observable-of-Observables by dropping the next inner Observable while the current inner is still executing.

exhaust ingores every new inner Observable if the previous Observable has not yet completed.

1
2
3
var clicks = Rx.Observable.fromEvent(document, 'click')
var higherOrder = clicks.map(ev => Rx.Observable.interval(1000).take(3))
higherOrder.exhaust()
  • exhaustMap

public exhaustMap(project: function(value: T, ?index: number): ObservableInput, resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any): Observable

Projects each source value to an Observable which is merged in the output Observable only if the previous projected Observable has completed.

  • expand

public expand(project: function(value: T, index: number), concurrent: number, scheduler: Scheduler): Observable

Recursively projects each source value to an Observable which is merged in the output Observable.

It’s similar to mergeMap, but applies the projection function to every source value as well as every output value. It’s recursive.

1
2
var clicks = Rx.Observable.fromEvent(document, 'click')
clicks.mapTo(1).expand(x => Rx.Observable.of(2*x).delay(1000)).take(10)
  • filter

public filter(predicate: function(value T, index: number): boolean, thisArg: any): Observable

Filter items emitted by the source Observable by only emitting those that satisfy a specified predicate.

1
2
var clicks = Rx.Observable.fromEvent(document, 'click')
clicks.filter(ev => Math.random() > 0.4)
  • find

public find(predicate: function(value: T, index: number, source: Observable<T>): boolean, thisArg: any): Observable<T>

Emits only the first value emitted by the source Observable that meets some condition.

  • findIndex

public findIndex(predicate: function(value: T, index: number, source: Observable<T>): boolean, thisArg: any): Observable<T>

It’s like find, but emits the index of the found value, not the value itself.

  • first

public first(predicate: function(value: T, index: number, source: Obsevable<T>): boolean, resultSelector: function(value: T, index: number): R, defaultValue: R): Observable<T|R>

Emits only the first value (or the fisrt value that meets some condition) emitted by the source Obsevable

  • forEach

public forEach(next: Function, PromiseCtor: PromiseConstructor): Promise

  • groupBy

public groupBy(keySelector: function(value: T): K, elementSelector: functioN(value: T): R, durationSelector: function(grouped: GroupedObservable<K|R>): Observable<any>): Observable<GroupedObservable<K, R>>

  • ignoreElements

public ignoreElements(): Observable

Iggnores all items emitted by the source Observable and only pass calls of complete or error

  • isEmpty()

public isEmpty(): Observable

  • last

public last(predicate: function): Observable

  • letProto

public letProto(func: *): Observable<T>

  • lift

public lift(operator: Operator): Observable

Creates a new Observable, with this Observable as the source, and the passed operator defined as the new observable’s operator.

Return a new Observable with the Operator applied.

  • map

public map(project: function(value: T, index: number): R, thisArg: any): Observable

Applies a given project function to each value emitted by the source Observable, and emits the resulting values as an Observable.

1
2
var clicks = Rx.Observable.fromEvent(document, 'click')
clicks.map(ev => ev.clientX)
  • mapTo

public mapTo(value: any): Observbale

Emits the given constant value on the output Observable every time the source Observable emits a value.

  • materialize

public materialize(): Observable<Notification<T>>

Represents all of the notifications from the source Observable as next emission marked with their orignal types within Notification objects.

  • max

public max(comparer: Function): Observable

1
2
3
4
5
6
7
8
9
10
11
12
13
Rx.Observable.of(5, 3, 9, 8).max()

interface Person {
name: string
age: number
}

Rx.Observable.of<Person>(
{ age: 7, name: 'Foo' },
{ age: 8, name: 'Bar' },
{ age: 0, name: 'For' },
)
.max<Person>((a, b) => a.age - b.age)
  • merge

public merge(other: ObservableInpt, concurrent: number, scheduler: Scheduer): Observable

Creates an output Observable which concurrently emits all values from every given input Observable.

Flatten multiple Observable together by blending their values into one Observable.

  • mergeAll

public mergeAll(concurrent: number): Obsevable

Converts a higher-order Observable into a first-order Observable which concurrently delivers all values that are emitted on the inner Observables.

  • mergeMap

public mergeMap(project: function(value: T, ?index: number): ObservableInput, resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any, concurrent: number): Observable

Projects each source value to an Observable which is merged in the output Observable.

Map each value to an Observable, then flattn all of these inner Observable using mergeAll

1
2
var letters = Rx.Observable.of('a', 'b', 'c')
letters.mergeMap(letter => Rx.Observable.interval(1000).map(i => i+letter))
  • mergeMapTo

public mergeMapTo(innerObservable: ObservableInput, resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any, concurrent: number): Observable

Projects each source value to the same Observable which is merged multiple times in the output Observable.

  • mergeScan

public mergeScan(accumulator: function(acc: R, value: T): Observable<R>, seed: *, concurrent: number): Observable<R>

Applies an accumulator function over the source Observable where the accumulator function itself retunrs an Observable, then each intermediate Observable is merged into the output Obsevable.

1
2
3
4
const click$ = Rx.Observable.fromEvent(document, 'click')
const one$ = click$.mapTo(1)
const seed = 0
const count$ = one$.mergeScan((acc, one) => Rx.Observable.of(acc + one), seed)
  • min

public min(comparer: Function): Observable<R>

  • multicast

public multicast(subjectOrSubjectFactory: Function | Subject, selector: Function): Observable

Returns an Observable tghat emits the results of invoking a specified selector on items emitted by a ConnectableObservable that shares a single subscription to the underlying stream.

  • observeOn

public observeOn(scheduler: *, delay: *): Observable<R>

  • pairwise

public pairwise(): Observable<Array<T>>

Groups pairs of consecutive emissions together and emits them as an array of two values.

Puts the current value and previous value together as an array, and emits that.

  • partition

`public partition(predicate: function(value: T, index: number): Boolean, thisArg: this): [Observable, Observable]

Splits the source Observable into two, one with values that satisfy a predicate, and another values that don’t satisfy the predicate.

It’s like filter, but returns two Observable, one like the output of filter, and the other with values that did not pass the condition.

  • pluck

public pluck(properties: ...string): Observable

Maps each source value (an object) to its specified nested property

Like map, but meant only for picking on eof the nested properties of every emitted object.

1
2
const clicks$ = Rx.Observable.fromEvent(document, 'click')
clicks$.pluck('target', 'tagName')
  • publish

public publish(selector: Function): *

Returns a ConnectableObservable, which is a variety of Observable that waits until connect method is called before it begins emitting items to those Observers that have subscribed to it.

  • publishBehavior

  • publishLast

  • publishReplay

  • race

public race(...observables): Observable

Returns an Observable that mirrors the first source Observable to emit an item from the combination of this Observable and supplied Observables.

  • reduce

public reduce(accumulator: function(acc: R, value: T, index: number): R, seed: R): Observable<R>

  • repeat

public repeat(count: number): Observable

  • repeatWhen

public repeatWhen(notifier: functioN(notification: Observable): Observable): Observable

1

  • retry

public retry(count: number): Observable

Returns an Observable that mirrors the source Observable with the exception of an error, if the source Observable calls error, this method will resubscribe to the source Observable for a maximum of count resubscriptions rather than propagating the error call.

  • retryWhen

public retryWhen(notifier: function(errors: Observable): Observable): Observable

  • sample

public sample(notifier: Observable<any>): Observable<T>

Emits the most recently emitted value from the source Observable whenever another Observable, the notifier emits.

It’s like sampleTime, but sampels whenever the notifier Observable emits.

  • sampleTime

public sampleTime(period: number, scheduler: Scheduler): Observable<T>

  • scan

public scan(accumulate: function(acc: R, value: T, index: number): R, seed: T|R): Observable<R>

Applies an accumulator function over the source Observable, and returns each intermediate result, with an optional seed value.

It’s like reduce, but emits the current accumulation whenever the source emits a value

  • sequenceEqual(compareTo: Observbale, comparor: function): Observable`

Compares all values of two observbales in sequence using an optional comparor function and returns an observable of a single boolean value representing whether or not the two sequences are equal.

Checks to see of all values emitted by both observables are equal in order.

  • share

public share(): Observable<T>

Returns a new Observable that multicasts (shares) the original Observable. As long as there is at least one Subscriber this Observable will be subscribed and emitting data. When all subscribers have unsubscribed it will unsubscribe from the source Observable. Because the Observable is multicasting it makes the stream hot. This is an alias for .publish().refCount().

  • single

public single(predicate: Function): Observable<T>

  • skip

public skip(count: number): Observable

  • skipUntil

public skipUntil(notifier: Observable): Observable<T>

Returns an Observable that skips items emitted by the source Observable until a second Observable emits an item.

  • skipWhile

public skipWhile(predicate: Function): Observable

Returns an Observable that skips all items emitted by the source Observable as long as a specified condition holds true, but emits all further source items as soon as the condition becomes false.

  • startWith

public startWith(values: ...T, scheduler: Scheduler): Observable

Returns an Observable that emits the items you specified before it begins to emit items by the source Observable.

  • subscribeOn

public subscribeOn(scheduler: Scheduler): Observable<T>

Asynchornously subscribes Observables to this Observable on the specified Scheduler.

  • switch

public switch(): Observable<T>

Converts a higher-order Observable into a first-order Observable by subscribing to only the most recently emitted of those inner Observables.

Flatten an Observable-of-Observables by dropping the previous inner Obsevable once a new one appears.

  • switchMap

public switchMap(project: function(value: T, ?index: number): ObservableInput, resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any): Observable

Projects each source value to an Observable which is merged in the output Observable, emitting values only from the most recent projected Observable.

Map each value to an Observable, then flatten all of these Observable using switch.

  • take

public take(count: number): Observable<T>

Emits only the first count values emitted by the source Observable.

Take the first count value from the source, then complete.

  • takeLast

takeLast(count: number): Observable<T>

Emits only the last count values emitted by the source Observable.

Remembers the latest count values, then emits those only when the source completes.

  • takeUntil

public takeUntil(notifier: Observable): Observable<T>

Emits the values emitted by the source Observable until a notifier Observable emits a value.

Let values pass until a second Observable, notifier, emits something. Then, it completes.

  • takeWhile

public takeWhile(predicate: function(value: T, index: number): boolean): Observable<T>

Emits values emitted by the source Observable so long as each value satisfies the given predicate, and then completes as soon as this predicate is not satisfied.

Take values from the source only while they pass the condition given, when the first value does not satisfy, it completes.

  • throttle

public throttle(durationSelector: function(value: T): SubscribableOrPromise): Observable<T>

Emits a value from the source Observable, then ignores subsequent source values for a duration determined by another Observable, then repeats this process.

It’s like throttleTime, but the silencing duration is determined by a second Observable.

Cannot emit more than one values until the second Observable emits a value.

  • throttleTime

public throttleTime(duration: number, scheduler: Scheduler): Observable<T>

Emits a value from the source Observable, then ignores subsequent source values for duration milliseconds, then repeats this process.

Lets a value pass, then ignores source values for the next duration milliseconds.

  • timeInterval

public timeInterval(scheduler: *): Observable<TimeInterval<any>>

  • timeout

public timeout(due: number, scheduler: Scheduler): Observable<T>

  • timeoutWith

public timeoutWith(due: *, withObservable: *, scheduler: Scheduler): Observable<R>

  • timestamp

public timestamp(scheduler: *): Observable<Timestamp<>>any

  • toArray

public toArray(): Observable<any[]>

  • toPromise

public toPromise(PromiseCtor: *): Promise<T>

1
Rx.Observable.of(42).toPromise().then(console.log)
  • window

public window(windowBoundaries: Observable<any>): Observable<Observable<T>>

Branch out the source Observable values as a nested Observable whenever windowBoundaries emits.

It’s like buffer, but emits a nested Observable instead of an array.

1
2
3
let clicks = Rx.Observable.fromEvent(document, 'click')
let interval = Rx.Observable.interval(1000)
clicks.window(interval)
  • windowCount

public windowCount(windowSize: number, startWindowEvery: number): Observable<Observable<T>>

Branch out the source Observable values as a nested Observable with each nested Observable emitting at most windowSize values.

It’s like bufferCount, but emits a nested Observable instead of an array.

  • windowToggle

public windowToggle(openings: Observable<O>, closingSelectors: function(value: O): Observable): Observable<Observable<T>>

Branch out the source Observable values as a nested Observable starting from an emission from openings and ending when the output of closingSelector

  • windowWhen(closingSelector: function(): Observable): Observable<Observable>`

Branch out the source Observable values as a nested Observable using a factory function of closing Observables to determine when to start a new window.

It’s like bufferWhen, but emits a nested Observable instead of an array.

  • withLatestFrom

public withLatestFrom(other: ObservableInput, project: Function): Observable

Combines the source Observable with other Observables to create an Observable whose values are calculated from the latest values of each, only when the source emit.

Whenever the source Observable emits a value, it computes a formula using that value plus the latest values from other input Observables, then emits the output of that formula.

  • zipAll

public zipAll(project: *): Observable<R>

  • zipProto

public zipProto(observables: *): Observable<R>

HTTP Timings in Node.js

Original

Some Basic Concepts:

  • IP(Internet Protocol): IP is a network-layer protocol, deals with network addressing and routing. IP is responsible for delivering packets from the source host to the destination host based on the packet headers across one or more IP network. It also defines packet structures that encapsulate the data to be delivered.

  • DNS(Domain Name Server): DNS is a hierarchical decentralized naming system used to resolve human-readable hostnames into machine-readable IP address.

  • TCP(Transmission Control Protocol): The TCP standard defines how to establish and maintain a network conversation between applications to exchange data. TCP provides reliable, ordered, and error-checked deliver of a stream of octects between applications running on hosts communicating over an IP network. An HTTP client initiates a request by establishing a TCP connection.

  • SSL(Secure Sockets Layer)/TLS(Transport Layer Security): TLS is a cryptographic protocol that provides communications security over a computer network. SSL is a deprecated predecessor to TLS. Both TLS and SSL use certificates to establish a secure connection. SSL certificates are not dependent on cryptographic protocols like TLS, a certificate contains a key pair: a public and a private key.

Timings Explained:

  • DNS Lookup: Time spent performing the DNS Lookup. DNS lookup resolves domain names to IP addresses.

  • TCP Connection: Time it took to establish TCP Connection between a source host and destination host. Connections must be properly established in a multi-step handshake process.

  • TLS handshake: Time spent completing a TLS handshake. During the handshake process endpoints exchange authentication and keys to establish or resume secure sessions. There is no TLS handshake with a not HTTPS request.

  • Time To First Byte(TTFB): Time spent waiting for the initial response. This time captures the latency of a round trip to the server in addition to the time spent waiting for the server to process the request and deliver the response.

  • Content Transfer: Time spent receiving the response data. The size of the response data and the available network bandwidth determinates its duration.

Measuring HTTP timings in Node.js

To measure HTTP timings in Node.js, we need to subscribe to a specific request, response and socket events. Here is a short code snippet how to do this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const timings = {
startAt: process.hrtime(),
dnsLookupAt: undefined,
tcpConnectionAt: undefined,
tlsHandshakeAt: undefined,
firstByteAt: undefined,
endAt: undefined,
}

const req = http.request({...}, (res) => {
res.once('readable', () => {
timings.firstByteAt = process.hrtime()
})
res.on('data', (chunk) => {
responseBody += chunk
})
res.on('end', () => {
timings.endAt = process.hrtime()
})
})

req.on('socket', (socket) => {
socket.on('lookup', () => {
timings.dnsLookupAt = process.hrtime()
})
socket.('connect', () => {
timings.tcpConnectionAt = process.hrtime()
})
socket.on('secureConnect, () => {
timings.tlsHandshake = process.hrtime()
})
})

Graphql-Koa-Starter

Init Project

Add tsconfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// tsconfig.json
{
"files": [
"server.ts"
],
"compilerOptions": {
"outDir": "./build",
"target": "es2015",
"module": "commonjs",
"moduleResolution": "Node",
"sourceMap": true,
"pretty": true,
"strictNullChecks": true,
"lib": [
"esnext"
]
},
"exclude": [
"node_modules",
"build"
]
}

Add tslint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// tslint.json
{
"rules": {
"align": [
true,
"parameters",
"arguments",
"statements"
],
"ban": false,
"class-name": true,
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"interface-name": false,
"jsdoc-format": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"no-any": false,
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-shadowed-variable": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-require-imports": true,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": false,
"no-var-keyword": true,
"no-var-requires": true,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single",
"jsx-single",
"avoid-escape"
],
"radix": true,
"semicolon": false,
"switch-default": false,
"triple-equals": [
true,
"allow-null-check"
],
"typedef": false,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

Add env

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// env.ts
import * as envalid from 'envalid'

const { url, bool, str } = envalid

const env = envalid.cleanEnv(process.env, {
SERVICE_URI: str({ default: 'https://reqres.in/api' }),
NODE_ENV: str({ devDefault: 'development', default: 'production' }),
LOG_LEVEL: str({ default: 'info' }),
CORS: bool({ devDefault: true, default: false }),
GRAPHIQL: bool({ devDefault: true, default: true }),
})

export default env

Add Nodemon

1
2
3
4
5
6
{
"watch": ["src"],
"ext": "ts",
"ignore": ["src/**/*.spec.ts"],
"exec": "ts-node ./server.ts"
}

Create Koa APP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/app.ts
import * as Koa from 'koa'
import * as cors from 'koa-cors'
import * as convert from 'koa-convert'
import { apiRouter } from './routers'

const app = new Koa()

if (process.env.CORS) {
app.use(convert(cors()))
}

app.use(apiRouter.routes()).use(apiRouter.allowedMethods())

export default app

Server Startup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// server.ts
import * as chalk from 'chalk'
import env from './env'
process.env = env
import app from './src/app'

const port = process.env.PORT || 3001

app.listen(port, () => {
if (process.env.NODE_ENV === 'development') {
if (process.env.GRAPHIQL) {
console.log('The GraphiQL App is running at: ')
console.log(chalk.cyan(`http://localhost:${port}/api/graphiql`))
} else {
console.log('Koa app is running at: ')
console.log(chalk.cyan(`http://localhost:${port}`))
}
} else {
console.log('Koa App is running')
}
})

Add Router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ./src/routers/graphql.ts
import * as Router from 'koa-router'
import * as GraphQLHttp from 'koa-graphql'
import schema from '../graphql/schema'

const router = new Router()
// console.log(process.env)

console.log(process.env)
router.all('/', GraphQLHttp({
schema,
graphiql: process.env.GRAPHIQL,
pretty: true,
errorFormat: error => {
const { message, locations, path, stack } = error
console.error(`GraphQL Error: `, {
message,
locations,
path,
stack,
})
}
}))


export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ./src/routers/index.ts
import * as Router from 'koa-router'
import graphQLRouter from './graphql'
import testRouter from './test'

const apiRouter = new Router({
prefix: '/api'
})

apiRouter.use('/graphql', graphQLRouter.routes(), graphQLRouter.allowedMethods())
apiRouter.use('/test', testRouter.routes(), testRouter.allowedMethods())

export {
apiRouter
}

Config Graphql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ./src/graphql/model.ts
import {
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
} from 'graphql'

const User = new GraphQLObjectType({
name: 'User',
description: 'A User',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
resolve: (user) => user.id,
},
firstName: {
type: new GraphQLNonNull(GraphQLString),
resolve: user => user.first_name
},
lastName: {
type: new GraphQLNonNull(GraphQLString),
resolve: user => user.last_name
}
})
})

export {
User
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ./src/graphql/query.ts
import { GraphQLObjectType, GraphQLList } from 'graphql'
import axios from 'axios'
import { User } from './model'

export default new GraphQLObjectType({
name: 'Query',
description: 'Query of GraphQL',
fields: () => ({
users: {
type: new GraphQLList(User),
resolve: (root, args) => {
return axios({
url: 'https://reqres.in/api/users',
}).then(res => res.data).then(res => res.data)
}
}
})
})
1
2
3
4
5
6
7
// ./src/graphql/schema.ts
import { GraphQLSchema } from 'graphql'
import query from './query'

export default new GraphQLSchema({
query
})

Tag Picture in Html

The picture element is a markup pattern that allows developers to declare multiple sources for an image.

This markup is a container used to specify multiple <source> elements for a specific <img> contained in it.

1
2
3
4
5
6
7
<picture>
<source media="(min-width: 40em)"
srcset="big.jpg 1x, big-hd.jgp 2x">
<source
srcset="small.jpg 1x, small-hd.jpg 2x">
<img src="fallback.jpg">
</picture>

Use the media attribute

The media attribute lets you specify a media query that the user agent will evaluates to select a <source> element. If the media query evaluates to false, the <source> element is skipped.

1
2
3
4
<picture>
<source srcset="logo-wide.jpg" media="(min-width: 600px)">
<img src="logo-narrow.jpg">
</picture>

The srcset and sizes attributes extend the img and source elements to provide a list of available image sources and their sizes. Browsers can then use this information to pick the best image source.

1
2
3
4
5
6
7
<img srcset="elva-fairy-320w.jpg 320w,
elva-fairy-480w.jpg 480w,
elva-fairy-800w.jpg 800w"
sizes="(max-width: 320px) 280px,
(max-width: 480px) 440px,
800px"
src="elva-fairy-800w.jpg" alt="Elva dressed as a fairy">

srcset defines the set of images we will allow the browser to choose between, and what size each image is.

Before each comma, we write:

  • An Image filename (elva-fair-320w.jpg)

  • A space

  • The image’s inherit width in pixels(480w) – note that this issue the w unit, not px as you might expect.

sizes defines a set of media conditions(e.g. screen widths) and indicates what image size would be best to choose, when certain media conditions are true.

Before each comma, we write:

  • A media condition(max-width: 480px) – when the viewport width is 480 px or less

  • A space

  • The width of the slot the image will fill when the media condition is true(440px)

Resolution switching

1
2
3
4
5
6
7
<img
srcset="elva-fairy-320w,jpg,
elva-fairy-480w.jpg 1.5x,
elva-fairy-640w.jpg 2x"
src="elva-fairy-640w.jpg"
alt="elva-fairy"
>

Simple Example of DAPP

Use truffle to init Project

Contract of Adoption

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// contracts/Adoption.sol
pragma solidity ^0.4.4;


constract Adoption {
address[16] public adopters;

function adopt (uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15);
adopters[petId] = msg.sender;
return petId;
}

function getAdopters () public returns (addresss[16]) {
return adopters;
}
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// test/TestAdoption.sol
pragma solidity ^0.4.4;

import "truffle/Assert.sol";
import "truffle/DeployedAddress.sol";
import "../contracts/Adoption.sol";


contract TestAdoption {
Adoption adoption = Adoption(DeployedAddress.Adoption());
function testUserCanAdoptPet () {
uint returnedPetId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returenedPetId, expected, "Adoption of Pet Id 8 should be recorded");
}

function testGetAdopterAddressByPetId () {
address expected = this;
address adopter = adoption.adopters(8);
Assert.equal(adopter, expected, "Owner of Pet Id 8 should be recorded");
}

function testGetAdopterAddressByPetIdInArray () {
address expected = this;
address[16] memory adopters = adoption.getAdopters();
Assert(expected, adopters[8], "Owner of Pet Id 8 should be recoreded");
}
}

Compile

Migrate

TestRPC

MetaMask

UI

initWeb3

1
2
3
4
5
6
7
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider
web3 = new Web3(App.web3Provider)
} else {
App.web3Provider = new web3.providers.HttpProvider('http://localhost:8545')
web3 = new Web3(App.web3Provider)
}

initContract

1
2
3
4
5
$.JSON('Adoption.json', function (data) {
const AdoptionArtifact = data
App.contracts.Adoption = TruffleContract(AdoptionArtifact)
App.contracts.Adoption.setProvider(App.web3Provider)
})

Invoke Contract

1
2
3
4
5
6
7
8
9
let adoptionInstance
App.contracts.Adoption.deployed()
.then(function (instance) {
adoptionInstance = instance
return adoptionInstance.getAdopters.call()
})
.then(function (adopters) {
// ...
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const petId = 8
let adoptionInstance
web3.eth.getAccounts(function (error, accounts) {
if (error) {
console.error(error.message)
}
const account = accounts[0]
App.contracts.Adoption.deployed()
.then(function (instance) {
adoptionInstance = instance
return adoptionInstance.adopt(petId, { from: account })
})
.then(function (result) {
// ...
})
})

Options of Webpack Dll Plugin

The DllPlugin and DllReferencePlugin provide means to split bundles in a way that can drastically improve build time performance.

DllPlugin

This plugin used in a separate webpack config exclusively to create a dll-only-bundle.

It creates a manifest.json file, which is used by the DllReferencePlugin to map dependencies.

  • context: (optional), context of requests in the manifest file(default to the webpack context).

  • name: name of the exposed dll function(TemplatePaths: [hash] & [name])

  • path: absolute path to the manifest json file(output)

1
new webpack.DllPlugin(option)

Creates a manifest.json which is written to the given absolute path.

It contains mappings from require and import requests, to module ids. It is used by the DllReferencePlugin.

Combine this plugin with output.library option to expose(aka, put into the global scope) the dll function

output.library expose the name to global scope

DllReferencePlugin.name add the name to the mapping file.

So it’s important to keep output.library and DllReferencePlugin.name same to set a right mapping.

DllReferencePlugin

This plugin is used in the primary webpack config, it references the dll-only-bundle to require the pre-built dependencies.

  • context: absolute path, context of the requests in the manifest(or content property)

  • manifest: an object containing content and name or a string to the absolute path of the JSON manifest to be loaded upon compilation.

  • content: (optional), the mappings from request to module id(default to manifest.content)

  • name: (optional), the name where the dll is exposed (default to manifest.name)

  • scope: (optional), prefix which is used for accessing the content of the dll

  • sourceType: (optional), how the dll is exposed.

1
new webpack.DllReferencePlugin(options)

References a dll manifest file to map dependencies names to module ids, then requires them as needed using the internal __webpack_require__ function.

Note: Keep the name consistent with output.library

Modes

This plugin can be used in two different modes, scoped and mapped.

Scoped Mode

The content of the dll is accessible under a module prefix, i.e. with scope = 'xyz' a file named abc in the dll can be access via require(xyz/abc).

Mapped Mode

The content of the dll is mapped to the current directory. If a required file matches a file in the dll(after resolving), then the file from the dll is usd instead.

Because this happens after resolving every file in the dll bundle, the same paths must be available for the consumer of the dll bundle, i.e. if the dll contains lodash and the file abc, require('lodash') and require('./abc') will be used from the dll, rather than building them into the main bundle.

1
2
3
4
5
6
// webpack.config.dll.js
new webpack.DllPlugin({
context: __dirname,
name: '[name]_[hash]',
path: path.join(__dirname', 'manifest.json'),
})
1
2
3
4
5
6
// webpack.config.base.js
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./manifest.json'),
name: './my-dll.js',
})

Notes

Output.library and DllPlugin.name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
output: {
path: path.resolve(__dirname, 'lib'),
filename: '[name]_[hash].js',
library: '[name]_[hash]', // this set the exposed function name to [name]_[hash]
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, 'lib', '[name]-manifest.json'), // put the manifest file in lib directory
name: '[name]_[hash]', // be consistent with library name to make the map correct
context: __dirname,
})
]
}

Context in Dev Config

Set the DllReferencePlugin.context same as DllPlugin.context so the manifest will bundle and resolve the dll files from the same root directory.

Add dll bundle to html

After bundle the dll, I should insert it betore app.js in the html file to provide the global function.

  • Step 1: Copy the dll files to the dist directory

I use the copy-webpack-plugin plugin

1
2
3
4
// webpack.config.js
new CopyPlugin([
{ from './lib/*.js' } // default to output.path, so the dll file will be copied to output.path/lib/*.js
])
  • Generate a manifest of the dll files for html-webpack-plugin to insert to the template

I use the webpack-manifest-plugin

1
2
// webpack.config.dll.js
new ManifestPlugin()

It will generate the manifest.json in output.path

1
2
3
{
"vendor.js": "vendor_0642aa84a6959d661519.js"
}
  • Insert the Manifest into the html
1
2
3
4
5
6
7
8
9
//parse the json
const fs = require('fs)
const manifest = JSON.parse(fs.readFileSync(require('./lib/manifest.json')))

// insert
new HtmlPlugin({
//...
dll: `./lib/${manifest['vendor.js']}`, // the dll file locates at ./lib/*.js
})

Summary

Dll Config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const webpack = require('webpack')
const path = require('path')
const ManifestPlugin = require('webpack-manifest-plugin')

module.exports = {
entry: {
vendor: ['./src/vendor'],
},
output: {
filename: '[name]_[hash].js',
path: path.resolve(__dirname, 'lib'),
library: '[name]_[hash]',
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',
path: path.resolve(__dirname, 'lib', '[name]-manifest.json'),
}),
new ManifestPlugin(),
],
}

Dev Config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const webpack = require('webpack')
const path = require('path')
const HtmlPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const fs = require('fs')

const manifest = JSON.parse(fs.readFileSync('./lib/manifest.json'))

module.exports = {
entry: {
app: ['./src/index'],
},
output: {
filename: 'scripts/[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new HtmlPlugin({
template: path.resolve(__dirname, 'src', 'templates', 'index.html'),
title: 'dev',
dll: `./lib/${manifest['vendor.js']}`,
}),
new webpack.DllReferencePlugin({
context: '.',
manifest: require('./lib/vendor-manifest'),
}),
new CopyPlugin([
{ from: './lib/*.js'}
]),
]
}

Optimize Web App

Optimizing All Assets

One of the most powerful, but under-utilized ways to significantly improve performance starts with understanding hwo the browser analyzes and serves assets. It turns out that browsers are pretty great at discovering resources while parsing and determing their priority of the fly. Here’s where the critical Request comes in:

A request is critical if it contains assets that are necessary to render the content within the users’ viewport.

For most sites, it’d be HTML, necessary CSS, a logo, a web font and maybe an image.

We can control this behavior by carefully picking critical resources and adjusting theri priority.

preload

With <link red='preload'> we are able to manually force assets’ priority to High ensuring that desired content will be rendered on time.

This technique can yield significant improvements in Time to Interactive metric, making optimal user experience possible.

Critical requests still seem like a black box for many, and the lack of sharable materials doesn’t help to change that.jj

Optimizing Images

Images often account for most of a web page’s transferred payload.

Choosing the right format

The initial choice falls between vector and raster graphics:

  • Vector: resolution independent, usually significantly smaller in size. Perfect for logos, iconography and simple assets comprising of basic shapes.

  • Raster: offers much more detailed results, ideal for photographs.

After making this decision, there are a fair bit of formats to choose from: JPEG, GIF, PNG-8, PNG-24 or newest formats such as WEBP or JPEG-XR, JPEG-2000.

  • JPEG: Imagery with many color(e.g. photos)

  • PNG-8: Imagery with a few color

  • PNG-24: Imagery with partial transparency

  • GIF: Animated imagery

Experimenting with new formats

WebP is easily the most popular contender, supporting both lossless and lossy compression, which makes it incredibly versatile. Lossless WebP is 26% smaller than PNGs and 25-34% smaller than JPGs. With 74% browser support it can safely be used with fallback. JPGs and PNGs can be converted to WebP in PS and other image processing app as well as through command line interface(brew install webp).

Responsible and responsive Imagery

W’re perfectly equipped to do so with picture element and srcset attribute(both have 85% support)

The srcset attribute

srcset works best in the resolution switching scenario – when we want to display imagery based on user’s screen density and size. Baseed on a set of predefined rules in srcset and size attributes the browser will pick the best image to serve accordingly to the viewport.

1
2
3
4
5
6
<img
srcset="cat-320w.jpg 320w cat-480w.jpg 480w cat-800w.jpg 800w",
sizes="(max-width: 320px) 100vw, (max-width: 480px) 80vw, 800px",
src="cat-800w.jpg"
alt="cat"
/>

The picture element

picture element and the media attribute are designed to make art direction easy. BY providing different sources for varying conditions.

Delivery with image CNDs

Optimizing Web Fonts

Choose the right format

There are four web font formats: EOT, TTF, WOFF and WOFF2.

TTF and WOFF are most widely adopted, boasting over 90% browser support. Depending on the support you’re targeting it’s most likely safe to server WOFF2 and fall back to WOFF for older browsers. The advantage of using WOFF2 is a set of custom preprocessing and compression algorithms resulting in approx 30% file-size reduction and impoved parsing capabilities.

When defining the sources of web fonts in @font-face use the format() hint to specify which format should be utilized.

use Unicode-range subsetting

Establish a font loading strategy

Fonts are render-blocking – because the browser has to build both the DOM and CSSOM first. Wdb fonts won’t be downloaded before they’re used in a CSS selector that matches an existing node. This behavior significantly delays text rendering, often causing the Flash of Invisible Text(FOIT).

Implementing a font strategy prevents users from not being able to access your content.

font-display is a new CSS property providing a non-javascript reliant solution.

1
2
3
4
5
6
@font-face {
font-family: Post Grotesk;
src: url('/fonts/Post-Growtest.woff2') format('woff2'),
url('/fonts/Post-Growtest.woff' format('woff')),
font-display: swap; // show fallback until web font is ready
}

Optmizing JS

Concept on BlockChain

A Blockchain is

A distributed database that is used to maintain a continuously growing list of records, called block.

A Block contains following information:

  • Index(Block #) - Which block is it? (Genesis block has index 0)

  • Hash: Is the block valid?

  • Previous Hash: It the previous block valid?

  • Timestamp: When was the block added?

  • Data: What information is stored on the block?

  • Nonce: How many iterations did we go through before we found a valid block?

Genesis Block

Every Blockchain will st art with the Genesis Block, each block on the blockchain is dependent on the previous block, so the Genesis Block is needed to mine our first block.

Hash

A hash value is a numeric value of a fixed length that uniquely identifies data.

The hash is calculated by taking the index, previous block hash, timestamp, block data, and nonce as input.

1
CryptoJS.SHA256(index + previousHash + timestamp + data + nonce)

Leading 0 in the block hash

Specified leading 0 is a minimum requirement for a valid hash. The number of leading 0 required is called difficulty.

The is also known as the Proof-of-Work system.

Nonce

A nonce is a number used to find a valid hash

1
2
3
4
5
6
7
8
9
let nonce = 0
let hash
let input

while (!isValidHashDifficulty(hash)) {
nonce += 1
input = index + previousHash + timeStamp + data + nonce
hash = CryptoJS.SHA256(input)
}

The nocne iterates until the hash is valid.

The process of finding a nonce that corresponds to a valid hash is mining.

As the difficulty increase, the number of possible valid hashes decrese. With less possible valid hashes, it takes more processing power to find a valid hash.

Ethereum Virtual Machine (EVM)

The Ethereum Virtual Machine or EVM is the runtime environment for smart contracts in Ethereum. It is not only sandboxed but actually completely isolated, which means that code running inside the EVM has no access to network, filesystem or other processes. Smart contract even have limited access to other smart contracts.

Accounts

There are two kinds of accounts in Ethereum which share the same address space:

  • External Account that are controlled by public-private key pairs(i.e. humans)

  • Contract Account that are controlled by the code stored together with accounts

The address of an external account is determined from the public key while the address of a contract is determined at the time the contract is created(it is derived from the creator address and the number of transactions sent from the address, the so-called ‘nonce’).

Every account has a persistent key-value store mapping 256-bit words to 256-bit words called storage.

Every account has a balance in Ether(in Wei to be exact) which can be modified by sending transactions that include Ether.

transactions

A transaction is a message that is sent from one account to another account(which might be the same or the special zero-account). It can include binary data(its payload) and Ether.

If the target account contains code, that code is executed and the payload is provided as input data.

If the target account is the zero-account(the account with the address 0), the transaction creates a new contract.

As memtioned, the address of that contract is not the zero address but an address derived from the sender and its number of transactions sent(nonce). The payload of such a contract creation transaction is taken to be EVM bytecode and executed to generate the new contract

The output of the bytecode, namely the new contract, is permenantly stored.

Gas

Each transaction is charged with a certain with a certain amount of gas, whose purpose is to limit the amount of work that is needed to executed the transaction and to pay for this execution.

While the EVM executes the transaction, the gas is gradually depleted according to specific rules.

The gas price is a value set by the creator of the transaction, who has to pay gas_price * gas.

Storage, Memory and the Stack

Each account has a persistent memory area which is called storage. Storage is a key-value store taht maps 256-bit word to 256-bit word.

The second memory area is called memory, of which a contract obtains a freshly cleared instance for each message call. Memory is linear and can be addressed at byte level, but reads are limited to a width of 256 bits, white writes can be either 8btis or 256bits wide.

Memory is expanded by a word(256-bit), when accessing (either reading or writing) a previously untouched memory word. At the time of expansion, the cost in gas must be paid. Memory is more costly the larger it grows(it scales quadratically).

The EVM is not a register machine but a stack machine, so all computations are performed on an area called the stack. It has limited to the top end in the following way:

  • it is possible to copy one of the topmost 16 elements to the top of the stack or swap the topmost 2(or 1, or more, depending on the opeartion) element fomr the stack and push the result onto the stack.

  • All other operation take the topmost 2(or 1, or more, depending on the operation) elements from the stack and push the result onto the stack.

  • It is possible to move stack elements to storage or memory

  • It is not possible to just access arbitrary elements deeper in the stack without removing the top of the stack.

Instruction Set

The instruction set of the EVM is kept minimal in order to avoid incorrect implementations which could cuase consensus problem. All instructions operate ont he basic data type, 256-bit words.

The usual arihmetic bit, logical and comparison operations are present. Conditional and unconditional jumps are possible. Furthermore, contracts can access relevant properties of the current block like its number and timestamp.

Message Calls

Contracts can call other contracts or send Ether to non-contract accounts by the means of the message calls.

Message calls are similar to transactions, in that tehy have a source, a target, data payload, Ether, gas, and return data.

In fact, every transaction consists of a top-level message call which in turn can create further message calls.

A contract can decide how much of its remaining gas should be sent with the inner message call and how much it wants to retain. If an out-of-gas exception happens in the inner call(or any other exception), this will be signalled by an error value put onto the stack. In this case, only the gas sent together with the call is used up.

Summarily, a called contract(which can be the same as caller) will receive a freshly cleared instance of memoery, and has access to the call payload - which will be provided in a separate area called the calldata. After it has finished execution, it can return data which will be stored at a location in the caller’s memory preallocated by the caller.

Calls are limited to depth of 1024, which means that for more complex operations, loops should be preferred over recursive calls.

Delegatecall / Callcode and Libraries

There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact taht the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.

This means that a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from teh called address.

This makes it possible to implement the ‘library’ feature in Solidity: Reusable library code that can be applied to a contract’s storage to implement a complex data structure.

Logs

It is possible to store data in a special indexed data structure that maps all the way up to the block level. This feature called logs is used by Solidity in order to implement events. Contracts cannot access log data after it has been created, but they can efficiently accessed from outside the blockchain.

Since some part of the log data is stored in bloom filters, it is possible to search for this data in an efficient and crypographically secure way, so network peers that do not download the whole blockchain(‘light clients’) can still find these logs.

Create

Contracts can even create other contract s using a special opcode. The only difference between create calls and normal message calls is that the payload data is executed and the result stored as code and the caller/creator receives the address of the new contract on the stack.

Self-destruct

The only possible that code is removed from the blockchain is when a contract at that address performs the selfdestruct operation. The remaining Ether stored at that address is sent to a designed target and then the storage and code is removed from the state.

Even if a contract’s code does not contain a call to selfdestruct, it can still perform that operation using delegatecall or callcode.

Build Node CLI

Dependencies

  • chalk: colorize the output

  • clear: clears the terminal screen

  • clui: draws command line tables, gauges and spinners

  • figlet: creates ASCII art from text

  • inquirer - create interactive command line user interface

  • minimist - parses argument options

  • preferences - manage CLI application encrypted preferences

chalk

  • chalk.<style>[.<style>...](string, [string...])

    chalk.red.bold.underline(‘Hello’, ‘World’)

  • chalk`string {<style> <style>… string} string`

    chalk`There are { bold 5280 feet } in a mile `

clear

1
2
const clear = require('clear')
clear()

clui

Display

  • Gauge

  • PrograssBar

  • Sparklines

  • Spinners

on Terminal

figlet

Display ASCII font on Terminal

1
2
3
4
5
6
7
8
figlet.text('Boo', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default'
}, function (err, data) {
if (err) return console.log(err)
return console.log(data)
})
1
2
3
4
5
figlet.textSync('Boo', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
})

inquirer

A collection of common interactive command line user interface.

  • inquirer.prompt(questions) => promise

    • questions an Array containing Question Object(using the reactive interface, you can also pass a Rx.observable instance)

      • Question Object:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // {
      // type: string, //['input', 'confirm', 'list', 'rawlist', 'expand', 'checkbox', 'password', 'editor']
      // name: string, // the name to use when storeing the answer in the answer hash
      // message: string|function, // The question to print, if defined as a function, the first parameter will be the current inquirer session answer
      // default: string|number|array|function, // default value to use
      // choices: array|function,
      // validate: function, // receive the user input and answers hash, should return true if the value is valid, and an error message(string) otherwise. If a false is returned, a default error message is provided.
      // filter: function, //
      // when: function|boolean, //
      // pageSize: number, //
      // }

minimist

preferences

Make CLI globally available

Add a shebang line to the top of index.js

1
#!/usr/bin/env node

Then add a bin prototype to package.json.

1
2
3
"bin": {
"cli-init": "./index.js"
}

Build a Node Command Line Interface

Start Up With Ts, React, Router, Redux, Rxjs

The Repo

Dependencies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
"devDependencies": {
"@types/react": "^16.0.5",
"@types/react-dom": "^15.5.4",
"@types/react-redux": "^5.0.6",
"@types/redux-actions": "^1.2.8",
"@types/webpack": "^3.0.10",
"@types/webpack-dev-server": "^2.4.1",
"autoprefixer": "^7.1.3",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"bundle-loader": "^0.5.5",
"eslint": "^4.6.1",
"eslint-config-airbnb": "^15.1.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-react": "^7.3.0",
"html-webpack-plugin": "^2.30.1",
"node-sass": "^4.5.3",
"precss": "^2.0.0",
"redux-devtools": "^3.4.0",
"redux-devtools-dock-monitor": "^1.1.2",
"redux-devtools-log-monitor": "^1.3.0",
"rimraf": "^2.6.1",
"sass-loader": "^6.0.6",
"ts-loader": "^2.3.4",
"typescript": "next",
"uglifyjs-webpack-plugin": "^0.4.6",
"webpack": "^3.5.5",
"webpack-dashboard": "^1.0.0-5",
"webpack-dev-server": "^2.7.1",
"webpack-merge": "^4.1.0"
},
"dependencies": {
"@types/node": "^8.0.26",
"@types/react-router-dom": "^4.0.7",
"babel-plugin-transform-runtime": "^6.23.0",
"extract-text-webpack-plugin": "^3.0.0",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-redux": "^5.0.6",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"redux": "^3.7.2",
"redux-actions": "^2.2.1",
"redux-observable": "^0.16.0",
"rxjs": "^5.4.3"
}

Project Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
.
├── config
│   ├── webpack.config.base.js
│   ├── webpack.config.dev.js
│   ├── webpack.config.prod.js
├── src
│   ├── actions
│   │   ├── index.tsx
│   │   ├── hello.tsx
│   ├── components
│   │   ├── AsyncRoute.tsx
│   ├── containers
│   │   ├── App.tsx
│   │   ├── Header.tsx
│   │   ├── Body.jsx
│   │   ├── Footer.tsx
│   │   ├── Index.tsx
│   │   ├── Page2.tsx
│   │   ├── DevTools.tsx
│   ├── epics
│   │   ├── index.tsx
│   │   ├── hello.tsx
│   ├── reducers
│   │   ├── index.tsx
│   │   ├── hello.tsx
│   ├── store
│   │   ├── index.tsx
│   │   ├── configureStore.dev.tsx
│   │   ├── configureStore.prod.tsx
│   ├── templates
│   │   ├── index.html
│   ├── utils
│   │   ├── connect.tsx
│   │   ├── nav.tsx
│   ├── app.tsx
├── package.json
├── tsconfig.json
├── postcss.config.js
├── .babelrc
├── .eslintrc.js
├── .gitignore
├── yarn.lock

Step 1: Create Project

1
2
# init project
yarn init -y
1
2
3
4
# add eslint
yarn add --dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react

eslint --init
1
2
# add webpack
yarn add --dev webpack webpack-dev-server webpack-dashboard webpack-merge @types/webpack @types/webpack-dev-server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# add loader for jsx
yarn add --dev babel-core babel-loader babel-preset-env babel-preset-react

# add loader for tsx
yarn add --dev ts-loader typescript@next

# add loader for css
yarn add --dev style-loader css-loader resolve-url-loader postcss-loader node-sass sass-loader

# add loader for file
yarn add --dev url-loader file-loader

# add plugin for js
yarn add babel-plugin-transform-runtime

# add plugin for postcss
yarn add --dev autoprefixer precss

# add plugin for html
yarn add --dev html-webpack-plugin

# add plugin for uglifyjs
yarn add --dev uglifyjs-webpack-plugin

# add plugin for extract css
yarn add --dev extract-text-webpack-plugin

set gitignore

1
2
3
4
5
6
7
8
# Logs
*.log

# dependencies
node_modules

# build
dist

set typescript config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"compilerOptions": {
"outDir": "dist",
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"noImplicitReturns": true,
"noImplicitThis": false,
"noImplicitAny": false,
"strictNullChecks": true
},
"exclude": [
"config",
"node_modules",
"dist",
"webpack",
"jest"
],
"types": [
"typePatches"
]
}

Set basic webpack config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// /config/webpack.config.base.js
const path = require('path')

module.exports = {
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'scripts/[name]-[hash].js',
chunkFilename: 'scripts/[name]-[chunkhash].js',
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/
}, {
test: /\.jsx?$/,
loader: 'babel-laoder',
exclude: /node_modules/,
}, {
test: /\.(png|jpg)$/,
loader: 'url-loader',
options: {
limit: 8192,
name: '[name]-[hash].[ext]',
}
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '.scss', '.css', '.json']
}
}

Set dev webpack config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// /config/webpack.config.dev.js
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlPlugin = require('html-webpack-plugin')
const DashboardPlugin = require('webpack-dashboard/plugin')
const baseConfig = require('./webpack.config.base')

const devConfig = {
entry: {
app: [
'webpack/hot/only-dev-server',
'webpack-dev-server/client?http://localhost:8080',
path.resolve(__dirname, '../src/app.tsx'),
],
},
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
name: '[local]-[name]-[hash]'
},
},
],
'resolve-url-loader',
'sass-loader',
},
],
},
devtool: 'source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.DefinePluing({
'process.env': {
NODE_ENV: 'development',
},
}),
new DashboardPlugin(),
new HtmlPlugin({
title: '开发',
template: path.resolve(__dirname, '../src/temlates/index.html'),
}),
],
devServer: {
hot: true,
compress: true,
historyApiFallback: true,
}
}

module.exports = merge(baseConfig, devConfig)

Set prod webpack config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// /config/webpack.config.prod.js

const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const HtmlPlugin = require('html-webpack-plugin')
const baseConfig = require('./webpack.config.base.js')

const prodConfig = {
entry: {
app: path.resolve(__dirname, '../src/app.tsx'),
},
module: {
rules: [
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
name: '[local]-[name]-[hash]',
importLoaders: 3,
},
},
],
exclude: /node_modules/,
}), {
loader: 'postcss-loader',
options: {
config: path.resolve(__dirname, './config/postcss.config.json')
}
},
'resolve-url-loader',
'sass-loader',
],
},
devtool: 'inline-source-map',
plugins: [
new webpack.DefinePluing({
'process.env.NODE_ENV': 'production',
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
filename: 'scripts/vendor-[hash].min.js',
}),
new HtmlPlugin({
title: '生产',
template: path.resolve(__dirnaem, '../src/templates/index.html'),
minify: {
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeTagWhitespace: true,
},
}),
new ExtractTextPlugin({
filename: 'styles/[name]-[contenthash].css',
}),
new UglifyJSPlugin(),
],
}

Set config refered by webpack(babelrc, postcss, index.html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// /.babelrc
{
"presets": [
[
"env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 9"],
}
}
],
"react",
],
"plugins": [
["transform-runtime", {
"polyfill": false,
"regenerator": true
}],
]
}
1
2
3
4
5
6
7
8
9
const precss = require('precss')
const autoprefixer = require('autoprefixer')

module.exports = {
plugins: [
precss,
autoprefixer,
],
}
1
2
3
4
5
6
7
8
9
10
11
12
<!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><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>

Step 2: Skeleton of React App(React + Router)

1
2
3
yarn add --dev bundle-laoder @types/react @types/react-dom @types/node @types/react-router-dom

yarn add react react-dom react-router-dom

In src/app.tsx render the App to html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import * as React from 'react'
import { render } from 'react-dom'
import App from './containers/App'

render(
<App />, document.getElementById('app') as HTMLElement
)

if (module.hot) {
module.hot.accept('./containers/App', () => {
const App = require('./containers/App').default
render (
<App />,
document.getElementById('app')
)
})
}

Create App Represental Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// `./src/containers/App.tsx`

import * as React from 'react'
import {
BrowserRouter as Router,
Route,
} from 'react-router-dom'
import Header from './Header'
import Body from './Body'
import Footer from './Footer'

export default class extends React.Component {
constructor () {
super ()
}
render () {
return (
<Router>
<div>
<Route path="/" component={Header} />
<Route path="/" component={Body} />
<Route path="/" component={Footer} />
</div>
</Router>
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ./src/containers/Header.tsx

import * as React from 'react'
import { goTo } from '..utils/nav'

class Header extends React.Component {
protected goTo = goTo.bind(this)
constructor () {
super()
this.state = {
navs: [
{ label: '首页', url: 'index' },
{ label: '第二页', url: 'page2' },
{ label: '第三页', url: 'page3' },
]
}
}

render () {
const { navs } = this.state
return (
<div>
{
navs.map(
(nav: {label: string, url: string}) => <span key={nav.url} onClick={this.goTo(nav.url)}>{nav.label}</span>
)
}
</div>
)
}
}

export default Header
1
2
3
4
5
6
7
// ./src/utils/nav.tsx

export function goTo(url: string) {
retrn () => {
this.props.history.push(url)
}
}

Same structure in Body and Footer.

Add AsyncRoute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import * as React from 'react'

interface PassedProps extends React.Props<any> {
load: any;
children: any;
}

class Bundle extends React.Component<PassedProps, any> {
constructor () {
super()
this.state = {
mod: null,
}
}
componentWillMount () {
this.load(this.props)
}
componentWillReceiveProps (nextProps) {
if (nextProps.load !== this.props.load) {
this.load(nextProps)
}
}
load (props) {
this.setState({
mod: null,
})
props.load((mod) => {
this.setState({
mod: mod.default || mod,
})
})
}
render () {
return this.state.mod ? this.props.children(this.state.mod) : null
}
}

export default module => routerProps => (<Bundle load={module}>
{ Comp => Comp ? <Comp {...routerProps} /> : null}
)

And use it in body container

1
2
3
4
5
6
7
8
9
10
11
import * as React from 'react'
import { Route } from 'react-router'

import Index from 'bundle-loader?lazy!./Index'
import AsyncRoute from '../components/AsyncRoute'

export default () => (
<div>
<Route path="/index" component={AsyncRoute(Index)}
</div>
)

Add Redux

1
2
3
yarn add --dev redux-devtools redux-devtools-dock-monitor redux-devtools-log-monitor

yarn add redux redux-actions react-redux

Create Actions

1
2
3
4
5
6
7
8
9
10
11
// ./src/actions/hello.tsx
import { createActions } from 'redux-actions'

export default createActions({
SAY_HELLO: text => ({ text })
})

// export
// {
// sayHello: (text: string) => ({ text: string })
// }
1
2
3
4
5
// ./src/actions/index.tsx
import helloActions from './hello'
export default {
...helloActions
}

Create Reducers

1
2
3
4
5
6
7
8
9
10
11
// ./src/reducers/hello.tsx
import { handleActions } from 'redux-actions'
import actions from '../actions'

export default handleActions({
[actions.sayHello.toString()]: (state, action) => {
return { ...state, ...action.payload }
},
}, {
text: '',
})
1
2
3
4
5
6
7
// ./src/reducers/index.tsx
import { combineReducers } from 'redux'
import helloReducer from './hello'

export default combineReducers({
hello: helloReducer,
})

Create Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ./src/store/configureStore.dev.tsx

import { createStore, applyMiddleware, compose, GenericStoreEnhancer } from 'redux'
import reducer from '../reducers'

const enhancer = compose(
// applyMiddleware(...middlewares)

window.devToolsExtension ? window.devToolsExtension() : f => f,
)

function configureStoreDev(initState: any): any {
const store = createStore(reducer, initState, enhancer)
if (module.hot) {
module.hot.accpet('../reducers', () => {
store.replaceReducer(require('../reducers').default)
})
}
return store
}

module.exports = configureStoreDev
1
2
3
4
5
6
7
8
9
10
// ./src/store/configureStoreProd

import { createStore, applyMiddleware } from 'redux'
import reducer from '../reducers'

function configureStoreProd (initState: any) {
return createStore(reducer, initState)
}

module.exports = configureStoreProd
1
2
3
4
5
6
7
// ./src/store/index.jsx

if (process.env.NODE_ENV == 'development') {
module.exports = require('./configureStore.dev')
} else {
module.exports = require('./configureStore.prod')
}

Add Provider to Main Container

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ./container/App.tsx
import * as React from 'react'
import {
BrowserRouter as Router,
Route,
} from 'react-router-dom'
import { Provider } from 'react-redux'
import Header from './Header'
import Body from './Body'
import Footer from './Footer'

const configureStore = require('../store')

const store = configureStore({})

export default class extends React.Component {
constructor () {
super()
}
render () {
returun (
<Router>
<Provider store={store}>
<Route path="/" component={Header} />
<Route path="/" component={Body} />
<Route path="/" component={Body} />
</Provider>
</Router>
)
}
}

custom connect to cache mapStateToProps and mapDispatchToProps

1
2
3
4
5
6
7
8
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actions from '../actions'

const mapStateToProps = (state: any) => state
const mapDispatchToProps = (dispatch: any) => (bindActionCreators(actions, dispatch))

export default (Comp: any) => connect(mapStateToProps, mapDispatchToProps)(Comp)

Connect the Presentational Container

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import * as React from 'react'
import connect from '../utils/connect'
import { goTo } from '../utils/nav'

interface PassedProps extends React.Props<any> {
sayHello: any;
}

class Header extends React.Component<PassedProps, any> {
protected goTo = goTo.bind(this)
constructor () {
super()
this.state = {
navs: [
{ label: '首页', url: 'index' },
{ label: '第二页', url: 'page2' },
{ label: '第三页', url: 'page3' },
]
}
}
componentDidMount () {
this.props.sayHello('world')
}
render () {
const { navs } = this.state
return (
<div>
{navs.map((nav: { label: string, url: string}) => <span
key={nav.url}
onClick={this.goTo(nav.url)}
>{nav.label}</span>)}
</div>
)
}
}

export default connect(Header)

Add Rxjs

1
yarn add rxjs redux-observable

Create Epics

1
2
3
4
5
6
7
8
9
10
11
// ./src/epics/hello.tsx
import 'rxjs'
export const sayHello = (action$, store) =>
.action$.ofType('SAY_HELLO')
.delay(1000)
.map(() => ({
type: 'SAY_HELLO',
payload: {
text: 'got hello'
}
}))
1
2
3
4
5
6
7
8
// ./src/epcis/index.tsx

import { combineEpics } from 'redux-observable'
import { sayHello } from './hello'

export default combineEpics(
sayHello,
)

Add Epic to Enhancer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { createStore, applyMiddleware, compose, GenericStoreEnhancer } from 'redux'
import { createEpicMiddleware } from 'redux-observable'
import reducer from '../reducers'
import epics from '../epics'

const epicMiddleware = createEpicMiddleware(epics)

const enhancer = compose(
applyMiddleware(epicMiddleware),
window.devToolsExtension ? window.devToolsExtension() : f => f
)

function configureStoreDev (initState: any) {
const store = createStore(reducer, initState, enhancer)
if (module.hot) {
module.hot.accept('../reducers', () => {
store.replaceReducer(require('../reducers').default)
})
}
return store
}

module.exports = configureStoreDev

Add Service Worker

1
yarn add sw-precache-webpack-plugin

Add the Plugin in webpack.config.prod.js

1
2
3
4
const SWPrecachePluing = require('sw-precache-webpack-plugin')
plugins: [
new SWPrecachePluing()
]

Add Bundle Analyzer

1
yarn add --dev webpack-bundle-anaylzer

Add the Plugin in webpack.config.dev.js

1
2
3
4
5
const BundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

plugins: [
new BundleAnalyzer()
]

Add Dll

Add webpack.config.dll.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// webpack.config.dll.js
const path = require('path')
const webpack = require('webpack')
const ManifestPlugin = require('webpack-manifest-plugin')

module.exports = {
entry: {
vendor: [/* ... */],
},
output: {
path: path.resolve(__dirname, '../lib'),
filename: '[name]_[hash:5].js',
library: '[name]_[hash:5]',
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash:5]',
path: path.resolve(__dirname, '../lib', '[name]-manifest.json'),
}),
new ManifestPlugin(),
],
}

Copy dll to dist and refer

1
2
3
4
5
6
7
8
9
10
// webpack.config.base.js
const vendorManifest = require('../lib/vendor-manifest.json')
plugins: [
new CopyPlugin([
{ from: path.resolve(__dirname, '../lib/*.js') },
]),
new webpack.DllReference({
manifest: vendorManifest,
}),
]

Insert Dll files in html

1
2
3
4
5
6
7
8
9
10
11
12
13
// webpack.config.dev.js
const fs = require('fs')
const manifest = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../lib/manifest.json')))

module.exports = {
// ...
plugins: [
new HtmlPlugin({
// ...
dll: `./lib/${manifest['vendor.js']}`,
}),
],
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×