Frontend Editor Developer Guide
1 Installation steps
1.1 Dev story
The frontend-module
acts like a web-site, web-app builder.
This application will be deployed in a Docker container and the output of the built application must be composed only from HTML, CSS and JavaScript (JS) files which will be archived and send through a POST request.
By default, the application is not usable, unless a post call is made to /setstudiodata
, with the purpose of initializing the application. This call must provide two variables (workspaceId, destinationPath) needed to deliver the final project. Once this call is made, the final project folder is cleared, and a new blank project is created.
After the call, the delivery of final project is allowed. Before the call, the delivery is inhibited: error code #1014 is generated.
Delivery of final project is done by zipping the HTML/JS/CSS files and making a post call to a URL provided within the VFOS_STUDIO_UPLOAD_URL
environment variable.
The request uses multi-part form structured data. Here is an example:
{
"workspaceId":"workspace_1234",
"destinationPath":"views/my_project",
"projectZip": <project zip file>
}
The application built with this software will be referenced as project and web interface of this software as UI.
1.2 Running in docker
-
Populate
VFOS_STUDIO_UPLOAD_URL
environment variable with host IP (for testing) -
docker build -t frontend-module
-
docker run -d -p 5050:5050 frontend-module
(e.g. docker run -d -p 5050:5050 frontend-module)
-d detached mode, -it iterative mode, -p host_port:container_port
1.3 Installation
! Verify that your node version matches .nvmrc
or is upper than it.
1.3.1 Linux
nvm use
- will inspect the file .nvmrc
and switch to that Node version.
nvm install
- will install the current Node version that the command nvm use
has switched to
1.3.2 Windows
nvm install 12.8.0
(check .nvmrc for the latest version)
back-end
$ npm ci
front-end
cd ui && npm ci
1.4 Environment variables
back-end
The required .env
variables are present in .env.sample
. All variables from this file can be specified also as environment variables. If both modes are utilized, the environment variable has higher priority.
NODE_PORT= <port_of_the_application>
CORS_ANYWHERE=<true_or_false>
CORS_ORIGINS=<array_with_allowed_origins>
PROJECT_FOLDER=<built_application_folder>
DEFAULT_PROJECT_NAME=<built_application_name>
VFOS_STUDIO_UPLOAD_URL=<the url used by the application to delivered the finished project>
UI_DIST_FOLDER=<ui_folder>
As for example:
NODE_PORT=5050
CORS_ANYWHERE=true
CORS_ORIGINS="["http://localhost:4200"]"
PROJECT_FOLDER=project
DEFAULT_PROJECT_NAME=my-project
VFOS_STUDIO_UPLOAD_URL=http://localhost:3001/upload
UI_DIST_FOLDER=ui/dist
The ConfigModule
from src/core/config
is responsible for retrieving the environment variables depending on the target environment where the application will run (development, test, production).
front-end
The environment variables are present in environment.ts
and environmnet.prod.ts
from ui/src/environments
. None of these are ignored because they cannot contain sensitive information, as they are visible on the front-end
anyway.
production: false,
// ? url
server: '<backend_server_url>',
api: '<backend_server_url>/api',
// ? project (thid party app)
defaultProjectName: '<same_with_DEFAULT_PROJECT_NAME>',
defaultProjectPath: '<PROJECT_FOLDER/DEFAULT_PROJECT_NAME/public',
The built application is loaded using an <iframe>
. Thus, the variables defaultProjectName
and defaultProjectPath
are used to retrieve the iFrame.
1.5 Running the app
Install dependencies for both back-end
and front-end
development
- First-run only: create & populate a file named
.env.development
(see ConfigService) - Run
npm run start:dev
to start theback-end
- In another terminal navigate in the
ui
folder and runnpm run build:watch
- Send a POST request to /setstudiodata to initialize project
production
- First-run only: create & populate a file named
.env.production
(see ConfigService) - Navigate to the
ui
folder and runnpm run build:prod
- In another terminal run
npm run build
to build theback-end
and after runnpm run start:prod
to start the application - Send a POST request to /setstudiodata to initialize project. Request body example for POST call to /setstudiodata:
{
"workspaceId": "workspace_1234",
"destinationPath": "views/my_project"
}
! If the NODE_ENV
variable comes from outside, like a process manager for example, then the script npm run start:lean
can be used to start the back-end
in production.
1.6 Built Project
The project that will be built with this application it will be written in Svelte.
The folder boilerplates
includes the code that will be used.
The compiler can be configured in boilerplates/create-app/src/webpack.config.js
.
The are two scripts available for this project in package.json
:
-
"dev": "webpack --watch"
- used to run webpack in watch mode and emit the files every time changes are notified; -
"build": "cross-env NODE_ENV=production webpack"
- used to build the project in production (minify, optimize and so on).
The output of the built project consists of HTML, CSS and JS files available in the [project_name]/public
and it will be visible on the UI using an iframe
, as for example:
<iframe src="/project/my-project/public"></iframe>
In order for the front-end code to access the project through the iframe
, the front-end
and the back-end
(server) must both be served from the same origin.
2 Back-end configuration
The back-end
code is written in
NestJS. Thus, the architecture is similar to the front-end
architecture.
The main file for back-end
is src/main.ts
which configures the NodeJS server and bootstraps the module within src/app.module
file.
2.1 NodeJS Server configuration
The NodeJS server is build with express
, because this is what NestJS is using under the hood. So, any configuration compatible with express
is compatible with this server.
2.1.1 CORS
The CORS_ANYWHERE
variable from .env
files differentiate between accepting all possible CORS origins or just the ones from the CORS_ORIGINS
variable. The server uses
CORS with credentials
option set to true:
app.enableCors({
origin: JSON.parse(configService.get('CORS_ORIGINS')),
credentials: true,
});
2.1.2 Cookie Parser
Cookie parser is enabled as well, with a secret key to use for signed cookies:
app.use(cookieParser(configService.get('COOKIE_SECRET')));
2.1.3 Proxy pass
Used in case the server is running behind a proxy:
app.set('trust proxy', 1);
2.2 Modules
The modules used by the application are imported
in the app.module
or in an internal module imported into app.module
. All of these modules are in the modules
folder. Some of the most notable modules are:
1. app.module
Main module of the app with no logic in the service. It only has two routes listed in the controller:
@Get('/ui/*')
- used to serve the UI of the application.@Get('/')
- used to redirect to the UI.
2. user.module
Used for any user related logic. It only has one route:
@Get('who-am-I')
- used to return the CSRF token (or any user information if needed in future)
3. dom-manipulation
The module where the main logic of the application will be. It will be responsible for creating the elements in the built project. Because the process of creating an html
element (more precisely the interpolation of a tag in the main file) is a short one, all the communication is done through HTTP.
4. project
This module contains the logic related to the project (e.g. create, build, start). Because certain operations may take a long time to process and a constant update needs to be sent to the web interface, this module has two entries:
-
project.controller.ts
- for HTTP requests (for light operations or blocking operations) -
project.socket.gateway.ts
- for WebSocket messages (for heavy non-blocking operations )
5. child-process
Used to execute shell background command issued most likely for the project. This module it’s only internal, thus it has no controller.
2.3 Commons & Core
The other logic of the application is split between commons
and core
.
2.3.1 Commons
The folder structure is created for most commons utilities. For now, only exception-filters
is populated. The application has a global exception filter to catch all the uncaught errors. This filter is declared in app.module
:
{
provide: APP_FILTER,
useClass: GlobalErrorFilter,
},
The application has two more filters for validation, one for messages received over HTTP and one for messages received over the Web Socket.
2.3.2 Core
Some of the core functionalities includes:
- Config module: This config module is used to load the
.env
variables. - Custom response: The application uses an internal module for most of the messages sent to the client.
- Reactive message: This module use RxJS (which is comes by default in NestJS) to emit multiple outputs. The main purpose is to be used to emit a new message when the project was updated.
2.4 Static files
The NodeJS server, serves two types of static files: project related and UI related.
// ? static files project
app.useStaticAssets(join(__dirname, '..', 'project'), {
prefix: '/project/',
});
// ? static files UI
app.useStaticAssets(
join(__dirname, '..', configService.get('UI_DIST_FOLDER')),
{
prefix: '/ui/',
},
);
2.5 Logging & monitoring
The application contains the internal NestJS logger, used to display messages in the console, or the plain console.log
method.
const logger = new Logger('Main');
For better logging and monitoring, winston can be integrated.
3 Front-end
3.1 Build & deploy config
The front-end
code is inside the folder named ui
and is built with Angular v9. As the front-end
is served by the back-end
code and not a different web server, Angular must be configured to emit the files in both production
and development
mode. At the same time, the deploy-url
variable and base-href
must be configured:
"build:watch": "ng build --watch --deploy-url /ui/ --base-href /ui/",
"build:prod": "ng build --prod --deploy-url /ui/ --base-href /ui/",
3.2 Polyfills
Messages printed by the console are disabled in production (see polyfills.ts
file).
if (environment.production) {
//@ts-ignore
window.console = {
log: function() {},
warn: function() {},
info: function() {},
error: function() {},
time: function() {},
timeEnd: function() {},
};
}
3.3 Styling
Angular Material is used and the main theme is defined in ui/src/styles/themes/_siemens.scss
.