Compare commits

...

15 Commits

Author SHA1 Message Date
Camel Aissani
acebd1e5c0 typo 2017-06-24 10:02:06 +02:00
Camel Aissani
d678d066c3 Update README.md 2017-06-24 09:53:59 +02:00
Camel Aissani
2245de31d7 Update README.md 2017-06-23 19:43:04 +02:00
Camel Aissani
065b500561 Merge branch 'master' of github.com:camelaissani/frontexpress 2017-06-23 12:34:50 +02:00
Camel Aissani
75b130ef91 clean/update package.json file 2017-06-23 12:34:29 +02:00
Camel Aissani
72598f09e1 Update README.md 2017-06-23 10:21:56 +02:00
Camel Aissani
d7cfd6d9f3 prepare new release 2017-06-22 21:28:31 +02:00
Camel Aissani
21f5dea449 Update README.md 2017-06-22 21:22:12 +02:00
Camel Aissani
a1e56dc6c3 fixed issues #5 #6 2017-06-22 21:17:18 +02:00
Camel Aissani
34b7580d18 Updated link to jsDelivr 2017-06-15 20:28:29 +02:00
Camel Aissani
4ab3fa2626 added PATCH http method - Fixed middleware callbacks 2017-06-14 22:47:59 +02:00
Camel Aissani
5b45250bf5 added frontexpress.js.min in package 2017-05-26 14:50:58 +02:00
Camel Aissani
787a22ed44 prepared new patch version 2017-05-26 14:27:12 +02:00
Camel Aissani
79062fe337 better support of es modules 2017-05-26 14:25:37 +02:00
Camel Aissani
f90743d4cf Update README.md 2017-01-16 08:23:22 +01:00
20 changed files with 1081 additions and 664 deletions

View File

@ -1,4 +1,3 @@
{
"presets": ["es2015"],
"plugins": ["add-module-exports"]
"presets": ["es2015"]
}

View File

@ -1,7 +0,0 @@
index.js
frontexpress.js
frontexpress.min.js
frontexpress.min.js.map
gzipsize.js
test
coverage

300
README.md
View File

