Skip to main content

Introducing Workflow Engine, try for FREE workflowengine.io.

Integration with Electron

Overview

In this article, we will run FormEngine inside Electron, and also make the Monaco editor work locally, without CDN.

Bootstrap application

We will use electron-react-boilerplate to create our simple Electron application. Open your shell and execute the following commands:

git clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git electron-formengine
cd electron-formengine
npm install

Now you can run application by executing following command:

npm run start

Electron FormEngine

Installing FormEngine

Let's install the packages, run the following commands:

npm install @react-form-builder/designer @react-form-builder/components-rsuite
npm install

Now change the contents of the App.tsx file:

src/renderer/App.tsx
import React from 'react'
import {BuilderView, FormBuilder} from '@react-form-builder/designer'

const builderView = new BuilderView([])

function App() {
return <FormBuilder view={builderView}/>
}

export default App

Great, now you can launch the application and see the error loading the Monaco scripts:

npm run start

Electron FormEngine

The thing is that the Monaco editor is set to download from CDN by default. Let's change this behavior.

Monaco settings

First of all, we need to add settings to load the Monaco editor from the local NPM package. Create a file src/renderer/monaco-settings.ts with the following contents:

src/renderer/monaco-settings.ts
import * as monaco from 'monaco-editor';
import {loader} from '@monaco-editor/react';

// eslint-disable-next-line no-restricted-globals
self.MonacoEnvironment = {
getWorkerUrl: (_, label) => {
if (label === 'json') {
return './json.worker.bundle.js';
}
if (label === 'css' || label === 'scss' || label === 'less') {
return './css.worker.bundle.js';
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return './html.worker.bundle.js';
}
if (label === 'typescript' || label === 'javascript') {
return './ts.worker.bundle.js';
}
return './editor.worker.bundle.js';
},
};

loader.config({monaco});

FormEngine is using JavaScript code actions, which violates the CSP policies defined in the src/renderer/index.ejs file. Remove the meta tag for "Content-Security-Policy" and your file will look like this:

src/renderer/index.ejs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Hello Electron React!</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

Now let's add the use of monaco-settings to App.tsx, and add the components. Change the contents of App.tsx to the following:

src/renderer/App.tsx
import './monaco-settings';
import React from 'react';
import {BuilderView, FormBuilder} from '@react-form-builder/designer';
import {rSuiteComponents} from '@react-form-builder/components-rsuite';

const components = rSuiteComponents.map((c) => c.build());
const builderView = new BuilderView(components);

export default function App() {
return (
<div style={{width: '97vw', height: '97vh'}}>
<FormBuilder view={builderView}/>
</div>
);
}

The last bit is fixing the webpack configuration files to package Monaco workers. You can find many examples of how to do this in the official Monaco repository. Take a look at this example.

Open .erb/configs/webpack.config.renderer.dev.ts and change entry to the highlighted one, also change output.filename as below:

.erb/configs/webpack.config.renderer.dev.ts
const configuration: webpack.Configuration = {
//...
entry: {
client: `webpack-dev-server/client?http://localhost:${port}/dist`,
devServer: 'webpack/hot/only-dev-server',
renderer: path.join(webpackPaths.srcRendererPath, 'index.tsx'),
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'json.worker': 'monaco-editor/esm/vs/language/json/json.worker',
'css.worker': 'monaco-editor/esm/vs/language/css/css.worker',
'html.worker': 'monaco-editor/esm/vs/language/html/html.worker',
'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker',
},

output: {
path: webpackPaths.distRendererPath,
publicPath: '/',
filename: '[name].bundle.js',
library: {
type: 'umd',
},
},
//...
};

Now let's add similar changes to the .erb/configs/webpack.config.renderer.prod.ts file - change entry and output.filename:

.erb/configs/webpack.config.renderer.prod.ts
const configuration: webpack.Configuration = {
//...
entry: {
renderer: path.join(webpackPaths.srcRendererPath, 'index.tsx'),
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'json.worker': 'monaco-editor/esm/vs/language/json/json.worker',
'css.worker': 'monaco-editor/esm/vs/language/css/css.worker',
'html.worker': 'monaco-editor/esm/vs/language/html/html.worker',
'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker',
},

output: {
path: webpackPaths.distRendererPath,
publicPath: './',
filename: '[name].bundle.js',
library: {
type: 'umd',
},
},
//...
};

Running application

Launch the application using the command:

npm run start

You will see something similar to what is shown in the screenshot below (the CSP warning only exists in development mode):

Electron FormEngine

Go to the "Settings" tab in the left panel and click "Add a code action" button. The code editor has successfully loaded:

Electron FormEngine

Building for production

To package the application for production run the command:

npm run package

You can find the binaries for your platform in the release/build folder. For example, for macOS this would be release/build/mac-arm64.

Let's check how the CSS editor works. Select the "Screen" component, and then open the "Style" tab in the right pane. Start typing text in the code editor, notice that IntelliSense is working:

Electron FormEngine

Conclusion

Packaging a FormEngine application in Electron is not much different from packaging any other React application, only packaging the Monaco editor can cause difficulties. We hope this article will help you save your time.