Commit 27a9fdac authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza
Browse files

Module: Web client using expressjs

parent e20d79da
......@@ -31,3 +31,22 @@ sudo apt install yarn
```
## Running dev server with hot reload
To start the web application with live-reload server and work on the application, run either `yarn run web.start:dev`
in root directory, or `yarn run start:dev` in _packages/ui-express_ module directory
This starts the _webpack-dev-server_ on port 3000: <http://localhost:3000/>
## Running SSR server
To start express server with server side rendering and test application as in staging environment run either `yarn run web.start:prod` in root directory, or `yarn run start:dev` in _packages/ui-express_ module directory
This starts the express server on port 3000: <http://localhost:3000/>
## Running tests
To start test `yarn run web.test:dev` in root directory, or `yarn run test` in _packages/ui-core_ module directory
This starts jest test runner with code coverage statistics
......@@ -11,11 +11,15 @@
"scripts": {
"clean": "lerna run clean",
"postinstall": "lerna bootstrap && lerna run setup && lerna link",
"build": "lerna run build"
"build": "lerna run build",
"web.start:dev": "cd packages/ui-express && yarn run start:dev",
"web.start:prod": "yarn run clean && lerna run build --stream && cd ./target/app/server && node server.js ",
"web.test:dev": "lerna run test --stream"
},
"workspaces": [
"packages/i18n",
"packages/client"
"packages/client",
"packages/ui-express"
],
"devDependencies": {
"lerna": "^3.0.0"
......
<!DOCTYPE html>
<html lang=SERVER_RENDERED_LANG dir=SERVER_RENDERED_DIR>
<head>
<title>Oh oh</title>
<base href="FRONTEND_PATH" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes" />
<meta name="author" content="Genesys Team, helpdesk@genesys-pgr.org" />
<style type="text/css" id="server-side-styles">
body {
font-family: 'Roboto', sans-serif;
padding: 0 !important;
margin: 0;
background: #e7e5df;
overflow-y: scroll !important;
}
</style>
</head>
<body>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<div>
<h1>ERROR_MESSAGE</h1>
<p>ERROR_DETAILS</p>
<p>ERROR_EXPLANATION</p>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang=SERVER_RENDERED_LANG dir=SERVER_RENDERED_DIR>
<head>
<title>SERVER_RENDERED_TITLE</title>
SERVER_RENDERED_HEADLINKS
<base href="FRONTEND_PATH" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes" />
<meta name="author" content="Genesys Team, helpdesk@genesys-pgr.org" />
SERVER_RENDERED_DESCRIPTION
<style type="text/css" id="server-side-styles">
SERVER_RENDERED_CSS
</style>
SERVER_RENDERED_BUNDLECSS
</head>
<body>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<div id="the-app">SERVER_RENDERED_HTML</div>
<script>
__PRELOADED_STATE__ = SERVER_RENDERED_STATE;
window.initialLanguage = SERVER_RENDERED_LANG;
window.initialI18nStore = SERVER_RENDERED_I18NSTORE;
window.localesMapping = <%= JSON.stringify(htmlWebpackPlugin.options.localesMapping) %>;
window.softwareVersion = <%= JSON.stringify(htmlWebpackPlugin.options.version) %>;
window.softwareCommit = <%= JSON.stringify(htmlWebpackPlugin.options.commithash) %>;
</script>
SERVER_RENDERED_BUNDLESCRIPTS
</body>
</html>
const path = require('path');
const webpack = require('webpack');
const MinifyPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
// other
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const ALLOW_ROBOTS = process.env.ALLOW_ROBOTS === 'true';
const ifThenElse = (condition, ifTrue, ifFalse) => condition ? ifTrue : ifFalse;
module.exports = {
name: 'server',
target: 'node',
mode: ifThenElse(IS_PRODUCTION, 'production', 'development'),
entry: {
server: ['./src/index.ts']
},
output: {
filename: '[name].js',
chunkFilename: '[name].js',
publicPath: '',
path: path.join(process.cwd(), '../../target/app/server')
},
resolve: {
extensions: [
'.ts', '.tsx', '.js', '.jsx'
],
modules: [path.resolve('./src'), path.resolve('./server'), 'node_modules'],
alias: {
'@gringlobal/client': path.resolve(__dirname, '../../client/src'),
'@gringlobal/i18n': path.resolve(__dirname, '../../i18n/src'),
'server': path.resolve(__dirname, '../server'),
}
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'awesome-typescript-loader',
},
],
},
]
},
plugins: [
// https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(IS_PRODUCTION ? 'production' : 'development'),
ALLOW_ROBOTS: JSON.stringify(ALLOW_ROBOTS ? 'true' : 'false'),
}
}),
new CopyWebpackPlugin([
{ from: 'react-loadable.json', to: 'react-loadable.json' },
]),
],
optimization: {
namedModules: true,
noEmitOnErrors: true,
// NOTE: Don't use splitChunks for server code
// splitChunks: {
// chunks: 'all',
// cacheGroups: {
// vendor: {
// test: /[\\/]node_modules[\\/]/,
// name: 'vendors',
// chunks: 'all'
// }
// },
// },
minimizer: ifThenElse(IS_PRODUCTION, [
//
new MinifyPlugin({
parallel: true,
terserOptions: {
ecma: undefined,
warnings: false,
parse: {},
compress: {},
mangle: false, // Note `mangle.properties` is `false` by default.
module: false,
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_classnames: true,
keep_fnames: true,
safari10: false,
},
}),
],
// is not production
[]
),
}
};
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WebpackChunkHash = require('webpack-chunk-hash');
const ManifestPlugin = require('webpack-manifest-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const GitRevisionPlugin = require('git-revision-webpack-plugin');
const gitRevisionPlugin = new GitRevisionPlugin();
const ReactLoadable = require('react-loadable/webpack');
const fs = require('fs');
// devserver configuration
const HOST = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 3000;
// other...
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const SSR = process.env.SSR;
const API_URL = process.env.API_URL || 'http://localhost:8080';
const CLIENT_ID = process.env.CLIENT_ID || 'defaultclient@localhost';
const CLIENT_SECRET = process.env.CLIENT_SECRET || 'changeme';
const sortedChunks = function(list) {
return function(chunk1, chunk2) {
const index1 = list.indexOf(chunk1.names[0]);
const index2 = list.indexOf(chunk2.names[0]);
if (index2 === -1 || index1 < index2) {
return -1;
}
if (index1 === -1 || index1 > index2) {
return 1;
}
return 0;
};
};
module.exports = {
stats: {
colors: true
},
devServer: {
hot: true,
inline: true,
contentBase: path.join(__dirname, '../../../'),
compress: true,
port: PORT,
host: HOST,
overlay: {
warnings: true,
errors: true
},
clientLogLevel: 'warning',
historyApiFallback: {
disableDotRule: true
},
proxy: {
'/proxy': {
target: API_URL,
logLevel: 'debug',
ws: true,
// secure: false,
pathRewrite(path, req) {
let p = path.replace('/proxy', '');
if (p.startsWith('/oauth/token')) {
const grantType = req.query.grant_type;
if (grantType === 'refresh_token') {
const refreshToken = req.query.refresh_token;
if (!refreshToken) {
// eslint-disable-next-line camelcase
req.query.grant_type = 'client_credentials';
p = p.replace('refresh_token', 'client_credentials');
}
}
if(grantType === 'client_credentials' || grantType === 'password' || grantType === 'refresh_token') {
p = `${p}&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}`;
}
// We need some, if not all headers
// // remove all headers from request
// req.headers = {};
} else if (p.indexOf('/google/verify-token') === 0) {
p = `${p}&clientId=${CLIENT_ID}`;
}
// If authorization header is not provided, use access_token from cookie
// console.log('Cookies', req.headers.cookie, req);
if (! req.headers.authorization && req.headers.cookie) {
const authorization = req.headers.cookie.match(/access_token=([^;]+)/);
// console.log('Cookies', req.headers.cookie, authorization);
if (authorization && authorization.length === 2) {
console.log('Injecting Authorization header from cookies');
req.headers.authorization = 'Bearer ' + authorization[1];
}
}
// console.log('Outgoing headers ' + p, req.headers);
return p;
},
onError(err, req, res) {
console.log(err);
},
onClose(res, socket, head) {
// view disconnected websocket connections
console.log('Client disconnected', res);
}
}
},
},
entry: {
genesys: [ 'babel-polyfill', './entrypoints/client.tsx' ],
},
output: {
filename: '[name].[hash].js',
chunkFilename: '[name].[hash].js',
path: path.join(process.cwd(), '../../target/app/assets'),
publicPath: ''
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
modules: [path.resolve('./src'), path.resolve('./server'), 'node_modules'],
alias: {
'@gringlobal/client': path.resolve(__dirname, '../../client/src'),
'@gringlobal/i18n': path.resolve(__dirname, '../../i18n/src'),
'server': path.resolve(__dirname, '../server'),
}
},
module: {
rules: [
{
test: /\.tsx?$/,
enforce: 'pre',
loader: 'eslint-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
use: [
{
loader: 'awesome-typescript-loader',
},
],
},
]
},
plugins: [
/* new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity
}),*/
// Git revision
new GitRevisionPlugin(),
// MD5 chunkhash
new WebpackChunkHash(),
// We use the generated manifest.js to look for current server.[hash].js and vendor.[chunkhash].js
new ManifestPlugin({
basePath: '/html/'
}),
// Inject scripts to app's real index.html
new HtmlWebpackPlugin({
title: 'Genesys', // The title to use for the generated HTML document
filename: 'index.html', // The file to write the HTML to
// minify: {...}, // Pass a html-minifier options object to minify the output
xhtml: true,
excludeAssets: [/server(\..+)*\.js/],
template: './entrypoints/index.html',
// chunksSortMode: sortedChunks(['vendor', 'common', 'genesys']),
// chunks: ['vendor', 'common', 'genesys'],
favicon: 'favicon.ico',
version: gitRevisionPlugin.version(),
commithash: gitRevisionPlugin.commithash(),
localesMapping: fs.readFileSync('../i18n/generated/locales/localesMapping.json', { encoding: 'utf8' }),
}),
// Inject scripts for SSR
new HtmlWebpackPlugin({
title: 'Genesys', // The title to use for the generated HTML document
filename: 'ssr-compiled.html', // The file to write the HTML to
// minify: {...}, // Pass a html-minifier options object to minify the output
xhtml: true,
excludeAssets: [/server(\..+)*\.js/],
template: './assets/html/ssr-template.html',
// chunksSortMode: sortedChunks(['vendor', 'common', 'genesys']),
// chunks: ['vendor', 'common', 'genesys'],
favicon: 'favicon.ico',
version: gitRevisionPlugin.version(),
commithash: gitRevisionPlugin.commithash(),
localesMapping: fs.readFileSync('../i18n/generated/locales/localesMapping.json', { encoding: 'utf8' }),
}),
new HtmlWebpackPlugin({
title: 'Genesys', // The title to use for the generated HTML document
filename: 'ssr-error.html', // The file to write the HTML to
xhtml: true,
excludeAssets: [/server(\..+)*\.ts/],
chunks: [],
template: './assets/html/ssr-error.html',
favicon: 'favicon.ico'
}),
// Defer/Async scripts
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'async',
// async: [ 'genesys' ],
}),
// Keep CSS separate
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
chunkFilename: '[name].[hash].css'
}),
new HtmlWebpackExcludeAssetsPlugin(),
new CopyWebpackPlugin([
{ from: '../i18n/locales', to: 'locales' },
{ from: '../i18n/generated/locales', to: 'locales' },
]),
new ReactLoadable.ReactLoadablePlugin({
filename: 'react-loadable.json',
}),
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
},
},
}
};
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const API_URL = process.env.API_URL || 'http://localhost:8080';
const CLIENT_ID = process.env.CLIENT_ID || 'defaultclient@localhost';
const CLIENT_SECRET = process.env.CLIENT_SECRET || 'changeme';
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const commonConfig = require('./webpack-base.config.js');
const GitRevisionPlugin = require('git-revision-webpack-plugin');
const gitRevisionPlugin = new GitRevisionPlugin();
module.exports = webpackMerge.smart(commonConfig, {
mode: 'development',
devtool: 'source-map',
entry: {},
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: false,
debug: true,
options: {
tslint: {
failOnHint: false
}
}
}),
new webpack.DefinePlugin({
'process.env': {
API_URL: JSON.stringify(API_URL),
CLIENT_ID: JSON.stringify(CLIENT_ID),
CLIENT_SECRET: JSON.stringify(CLIENT_SECRET),
GOOGLE_CLIENT_ID: JSON.stringify(GOOGLE_CLIENT_ID),
PROJECT_VERSION: JSON.stringify(gitRevisionPlugin.version()),
PROJECT_COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
}
}),
// hot module replacement for webpack-dev-server
new webpack.HotModuleReplacementPlugin()
],
});
const path = require('path');
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MinifyPlugin = require('terser-webpack-plugin');
const GitRevisionPlugin = require('git-revision-webpack-plugin');
const gitRevisionPlugin = new GitRevisionPlugin();
const commonConfig = require('./webpack-base.config.js');
module.exports = webpackMerge.smart(commonConfig, {
mode: 'production',
devServer: {
hot: false,
inline: false,
compress: true,
},
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
// https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production'),
PROJECT_VERSION: JSON.stringify(gitRevisionPlugin.version()),
PROJECT_COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()),
}
}),
],
optimization: {
noEmitOnErrors: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
},
},
minimizer: [
//
new MinifyPlugin({
parallel: true,
terserOptions: {
ecma: undefined,
warnings: false,
parse: {},
compress: {},
mangle: true, // Note `mangle.properties` is `false` by default.
module: false,
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_classnames: undefined,
keep_fnames: false,
safari10: false,
},
}),
// Minify CSS
new OptimizeCssAssetsPlugin({
// cssProcessor: require('cssnano'),
// cssProcessorOptions: {
// discardComments: {
// removeAll: true
// }
// },