@ -1,38 +1,16 @@
![frontexpress](http://fontmeme.com/embed.php?text=frontexpress&name=Atype%201%20Light.ttf&size=90&style_color=6F6F75)
Frontexpress manages routes in browser like [ExpressJS](http://expressjs.com/) does on Node.
A simple vanilla JavaScript router a la [ExpressJS](http://expressjs.com/).
Same language same API on all the stack.
Code the front-end like the back-end.
[frontexpress demo](https://github.com/camelaissani/frontexpress-demo)
[![Build Status](https://travis-ci.org/camelaissani/frontexpress.svg?branch=master)](https://travis-ci.org/camelaissani/frontexpress)
[![Code Climate](https://codeclimate.com/github/camelaissani/frontexpress/badges/gpa.svg)](https://codeclimate.com/github/camelaissani/frontexpress)
[![Coverage Status](https://coveralls.io/repos/github/camelaissani/frontexpress/badge.svg?branch=master)](https://coveralls.io/github/camelaissani/frontexpress?branch=master)
![dependencies](https://img.shields.io/gemnasium/mathiasbynens/he.svg)
![Size Shield](https://img.shields.io/badge/size-2.86kb-brightgreen.svg)
Code the front-end logic with the same style than on the back-end with express
```js
import frontexpress from 'frontexpress';
// Front-end application
const app = frontexpress();
// front-end logic on navigation path "/page1"
app.get('/page1', (req, res) => {
document.querySelector('.content').innerHTML = `<h1>Page 1 content</h1>`;
});
// front-end logic on navigation path "/page2"
app.get('/page2', (req, res) => {
document.querySelector('.content').innerHTML = `<h1>Page 2 content</h1>`;
});
// start front-end application
app.listen(() => {
// on DOM ready
});
```
![Size Shield](https://img.shields.io/badge/size-3.26kb-brightgreen.svg)
## Installation
@ -50,45 +28,33 @@ $ bower install frontexpress
### From CDN
On [jsDelivr](http://www.jsdelivr.com/?query=frontexpress)
On [jsDelivr](https://cdn.jsdelivr.net/npm/frontexpress@1.2.0/frontexpress.min.js)
## Quick Start
## Usage
The quickest way to get started with frontexpress is to clone the [frontexpress-demo](https://github.com/camelaissani/frontexpress-demo) repository.
```js
import frontexpress from 'frontexpress';
## Tests
// Front-end application
const app = frontexpress();
Clone the git repository:
// front-end logic on navigation path "/page1"
app.get('/page1', (req, res) => {
document.querySelector('.content').innerHTML = res.responseText;
});
```bash
$ git clone git@github.com:camelaissani/frontexpress.git
$ cd frontexpress
// front-end logic on navigation path "/page2"
app.get('/page2', (req, res) => {
document.querySelector('.content').innerHTML = res.responseText;
});
// start front-end application
app.listen();
```
Install the dependencies and run the test suite:
### Routes
```bash
$ npm install
$ npm test
```
## Navigation path and frontexpress routing
### Disclaimer
>
> In this first version of frontexpress, the API is not completely the mirror of the expressjs one.
>
> There are some missing methods. Currently, the use, get, post... methods having a middleware array as parameter are not available.
> The string pattern to define route paths is not yet implemented.
>
> Obviously, the objective is to have the same API as expressjs when the methods make sense browser side.
>
### Basic routing
Listen navigation (GET request) on path /hello:
Listen GET requests on path /hello:
```js
app.get('/hello', (req, res) => {
@ -96,7 +62,7 @@ app.get('/hello', (req, res) => {
});
```
Listen a POST request on path /item:
Listen POST requests on path /item:
```js
app.post('/item', (req, res) => {
@ -104,9 +70,7 @@ app.post('/item', (req, res) => {
});
```
### Routing based on RegExp
Listen navigation on paths which start with /api/:
Listen GET requests on path starting with /api/:
```js
app.get(/^api\//, (req, res) => {
@ -114,6 +78,68 @@ app.get(/^api\//, (req, res) => {
});
```
Get parameters from path
```js
app.get('/product/:id', (req, res) => {
// if we have /product/42 then
// req.params.id = 42
});
```
```js
app.get('/user/:firstname?/:lastname', (req, res) => {
// if we have /user/camel/aissani then
// req.params.firstname = 'camel'
// req.params.lastname = 'aissani'
// if we have /user/aissani then
// req.params.firstname = undefined
// req.params.lastname = 'aissani'
});
```
```js
app.get('/user/:id', (req, res) => {
// if we have /user/1,2,3 then
// req.params.id = [1,2,3]
});
```
You can have the full capabilities of Express-style path with this plugin [frontexpress-path-to-regexp](https://github.com/camelaissani/frontexpress-path-to-regexp)
### Middleware object
The middleware object gives access to more hooks
```js
class MyMiddleware = new Middleware {
entered(req) {
// before request sent
}
updated(req, res) {
// after request sent
// res has the request response
window.alert('Hello World');
}
exited(req) {
// before a new request sent
}
failed(req, res) {
// on request failed
}
next() {
// for chaining
return true;
}
}
app.get('/hello', new MyMiddleware());
```
### Chain handlers
You can provide multiple handlers functions on a navigation path. Invoking ```next()``` function allows to chain the handlers.
@ -138,7 +164,7 @@ h2!
h3 is ignored because ```next()``` function was not invoked.
### app.route()
#### app.route()
You can create chainable route handlers for a route path by using ```app.route()```.
@ -149,7 +175,7 @@ app.route('/book')
.put((req, res) => { console.log('Update the book') });
```
### frontexpress.Router
#### frontexpress.Router
Use the ```frontexpress.Router``` class to create modular, mountable route handlers.
@ -187,121 +213,77 @@ import birds from './birds';
app.use('/birds', birds);
```
## API
## Plugins
| | Method | Short description |
| :------------- | :--------------| :----------------- |
|Frontexpress |||
||[frontexpress()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpress-1)|Creates an instance of application|
||[frontexpress.Router()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressrouter)|Creates a Router object|
||[frontexpress.Middleware](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressmiddleware)|Returns the Middleware class |
||||
|Application |||
||[set(setting, value)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationsetsetting-val)|Assigns a setting|
||[listen(callback)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationlistencallback)|Starts the application|
||[route(uri)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationrouteuri)|Gets a Router initialized with a root path|
||[use(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationuseuri-middleware)|Sets a middleware|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
||[httpGet(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a GET ajax request|
||[httpPost(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a POST ajax request|
||[httpPut(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a PUT ajax request|
||[httpDelete(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a DELETE ajax request|
||||
|Router |||
||[use(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerusemiddleware)|Sets a middleware|
||[all(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerallmiddleware)|Sets a middleware on all HTTP method requests|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
|Middleware |||
||[entered(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareenteredrequest)|Invoked by the app before an ajax request is sent|
||[exited(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareexitedrequest)|Invoked by the app before a new ajax request is sent|
||[updated(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareupdatedrequest-response)|Invoked by the app after an ajax request has responded|
||[failed(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarefailedrequest-response)|Invoked by the app after an ajax request has failed|
||[next()](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarenext)|Allows to break the middleware chain execution|
### Extend frontexpress via plugins:
- [frontexpress-path-to-regexp](https://github.com/camelaissani/frontexpress-path-to-regexp): Add the ability to support Express-style path string such as /user/:name, /user*...
### middleware function
Others are coming
After registering a middleware function, the application invokes it with these parameters:
### Write your own plugin
It consists to simply create an object with two properties:
- **name**: the name of your plugin
- **plugin**: the function containing the implementation
Let's assume that we have implemented this plugin in the `frontexpress-my-plugin.js` file as below:
```js
(request, response, next) => {
next();
export default {
name: 'My plugin',
plugin(app) {
// the plugin implementation goes here
// Some ideas
// you can get settings
// const transformer = app.get('http GET transformer');
//
// you can set settings
// app.set('http requester', {
// fetch() {
// ...
// }});
//
// you can complete routes
// app.get(...)
}
};
```
**request**: `Object`, the ajax request information sent by the app
**response**: `Object`, the response of request
**next**: `Function`, the `next()` function to call to not break the middleware execution chain
### request object
To use it:
```js
{
method,
uri,
headers,
data,
history: {
state,
title,
uri
}
}
import frontexpress from 'frontexpress';
import myPlugin from 'frontexpress-my-plugin';
// Front-end application
const app = frontexpress();
// tell to frontexpress to use your plugin
app.use(myPlugin);
```
**method**: `String`, HTTP methods 'GET', 'POST'...
## More
**uri**: `String`, path
[API](https://github.com/camelaissani/frontexpress/blob/master/docs/api.md)
**headers**: `Object`, custom HTTP headers
## Tests
**data**: `Object`, data attached to the request
Clone the git repository:
**history**: `Object`, object with properties state, title and uri
**If the history object is set, it will activate the browser history management.** See [browser pushState() method](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) for more information about state, title, and uri (url).
> uri and history.uri can be different.
### response object
```js
{
status,
statusText,
responseText,
errorThrown,
errors
}
```bash
$ git clone git@github.com:camelaissani/frontexpress.git
$ cd frontexpress
```
**status**: `Number`, HTTP status 200, 404, 401, 500...
**statusText**: `String`
**responseText**: `String` response content
**errorThrown**: `Object` exception thrown (if request fails)
**errors**: `String` error description (if request fails)
Install the dependencies and run the test suite:
```bash
$ npm install
$ npm test
```
## License
[MIT](LICENSE)
[MIT](LICENSE)

View File

@ -21,6 +21,7 @@
"ignore": [
"**/.*",
"index.js",
"gzipsize.js",
"rollup.config.dev.js",
"rollup.config.prod.js",
"node_modules",

109
docs/api.md Normal file
View File

@ -0,0 +1,109 @@
# API
| | Method | Short description |
| :------------- | :--------------| :----------------- |
|Frontexpress |||
||[frontexpress()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpress-1)|Creates an instance of application|
||[frontexpress.Router()](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressrouter)|Creates a Router object|
||[frontexpress.Middleware](https://github.com/camelaissani/frontexpress/blob/master/docs/frontexpress.md#frontexpressmiddleware)|Returns the Middleware class |
||||
|Application |||
||[set(setting, value)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationsetsetting-val)|Assigns a setting|
||[listen(callback)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationlistencallback)|Starts the application|
||[route(uri)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationrouteuri)|Gets a Router initialized with a root path|
||[use(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationuseuri-middleware)|Sets a middleware|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationgeturi-middleware-applicationposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
||[httpGet(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a GET ajax request|
||[httpPost(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a POST ajax request|
||[httpPut(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a PUT ajax request|
||[httpDelete(request, success, failure)](https://github.com/camelaissani/frontexpress/blob/master/docs/application.md#applicationhttpgetrequest-success-failure-applicationhttppostrequest-success-failure)|Invokes a DELETE ajax request|
||||
|Router |||
||[use(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerusemiddleware)|Sets a middleware|
||[all(middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routerallmiddleware)|Sets a middleware on all HTTP method requests|
||||
||[get(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a GET request|
||[post(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a POST request|
||[put(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a PUT request|
||[delete(uri, middleware)](https://github.com/camelaissani/frontexpress/blob/master/docs/router.md#routergeturi-middleware-routerposturi-middleware)|Applies a middleware on given path for a DELETE request|
||||
|Middleware |||
||[entered(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareenteredrequest)|Invoked by the app before an ajax request is sent|
||[exited(request)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareexitedrequest)|Invoked by the app before a new ajax request is sent|
||[updated(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewareupdatedrequest-response)|Invoked by the app after an ajax request has responded|
||[failed(request, response)](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarefailedrequest-response)|Invoked by the app after an ajax request has failed|
||[next()](https://github.com/camelaissani/frontexpress/blob/master/docs/middleware.md#middlewarenext)|Allows to break the middleware chain execution|
# middleware function
After registering a middleware function, the application invokes it with these parameters:
```js
(request, response, next) => {
next();
}
```
**request**: `Object`, the ajax request information sent by the app
**response**: `Object`, the response of request
**next**: `Function`, the `next()` function to call to not break the middleware execution chain
# request object
```js
{
method,
uri,
headers,
data,
history: {
state,
title,
uri
}
}
```
**method**: `String`, HTTP methods 'GET', 'POST'...
**uri**: `String`, path
**headers**: `Object`, custom HTTP headers
**data**: `Object`, data attached to the request
**history**: `Object`, object with properties state, title and uri
>**If the history object is set, it will activate the browser history management.** See [browser pushState() method](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) for more information about state, title, and uri (url).
> uri and history.uri can be different.
**params**: `Object`, object containing the path parameters
# response object
```js
{
status,
statusText,
responseText,
errorThrown,
errors
}
```
**status**: `Number`, HTTP status 200, 404, 401, 500...
**statusText**: `String`
**responseText**: `String` response content
**errorThrown**: `Object` exception thrown (if request fails)
**errors**: `String` error description (if request fails)

View File

@ -6,9 +6,9 @@ var frontexpress = (function () {
* @private
*/
var HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
var HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
// not supported yet
// HEAD', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH';
// HEAD', 'CONNECT', 'OPTIONS', 'TRACE';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
@ -136,200 +136,6 @@ var toConsumableArray = function (arr) {
}
};
/**
* Module dependencies.
* @private
*/
var Requester = function () {
function Requester() {
classCallCheck(this, Requester);
}
createClass(Requester, [{
key: 'fetch',
/**
* Make an ajax request.
*
* @param {Object} request
* @param {Function} success callback
* @param {Function} failure callback
* @private
*/
value: function fetch(request, resolve, reject) {
var method = request.method,
uri = request.uri,
headers = request.headers,
data = request.data;
var success = function success(responseText) {
resolve(request, {
status: 200,
statusText: 'OK',
responseText: responseText
});
};
var fail = function fail(_ref) {
var status = _ref.status,
statusText = _ref.statusText,
errorThrown = _ref.errorThrown;
reject(request, {
status: status,
statusText: statusText,
errorThrown: errorThrown,
errors: 'HTTP ' + status + ' ' + (statusText ? statusText : '')
});
};
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
//XMLHttpRequest.DONE
if (xmlhttp.status === 200) {
success(xmlhttp.responseText);
} else {
fail({ status: xmlhttp.status, statusText: xmlhttp.statusText });
}
}
};
try {
xmlhttp.open(method, uri, true);
if (headers) {
Object.keys(headers).forEach(function (header) {
xmlhttp.setRequestHeader(header, headers[header]);
});
}
if (data) {
xmlhttp.send(data);
} else {
xmlhttp.send();
}
} catch (errorThrown) {
fail({ errorThrown: errorThrown });
}
}
}]);
return Requester;
}();
/**
* Module dependencies.
* @private
*/
/**
* Settings object.
* @private
*/
var Settings = function () {
/**
* Initialize the settings.
*
* - setup default configuration
*
* @private
*/
function Settings() {
classCallCheck(this, Settings);
// default settings
this.settings = {
'http requester': new Requester(),
'http GET transformer': {
uri: function uri(_ref) {
var _uri = _ref.uri,
headers = _ref.headers,
data = _ref.data;
if (!data) {
return _uri;
}
var uriWithoutAnchor = _uri,
anchor = '';
var match = /^(.*)(#.*)$/.exec(_uri);
if (match) {
var _$exec = /^(.*)(#.*)$/.exec(_uri);
var _$exec2 = slicedToArray(_$exec, 3);
uriWithoutAnchor = _$exec2[1];
anchor = _$exec2[2];
}
uriWithoutAnchor = Object.keys(data).reduce(function (gUri, d, index) {
gUri += '' + (index === 0 && gUri.indexOf('?') === -1 ? '?' : '&') + d + '=' + data[d];
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
}
// 'http POST transformer': {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// }
};
this.rules = {
'http requester': function httpRequester(requester) {
if (typeof requester.fetch !== 'function') {
throw new TypeError('setting http requester has no fetch method');
}
}
};
}
/**
* Assign `setting` to `val`
*
* @param {String} setting
* @param {*} [val]
* @private
*/
createClass(Settings, [{
key: 'set',
value: function set$$1(name, value) {
var checkRules = this.rules[name];
if (checkRules) {
checkRules(value);
}
this.settings[name] = value;
}
/**
* Return `setting`'s value.
*
* @param {String} setting
* @private
*/
}, {
key: 'get',
value: function get$$1(name) {
return this.settings[name];
}
}]);
return Settings;
}();
/**
* Middleware object.
* @public
@ -361,61 +167,66 @@ var Middleware = function () {
* @public
*/
// entered(request) { }
/**
* Invoked by the app before a new ajax request is sent or before the DOM is unloaded.
* See Application#_callMiddlewareExited documentation for details.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @public
*/
// exited(request) { }
/**
* Invoked by the app after an ajax request has responded or on DOM ready
* (document.readyState === 'interactive').
* See Application#_callMiddlewareUpdated documentation for details.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @param {Object} response
* @public
*/
// updated(request, response) { }
/**
* Invoked by the app when an ajax request has failed.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @param {Object} response
* @public
*/
// failed(request, response) { }
/**
* Allow the hand over to the next middleware object or function.
*
* Override this method and return `false` to break execution of
* middleware chain.
*
* @return {Boolean} `true` by default
*
* @public
*/
createClass(Middleware, [{
key: 'entered',
value: function entered(request) {}
/**
* Invoked by the app before a new ajax request is sent or before the DOM is unloaded.
* See Application#_callMiddlewareExited documentation for details.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @public
*/
}, {
key: 'exited',
value: function exited(request) {}
/**
* Invoked by the app after an ajax request has responded or on DOM ready
* (document.readyState === 'interactive').
* See Application#_callMiddlewareUpdated documentation for details.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @param {Object} response
* @public
*/
}, {
key: 'updated',
value: function updated(request, response) {}
/**
* Invoked by the app when an ajax request has failed.
*
* Override this method to add your custom behaviour
*
* @param {Object} request
* @param {Object} response
* @public
*/
}, {
key: 'failed',
value: function failed(request, response) {}
/**
* Allow the hand over to the next middleware object or function.
*
* Override this method and return `false` to break execution of
* middleware chain.
*
* @return {Boolean} `true` by default
*
* @public
*/
}, {
key: 'next',
value: function next() {
return true;
@ -538,26 +349,11 @@ var Router = function () {
}, {
key: 'routes',
value: function routes(uri, method) {
value: function routes(application, request) {
request.params = request.params || {};
var isRouteMatch = application.get('route matcher');
return this._routes.filter(function (route) {
if (route.method && route.method !== method) {
return false;
}
if (!route.uri || !uri) {
return true;
}
//remove query string from uri to test
//remove anchor from uri to test
var match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(uri);
var baseUriToCheck = match[1] || match[2] || match[3];
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri);
}
return route.uri === baseUriToCheck;
return isRouteMatch(request, route);
});
}
@ -719,6 +515,284 @@ HTTP_METHODS.forEach(function (method) {
};
});
function routeMatcher(request, route) {
// check if http method are equals
if (route.method && route.method !== request.method) {
return false;
}
// route and uri not defined always match
if (!route.uri || !request.uri) {
return true;
}
//remove query string and anchor from uri to test
var match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(request.uri);
var baseUriToCheck = match[1] || match[2] || match[3];
// if route is a regexp path
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri) !== null;
}
// if route is parameterized path
if (route.uri.indexOf(':') !== -1) {
var decodeParmeterValue = function decodeParmeterValue(v) {
return !isNaN(parseFloat(v)) && isFinite(v) ? Number.isInteger(v) ? Number.parseInt(v, 10) : Number.parseFloat(v) : v;
};
// figure out key names
var keys = [];
var keysRE = /:([^\/\?]+)\??/g;
var keysMatch = keysRE.exec(route.uri);
while (keysMatch != null) {
keys.push(keysMatch[1]);
keysMatch = keysRE.exec(route.uri);
}
// change parameterized path to regexp
var regExpUri = route.uri
// :parameter?
.replace(/\/:[^\/]+\?/g, '(?:\/([^\/]+))?')
// :parameter
.replace(/:[^\/]+/g, '([^\/]+)')
// escape all /
.replace('/', '\\/');
// checks if uri match
var routeMatch = baseUriToCheck.match(new RegExp('^' + regExpUri + '$'));
if (!routeMatch) {
return false;
}
// update params in request with keys
request.params = Object.assign(request.params, keys.reduce(function (acc, key, index) {
var value = routeMatch[index + 1];
if (value) {
value = value.indexOf(',') !== -1 ? value.split(',').map(function (v) {
return decodeParmeterValue(v);
}) : value = decodeParmeterValue(value);
}
acc[key] = value;
return acc;
}, {}));
return true;
}
// if route is a simple path
return route.uri === baseUriToCheck;
}
/**
* Module dependencies.
* @private
*/
var Requester = function () {
function Requester() {
classCallCheck(this, Requester);
}
createClass(Requester, [{
key: 'fetch',
/**
* Make an ajax request.
*
* @param {Object} request
* @param {Function} success callback
* @param {Function} failure callback
* @private
*/
value: function fetch(request, resolve, reject) {
var method = request.method,
uri = request.uri,
headers = request.headers,
data = request.data;
var success = function success(responseText) {
resolve(request, {
status: 200,
statusText: 'OK',
responseText: responseText
});
};
var fail = function fail(_ref) {
var status = _ref.status,
statusText = _ref.statusText,
errorThrown = _ref.errorThrown;
reject(request, {
status: status,
statusText: statusText,
errorThrown: errorThrown,
errors: 'HTTP ' + status + ' ' + (statusText ? statusText : '')
});
};
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
//XMLHttpRequest.DONE
if (xmlhttp.status === 200) {
success(xmlhttp.responseText);
} else {
fail({ status: xmlhttp.status, statusText: xmlhttp.statusText });
}
}
};
try {
xmlhttp.open(method, uri, true);
if (headers) {
Object.keys(headers).forEach(function (header) {
xmlhttp.setRequestHeader(header, headers[header]);
});
}
if (data) {
xmlhttp.send(data);
} else {
xmlhttp.send();
}
} catch (errorThrown) {
fail({ errorThrown: errorThrown });
}
}
}]);
return Requester;
}();
var httpGetTransformer = {
uri: function uri(_ref2) {
var _uri = _ref2.uri,
headers = _ref2.headers,
data = _ref2.data;
if (!data) {
return _uri;
}
var uriWithoutAnchor = _uri,
anchor = '';
var match = /^(.*)(#.*)$/.exec(_uri);
if (match) {
var _$exec = /^(.*)(#.*)$/.exec(_uri);
var _$exec2 = slicedToArray(_$exec, 3);
uriWithoutAnchor = _$exec2[1];
anchor = _$exec2[2];
}
uriWithoutAnchor = Object.keys(data).reduce(function (gUri, d, index) {
gUri += '' + (index === 0 && gUri.indexOf('?') === -1 ? '?' : '&') + d + '=' + data[d];
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
};
// export const httpPostTransformer = {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// };
/**
* Module dependencies.
* @private
*/
function errorIfNotFunction(toTest, message) {
if (typeof toTest !== 'function') {
throw new TypeError(message);
}
}
/**
* Settings object.
* @private
*/
var Settings = function () {
/**
* Initialize the settings.
*
* - setup default configuration
*
* @private
*/
function Settings() {
classCallCheck(this, Settings);
// default settings
this.settings = {
'http requester': new Requester(),
'http GET transformer': httpGetTransformer,
// 'http POST transformer': httpPostTransformer,
'route matcher': routeMatcher
};
this.rules = {
'http requester': function httpRequester(requester) {
errorIfNotFunction(requester.fetch, 'setting http requester has no fetch function');
},
'http GET transformer': function httpGETTransformer(transformer) {
if (!transformer || !transformer.uri && !transformer.headers && !transformer.data) {
throw new TypeError('setting http transformer one of functions: uri, headers, data is missing');
}
},
'route matcher': function routeMatcher$$1(_routeMatcher) {
errorIfNotFunction(_routeMatcher, 'setting route matcher is not a function');
}
};
}
/**
* Assign `setting` to `val`
*
* @param {String} setting
* @param {*} [val]
* @private
*/
createClass(Settings, [{
key: 'set',
value: function set$$1(name, value) {
var checkRules = this.rules[name];
if (checkRules) {
checkRules(value);
}
this.settings[name] = value;
}
/**
* Return `setting`'s value.
*
* @param {String} setting
* @private
*/
}, {
key: 'get',
value: function get$$1(name) {
return this.settings[name];
}
}]);
return Settings;
}();
/**
* Module dependencies.
* @private
@ -742,9 +816,8 @@ var Application = function () {
classCallCheck(this, Application);
this.routers = [];
// this.isDOMLoaded = false;
// this.isDOMReady = false;
this.settings = new Settings();
this.plugins = [];
}
/**
@ -763,6 +836,8 @@ var Application = function () {
createClass(Application, [{
key: 'set',
value: function set$$1() {
var _settings;
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
@ -773,10 +848,7 @@ var Application = function () {
}
// set behaviour
var name = args[0],
value = args[1];
this.settings.set(name, value);
(_settings = this.settings).set.apply(_settings, args);
return this;
}
@ -802,6 +874,12 @@ var Application = function () {
value: function listen(callback) {
var _this = this;
var request = { method: 'GET', uri: window.location.pathname + window.location.search };
var response = { status: 200, statusText: 'OK' };
var currentRoutes = this._routes(request);
this._callMiddlewareMethod('entered', currentRoutes, request);
// manage history
window.onpopstate = function (event) {
if (event.state) {
@ -809,38 +887,31 @@ var Application = function () {
_request = _event$state.request,
_response = _event$state.response;
var _currentRoutes = _this._routes(_request.uri, _request.method);
_this._callMiddlewareMethod('entered', _currentRoutes, _request);
_this._callMiddlewareMethod('updated', _currentRoutes, _request, _response);
['exited', 'entered', 'updated'].forEach(function (middlewareMethod) {
return _this._callMiddlewareMethod(middlewareMethod, _this._routes(_request), _request, _response);
});
}
};
// manage page loading/refreshing
var request = { method: 'GET', uri: window.location.pathname + window.location.search };
var response = { status: 200, statusText: 'OK' };
var currentRoutes = this._routes();
window.onbeforeunload = function () {
_this._callMiddlewareMethod('exited');
};
var whenPageIsInteractiveFn = function whenPageIsInteractiveFn() {
_this.plugins.forEach(function (pluginObject) {
return pluginObject.plugin(_this);
});
_this._callMiddlewareMethod('updated', currentRoutes, request, response);
if (callback) {
callback(request, response);
}
};
window.onbeforeunload = function () {
_this._callMiddlewareMethod('exited');
};
document.onreadystatechange = function () {
// DOM ready state
switch (document.readyState) {
case 'loading':
_this._callMiddlewareMethod('entered', currentRoutes, request);
break;
case 'interactive':
whenPageIsInteractiveFn();
break;
if (document.readyState === 'interactive') {
whenPageIsInteractiveFn();
}
};
@ -873,6 +944,7 @@ var Application = function () {
/**
* Use the given middleware function or object, with optional _uri_.
* Default _uri_ is "/".
* Or use the given plugin
*
* // middleware function will be applied on path "/"
* app.use((req, res, next) => {console.log('Hello')});
@ -880,8 +952,16 @@ var Application = function () {
* // middleware object will be applied on path "/"
* app.use(new Middleware());
*
* // use a plugin
* app.use({
* name: 'My plugin name',
* plugin(application) {
* // here plugin implementation
* }
* });
*
* @param {String} uri
* @param {Middleware|Function} middleware object or function
* @param {Middleware|Function|plugin} middleware object, middleware function, plugin
* @return {app} for chaining
*
* @public
@ -897,19 +977,24 @@ var Application = function () {
var _toParameters = toParameters(args),
baseUri = _toParameters.baseUri,
router = _toParameters.router,
middleware = _toParameters.middleware;
middleware = _toParameters.middleware,
plugin = _toParameters.plugin;
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach(function (method) {
router[method.toLowerCase()](middleware);
});
if (plugin) {
this.plugins.push(plugin);
} else {
throw new TypeError('method takes at least a middleware or a router');
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach(function (method) {
router[method.toLowerCase()](middleware);
});
} else {
throw new TypeError('method takes at least a middleware or a router');
}
this.routers.push(router);
}
this.routers.push(router);
return this;
}
@ -923,16 +1008,13 @@ var Application = function () {
}, {
key: '_routes',
value: function _routes() {
var uri = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.pathname + window.location.search;
var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'GET';
value: function _routes(request) {
var _this2 = this;
var currentRoutes = [];
this.routers.forEach(function (router) {
currentRoutes.push.apply(currentRoutes, toConsumableArray(router.routes(uri, method)));
});
return currentRoutes;
return this.routers.reduce(function (acc, router) {
acc.push.apply(acc, toConsumableArray(router.routes(_this2, request)));
return acc;
}, []);
}
/**
@ -992,7 +1074,7 @@ var Application = function () {
}, {
key: '_fetch',
value: function _fetch(req, resolve, reject) {
var _this2 = this;
var _this3 = this;
var method = req.method,
uri = req.uri,
@ -1007,16 +1089,16 @@ var Application = function () {
_headersFn = httpMethodTransformer.headers,
_dataFn = httpMethodTransformer.data;
uri = _uriFn ? _uriFn({ uri: uri, headers: headers, data: data }) : uri;
headers = _headersFn ? _headersFn({ uri: uri, headers: headers, data: data }) : headers;
data = _dataFn ? _dataFn({ uri: uri, headers: headers, data: data }) : data;
req.uri = _uriFn ? _uriFn({ uri: uri, headers: headers, data: data }) : uri;
req.headers = _headersFn ? _headersFn({ uri: uri, headers: headers, data: data }) : headers;
req.data = _dataFn ? _dataFn({ uri: uri, headers: headers, data: data }) : data;
}
// calls middleware exited method
this._callMiddlewareMethod('exited');
// gathers all routes impacted by the uri
var currentRoutes = this._routes(uri, method);
var currentRoutes = this._routes(req);
// calls middleware entered method
this._callMiddlewareMethod('entered', currentRoutes, req);
@ -1026,12 +1108,12 @@ var Application = function () {
if (history) {
window.history.pushState({ request: request, response: response }, history.title, history.uri);
}
_this2._callMiddlewareMethod('updated', currentRoutes, request, response);
_this3._callMiddlewareMethod('updated', currentRoutes, request, response);
if (resolve) {
resolve(request, response);
}
}, function (request, response) {
_this2._callMiddlewareMethod('failed', currentRoutes, request, response);
_this3._callMiddlewareMethod('failed', currentRoutes, request, response);
if (reject) {
reject(request, response);
}
@ -1133,29 +1215,25 @@ HTTP_METHODS.reduce(function (reqProto, method) {
}, Application.prototype);
function toParameters(args) {
var _args, _args2, _args3, _args4;
var baseUri = void 0,
middleware = void 0,
router = void 0,
plugin = void 0,
which = void 0;
if (args && args.length > 0) {
if (args.length === 1) {
var _args = slicedToArray(args, 1);
which = _args[0];
} else {
var _args2 = slicedToArray(args, 2);
args.length === 1 ? (_args = args, _args2 = slicedToArray(_args, 1), which = _args2[0], _args) : (_args3 = args, _args4 = slicedToArray(_args3, 2), baseUri = _args4[0], which = _args4[1], _args3);
baseUri = _args2[0];
which = _args2[1];
}
if (which instanceof Router) {
router = which;
} else if (which instanceof Middleware || typeof which === 'function') {
middleware = which;
}
if (which instanceof Router) {
router = which;
} else if (which instanceof Middleware || typeof which === 'function') {
middleware = which;
} else if (which && which.plugin && typeof which.plugin === 'function') {
plugin = which;
}
return { baseUri: baseUri, middleware: middleware, router: router, which: which };
return { baseUri: baseUri, middleware: middleware, router: router, plugin: plugin, which: which };
}
/**

2
frontexpress.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,9 +26,8 @@ export default class Application {
constructor() {
this.routers = [];
// this.isDOMLoaded = false;
// this.isDOMReady = false;
this.settings = new Settings();
this.plugins = [];
}
@ -52,8 +51,7 @@ export default class Application {
}
// set behaviour
const [name, value] = args;
this.settings.set(name, value);
this.settings.set(...args);
return this;
}
@ -76,43 +74,41 @@ export default class Application {
*/
listen(callback) {
const request = {method: 'GET', uri: window.location.pathname + window.location.search};
const response = {status: 200, statusText: 'OK'};
const currentRoutes = this._routes(request);
this._callMiddlewareMethod('entered', currentRoutes, request);
// manage history
window.onpopstate = (event) => {
if (event.state) {
const {request, response} = event.state;
const currentRoutes = this._routes(request.uri, request.method);
this._callMiddlewareMethod('entered', currentRoutes, request);
this._callMiddlewareMethod('updated', currentRoutes, request, response);
[
'exited',
'entered',
'updated'
].forEach(middlewareMethod => this._callMiddlewareMethod(middlewareMethod, this._routes(request), request, response));
}
};
// manage page loading/refreshing
const request = {method: 'GET', uri: window.location.pathname + window.location.search};
const response = {status: 200, statusText: 'OK'};
const currentRoutes = this._routes();
window.onbeforeunload = () => {
this._callMiddlewareMethod('exited');
};
const whenPageIsInteractiveFn = () => {
this.plugins.forEach(pluginObject => pluginObject.plugin(this));
this._callMiddlewareMethod('updated', currentRoutes, request, response);
if (callback) {
callback(request, response);
}
};
window.onbeforeunload = () => {
this._callMiddlewareMethod('exited');
};
document.onreadystatechange = () => {
// DOM ready state
switch (document.readyState) {
case 'loading':
this._callMiddlewareMethod('entered', currentRoutes, request);
break;
case 'interactive':
if (document.readyState === 'interactive') {
whenPageIsInteractiveFn();
break;
}
};
@ -145,6 +141,7 @@ export default class Application {
/**
* Use the given middleware function or object, with optional _uri_.
* Default _uri_ is "/".
* Or use the given plugin
*
* // middleware function will be applied on path "/"
* app.use((req, res, next) => {console.log('Hello')});
@ -152,26 +149,38 @@ export default class Application {
* // middleware object will be applied on path "/"
* app.use(new Middleware());
*
* // use a plugin
* app.use({
* name: 'My plugin name',
* plugin(application) {
* // here plugin implementation
* }
* });
*
* @param {String} uri
* @param {Middleware|Function} middleware object or function
* @param {Middleware|Function|plugin} middleware object, middleware function, plugin
* @return {app} for chaining
*
* @public
*/
use(...args) {
let {baseUri, router, middleware} = toParameters(args);
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach((method) => {
router[method.toLowerCase()](middleware);
});
let {baseUri, router, middleware, plugin} = toParameters(args);
if (plugin) {
this.plugins.push(plugin);
} else {
throw new TypeError('method takes at least a middleware or a router');
if (router) {
router.baseUri = baseUri;
} else if (middleware) {
router = new Router(baseUri);
HTTP_METHODS.forEach((method) => {
router[method.toLowerCase()](middleware);
});
} else {
throw new TypeError('method takes at least a middleware or a router');
}
this.routers.push(router);
}
this.routers.push(router);
return this;
}
@ -184,13 +193,11 @@ export default class Application {
* @private
*/
_routes(uri=window.location.pathname + window.location.search, method='GET') {
const currentRoutes = [];
this.routers.forEach((router) => {
currentRoutes.push(...router.routes(uri, method));
});
return currentRoutes;
_routes(request) {
return this.routers.reduce((acc, router) => {
acc.push(...router.routes(this, request));
return acc;
}, []);
}
@ -253,16 +260,16 @@ export default class Application {
const httpMethodTransformer = this.get(`http ${method} transformer`);
if (httpMethodTransformer) {
const {uri: _uriFn, headers: _headersFn, data: _dataFn } = httpMethodTransformer;
uri = _uriFn ? _uriFn({uri, headers, data}) : uri;
headers = _headersFn ? _headersFn({uri, headers, data}) : headers;
data = _dataFn ? _dataFn({uri, headers, data}) : data;
req.uri = _uriFn ? _uriFn({uri, headers, data}) : uri;
req.headers = _headersFn ? _headersFn({uri, headers, data}) : headers;
req.data = _dataFn ? _dataFn({uri, headers, data}) : data;
}
// calls middleware exited method
this._callMiddlewareMethod('exited');
// gathers all routes impacted by the uri
const currentRoutes = this._routes(uri, method);
const currentRoutes = this._routes(req);
// calls middleware entered method
this._callMiddlewareMethod('entered', currentRoutes, req);
@ -369,19 +376,17 @@ HTTP_METHODS.reduce((reqProto, method) => {
export function toParameters(args) {
let baseUri, middleware, router, which;
if (args && args.length > 0) {
if (args.length === 1) {
[which,] = args;
} else {
[baseUri, which,] = args;
}
let baseUri, middleware, router, plugin, which;
if (which instanceof Router) {
router = which;
} else if ((which instanceof Middleware) || (typeof which === 'function')) {
middleware = which;
}
args.length === 1 ? [which,] = args : [baseUri, which,] = args;
if (which instanceof Router) {
router = which;
} else if (which instanceof Middleware || typeof which === 'function') {
middleware = which;
} else if(which && which.plugin && typeof which.plugin === 'function') {
plugin = which;
}
return {baseUri, middleware, router, which};
return {baseUri, middleware, router, plugin, which};
}

View File

@ -3,6 +3,6 @@
* @private
*/
export default ['GET', 'POST', 'PUT', 'DELETE'];
export default ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
// not supported yet
// HEAD', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH';
// HEAD', 'CONNECT', 'OPTIONS', 'TRACE';

View File

@ -27,7 +27,7 @@ export default class Middleware {
* @public
*/
// entered(request) { }
entered(request) { }
/**
@ -40,7 +40,7 @@ export default class Middleware {
* @public
*/
// exited(request) { }
exited(request) { }
/**
@ -55,7 +55,7 @@ export default class Middleware {
* @public
*/
// updated(request, response) { }
updated(request, response) { }
/**
@ -67,7 +67,7 @@ export default class Middleware {
* @param {Object} response
* @public
*/
// failed(request, response) { }
failed(request, response) { }
/**

View File

@ -67,3 +67,34 @@ export default class Requester {
}
}
}
export const httpGetTransformer = {
uri({uri, headers, data}) {
if (!data) {
return uri;
}
let [uriWithoutAnchor, anchor] = [uri, ''];
const match = /^(.*)(#.*)$/.exec(uri);
if (match) {
[,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);
}
uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {
gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
};
// export const httpPostTransformer = {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// };

View File

@ -135,26 +135,11 @@ export default class Router {
* @private
*/
routes(uri, method) {
routes(application, request) {
request.params = request.params || {};
const isRouteMatch = application.get('route matcher');
return this._routes.filter((route) => {
if (route.method && route.method !== method) {
return false;
}
if (!route.uri || !uri) {
return true;
}
//remove query string from uri to test
//remove anchor from uri to test
const match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(uri);
const baseUriToCheck = match[1] || match[2] || match[3];
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri);
}
return route.uri === baseUriToCheck;
return isRouteMatch(request, route);
});
}
@ -267,3 +252,71 @@ HTTP_METHODS.forEach((method) => {
return this;
};
});
export function routeMatcher(request, route) {
// check if http method are equals
if (route.method && route.method !== request.method) {
return false;
}
// route and uri not defined always match
if (!route.uri || !request.uri) {
return true;
}
//remove query string and anchor from uri to test
const match = /^(.*)\?.*#.*|(.*)(?=\?|#)|(.*[^\?#])$/.exec(request.uri);
const baseUriToCheck = match[1] || match[2] || match[3];
// if route is a regexp path
if (route.uri instanceof RegExp) {
return baseUriToCheck.match(route.uri) !== null;
}
// if route is parameterized path
if (route.uri.indexOf(':') !== -1) {
const decodeParmeterValue = (v) => {
return !isNaN(parseFloat(v)) && isFinite(v) ? (Number.isInteger(v) ? Number.parseInt(v, 10) : Number.parseFloat(v)) : v;
};
// figure out key names
const keys = [];
const keysRE = /:([^\/\?]+)\??/g;
let keysMatch = keysRE.exec(route.uri);
while (keysMatch != null) {
keys.push(keysMatch[1]);
keysMatch = keysRE.exec(route.uri);
}
// change parameterized path to regexp
const regExpUri = route.uri
// :parameter?
.replace(/\/:[^\/]+\?/g, '(?:\/([^\/]+))?')
// :parameter
.replace(/:[^\/]+/g, '([^\/]+)')
// escape all /
.replace('/', '\\/');
// checks if uri match
const routeMatch = baseUriToCheck.match(new RegExp(`^${regExpUri}$`));
if (!routeMatch) {
return false;
}
// update params in request with keys
request.params = Object.assign(request.params, keys.reduce((acc, key, index) => {
let value = routeMatch[index + 1];
if (value) {
value = value.indexOf(',') !== -1 ? value.split(',').map(v => decodeParmeterValue(v)) : value = decodeParmeterValue(value);
}
acc[key] = value;
return acc;
}, {}));
return true;
}
// if route is a simple path
return route.uri === baseUriToCheck;
}

View File

@ -2,9 +2,15 @@
* Module dependencies.
* @private
*/
import {routeMatcher} from './router';
import Requester, {httpGetTransformer} from './requester';
import Requester from './requester';
function errorIfNotFunction(toTest, message) {
if(typeof toTest !== 'function') {
throw new TypeError(message);
}
}
/**
* Settings object.
@ -26,43 +32,22 @@ export default class Settings {
// default settings
this.settings = {
'http requester': new Requester(),
'http GET transformer': {
uri({uri, headers, data}) {
if (!data) {
return uri;
}
let [uriWithoutAnchor, anchor] = [uri, ''];
const match = /^(.*)(#.*)$/.exec(uri);
if (match) {
[,uriWithoutAnchor, anchor] = /^(.*)(#.*)$/.exec(uri);
}
uriWithoutAnchor = Object.keys(data).reduce((gUri, d, index) => {
gUri += `${(index === 0 && gUri.indexOf('?') === -1)?'?':'&'}${d}=${data[d]}`;
return gUri;
}, uriWithoutAnchor);
return uriWithoutAnchor + anchor;
}
}
// 'http POST transformer': {
// headers({uri, headers, data}) {
// if (!data) {
// return headers;
// }
// const updatedHeaders = headers || {};
// if (!updatedHeaders['Content-Type']) {
// updatedHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
// }
// return updatedHeaders;
// }
// }
'http GET transformer': httpGetTransformer,
// 'http POST transformer': httpPostTransformer,
'route matcher': routeMatcher
};
this.rules = {
'http requester': (requester) => {
if(typeof requester.fetch !== 'function') {
throw new TypeError('setting http requester has no fetch method');
errorIfNotFunction(requester.fetch , 'setting http requester has no fetch function');
},
'http GET transformer': (transformer) => {
if (!transformer || (!transformer.uri && !transformer.headers && !transformer.data)) {
throw new TypeError('setting http transformer one of functions: uri, headers, data is missing');
}
},
'route matcher': (routeMatcher) => {
errorIfNotFunction(routeMatcher, 'setting route matcher is not a function');
}
};
}

View File

@ -1,15 +1,16 @@
{
"name": "frontexpress",
"version": "1.0.1",
"version": "1.2.0",
"description": "Frontexpress manages routes in browser like ExpressJS on Node",
"main": "dist/frontexpress.js",
"jsnext:main": "lib/frontexpress.js",
"scripts": {
"lint": "eslint .",
"only-test": "mocha --compilers js:babel-core/register",
"test-only": "mocha --compilers js:babel-core/register",
"test": "npm run lint && babel-node node_modules/.bin/babel-istanbul cover node_modules/.bin/_mocha",
"gzipsize": "babel-node gzipsize.js",
"frontpackage": "rollup -c rollup.config.dev.js && rollup -c rollup.config.prod.js && npm run gzipsize",
"prepublish": "rimraf dist && babel lib -d dist"
"prepublish": "rimraf dist && babel lib -d dist && npm run frontpackage"
},
"author": "Camel Aissani <camel.aissani@gmail.com> (https://nuageprive.fr)",
"license": "MIT",
@ -33,23 +34,28 @@
"babel-core": "^6.21.0",
"babel-eslint": "^7.1.1",
"babel-istanbul": "^0.12.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-preset-babili": "0.0.9",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-rollup": "^3.0.0",
"babel-register": "^6.18.0",
"bytesize": "^0.2.0",
"chai": "^3.5.0",
"eslint": "^3.12.2",
"eslint-loader": "^1.6.1",
"expose-loader": "^0.7.1",
"chai": "^4.*",
"eslint": "^3.*",
"istanbul": "^0.4.5",
"mocha": "^3.2.0",
"rimraf": "^2.5.4",
"rollup": "^0.38.3",
"rollup": "^0.*",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-uglify": "^1.0.1",
"sinon": "^1.17.6",
"uglify-js": "github:mishoo/UglifyJS2#harmony"
}
"rollup-plugin-uglify-es": "0.0.1",
"sinon": "^1.*"
},
"files": [
"dist/",
"docs/",
"lib/",
"README.md",
"LICENCE",
"frontexpress.js",
"frontexpress.min.js",
"frontexpress.min.js.map"
]
}

View File

@ -1,5 +1,4 @@
import uglify from 'rollup-plugin-uglify';
import { minify } from 'uglify-js';
import uglify from 'rollup-plugin-uglify-es';
import babel from 'rollup-plugin-babel';
export default {
@ -13,13 +12,6 @@ export default {
babelrc: false,
presets: ['es2015-rollup']
}),
uglify({
compress: {
warnings: false,
},
output: {
comments: false
}
}, minify)
uglify()
]
};

View File

@ -989,4 +989,22 @@ describe('Application', () => {
});
});
});
describe('plugin management', () => {
it('setup a plugin', (done) => {
const app = frontexpress();
app.use({
name: 'my plugin',
plugin(application) {
done();
}
});
app.listen();
//simulate readystatechange
document.readyState = 'interactive';
document.onreadystatechange();
});
});
});

20
test/middleware-test.js Normal file
View File

@ -0,0 +1,20 @@
/*eslint-env mocha*/
import {assert} from 'chai';
import Middleware from '../lib/middleware';
describe('Middleware', () => {
it('check exposed methods', () => {
const middleware = new Middleware();
assert(middleware.entered);
assert(middleware.exited);
assert(middleware.updated);
assert(middleware.failed);
assert(middleware.next);
middleware.entered();
middleware.exited();
middleware.updated();
middleware.failed();
assert(middleware.next());
});
});

View File

@ -4,6 +4,9 @@ import sinon from 'sinon';
import frontexpress from '../lib/frontexpress';
import HTTP_METHODS from '../lib/methods';
const application = frontexpress();
const routeMatcher = application.get('route matcher');
describe('Router', () => {
describe('generated methods', () => {
@ -12,6 +15,7 @@ describe('Router', () => {
assert(typeof router.all === 'function');
assert(typeof router.get === 'function');
assert(typeof router.put === 'function');
assert(typeof router.patch === 'function');
assert(typeof router.post === 'function');
assert(typeof router.delete === 'function');
});
@ -46,7 +50,7 @@ describe('Router', () => {
router.get(middleware);
const r1 = router.routes('/', 'GET');
const r1 = router.routes(application, {uri: '/', method: 'GET'});
assert(r1.length === 1);
assert(r1[0].uri === undefined);
assert(r1[0].method === 'GET');
@ -64,37 +68,37 @@ describe('Router', () => {
.post('/route2', middleware2)
.all('/route3', middleware3);
const r1 = router.routes('/route1', 'GET');
const r1 = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r1.length === 1);
assert(r1[0].uri === '/route1');
assert(r1[0].method === 'GET');
assert(r1[0].middleware === middleware1);
const r2 = router.routes('/route2', 'POST');
const r2 = router.routes(application, {uri: '/route2', method: 'POST'});
assert(r2.length === 1);
assert(r2[0].uri === '/route2');
assert(r2[0].method === 'POST');
assert(r2[0].middleware === middleware2);
let r3 = router.routes('/route3', 'GET');
let r3 = router.routes(application, {uri: '/route3', method: 'GET'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'GET');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'POST');
r3 = router.routes(application, {uri: '/route3', method: 'POST'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'POST');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'PUT');
r3 = router.routes(application, {uri: '/route3', method: 'PUT'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'PUT');
assert(r3[0].middleware === middleware3);
r3 = router.routes('/route3', 'DELETE');
r3 = router.routes(application, {uri: '/route3', method: 'DELETE'});
assert(r3.length === 1);
assert(r3[0].uri === '/route3');
assert(r3[0].method === 'DELETE');
@ -107,7 +111,7 @@ describe('Router', () => {
router.get(/^\/route1/, middleware);
const r = router.routes('/route1', 'GET');
const r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/route1').toString());
@ -120,7 +124,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
const r = router.routes('/route1/subroute', 'GET');
const r = router.routes(application, {uri: '/route1/subroute', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
});
@ -130,7 +134,7 @@ describe('Router', () => {
router.get(new frontexpress.Middleware());
const r = router.routes('/route1', 'GET');
const r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1');
});
@ -140,7 +144,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
const r = router.routes('/route1/subroute', 'GET');
const r = router.routes(application, {uri: '/route1/subroute', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
});
@ -150,7 +154,7 @@ describe('Router', () => {
router.get('/subroute ', new frontexpress.Middleware());
let r = router.routes('/route1/subroute', 'GET');
let r = router.routes(application, {uri: '/route1/subroute', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
@ -160,7 +164,7 @@ describe('Router', () => {
router.get(new frontexpress.Middleware());
r = router.routes('/route1', 'GET');
r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1');
});
@ -170,7 +174,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
let r = router.routes('/route1/subroute?a=b&c=d', 'GET');
let r = router.routes(application, {uri: '/route1/subroute?a=b&c=d', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -181,7 +185,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
let r = router.routes('/route1/subroute#a=b&c=d', 'GET');
let r = router.routes(application, {uri: '/route1/subroute#a=b&c=d', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -192,7 +196,7 @@ describe('Router', () => {
router.get('/subroute', new frontexpress.Middleware());
let r = router.routes('/route1/subroute?a=b&c=d#anchor1', 'GET');
let r = router.routes(application, {uri: '/route1/subroute?a=b&c=d#anchor1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1/subroute');
assert(r[0].data === undefined);
@ -264,7 +268,7 @@ describe('Router', () => {
router.get(middleware);
const r = router.routes('/', 'GET');
const r = router.routes(application, {uri: '/', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/');
assert(r[0].method === 'GET');
@ -277,7 +281,7 @@ describe('Router', () => {
router.get('/route1', middleware);
const r = router.routes('/route1', 'GET');
const r = router.routes(application, {uri: '/route1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri === '/route1');
assert(r[0].method === 'GET');
@ -295,7 +299,7 @@ describe('Router', () => {
const middleware = new frontexpress.Middleware();
router.get(middleware);
const r = router.routes('/part1', 'GET');
const r = router.routes(application, {uri: '/part1', method: 'GET'});
assert(r.length === 1);
assert(r[0].uri instanceof RegExp);
assert(r[0].uri.toString() === new RegExp('^\/part').toString());
@ -303,4 +307,113 @@ describe('Router', () => {
assert(r[0].middleware === middleware);
});
});
describe('check route matcher', () => {
it('/', () => {
const route = {uri: '/', method: 'GET'};
const request = {uri: '/', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
});
it('/a/b/c', () => {
const route = {uri: '/a/b/c', method: 'GET'};
let request = {uri: '/a/b/c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
request = {uri: '/a/b/c/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/^\//', () => {
const route = {uri: /^\//, method: 'GET'};
const request = {uri: '/a/b/c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {});
});
it('/:id', () => {
const route = {uri: '/:id', method: 'GET'};
const request = {uri: '/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
});
it('/user/:id', () => {
const route = {uri: '/user/:id', method: 'GET'};
let request = {uri: '/user/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
request = {uri: '/user/100.2122', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 100.2122);
request = {uri: '/user', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
request = {uri: '/user/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:id with id as coma separated values', () => {
const route = {uri: '/user/:id', method: 'GET'};
let request = {uri: '/user/1,2,3', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: [1,2,3]});
request = {uri: '/user/1.5,2.55,4.25', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: [1.5,2.55,4.25]});
request = {uri: '/user/a,b,c', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {id: ['a','b','c']});
});
it('/user/:id?', () => {
const route = {uri: '/user/:id?', method: 'GET'};
let request = {uri: '/user/1000', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.strictEqual(request.params.id, 1000);
request = {uri: '/user', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), true);
assert.deepEqual(request.params, {id: undefined});
request = {uri: '/user/', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:firstname/:lastname', () => {
const route = {uri: '/user/:firstname/:lastname', method: 'GET'};
let request = {uri: '/user/camel/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} );
request = {uri: '/user/camel', method: 'GET', params: {}};
assert.strictEqual(routeMatcher(request, route), false);
});
it('/user/:firstname?/:lastname', () => {
const route = {uri: '/user/:firstname?/:lastname', method: 'GET'};
let request = {uri: '/user/camel/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: 'camel', lastname:'aissani'} );
request = {uri: '/user/aissani', method: 'GET', params: {}};
assert(routeMatcher(request, route));
assert.deepEqual(request.params, {firstname: undefined, lastname:'aissani'} );
});
});
});

View File

@ -1,11 +1,43 @@
/*eslint-env mocha*/
import {assert} from 'chai';
import chai, {assert} from 'chai';
import Settings from '../lib/settings';
describe('Settings', () => {
const settings = new Settings();
describe('http GET method transformer', () => {
it('check setting rule', () => {
const defaultHttpGetTransformer = settings.get('http GET transformer');
chai.expect(() => settings.set('http GET transformer', null)).to.throw(TypeError);
chai.expect(() => settings.set('http GET transformer', {})).to.throw(TypeError);
chai.expect(() => settings.set('http GET transformer', {foo:()=>{}})).to.throw(TypeError);
const uri = () => {};
settings.set('http GET transformer', {uri});
assert.deepEqual(settings.get('http GET transformer'), {uri});
const headers = () => {};
settings.set('http GET transformer', {headers});
assert.deepEqual(settings.get('http GET transformer'), {headers});
const data = () => {};
settings.set('http GET transformer', {data});
assert.deepEqual(settings.get('http GET transformer'), {data});
settings.set('http GET transformer', defaultHttpGetTransformer);
const defaultRouteMatcher = settings.get('route matcher');
chai.expect(() => settings.set('route matcher', null)).to.throw(TypeError);
chai.expect(() => settings.set('route matcher', {})).to.throw(TypeError);
chai.expect(() => settings.set('route matcher', 1)).to.throw(TypeError);
const routeMatcher = () => {};
settings.set('route matcher', routeMatcher);
assert.strictEqual(settings.get('route matcher'), routeMatcher);
});
it('simple uri', () => {
const uriFn = settings.get('http GET transformer').uri;
const dataFn = settings.get('http GET transformer').data;