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

  1. Populate VFOS_STUDIO_UPLOAD_URL environment variable with host IP (for testing)

  2. docker build -t frontend-module

  3. 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

  1. First-run only: create & populate a file named .env.development (see ConfigService)
  2. Run npm run start:dev to start the back-end
  3. In another terminal navigate in the ui folder and run npm run build:watch
  4. Send a POST request to /setstudiodata to initialize project

production

  1. First-run only: create & populate a file named .env.production (see ConfigService)
  2. Navigate to the ui folder and run npm run build:prod
  3. In another terminal run npm run build to build the back-end and after run npm run start:prod to start the application
  4. 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:

  1. "dev": "webpack --watch" - used to run webpack in watch mode and emit the files every time changes are notified;

  2. "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,
});

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.

Previous
Next