Decouple client from server
Add Vite dependency and remove server side rendering to make it
possible to run the client independently.
Remove webpack config, replace with the `Vite` build tool.
GitLab: #55
Change-Id: I3a05d2e86cf6cb0ab91e77b3696f393132137575
diff --git a/client/.env.development b/client/.env.development
new file mode 100644
index 0000000..2accbba
--- /dev/null
+++ b/client/.env.development
@@ -0,0 +1 @@
+ESLINT_NO_DEV_ERRORS=true
diff --git a/client/.eslintrc.json b/client/.eslintrc.json
index e94691b..977d4a0 100644
--- a/client/.eslintrc.json
+++ b/client/.eslintrc.json
@@ -4,11 +4,7 @@
"version": "detect"
}
},
- "extends": [
- "plugin:react/recommended",
- "plugin:react-hooks/recommended",
- "../.eslintrc.cjs"
- ],
+ "extends": ["plugin:react/recommended", "plugin:react-hooks/recommended", "../.eslintrc.cjs"],
"rules": {
"react-hooks/exhaustive-deps": "error",
"react/display-name": "off",
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..567609b
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/client/README.md b/client/README.md
index 3f3af69..404ef57 100644
--- a/client/README.md
+++ b/client/README.md
@@ -1,7 +1,56 @@
# jami-web client
+
This is the client for jami-web
-## Translations
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.<br />
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+
+The page will reload if you make edits.<br />
+You will also see any lint errors in the console.
+
+### `npm test`
+
+Run all tests.<br />
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.<br />
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+Your app is ready to be deployed!
+
+See the section about [deployment](https://vitejs.dev/guide/static-deploy.html) for more information.
+
+### `npm run start:prod`
+
+Preview the production build locally.
+
+### `npm run clean`
+
+Clean build files
+
+### `npm run lint`
+
+Verify that no lint errors are present. Use `npm run lint:fix` to fix some errors.
+
+### `npm run format`
+
+Format all files with prettier. Use `npm run format:check` to verify that all files are formatted without changing any.
+
+## `npm run extract-translations`
+
+Update the translation files.
+
The translations are handled by [i18next](https://www.i18next.com/)
-Execute `npm run extract-translations` from the client's folder in order to update the translation files.
+## Learn More
+
+You can learn more in the [Vite documentation](https://vitejs.dev/guide/).
+
+To learn React, check out the [React documentation](https://reactjs.org/).
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..f308177
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" href="/favicon.png" />
+ <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
+ <meta name="description" content="Jami Web" />
+ <title>Jami Web</title>
+ </head>
+
+ <body>
+ <noscript>You need to enable JavaScript to run this app.</noscript>
+ <script type="module" src="src/index.tsx"></script>
+ <div id="root"></div>
+ </body>
+</html>
diff --git a/client/package.json b/client/package.json
index 585e35f..6eb5ca7 100644
--- a/client/package.json
+++ b/client/package.json
@@ -2,11 +2,13 @@
"name": "jami-web-client",
"version": "0.1.0",
"private": true,
- "type": "module",
"scripts": {
- "build": "webpack",
+ "start": "vite",
+ "start:prod": "vite preview",
+ "build": "tsc && vite build",
"clean": "rm -rf dist",
- "lint": "eslint src",
+ "test": "jest src",
+ "lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
"format": "prettier --write src",
"format:check": "prettier --check src",
@@ -24,7 +26,6 @@
]
},
"dependencies": {
- "@babel/runtime": "7.18.9",
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@mui/icons-material": "^5.10.3",
@@ -40,13 +41,11 @@
"@types/jest": "^28.1.8",
"axios": "^0.27.2",
"dayjs": "^1.11.5",
- "dotenv": "^16.0.2",
"emoji-picker-react": "^3.6.1",
"framer-motion": "^7.3.5",
"i18next": "^21.9.2",
"jami-web-common": "file:../common",
"qrcode.react": "^3.1.0",
- "raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-emoji-render": "^1.2.4",
@@ -58,30 +57,17 @@
"socket.io-client": "^4.5.2"
},
"devDependencies": {
- "@babel/core": "^7.19.1",
- "@babel/plugin-transform-runtime": "^7.19.1",
- "@babel/preset-env": "^7.19.1",
- "@babel/preset-react": "^7.18.6",
- "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
- "@svgr/webpack": "^6.3.1",
"@types/node": "^18.7.13",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@types/react-modal": "^3.13.1",
- "babel-loader": "^8.2.5",
- "copy-webpack-plugin": "^11.0.0",
- "css-loader": "^6.7.1",
+ "@vitejs/plugin-react": "^2.1.0",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
- "html-webpack-plugin": "^5.5.0",
"i18next-parser": "^6.5.0",
- "react-refresh": "^0.14.0",
"sass": "^1.54.5",
- "sass-loader": "^13.0.2",
- "style-loader": "^3.3.1",
- "ts-loader": "^9.3.1",
"typescript": "^4.7.4",
- "webpack": "^5.74.0",
- "webpack-cli": "^4.10.0"
+ "vite": "^3.1.8",
+ "vite-plugin-svgr": "^2.2.2"
}
}
diff --git a/client/src/AuthManager.ts b/client/src/AuthManager.ts
index 29c5ffc..91ac981 100644
--- a/client/src/AuthManager.ts
+++ b/client/src/AuthManager.ts
@@ -61,14 +61,6 @@
this.tasks = [];
this.onAuthChanged = undefined;
-
- // @ts-ignore
- if (initData) {
- console.log('Using static initData');
- // @ts-ignore
- this.setInitData(initData);
- return;
- }
}
isAuthenticated() {
diff --git a/client/src/components/ContactList.js b/client/src/components/ContactList.jsx
similarity index 98%
rename from client/src/components/ContactList.js
rename to client/src/components/ContactList.jsx
index 7f8269b..2aee98b 100644
--- a/client/src/components/ContactList.js
+++ b/client/src/components/ContactList.jsx
@@ -21,8 +21,8 @@
import { useEffect, useState } from 'react';
import Modal from 'react-modal';
-import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import authManager from '../AuthManager';
+import { useAppDispatch, useAppSelector } from '../redux/hooks';
import ConversationAvatar from './ConversationAvatar';
const customStyles = {
diff --git a/client/src/components/ConversationList.tsx b/client/src/components/ConversationList.tsx
index b70001a..9ad47f6 100644
--- a/client/src/components/ConversationList.tsx
+++ b/client/src/components/ConversationList.tsx
@@ -22,7 +22,7 @@
import { Conversation } from 'jami-web-common';
import { useEffect } from 'react';
-import { useAppSelector } from '../../redux/hooks';
+import { useAppSelector } from '../redux/hooks';
import ConversationListItem from './ConversationListItem';
type ConversationListProps = {
diff --git a/client/src/components/ConversationListItem.js b/client/src/components/ConversationListItem.jsx
similarity index 98%
rename from client/src/components/ConversationListItem.js
rename to client/src/components/ConversationListItem.jsx
index 0bcb0b3..16c1ccc 100644
--- a/client/src/components/ConversationListItem.js
+++ b/client/src/components/ConversationListItem.jsx
@@ -22,9 +22,9 @@
import Modal from 'react-modal';
import { useNavigate, useParams } from 'react-router-dom';
-import { setRefreshFromSlice } from '../../redux/appSlice';
-import { useAppDispatch } from '../../redux/hooks';
import authManager from '../AuthManager';
+import { setRefreshFromSlice } from '../redux/appSlice';
+import { useAppDispatch } from '../redux/hooks';
import ConversationAvatar from './ConversationAvatar';
import { RemoveContactIcon, VideoCallIcon } from './SvgIcon.tsx';
import { AudioCallIcon, BlockContactIcon, ContactDetailsIcon, CrossIcon, MessageIcon } from './SvgIcon.tsx';
diff --git a/client/src/components/ConversationsOverviewCard.js b/client/src/components/ConversationsOverviewCard.jsx
similarity index 100%
rename from client/src/components/ConversationsOverviewCard.js
rename to client/src/components/ConversationsOverviewCard.jsx
diff --git a/client/src/components/ListItemLink.js b/client/src/components/ListItemLink.jsx
similarity index 100%
rename from client/src/components/ListItemLink.js
rename to client/src/components/ListItemLink.jsx
diff --git a/client/src/components/Message.js b/client/src/components/Message.jsx
similarity index 100%
rename from client/src/components/Message.js
rename to client/src/components/Message.jsx
diff --git a/client/src/components/MessageList.js b/client/src/components/MessageList.jsx
similarity index 100%
rename from client/src/components/MessageList.js
rename to client/src/components/MessageList.jsx
diff --git a/client/src/components/UsernameChooser.js b/client/src/components/UsernameChooser.jsx
similarity index 100%
rename from client/src/components/UsernameChooser.js
rename to client/src/components/UsernameChooser.jsx
diff --git a/client/src/components/welcome.js b/client/src/components/welcome.jsx
similarity index 90%
rename from client/src/components/welcome.js
rename to client/src/components/welcome.jsx
index ca160a0..39e222b 100644
--- a/client/src/components/welcome.js
+++ b/client/src/components/welcome.jsx
@@ -19,8 +19,6 @@
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';
-import JamiLogo from '../../public/jami-logo-icon.svg';
-
const list = {
hidden: { opacity: 0 },
visible: {
@@ -57,7 +55,14 @@
}}
>
<motion.div variants={item}>
- <JamiLogo size="32px" />
+ <img
+ src="/jami-logo-icon.svg"
+ style={{
+ width: '32',
+ height: '32',
+ }}
+ alt="jami n/logo"
+ />
</motion.div>
<motion.h1 variants={item}>Welcome to Jami</motion.h1>
{props.showSetup && (
diff --git a/client/src/i18n.ts b/client/src/i18n.ts
index 63d7e1c..37f9f7f 100644
--- a/client/src/i18n.ts
+++ b/client/src/i18n.ts
@@ -18,11 +18,11 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
-import translationEn from '../public/locale/en/translation.json';
-import translationFr from '../public/locale/fr/translation.json';
+import translationEn from './locale/en/translation.json';
+import translationFr from './locale/fr/translation.json';
i18n.use(initReactI18next).init({
- debug: process.env.NODE_ENV == 'development',
+ debug: import.meta.env.DEV,
lng: 'en',
interpolation: {
escapeValue: false,
diff --git a/client/src/index.ejs b/client/src/index.ejs
deleted file mode 100644
index d68639a..0000000
--- a/client/src/index.ejs
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
- <meta charset="utf-8" />
- <link rel="icon" href="/favicon.png" />
- <link rel="apple-touch-icon" href="/logo192.png" />
- <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
- <meta name="description" content="Jami Web" />
- <script>const initData=<%- initdata %></script>
- <title>Jami Web</title>
-</head>
-
-<body>
- <div id="root"></div>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/client/src/index.tsx b/client/src/index.tsx
index fdd2fe7..83c2ada 100644
--- a/client/src/index.tsx
+++ b/client/src/index.tsx
@@ -15,7 +15,6 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-/// <reference types="webpack/module" />
'use strict';
import './index.scss';
import './i18n';
@@ -23,15 +22,14 @@
// import config from "../sentry-client.config.json"
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { StrictMode } from 'react';
-import { render } from 'react-dom';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import socketio from 'socket.io-client';
-import { store } from '../redux/store';
import App from './App';
import { SocketProvider } from './contexts/Socket';
+import { store } from './redux/store';
const queryClient = new QueryClient({
defaultOptions: {
@@ -61,12 +59,3 @@
</StrictMode>
</Provider>
);
-
-if (import.meta.webpackHot)
- import.meta.webpackHot.accept('./App', () => {
- try {
- render(<App />, container);
- } catch (e) {
- location.reload();
- }
- });
diff --git a/client/public/locale/en/translation.json b/client/src/locale/en/translation.json
similarity index 100%
rename from client/public/locale/en/translation.json
rename to client/src/locale/en/translation.json
diff --git a/client/public/locale/fr/translation.json b/client/src/locale/fr/translation.json
similarity index 100%
rename from client/public/locale/fr/translation.json
rename to client/src/locale/fr/translation.json
diff --git a/client/src/pages/AccountSettings.tsx b/client/src/pages/AccountSettings.tsx
index 35c0678..fe98277 100644
--- a/client/src/pages/AccountSettings.tsx
+++ b/client/src/pages/AccountSettings.tsx
@@ -20,11 +20,11 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router';
-import { setAccountId, setAccountObject } from '../../redux/appSlice';
-import { useAppDispatch } from '../../redux/hooks';
import authManager from '../AuthManager';
import AccountPreferences from '../components/AccountPreferences';
import Header from '../components/Header';
+import { setAccountId, setAccountObject } from '../redux/appSlice';
+import { useAppDispatch } from '../redux/hooks';
type AccountSettingsProps = {
accountId?: string;
diff --git a/client/src/pages/AddContactPage.tsx b/client/src/pages/AddContactPage.tsx
index d9c8394..666625c 100644
--- a/client/src/pages/AddContactPage.tsx
+++ b/client/src/pages/AddContactPage.tsx
@@ -19,9 +19,9 @@
import { Box, Card, CardContent, Container, Fab, Typography } from '@mui/material';
import { useNavigate, useParams } from 'react-router-dom';
-import { setRefreshFromSlice } from '../../redux/appSlice';
-import { useAppDispatch } from '../../redux/hooks';
import authManager from '../AuthManager';
+import { setRefreshFromSlice } from '../redux/appSlice';
+import { useAppDispatch } from '../redux/hooks';
type AddContactPageProps = {
accountId: string;
diff --git a/client/src/pages/Messenger.tsx b/client/src/pages/Messenger.tsx
index 1fd2252..6aeb840 100644
--- a/client/src/pages/Messenger.tsx
+++ b/client/src/pages/Messenger.tsx
@@ -20,7 +20,6 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router';
-import { useAppSelector } from '../../redux/hooks';
import authManager from '../AuthManager';
//import Sound from 'react-sound';
import ConversationList from '../components/ConversationList';
@@ -28,6 +27,7 @@
import Header from '../components/Header';
import LoadingPage from '../components/Loading';
import NewContactForm from '../components/NewContactForm';
+import { useAppSelector } from '../redux/hooks';
import AddContactPage from './AddContactPage';
type MessengerProps = {
diff --git a/client/redux/appSlice.ts b/client/src/redux/appSlice.ts
similarity index 98%
rename from client/redux/appSlice.ts
rename to client/src/redux/appSlice.ts
index 030a45a..d5c01ee 100644
--- a/client/redux/appSlice.ts
+++ b/client/src/redux/appSlice.ts
@@ -19,7 +19,7 @@
import { Account } from 'jami-web-common';
// Define a type for the slice state
-interface appState {
+export interface appState {
accountId: string;
accountObject: Account | null;
refresh: boolean;
diff --git a/client/redux/hooks.ts b/client/src/redux/hooks.ts
similarity index 100%
rename from client/redux/hooks.ts
rename to client/src/redux/hooks.ts
diff --git a/client/redux/store.ts b/client/src/redux/store.ts
similarity index 100%
rename from client/redux/store.ts
rename to client/src/redux/store.ts
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 9af3397..3a83682 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -3,8 +3,12 @@
"compilerOptions": {
/* Specify what JSX code is generated. */
"jsx": "react-jsx",
+ /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
/* Specify an output folder for all emitted files. */
- "outDir": "./dist/"
+ "outDir": "dist",
+ /* Specify type package names to be included without being referenced in a source file. */
+ "types": ["@testing-library/jest-dom", "@types/jest", "node", "vite/client", "vite-plugin-svgr/client"]
},
- "include": ["src/"]
+ "include": ["src"]
}
diff --git a/client/webpack.config.d.ts b/client/vite.config.ts
similarity index 62%
rename from client/webpack.config.d.ts
rename to client/vite.config.ts
index 8deefa8..ee35cfc 100644
--- a/client/webpack.config.d.ts
+++ b/client/vite.config.ts
@@ -15,8 +15,26 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { Configuration } from 'webpack';
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vite';
+import svgrPlugin from 'vite-plugin-svgr';
-declare const config: Configuration;
-
-export default config;
+export default defineConfig({
+ server: {
+ host: '0.0.0.0',
+ port: 3000,
+ proxy: {
+ '^/(api)|(auth)|(setup)': {
+ target: 'http://localhost:3001',
+ secure: false,
+ },
+ },
+ },
+ preview: {
+ port: 8080,
+ },
+ define: {
+ global: {},
+ },
+ plugins: [react(), svgrPlugin()],
+});
diff --git a/client/webpack.config.js b/client/webpack.config.js
deleted file mode 100644
index cf620bc..0000000
--- a/client/webpack.config.js
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2022 Savoir-faire Linux Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation; either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program. If not, see
- * <https://www.gnu.org/licenses/>.
- */
-'use strict';
-
-import CopyWebpackPlugin from 'copy-webpack-plugin';
-import dotenv from 'dotenv';
-import HtmlWebpackPlugin from 'html-webpack-plugin';
-import { dirname, resolve } from 'path';
-import { fileURLToPath } from 'url';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-dotenv.config({ path: resolve(__dirname, '..', '.env') });
-
-const mode = process.env.NODE_ENV || 'development';
-
-let entry = [resolve(__dirname, 'src', 'index.tsx')];
-let plugins = [
- new HtmlWebpackPlugin({
- template: '!!raw-loader!' + resolve(__dirname, 'src', 'index.ejs'),
- filename: 'index.ejs',
- }),
- new CopyWebpackPlugin({
- patterns: [
- {
- from: resolve(__dirname, 'public'),
- to: resolve(__dirname, 'dist'),
- },
- ],
- }),
-];
-
-let devtool = undefined;
-let babelLoaderPlugins = ['@babel/plugin-transform-runtime'];
-
-if (mode === 'development') {
- const webpack = (await import('webpack')).default;
- const ReactRefreshWebpackPlugin = (await import('@pmmmwh/react-refresh-webpack-plugin')).default;
- entry = ['webpack-hot-middleware/client', ...entry];
- plugins = [new webpack.HotModuleReplacementPlugin(), new ReactRefreshWebpackPlugin(), ...plugins];
- babelLoaderPlugins = [...babelLoaderPlugins, 'react-refresh/babel'];
- devtool = 'inline-source-map';
-}
-console.log(`Webpack configured for ${mode}`);
-
-export default {
- entry,
- output: {
- path: resolve(__dirname, 'dist'),
- filename: 'bundle.js',
- publicPath: '/',
- },
- devtool,
- mode,
- module: {
- rules: [
- {
- test: /\.[tj]sx?$/,
- resolve: {
- fullySpecified: false,
- extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
- },
- exclude: /node_modules/,
- use: {
- loader: 'babel-loader',
- options: {
- plugins: babelLoaderPlugins,
- presets: [
- [
- '@babel/preset-env',
- {
- useBuiltIns: 'entry',
- corejs: { version: '3.10', proposals: true },
- },
- ],
- [
- '@babel/preset-react',
- {
- runtime: 'automatic',
- },
- ],
- ['@babel/preset-typescript'],
- ],
- },
- },
- },
- {
- test: /\.s[ac]ss$/i,
- use: ['style-loader', 'css-loader', 'sass-loader'],
- },
- {
- test: /\.svg$/,
- use: ['@svgr/webpack'],
- },
- {
- // test: /\.tsx?$/,
- test: /\.(js|jsx|ts|tsx)?$/,
- exclude: /node_modules/,
- loader: 'ts-loader',
- },
- ],
- },
- resolve: {
- modules: ['node_modules', resolve(__dirname)],
- extensions: ['.ts', '.tsx', '.js', '.jsx'],
- },
- plugins,
-};