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
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:
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
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:
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:
<!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:
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:
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
:
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):
Go to the "Settings" tab in the left panel and click "Add a code action" button. The code editor has successfully loaded:
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:
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.