# Introduction
Freshworks developer platform now supports React local development natively in the FDK through Webpack 5 (opens new window). This documentation is intended to walk you through the implementation in the FDK, and the process to follow to get started with the React development using the FDK.
# Implementation
The FDK comes built-in with Webpack 5 and a Webpack configuration file to mount during the compilation and the build phases, whenever the FDK detects the project is developed with React, the project is compiled using the Webpack with default Webpack configuration.
Though the FDK has a default Webpack configuration for React apps, It is possible to provide custom configurations, the guidelines to define custom Webpack configurations are addressed in the latter part of this documentation.
# Create your first React app
To create a new React project,
Update the FDK to the latest version.
Create a new folder named my_app and open the terminal/command prompt inside the newly created folder
vel@freshworks:~$ mkdir my_app && cd my_app
- Run fdk create and select the product of your choice.
- Once you have chosen the desired product and select your_first_react_app template.
- After creating the project, run npm install to install all the dependencies and devDependencies.
vel@freshworks:~/my_app$ npm install
# React app folder structure
The React App in the Freshworks ecosystem is similar to the React app created using create-react-app or a React app bundled using the Webpack, with some minor changes in the folder structure to support integration with the FDK.
The folder structure of the React app is explained below
├── __mocks__
│ └── svgrMock.js
├── app
│ ├── icon.svg
│ └── index.html
├── config
│ └── iparams.json
├── jest.config.js
├── manifest.json
├── package.json
├── public
│ └── index.html
├── setUpTests.js
└── src
├── App.css
├── App.js
├── App.test.js
├── assets
│ ├── icon.svg
│ └── logo.svg
├── components
│ └── HelloUser.js
├── hooks
│ └── useScript.js
├── index.css
├── index.js
└── logo.svg
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# The manifest.json file
The manifest.json
file contains the app metadata about your app, such as app locations, platform version, and other app related information
# The package.json file
The package.json
file contains the information about the framework used in the app, the dependencies, and devDependencies used by the app, and configurations if any.
# The app folder
The app
folder contains the built/compiled assets of the app. The content of the app folder is served by the FDK in http://localhost:10001/iframe
during fdk run
WARNING
Do not delete, replace or modify the
index.html
file inside the app folder, any changes made will be overwritten during the build. If you need to add or remove anything in theapp/index.html
, make sure you do it inpublic/index.html
as it serves as the template file forapp/index.html
Replace the
icon.svg
file in the app folder, if you choose to use a custom icon for the app. Make sure you change the name of the icon inmanifest.json
to the replaced/newly added image.If you choose to use a custom Webpack config, make sure the output always points to the app folder or its subfolders.
# The Config folder
The config
folder contains the installation parameter of the app.
WARNING
- Do not define your custom webpack config inside the
config
folder
# The src folder
The src
folder contains your react components and services.
# The public folder
The public
folder contains an index.html
file which serves as a template to the app/index.html
. Adding CSS or scripts to the app can be done in the public/index.html
.
# The jest.config.js file
The jest.config.js contains the configurations related to jest unit tests. Alternatively, this can be directly defined in the package.json
like shown below.
package.json
"scripts": {
"test": "jest test --coverage"
},
"jest": {
"roots": [
"./app/src"
]
}
2
3
4
5
6
7
8
# The setUpTests.js file
The setupTests.js
is needed when your app uses a browser API that you need to mock in your tests or if you need a global setup before running your tests, It will be automatically executed before running your tests.
To know more about setupTests.js
, please visit this (opens new window) link
# The __mocks__
folder
The __mocks__
folder contains all the manual mocks required by jest as per jest's naming convention.
Please visit this (opens new window) link to know more about __mocks__
# Run your first React app using the FDK
Running a React app locally using the FDK is similar to running any other app,
Open the app folder in terminal and run fdk run
vel@freshworks:~/my_app$ fdk run
1The FDK runs the app with Webpack if
package.json
is present in the root folder of the project with the following object,"fdkConfig": { "frontendFramework": "react", "customConfig": "" }
1
2
3
4The
frontendFramework
key in thefdkConfig
objects denotes the framework of the project, FDK currently supports- React
- Vue
- Vue3
The
customConfig
key denotes the path of the custom Webpack config you want to provide, although this is not mandatory, the FDK will use the default config if any of the following scenarios hold.- when there is no
customConfig
key in fdkConfig - when
customConfig
is an empty string - when the path provided is not valid.
- when there is no
WARNING
The path to the custom Webpack config module should be relative to the app's root folder.
# Lifecycle of an FDK React App
The lifecycle/App execution flow of a React app in FDK is shown in the image below.
# Usage of existing frontend platform features in React
All the frontend features and interfaces should work as they would in the normal frontend app created using vanillaJS or JQuery, Although there few features that had to be implemented differently due to the restrictions imposed by React.
# Injecting the freshclient.js
The fresh_client.js
is the interface that bridges your app and the developer platform. The fresh_client.js
enables you to access the platform features such as request, db, interface, and instance through the client object.
In the normal vanilla Freshworks application, the `fresh_client.js' is included in the template.html as script src like shown below
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<script src="https://static.freshdev.io/fdk/2.0/assets/fresh_client.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
Unlike the vanillaJS application, we will not be able to include the fresh_client.js
inside the index.html, instead, the script is added to the app using React hooks like the example below
import React, { useState, useEffect } from "react";
import "./App.css";
import Child from "./compoenents/Child";
const App = () => {
const [loaded, setLoaded] = useState(false);
const [child, setChild] = useState(<h3>App is loading</h3>);
useEffect(() => {
const script = document.createElement("script");
script.src = "https://static.freshdev.io/fdk/2.0/assets/fresh_client.js";
script.addEventListener("load", () => setLoaded(true));
script.defer = true;
document.head.appendChild(script);
}, []);
useEffect(() => {
if (!loaded) return;
app.initialized().then((client) => {
setChild(<Child client={client} />);
});
}, [loaded]);
return (
<div>
<header>My React App</header>
{child}
</div>
);
};
export default App;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Passing the client Object to the child components.
Once the fresh_client.js
is loaded, all the platform features can be accessed in the React app through the client object, the client object can then be passed down to the child components as a prop like shown below
return (
<div>
<Child client={client} />
</div>
);
2
3
4
5
you can find a sample app that addresses passing down of props to the child component in this link
# Render App in multiple app locations
One of the most significant features of the Freshworks developer platform is to render an app in multiple locations, and it can be achieved by defining multiple template HTML
files in manifest.json like shown in the example below
manifest.json
{
"platform-version": "2.1",
"product": {
"freshdesk": {
"location": {
"ticket_sidebar": {
"url": "index.html",
"icon": "icon.svg"
},
"full_page_app": {
"url": "full_page.html",
"icon": "icon.svg"
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Since React is a Single Page Application framework it is not possible to define multiple HTML
files for a single app
but you can make use of the instance to achieve the same behavior and render different React components based on the app location instead of template HTML
file
App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import HelloUser from "./components/HelloUser";
import Fullpage from "./components/Fullpage";
import Modal from "./components/Modal";
const App = () => {
const [loaded, setLoaded] = useState(false);
const [child, setChild] = useState(<h3>App is loading</h3>);
useEffect(() => {
const script = document.createElement("script");
script.src = "https://static.freshdev.io/fdk/2.0/assets/fresh_client.js";
script.addEventListener("load", () => setLoaded(true));
script.defer = true;
document.head.appendChild(script);
}, []);
useEffect(() => {
if (!loaded) return;
app.initialized().then((client) => {
client.instance.context().then(function (data) {
let location = data.location;
if (location === "ticket_sidebar") {
setChild(<HelloUser client={client} />);
}
if (location === "full_page_app") {
setChild(<Fullpage client={client} />);
}
if (location === "modal") {
setChild(<Modal client={client} />);
}
});
});
}, [loaded]);
return <div>{child}</div>;
};
export default App;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
manifest.json
{
"platform-version": "2.1",
"product": {
"freshdesk": {
"location": {
"ticket_sidebar": {
"url": "index.html",
"icon": "icon.svg"
},
"full_page_app": {
"url": "index.html",
"icon": "icon.svg"
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
The logic discussed above can also be applied to interface methods like modals.
TIP
All the app locations and interface methods in a React app should point to the same template HTML file, for eg: index.html or the custom HTML defined by you in the Webpack config, though it is possible to use multiple HTML files and initialize fresh_client.js in all the HTML files, it is not recommended.
# Custom Webpack Config
FDK comes with a provision to provide custom webpack configuration, you can define the custom webpack configuration by providing a path to the configuration on the package.json file of the app.
The path to the configuration is provided in the configPath
of the fdkConfig
in package.json
package.json
{
"name": "react-webpack-dev",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"fdkConfig":{
"frontendFramework": "react",
"configPath": "webpack-config/webpack.config.js" // path to Your Custom Webpack config, under /webpack-config folder
},
"devDependencies": {
"@babel/plugin-transform-spread": "^7.13.0",
"@testing-library/jest-dom": "^5.11.6",
"html-loader": "^1.3.2",
"jest": "^26.6.3",
"jest-css-modules": "^2.1.0"
},
"dependencies": {
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.0"
},
"scripts": {
"test": "jest test --coverage",
"code-sanity": ""
},
"jest": {
"roots": [
"./app/src"
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Default config
The code snippet shown below is the default webpack configuration that comes with the FDK, you can choose to make whatever changes you wish to the configuration, but make sure you follow the guidelines given below
- The
output
should always point to or be inside the app directory, so the app can be packed properly during fdk pack - If you use any new dependencies in the configuration, make sure you install the dependencies inside the project root.
- Prefix the paths with
${process.cwd()
so the FDK can locate the files inside your app folder
'use strict';
const HtmlWebPackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
main: ['@babel/polyfill', `${process.cwd()}/src/index.js`]
},
output: {
globalObject: 'this',
path: `${process.cwd()}/app/scripts`,
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].js',
publicPath: './scripts'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx|test.js)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.(css|scss)$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name][contenthash:8].[ext]',
outputPath: '/assets/img',
esModule: false
}
}
]
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader'
}
]
}
]
},
plugins: [
new CleanWebpackPlugin({
dangerouslyAllowCleanPatternsOutsideProject: true,
dry: false
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[name].[contenthash:8].css'
}),
new HtmlWebPackPlugin({
template: `${process.cwd()}/public/index.html`,
filename: `${process.cwd()}/app/index.html`
})
],
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all'
}
}
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Sample Custom config
A sample custom config to place the built javascript assets inside js
folder instead of the default scripts
folder
'use strict';
const HtmlWebPackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
main: ['@babel/polyfill', `${process.cwd()}/src/index.js`]
},
output: {
globalObject: 'this',
path: `${process.cwd()}/app/scripts`,
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].js',
publicPath: './js'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx|test.js)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.(css|scss)$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name][contenthash:8].[ext]',
outputPath: '/assets/images',
esModule: false
}
}
]
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader'
}
]
}
]
},
plugins: [
new CleanWebpackPlugin({
dangerouslyAllowCleanPatternsOutsideProject: true,
dry: false
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[name].[contenthash:8].css'
}),
new HtmlWebPackPlugin({
template: `${process.cwd()}/public/index.html`,
filename: `${process.cwd()}/app/index.html`
})
],
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all'
}
}
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88