Commit c2dede87 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza

Module: Electron packaged app

parent ec05fa46
......@@ -15,11 +15,14 @@
"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"
"electron.start:dev": "cd packages/electron && yarn run start:dev",
"electron.start:prod": "cd packages/electron && yarn run start:prod"
},
"workspaces": [
"packages/i18n",
"packages/client",
"packages/ui-express"
"packages/electron"
],
"devDependencies": {
"lerna": "^3.0.0"
......
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
},
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'), 'node_modules'],
alias: {
'@grin-global/ui-core': path.resolve(__dirname, '../../ui-core/src'),
'@': path.resolve(__dirname, '../../ui-core/src'),
}
},
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'],
version: gitRevisionPlugin.version(),
commithash: gitRevisionPlugin.commithash(),
localesMapping: fs.readFileSync('../ui-core/generated/locales/localesMapping.json', { encoding: 'utf8' }),
}),
// TODO
// // 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: './server/ssr-template.html',
// // chunksSortMode: sortedChunks(['vendor', 'common', 'genesys']),
// // chunks: ['vendor', 'common', 'genesys'],
// favicon: 'favicon.ico',
// version: gitRevisionPlugin.version(),
// commithash: gitRevisionPlugin.commithash(),
// localesMapping: fs.readFileSync('./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: './server/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 HtmlWebpackPlugin = require('html-webpack-plugin');
const fs = require('fs');
module.exports = [
{
mode: 'production',
entry: './src/app.ts',
target: 'electron-main',
module: {
rules: [{
test: /\.ts$/,
include: /src/,
use: [{ loader: 'awesome-typescript-loader' }]
}]
},
output: {
path: path.join(process.cwd(), 'target', 'electron'),
filename: 'electron.js'
},
},
{
mode: 'production',
entry: './entrypoints/client.tsx',
target: 'electron-renderer',
output: {
filename: '[name].[hash].js',
chunkFilename: '[name].[hash].js',
path: path.join(process.cwd(), 'target', 'electron'),
publicPath: ''
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
modules: [path.resolve('./src'), 'node_modules'],
alias: {
'@grin-global/ui-core': path.resolve(__dirname, '../../ui-core/src'),
'@': path.resolve(__dirname, '../../ui-core/src'),
}
},
module: {
rules: [
{
test: /\.tsx?$/,
enforce: 'pre',
loader: 'eslint-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
use: [
{
loader: 'awesome-typescript-loader',
},
],
},
]
},
plugins: [
// Inject scripts to app's real index.html
new HtmlWebpackPlugin({
template: './entrypoints/index.html'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
},
},
}
}
];
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { applyMiddleware, compose, createStore } from 'redux';
import { Provider } from 'react-redux';
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
import { createBrowserHistory as createHistory } from 'history';
import thunk from 'redux-thunk';
import { I18nextProvider, withSSR } from 'react-i18next';
import createSagaMiddleware from 'redux-saga'
// i18n
// eslint-disable-next-line no-restricted-imports
import { i18nClient } from '@grin-global/i18n/i18n-client';
// Reducer
import rootReducer from 'reducer';
// Ui
import { routes } from 'ui/routes';
import renderRoutes from '@grin-global/ui-core/ui/renderRoutes';
const virtualPath = document.baseURI.replace(/^(https?:\/\/[^\/]+)?(.*)\/$/, '$2');
const historyOptions = { basename: `${virtualPath}` };
const history = createHistory(historyOptions);
const sagaMiddleware = createSagaMiddleware();
declare const window: Window & { devToolsExtension: any, __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any, initialLanguage: any, initialI18nStore: any, localeMapping: any };
const initialLanguage = window.initialLanguage || 'en';
const initialI18nStore = window.initialI18nStore || {};
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const initialState = {};
const SsrI18nProvider = withSSR()(I18nextProvider) as any;
const store = composeEnhancers(applyMiddleware(thunk, routerMiddleware(history), sagaMiddleware))(createStore)(rootReducer(history), initialState);
// TODO SSR
ReactDOM.hydrate(
<Provider store={ store }>
<ConnectedRouter history={ history }>
<SsrI18nProvider i18n={ i18nClient } initialLanguage={ initialLanguage } initialI18nStore={ initialI18nStore }>
{ renderRoutes(routes) }
</SsrI18nProvider>
</ConnectedRouter>
</Provider>,
document.getElementById('the-app'),
);
<!DOCTYPE html>
<html>
<head>
<title>GRIN-Global CE</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes" />
<meta name="author" content="GRIN-Global Community, helpdesk@grin-global.org" />
</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"></div>
<script>
__PRELOADED_STATE__ = undefined;
window.initialLanguage = undefined;
window.initialI18nStore = undefined;
window.localesMapping = <%= JSON.stringify(htmlWebpackPlugin.options.localesMapping) %>;
</script>
</body>
</html>
{
"name": "gringlobal-electron",
"version": "1.0.0",
"homepage": "https://gitlab.croptrust.org/grin-global/grin-global-ui",
"author": {
"email": "helpdesk@genesys-pgr.org",
"name": "Croptruts org"
},
"license": " Apache-2.0",
"main": "./src/app.ts",
"scripts": {
"clean": "rimraf target",
"build:dev": "cross-env NODE_ENV=development webpack-dev-server --config config/webpack-development.config.js",
"build:prod": "yarn run clean && cross-env NODE_ENV=production webpack --config config/webpack-production.config.js",
"start:dev": "concurrently \"yarn run build:dev\" \"wait-on http://localhost:3000 && export ELECTRON_START_URL='http://localhost:3000' && electron .\"",
"start:prod": "yarn run build:prod && electron ./target/electron/electron.js",
"package:linux": "yarn run clean && yarn run build:prod && electron-builder --linux",
"package:mac": "yarn run clean && yarn run build:prod && electron-builder --mac",
"package:windows": "yarn run clean && yarn run build:prod && electron-builder --windows"
},
"dependencies": {
"@gringlobal/client": "file:../client",
"axios": "^0.19.2",
"connected-react-router": "^6.6.1",
"cross-env": "^7.0.0",
"electron-is-dev": "^1.1.0",
"history": "^4.10.1",
"i18next": "^19.0.3",
"i18next-browser-languagedetector": "^4.0.1",
"i18next-express-middleware": "^1.9.1",
"i18next-sync-fs-backend": "^1.1.1",
"i18next-xhr-backend": "^3.2.2",
"immutability-helper": "^3.0.1",
"js-md5": "^0.7.3",
"path": "^0.12.7",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-i18next": "^11.3.1",
"react-loadable": "latest",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"redux": "^4.0.5",
"redux-form": "^8.2.6",
"redux-saga": "^1.1.3",
"redux-thunk": "^2.3.0",
"url-template": "^2.0.8"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@types/node": "13.5.3",
"@types/react": "16.9.19",
"@types/react-router": "5.1.4",
"@types/react-router-dom": "5.1.3",
"@types/redux-form": "8.2.0",
"@types/webpack-env": "^1.15.1",
"@typescript-eslint/eslint-plugin": "^2.17.0",
"@typescript-eslint/eslint-plugin-tslint": "^2.17.0",
"@typescript-eslint/parser": "^2.18.0",
"awesome-typescript-loader": "^5.2.1",
"babel-eslint": "^10.0.3",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-react-transform": "^3.0.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-typescript": "^7.0.0-alpha.19",
"concurrently": "^5.1.0",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^7.0.0",
"electron": "8.0.0",
"electron-builder": "^22.3.2",
"es6-promise": "^4.2.8",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.10.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prefer-arrow": "^1.1.7",
"eslint-plugin-react": "^7.18.0",
"git-revision-webpack-plugin": "^3.0.4",
"html-webpack-exclude-assets-plugin": "0.0.7",
"html-webpack-plugin": "^3.2.0",
"lerna": "^3.20.2",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"react-hot-loader": "^4.12.18",
"rimraf": "^3.0.1",
"script-ext-html-webpack-plugin": "^2.1.4",
"terser-webpack-plugin": "^2.3.4",
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"tslint-loader": "^3.5.4",
"tslint-react": "^4.2.0",
"typescript": "^3.7.5",
"wait-on": "^4.0.0",
"webpack": "^4.41.5",
"webpack-chunk-hash": "^0.6.0",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-hot-middleware": "^2.25.0",
"webpack-manifest-plugin": "^2.2.0",
"webpack-merge": "^4.2.2"
},
"build": {
"asar": true,
"artifactName": "grin-global-client",
"appId": "org.grin-global",
"productName": "GRIN-Global",
"linux": {
"target": [
"AppImage",
"deb"
]
},
"win": {
"target": [
"portable",
"nsis"
]
},
"mac": {
"target": [
"dmg",
"pkg"
]
},
"extraResources": [
{
"from": "./target/electron",
"to": "app"
}
],
"files": [
"./src/app.ts",
"./target/electron/*",
"node_modules/**/*"
],
"directories": {
"output": "./target/packaged",
"buildResources": "assets"
}
}
}
const { app, BrowserWindow } = require('electron');
const isDev = require('electron-is-dev');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
});
if (process.env.ELECTRON_START_URL) {
mainWindow.loadURL(process.env.ELECTRON_START_URL);
} else {
mainWindow.loadFile('index.html');
}
if (isDev) {
mainWindow.webContents.openDevTools()
}
mainWindow.webContents.openDevTools();
mainWindow.on('closed', function() {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', function() {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', function() {
if (mainWindow === null) {
createWindow();
}
});
import { combineReducers } from 'redux';
import coreReducers from '@grin-global/ui-core/reducer';
// model reducers
const rootReducer = (history?) => (combineReducers({
// model reducers
...coreReducers(history),
}));
export default rootReducer;
// model
import IRoute from '@grin-global/ui-core/model/common/IRoute';
// core
import { publicCoreRoutes } from '@grin-global/ui-core/ui/routes';
// User
// import { userPublicRotes } from 'user/routes';
import App from '@grin-global/ui-core/ui/App';
export const routes: IRoute[] = [
{
component: App,
routes: [
// ...userPublicRotes,
...publicCoreRoutes,
],
},