├── .babelrc
├── .eslintrc
├── .github
└── workflows
│ └── integration.yml
├── .gitignore
├── .npmignore
├── .prettierrc
├── README.md
├── logo.svg
├── package-lock.json
├── package.json
├── rollup.config.js
└── src
├── components
├── App.js
├── Error.js
├── Faker.js
├── Head.js
├── Logger.js
├── Middleware.js
├── Res.js
├── Router.js
├── Routes.js
├── Static.js
├── constants.js
└── index.js
├── context.js
├── index.js
├── renderer
├── generateRoute.js
├── helpers.js
├── index.js
├── renderHTML.js
└── renderPage.js
└── utils
├── common.js
├── fakerUtil.js
└── propsUtil.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "esmodules": true
8 | }
9 | }
10 | ]
11 | ],
12 | "plugins": ["@babel/plugin-transform-react-jsx"]
13 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb", "prettier"],
3 | "plugins": ["prettier"],
4 | "rules": {
5 | "prettier/prettier": ["error"],
6 | "react/prop-types": "warn",
7 | "react/jsx-filename-extension": 0,
8 | "react/jsx-one-expression-per-line": 0,
9 | "react/jsx-props-no-spreading": 0,
10 | "react/require-default-props": 0,
11 | "react/forbid-prop-types": 0,
12 | "jsx-a11y/alt-text": 0,
13 | "import/prefer-default-export": 0,
14 | "import/no-extraneous-dependencies": 0,
15 | "no-param-reassign": 0,
16 | "no-restricted-syntax": 0,
17 | "guard-for-in": 0,
18 | "no-plusplus": 0
19 | },
20 | "ignorePatterns": "/dist"
21 | }
22 |
--------------------------------------------------------------------------------
/.github/workflows/integration.yml:
--------------------------------------------------------------------------------
1 | name: Run linter and build
2 |
3 | on:
4 | pull_request:
5 | branches: [master]
6 |
7 | jobs:
8 | lint_and_test:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Install dependencies
14 | run: npm ci
15 | - name: Validate
16 | run: npm run validate
17 | - name: Build
18 | run: npm run build
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .vscode
3 | .npmrc
4 | dist
5 | app
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | app
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reactend / Express
2 |
3 | React-like http-server on Nodejs
4 |
5 | ### 🕹 [Playground on repl.it](https://repl.it/@orkhanjafarov/reactend-playground?v=1)
6 |
7 | ### 📄 [Reactend Template](https://github.com/gigantz/reactend-template)
8 |
9 |
10 |
11 | 
12 |
13 |
14 | ## What's that?
15 |
16 | - Node.js http-server based on React-Components
17 | - Express.js inside
18 | - Get, Post, Delete and etc. components to use router method
19 | - `Get(render)` and `Res.Render` to render your regular React DOM Components
20 | - useContext(ReqResContext) hook to access `req, res`
21 | - Support `styled-components`
22 | - Built-in logger (morgan)
23 | - Middleware component in Router and its Routes
24 | - `handler` prop in Route components to use as regular controller
25 |
26 | _and many many features that should be documented..._
27 |
28 |
29 | ## Get started
30 |
31 | Run this to create reactend project on your local machine
32 |
33 | ```
34 | npx create-reactend my-app
35 | ```
36 |
37 |
38 |
39 | You choose template (default: basic)
40 |
41 | ```
42 | npx create-reactend my-app --template faker
43 | ```
44 |
45 |
46 |
47 | ## Code Example
48 |
49 | ```js
50 | import React from 'react';
51 | import { resolve } from 'path';
52 |
53 | import { registerApp, App, Static, Router, Get, Post, Res, Logger } from '@reactend/express';
54 |
55 | const ExpressApp = () => (
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | console.log(req.originalUrl)}
70 | />
71 |
72 |
73 |
74 |
75 | );
76 |
77 | registerApp(ExpressApp);
78 | ```
79 |
80 |
81 |
82 | ## You can use this way too
83 |
84 | ```js
85 | import cors from 'cors';
86 | ;
87 | ```
88 |
89 | ```js
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Shut Up And Take My Money!
} />
105 |
106 |
107 |
108 |
117 |
118 | ```
119 |
120 |
121 |
122 | ## Components
123 |
124 | _This minor description for now (Docs is on the way)_
125 | `` - App Instance (props: port)
126 | `` - Static route (props: publicPath, path, options)
127 | `` - Router-Provider (props: path)
128 | `, and ...` - Route component (props: path, content,
handler, status)
129 | `` - Middleware (props: handler)
130 | `` - morgan logger (props: mode, disabled)
131 | `` - Response components
132 | `` - Render (props: component)
133 | `` - Response send (props: json, text, contentType)
134 | `` - Response Status (props: statusCode)
135 | `` - Response Send File (props: path, options,
onError)
136 | `` - Redirect (props: path, statusCode)
137 | `` - Redirect (props: length, locale, map)
138 |
139 |
140 |
141 | ---
142 |
143 | ## Contact me
144 |
145 | Email me if you have any idea and you would like to be contributor [orkhanjafarovr@gmail.com](mailto:orkhanjafarovr@gmail.com)
146 |
147 | Cheers ✨
148 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@reactend/express",
3 | "version": "1.0.17",
4 | "description": "React-like http-server on Nodejs",
5 | "main": "dist/index.js",
6 | "repository": {
7 | "url": "git@github.com:reactend/reactend-express.git"
8 | },
9 | "scripts": {
10 | "dev": "nodemon ./app/index.js",
11 | "build": "NODE_ENV=production rollup -c rollup.config.js",
12 | "prepublishOnly": "npm run build",
13 | "validate": "eslint '*.js' && prettier '*.js' --write"
14 | },
15 | "keywords": [
16 | "reactend",
17 | "express",
18 | "react",
19 | "http-server"
20 | ],
21 | "author": "Orkhan Jafarov",
22 | "license": "ISC",
23 | "husky": {
24 | "hooks": {
25 | "pre-commit": [
26 | "lint-staged"
27 | ]
28 | }
29 | },
30 | "lint-staged": {
31 | "*.js": [
32 | "eslint",
33 | "prettier --write"
34 | ]
35 | },
36 | "dependencies": {
37 | "compression": "^1.7.4",
38 | "cookie-parser": "^1.4.5",
39 | "express": "^4.17.1",
40 | "faker": "^5.4.0",
41 | "morgan": "^1.10.0",
42 | "prop-types": "^15.7.2",
43 | "react-reconciler": "^0.26.1"
44 | },
45 | "peerDependencies": {
46 | "react": "^17.0.1",
47 | "react-dom": "^17.0.1",
48 | "react-helmet": "^6.1.0",
49 | "styled-components": "^5.2.1"
50 | },
51 | "devDependencies": {
52 | "@babel/core": "^7.12.13",
53 | "@babel/plugin-transform-react-jsx": "^7.12.13",
54 | "@babel/preset-env": "^7.12.13",
55 | "@rollup/plugin-babel": "^5.2.3",
56 | "@rollup/plugin-commonjs": "^17.1.0",
57 | "eslint": "^7.19.0",
58 | "eslint-config-airbnb": "^18.2.1",
59 | "eslint-config-prettier": "^7.2.0",
60 | "eslint-plugin-import": "^2.22.1",
61 | "eslint-plugin-jsx-a11y": "^6.4.1",
62 | "eslint-plugin-prettier": "^3.3.1",
63 | "eslint-plugin-react": "^7.22.0",
64 | "eslint-plugin-react-hooks": "^4.2.0",
65 | "husky": "^4.3.8",
66 | "lint-staged": "^10.5.4",
67 | "nodemon": "^2.0.7",
68 | "npm-run-all": "^4.1.5",
69 | "prettier": "^2.2.1",
70 | "rollup": "^2.38.5"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import commonjs from '@rollup/plugin-commonjs';
3 |
4 | export default {
5 | input: 'src/index.js',
6 | output: {
7 | file: 'dist/index.js',
8 | format: 'cjs',
9 | },
10 | external: ['react', 'react-dom', 'prop-types', 'styled-components'],
11 | plugins: [
12 | babel({
13 | presets: [
14 | [
15 | '@babel/preset-env',
16 | {
17 | targets: {
18 | esmodules: true,
19 | },
20 | },
21 | ],
22 | ],
23 | plugins: ['@babel/plugin-transform-react-jsx'],
24 | }),
25 | commonjs(),
26 | ],
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const App = ({ children, port }) => {children};
5 |
6 | App.propTypes = {
7 | port: PropTypes.number,
8 | children: PropTypes.node,
9 | };
10 |
--------------------------------------------------------------------------------
/src/components/Error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const isDev = process.env.NODE_ENV !== 'production';
5 | const color = '#83CD29';
6 | const msgStyle = { backgroundColor: '#222', color, padding: 5, borderRadius: 4, fontSize: 15 };
7 |
8 | export const Error = ({ title, msg, error }) => (
9 |
19 |
40 |
{title || '🐛 Reactend Error'}
41 | {isDev && (
42 | <>
43 | {msg}
44 |
45 | message:
46 | {error.message && {error.message}
}
47 |
48 | stack:
49 | {error.stack && {error.stack}
}
50 | >
51 | )}
52 |
53 |
54 | );
55 |
56 | Error.propTypes = {
57 | title: PropTypes.string,
58 | msg: PropTypes.string.isRequired,
59 | error: PropTypes.any,
60 | };
61 |
--------------------------------------------------------------------------------
/src/components/Faker.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { transformFakerMap } from '../utils/fakerUtil';
5 | import { Middleware } from './Middleware';
6 | import { passValues } from '../utils/propsUtil';
7 |
8 | export const Faker = ({ map, length, locale }) => (
9 | {
11 | let json;
12 | const params = passValues(req, { length, locale });
13 |
14 | if (length) {
15 | json = Array.from({ length: +(params.length || 5) }, () =>
16 | transformFakerMap(map, params.locale || 'en')
17 | );
18 | } else {
19 | json = transformFakerMap(map, params.locale);
20 | }
21 |
22 | res.send(json);
23 | }}
24 | />
25 | );
26 |
27 | Faker.propTypes = {
28 | map: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
29 | length: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
30 | locale: PropTypes.oneOfType([
31 | PropTypes.string,
32 | PropTypes.oneOf([
33 | 'az',
34 | 'cz',
35 | 'de',
36 | 'de_AT',
37 | 'de_CH',
38 | 'en',
39 | 'en_AU',
40 | 'en_AU_ocker',
41 | 'en_BORK',
42 | 'en_CA',
43 | 'en_GB',
44 | 'en_IE',
45 | 'en_IND',
46 | 'en_US',
47 | 'en_ZA',
48 | 'es',
49 | 'es_MX',
50 | 'fa',
51 | 'fi',
52 | 'fr',
53 | 'fr_CA',
54 | 'fr_CH',
55 | 'ge',
56 | 'hy',
57 | 'hr',
58 | 'id_ID',
59 | 'it',
60 | 'ja',
61 | 'ko',
62 | 'nb_NO',
63 | 'ne',
64 | 'nl',
65 | 'nl_BE',
66 | 'pl',
67 | 'pt_BR',
68 | 'pt_PT',
69 | 'ro',
70 | 'ru',
71 | 'sk',
72 | 'sv',
73 | 'tr',
74 | 'uk',
75 | 'vi',
76 | 'zh_CN',
77 | 'zh_TW',
78 | ]),
79 | ]),
80 | };
81 |
--------------------------------------------------------------------------------
/src/components/Head.js:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet';
2 |
3 | export const Head = Helmet;
4 |
--------------------------------------------------------------------------------
/src/components/Logger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /**
5 | * @param {{
6 | * mode: 'skip' | 'stream' | 'combined' | 'common' | 'dev' | 'short' | 'tiny'
7 | * disabled: Boolean
8 | * }} props
9 | */
10 | export const Logger = ({ mode, disabled }) => ;
11 | Logger.propTypes = {
12 | mode: PropTypes.oneOf(['skip', 'stream', 'combined', 'common', 'dev', 'short', 'tiny'])
13 | .isRequired,
14 | disabled: PropTypes.bool,
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/Middleware.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const Middleware = ({ handler }) => ;
5 | Middleware.propTypes = {
6 | handler: PropTypes.func.isRequired,
7 | };
8 |
--------------------------------------------------------------------------------
/src/components/Res.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Faker } from './Faker';
4 |
5 | /**
6 | * @param {{ name: 'Accept-Patch' | 'Accept-Ranges' | 'Age' | 'Allow' | 'Alt-Svc' | 'Cache-Control' | 'Connection' | 'Content-Disposition' | 'Content-Encoding' | 'Content-Language' | 'Content-Length' | 'Content-Location' | 'Content-Range' | 'Content-Type' | 'Date' | 'Delta-Base' | 'ETag' | 'Expires' | 'IM' | 'Last-Modified' | 'Link' | 'Location' | 'Pragma' | 'Proxy-Authenticate' | 'Public-Key-Pins' | 'Retry-After' | 'Server' | 'Set-Cookie' | 'Strict-Transport-Security' | 'Trailer' | 'Transfer-Encoding' | 'Tk' | 'Upgrade' | 'Vary' | 'Via' | 'Warning' | 'WWW-Authenticate' | 'Content-Security-Policy' | 'Refresh' | 'X-Powered-By' | 'X-Request-ID' | 'X-UA-Compatible' | 'X-XSS-Protection' }} props
7 | */
8 | const Header = ({ name, value }) => ;
9 |
10 | Header.propTypes = {
11 | name: PropTypes.string.isRequired,
12 | value: PropTypes.any,
13 | };
14 |
15 | const Render = ({ component }) => ;
16 |
17 | Render.propTypes = {
18 | component: PropTypes.func,
19 | };
20 |
21 | const Content = ({ json, contentType, text }) => {
22 | const ComponentsArray = [];
23 |
24 | if (contentType)
25 | ComponentsArray.push();
26 | if (json) ComponentsArray.push();
27 | if (text) ComponentsArray.push();
28 |
29 | return ComponentsArray;
30 | };
31 |
32 | Content.propTypes = {
33 | json: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
34 | contentType: PropTypes.string,
35 | text: PropTypes.string,
36 | };
37 |
38 | const Status = ({ statusCode }) => ;
39 | Status.propTypes = {
40 | statusCode: PropTypes.number,
41 | };
42 |
43 | const Redirect = ({ path, statusCode }) => (
44 |
45 | );
46 |
47 | Redirect.propTypes = {
48 | path: PropTypes.string.isRequired,
49 | statusCode: PropTypes.number,
50 | };
51 |
52 | const SendFile = ({ path, options, onError = () => {} }) => (
53 |
54 | );
55 |
56 | SendFile.propTypes = {
57 | path: PropTypes.string.isRequired,
58 | options: PropTypes.object,
59 | onError: PropTypes.func,
60 | };
61 |
62 | export const Res = {
63 | Header,
64 | Render,
65 | Content,
66 | Status,
67 | Redirect,
68 | SendFile,
69 | Faker,
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/Router.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const Router = ({ path, caseSensitive, mergeParams, strict, children }) => (
5 |
11 | {children}
12 |
13 | );
14 |
15 | Router.propTypes = {
16 | path: PropTypes.string.isRequired,
17 | caseSensitive: PropTypes.bool,
18 | mergeParams: PropTypes.bool,
19 | strict: PropTypes.bool,
20 | children: PropTypes.node,
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/Routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Res } from './Res';
4 |
5 | const BaseRoute = (method) => {
6 | const RouteComponent = ({ children, path, handler, render, status, json, text }) => (
7 |
8 | {children}
9 | {render && }
10 | {status && }
11 | {text && }
12 | {json && }
13 |
14 | );
15 |
16 | RouteComponent.propTypes = {
17 | children: PropTypes.node,
18 | path: PropTypes.string,
19 | handler: PropTypes.func,
20 | render: PropTypes.func,
21 | status: PropTypes.number,
22 | json: PropTypes.any,
23 | text: PropTypes.string,
24 | };
25 |
26 | return RouteComponent;
27 | };
28 |
29 | export const Get = BaseRoute('get');
30 | export const Post = BaseRoute('post');
31 | export const Put = BaseRoute('put');
32 | export const HeadRoute = BaseRoute('head');
33 | export const Delete = BaseRoute('delete');
34 | export const Options = BaseRoute('options');
35 | export const Trace = BaseRoute('trace');
36 | export const Copy = BaseRoute('copy');
37 | export const Lock = BaseRoute('lock');
38 | export const Mkcol = BaseRoute('mkcol');
39 | export const Move = BaseRoute('move');
40 | export const Purge = BaseRoute('purge');
41 | export const Propfind = BaseRoute('propfind');
42 | export const Proppatch = BaseRoute('proppatch');
43 | export const Unlock = BaseRoute('unlock');
44 | export const Report = BaseRoute('report');
45 | export const Mkactivity = BaseRoute('mkactivity');
46 | export const Checkout = BaseRoute('checkout');
47 | export const Merge = BaseRoute('merge');
48 | export const Msearch = BaseRoute('m-search');
49 | export const Notify = BaseRoute('notify');
50 | export const Subscribe = BaseRoute('subscribe');
51 | export const Unsubscribe = BaseRoute('unsubscribe');
52 | export const Patch = BaseRoute('patch');
53 | export const Search = BaseRoute('search');
54 | export const Connect = BaseRoute('connect');
55 |
--------------------------------------------------------------------------------
/src/components/Static.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const Static = ({ publicPath, path, options }) => (
5 |
6 | );
7 |
8 | Static.propTypes = {
9 | publicPath: PropTypes.string.isRequired,
10 | path: PropTypes.string,
11 | options: PropTypes.object,
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/constants.js:
--------------------------------------------------------------------------------
1 | export const CTYPES = {
2 | app: 'app$',
3 | router: 'router$',
4 | route: 'route$',
5 | middleware: 'middleware$',
6 | param: 'param$',
7 | logger: 'logger$',
8 | static: 'static$',
9 | };
10 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export * from './App';
2 | export * from './Head';
3 | export * from './Static';
4 | export * from './Router';
5 | export * from './Routes';
6 | export * from './Res';
7 | export * from './Logger';
8 | export * from './Middleware';
9 |
--------------------------------------------------------------------------------
/src/context.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | export const ReqResContext = createContext({ req: null, res: null });
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export * from './renderer';
2 | export * from './components';
3 | export * from './context';
4 |
--------------------------------------------------------------------------------
/src/renderer/generateRoute.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-return-await */
2 | import React from 'react';
3 | import { replaceValues } from '../utils/propsUtil';
4 | import { renderPage } from './renderPage';
5 | import { log } from './helpers';
6 | import { Error } from '../components/Error';
7 |
8 | async function paramfn(sq, req, res, next, options) {
9 | // eslint-disable-next-line no-restricted-syntax
10 | for (const param of sq) {
11 | switch (param.type) {
12 | case 'header':
13 | res.setHeader(param.content.name, param.content.value);
14 | break;
15 | case 'json':
16 | res.send(param.content);
17 | break;
18 | case 'text':
19 | res.send(replaceValues(req, param.content));
20 | break;
21 | case 'status':
22 | res.statusCode = param.content;
23 | break;
24 | case 'contentType':
25 | res.setHeader('Content-Type', param.content);
26 | break;
27 | case 'redirect':
28 | if (param.content.statusCode) res.redirect(param.content.statusCode, param.content.path);
29 | else res.redirect(param.content.path);
30 | // res.end();
31 | break;
32 | case 'render':
33 | // eslint-disable-next-line no-await-in-loop
34 | res.send(await renderPage(param.content, { req, res }, options));
35 | break;
36 | case 'send-file':
37 | res.sendFile(param.content.path, param.content.options, (err) => {
38 | if (err) {
39 | param.content.onError(err);
40 | next();
41 | }
42 | });
43 | break;
44 | default:
45 | }
46 | }
47 | }
48 |
49 | export function generateRoute(router, props, options = {}) {
50 | router[props.method](
51 | props.path || '/',
52 | ...[
53 | ...(props.middlewares || []),
54 | async (req, res, next) => {
55 | if (props.handler)
56 | try {
57 | await props.handler(
58 | req,
59 | res,
60 | next,
61 | async (Component) => await renderPage(Component, { req, res }, options)
62 | );
63 | } catch (error) {
64 | const msg = `Error in the handler passed to this route <${props.method[0].toUpperCase()}${props.method.slice(
65 | 1
66 | )} path="${props.path || '/'}">`;
67 |
68 | log('error', msg);
69 |
70 | res.writableEnded = true;
71 | res.statusCode = 500;
72 | if (props.method === 'get') {
73 | res.end(
74 | await renderPage(() => , { req, res }, options)
75 | );
76 | } else {
77 | res.end(msg);
78 | }
79 | }
80 |
81 | if (props.paramsSeq && !res.writableEnded) {
82 | await paramfn(props.paramsSeq, req, res, next, options);
83 | }
84 | },
85 | ]
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/src/renderer/helpers.js:
--------------------------------------------------------------------------------
1 | export const colors = {
2 | reset: '\x1b[0m',
3 | bright: '\x1b[1m',
4 | dim: '\x1b[2m',
5 | underscore: '\x1b[4m',
6 | blink: '\x1b[5m',
7 | reverse: '\x1b[7m',
8 | hidden: '\x1b[8m',
9 |
10 | fg: {
11 | black: '\x1b[30m',
12 | red: '\x1b[31m',
13 | green: '\x1b[32m',
14 | yellow: '\x1b[33m',
15 | blue: '\x1b[34m',
16 | magenta: '\x1b[35m',
17 | cyan: '\x1b[36m',
18 | white: '\x1b[37m',
19 | crimson: '\x1b[38m', // Scarlet
20 | },
21 | bg: {
22 | black: '\x1b[40m',
23 | red: '\x1b[41m',
24 | green: '\x1b[42m',
25 | yellow: '\x1b[43m',
26 | blue: '\x1b[44m',
27 | magenta: '\x1b[45m',
28 | cyan: '\x1b[46m',
29 | white: '\x1b[47m',
30 | crimson: '\x1b[48m',
31 | },
32 | };
33 |
34 | const libName = '⚡️ reactend';
35 |
36 | export function log(type, msg) {
37 | switch (type) {
38 | case 'success':
39 | console.log(`${colors.fg.green}${colors.bright}[${libName}] ${msg}${colors.reset}`);
40 | break;
41 | case 'warn':
42 | console.log(`${colors.fg.yellow}[${libName}] ${msg}${colors.reset}`);
43 | break;
44 | case 'error':
45 | console.log(`${colors.fg.red}[${libName}] ${msg}${colors.reset}`);
46 | break;
47 | default:
48 | console.log(`[${libName}] ${msg}`);
49 | break;
50 | }
51 | }
52 |
53 | export const METHODS = [
54 | 'get',
55 | 'post',
56 | 'put',
57 | 'head',
58 | 'delete',
59 | 'options',
60 | 'trace',
61 | 'copy',
62 | 'lock',
63 | 'mkcol',
64 | 'move',
65 | 'purge',
66 | 'propfind',
67 | 'proppatch',
68 | 'unlock',
69 | 'report',
70 | 'mkactivity',
71 | 'checkout',
72 | 'merge',
73 | 'm-search',
74 | 'notify',
75 | 'subscribe',
76 | 'unsubscribe',
77 | 'patch',
78 | 'search',
79 | 'connect',
80 | ];
81 |
--------------------------------------------------------------------------------
/src/renderer/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | /* eslint-disable no-useless-return */
3 | /* eslint-disable no-unused-vars */
4 | import React from 'react';
5 | import ReactReconciler from 'react-reconciler';
6 | import express from 'express';
7 | import cookieParser from 'cookie-parser';
8 | import compression from 'compression';
9 | import logger from 'morgan';
10 |
11 | import { log } from './helpers';
12 | import { generateRoute } from './generateRoute';
13 | import { renderHTML } from './renderHTML';
14 | import { CTYPES } from '../components/constants';
15 |
16 | let options = {
17 | appHOC: (Component) => ,
18 | renderHTML,
19 | };
20 |
21 | const reconciler = ReactReconciler({
22 | getRootHostContext(rootContainerInstance) {},
23 | getChildHostContext(parentHostContext, type, rootContainerInstance) {},
24 | getPublicInstance(instance) {},
25 | prepareForCommit(containerInfo) {},
26 | resetAfterCommit(containerInfo) {},
27 | createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
28 | if (type === CTYPES.app) {
29 | const app = express();
30 | app.use(compression());
31 | app.use(express.json());
32 | app.use(express.urlencoded({ extended: true }));
33 | app.use(cookieParser());
34 | app.listen(props.port || 8080, () =>
35 | log('success', `app is running on ${props.port || 8080}`)
36 | );
37 | return app;
38 | }
39 |
40 | if (type === CTYPES.router) {
41 | const router = express.Router({
42 | caseSensitive: !!props.caseSensitive,
43 | mergeParams: !!props.mergeParams,
44 | strict: !!props.strict,
45 | });
46 |
47 | return { routerInstance: router, path: props.path };
48 | }
49 |
50 | if (type === CTYPES.route) {
51 | const paramsSeq = [];
52 | const middlewares = [];
53 |
54 | return {
55 | type,
56 | props: { ...props, paramsSeq, middlewares },
57 | };
58 | }
59 |
60 | if (type === CTYPES.middleware) {
61 | return {
62 | type,
63 | props,
64 | };
65 | }
66 |
67 | if (type === CTYPES.param) {
68 | return {
69 | type,
70 | props,
71 | };
72 | }
73 |
74 | if (type === CTYPES.static) {
75 | return {
76 | path: props.path,
77 | static: express.static(props.publicPath, props.options),
78 | };
79 | }
80 |
81 | // eslint-disable-next-line react/destructuring-assignment
82 | if (type === CTYPES.logger) {
83 | return {
84 | type,
85 | props,
86 | };
87 | }
88 |
89 | return null;
90 | },
91 |
92 | appendInitialChild(parentInstance, child) {
93 | if (child.routerInstance) {
94 | if (parentInstance.routerInstance) {
95 | parentInstance.routerInstance.use(child.path || '/', child.routerInstance);
96 | } else {
97 | parentInstance.use(child.path || '/', child.routerInstance);
98 | }
99 | return;
100 | }
101 |
102 | if (child.type === CTYPES.route) {
103 | generateRoute(parentInstance.routerInstance, child.props, options);
104 | return;
105 | }
106 |
107 | if (child.type === CTYPES.middleware) {
108 | if (parentInstance.routerInstance) parentInstance.routerInstance.use(child.props.handler);
109 | if (parentInstance) {
110 | parentInstance.props.middlewares.push(child.props.handler);
111 | }
112 | return;
113 | }
114 |
115 | if (child.type === CTYPES.param) {
116 | parentInstance.props.paramsSeq.push(child.props);
117 | }
118 |
119 | if (child.static) {
120 | parentInstance.use(...(child.path ? [child.path, child.static] : [child.static]));
121 | return;
122 | }
123 |
124 | if (child.type === CTYPES.logger) {
125 | if (!child.props.disabled) {
126 | parentInstance.use(logger(child.props.mode));
127 | }
128 | return;
129 | }
130 | },
131 |
132 | finalizeInitialChildren(instance, type, props, rootContainerInstance, hostContext) {},
133 | prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, hostContext) {},
134 | shouldSetTextContent(type, props) {},
135 | shouldDeprioritizeSubtree(type, props) {},
136 | createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {
137 | return text;
138 | },
139 |
140 | now: null,
141 |
142 | isPrimaryRenderer: true,
143 | scheduleDeferredCallback: '',
144 | cancelDeferredCallback: '',
145 |
146 | supportsMutation: true,
147 |
148 | commitMount(instance, type, newProps, internalInstanceHandle) {},
149 | commitUpdate(instance, updatePayload, type, oldProps, newProps, internalInstanceHandle) {},
150 | resetTextContent(instance) {},
151 | commitTextUpdate(textInstance, oldText, newText) {},
152 | appendChild(parentInstance, child) {},
153 | appendChildToContainer(container, child) {
154 | if (child.type === CTYPES.app) {
155 | container = child;
156 | }
157 | },
158 | insertBefore(parentInstance, child, beforeChild) {},
159 | insertInContainerBefore(container, child, beforeChild) {},
160 | removeChild(parentInstance, child) {},
161 | removeChildFromContainer(container, child) {},
162 | clearContainer(container, child) {},
163 | });
164 |
165 | export const registerApp = (App, custom = options) => {
166 | options = {
167 | ...options,
168 | ...custom,
169 | };
170 | log('success', `starting...`);
171 | const container = reconciler.createContainer(null, false, false);
172 | reconciler.updateContainer(, container, null, null);
173 | };
174 |
--------------------------------------------------------------------------------
/src/renderer/renderHTML.js:
--------------------------------------------------------------------------------
1 | export const renderHTML = ({ head, styles, root }) =>
2 | `
3 |
4 |
5 |
6 |
7 | ${head}
8 | ${styles}
9 |
10 |
11 |
12 | ${root}
13 |
14 |
15 |
16 | `;
17 |
--------------------------------------------------------------------------------
/src/renderer/renderPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { renderToString } from 'react-dom/server';
3 | import { ServerStyleSheet } from 'styled-components';
4 | import { Head } from '../components';
5 | import { Error } from '../components/Error';
6 |
7 | import { ReqResContext } from '../context';
8 | import { log } from './helpers';
9 |
10 | export async function renderPage(Component, ctx, options) {
11 | try {
12 | const { appHOC, renderHTML } = options;
13 | const PrenderedComponent = await Component({ ctx });
14 |
15 | const sheet = new ServerStyleSheet();
16 | const root = renderToString(
17 | sheet.collectStyles(
18 |
19 | {appHOC(() => PrenderedComponent)}
20 |
21 | )
22 | );
23 |
24 | const styles = sheet.getStyleTags();
25 | const helmet = Head.renderStatic();
26 | const head = [helmet.title, helmet.meta, helmet.link].map((h) => h.toString()).join('\n');
27 |
28 | return renderHTML({ head, styles, root });
29 | } catch (error) {
30 | const msg = `Error while rendering React DOM Component at "${ctx.req.originalUrl}" path`;
31 | log('error', msg);
32 |
33 | ctx.res.end(await renderPage(() => , ctx, options));
34 | return false;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/common.js:
--------------------------------------------------------------------------------
1 | export function get(object, keys, defaultVal) {
2 | keys = Array.isArray(keys) ? keys : keys.split('.');
3 | object = object[keys[0]];
4 | if (object && keys.length > 1) {
5 | return get(object, keys.slice(1));
6 | }
7 | return object === undefined ? defaultVal : object;
8 | }
9 |
10 | // eslint-disable-next-line consistent-return
11 | export function set(object, keys, val) {
12 | keys = Array.isArray(keys) ? keys : keys.split('.');
13 | if (keys.length > 1) {
14 | object[keys[0]] = object[keys[0]] || {};
15 | return set(object[keys[0]], keys.slice(1), val);
16 | }
17 | object[keys[0]] = val;
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/fakerUtil.js:
--------------------------------------------------------------------------------
1 | import faker from 'faker';
2 | import { get } from './common';
3 |
4 | function getFakerMethod(methodPath) {
5 | try {
6 | const method = get(faker, methodPath);
7 | if (typeof method === 'function') {
8 | return method();
9 | }
10 | return methodPath;
11 | } catch (error) {
12 | return methodPath;
13 | }
14 | }
15 |
16 | /**
17 | * @param {Object} objMap
18 | * @param {'az' | 'cz' | 'de' | 'de_AT' | 'de_CH' | 'en' | 'en_AU' | 'en_AU_ocker' | 'en_BORK' | 'en_CA' | 'en_GB' | 'en_IE' | 'en_IND' | 'en_US' | 'en_ZA' | 'es' | 'es_MX' | 'fa' | 'fi' | 'fr' | 'fr_CA' | 'fr_CH' | 'ge' | 'hy' | 'hr' | 'id_ID' | 'it' | 'ja' | 'ko' | 'nb_NO' | 'ne' | 'nl' | 'nl_BE' | 'pl' | 'pt_BR' | 'pt_PT' | 'ro' | 'ru' | 'sk' | 'sv' | 'tr' | 'uk' | 'vi' | 'zh_CN' | 'zh_TW'} locale
19 | */
20 | export function transformFakerMap(objMap, locale = 'en') {
21 | const jsonOutput = JSON.parse(JSON.stringify(objMap));
22 | faker.setLocale(locale);
23 |
24 | function assign(obj) {
25 | if (Array.isArray(obj) && obj.every((n) => typeof n !== 'string'))
26 | obj.forEach((o) => assign(o));
27 | else if (Array.isArray(obj) && obj.every((n) => typeof n === 'string')) {
28 | for (let i = 0; i < obj.length; i++) {
29 | obj[i] = getFakerMethod(obj[i]);
30 | }
31 | } else {
32 | for (const prop in obj) {
33 | if (typeof obj[prop] === 'string' && Number.isNaN(+prop)) {
34 | obj[prop] = getFakerMethod(obj[prop]);
35 | } else if (typeof obj[prop] === 'string' && !Number.isNaN(+prop)) {
36 | obj[+prop] = getFakerMethod(obj[+prop]);
37 | } else if (Array.isArray(obj[prop]) && obj[prop].every((n) => typeof n !== 'string')) {
38 | obj[prop].forEach((o) => assign(o));
39 | } else if (prop in obj) {
40 | assign(obj[prop]);
41 | }
42 | }
43 | }
44 | }
45 |
46 | assign(jsonOutput);
47 | return jsonOutput;
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/propsUtil.js:
--------------------------------------------------------------------------------
1 | const PREFIXES = {
2 | param: '$param.',
3 | query: '$query.',
4 | body: '$body.',
5 | };
6 |
7 | export function getFromParams(variable, params) {
8 | if (typeof variable === 'string' && variable.indexOf(PREFIXES.param) === 0) {
9 | const [, parsedVariable] = variable.split(PREFIXES.param);
10 | if (parsedVariable in params) return params[parsedVariable];
11 | }
12 | return null;
13 | }
14 |
15 | export function getFromQuery(variable, query) {
16 | if (typeof variable === 'string' && variable.indexOf(PREFIXES.query) === 0) {
17 | const [, parsedVariable] = variable.split(PREFIXES.query);
18 | if (parsedVariable in query) return query[parsedVariable];
19 | }
20 |
21 | return null;
22 | }
23 |
24 | export function getFromBody(variable, body) {
25 | if (typeof variable === 'string' && variable.indexOf(PREFIXES.body) === 0) {
26 | const [, parsedVariable] = variable.split(PREFIXES.body);
27 | if (parsedVariable in body) return body[parsedVariable];
28 | }
29 |
30 | return null;
31 | }
32 |
33 | export function passValues(req, input) {
34 | const obj = { ...input };
35 |
36 | for (const prop in obj) {
37 | const paramResult = getFromParams(obj[prop], req.params);
38 | const queryResult = getFromQuery(obj[prop], req.query);
39 |
40 | if (paramResult) obj[prop] = paramResult;
41 | else if (queryResult) obj[prop] = queryResult;
42 | else obj[prop] = undefined;
43 | }
44 |
45 | return obj;
46 | }
47 |
48 | export function replaceValues(req, text) {
49 | return text.replace(/(? {
50 | const paramResult = getFromParams(value, req.params);
51 | const queryResult = getFromQuery(value, req.query);
52 |
53 | if (paramResult) return paramResult;
54 | if (queryResult) return queryResult;
55 | return value;
56 | });
57 | }
58 |
--------------------------------------------------------------------------------