Beyond Web-Apps

React, Javascript and WebAssembly to Port Legacy Native Apps



Β open-source, cross-platform game creator designed to be used by everyone

  • An editor for games
  • Visual programming
  • Output HTML5 games


The editor is getting old

  • Lots of cross-platforms issues with the UI toolkit, support for macOS and Linux is bad.
  • Iterating on the software is slow (C++ 😬)
  • Building UI is slow...
  • ...and limited to old UI components...
  • ...and UX would need some enhancements πŸ˜‡
  • The entry barrier for new contributors is high (C++ 😬)

It's time to react!

What do we want?

  • A new editor!
    • Cross-platform, better UI building paradigm, faster development, lower entry barrier
  • No full rewrite!
    • Full compatibility with the existing projects.
    • Nobody got time to wait for a rewrite.

Could web technologies help?

GDevelop C++ codebase

New GDevelop editor

GUI (windows, dialogs...)


Core classes (game, scene, object, textures...)
Game exporter

React.js powered interface

Node.js/browser adapters

X-platform toolkit (wxWidgets)


Using Emscripten

git clone && cd emsdk
./emsdk install latest && ./emsdk activate latest
./emcc tests/hello_world.c
node a.out.js

For large projects, Emscripten provides replacements that swap GCC or Clang by Emscripten in build system (automake, CMake...)

Expose existing classes

class ObjectsContainer {
  Object& InsertObject(const Object& object, std::size_t position) { ... };
  Object& GetObject(const String& name) { ... };

class Layout : public ObjectsContainer {
  void setName(const gd::String & name_) { name = name_; };
  const gd::String & getName() { return name; };

  // ...
interface Layout {
  void setName([Const] DOMString name);
  [Const, Ref] DOMString getName();

  // Inherited from ObjectsContainer
  [Ref] Object insertObject([Ref] gdObject object, unsigned long pos);
  [Ref] Object getObject([Const] DOMString name);

"WebIDL" bindings for Emscripten WebIDL binder:

Use Emscripten classes

Module().then(gd => {
  const layout = new gd.Layout();
  layout.setName("My game level");
  const object = new gd.Object();
  object.setName("My character");
  layout.insertObject(object, 0);

  console.log(layout.getName()); // "My game level"
  console.log(object.getObject("My character")); // Returns a gd.Object
  • Primitive types are converted, including JS strings (to char*)
  • Objects references are converted to pointers (or references) by the webIDL generated glue code
  • std::iostream is binded to console.log

Things to know

const layout = new gd.Layout();
// ...
gd.destroy(layout); // Binded objects need manual destruction
  • Memory management requires care!
  • Output files can be large (~3mb for GDevelop), but gzip helps.
  • For React:
componentDidMount() { this._project = new gd.Project(); }
componentWillUnmount() { gd.destroy(this._project); }

or use an Effect Hook πŸ”₯

Things to know

  • A complete test set is invaluable for ensuring that no errors are in the bindings.
abort(16). Build with -s ASSERTIONS=1 for more info.
  • Wrong types of parameters
  • Parameter forgotten
  • Using a deleted object

Finding a component library

The goal is to get started with all the classical components of a user interface without rewriting everything

  • Extensive list of high quality components
  • Good theming support
  • Support for (nested) dialogs
  • Flexibility of components
  • Documentation quality
  • Performance (blazing fast? ⚑️)

Large lists (with drag'n'drop)

We need to display lists from dozens to hundred of elements

πŸ‘‰ Virtualized lists

const SortableObjectRow = SortableElement(props => (
  <ObjectRow ... />

const ObjectsList = (props) => (
    rowRenderer={({ index, key, style }) => {
      <SortableObjectRow />

const SortableObjectsList = SortableContainer(ObjectsList);

  onSortEnd={({ oldIndex, newIndex }) => ...}

+ react-sortable-hoc

const ObjectRow = () => <div>...</div>;

const ObjectsList = (props) => ( => <ObjectRow ... />)

Moveable editors

Game editors are traditionally composed of multiple editors.


πŸ‘‰ Use React tiling window manager libraries

Keyboard handling

Focus/tab browsing


πŸ‘‰ Give focus() to your components

componentDidMount() {
  if (this.props.focusOnMount) {

or use a Hook πŸ”₯

πŸ‘‰ Abstract keyboard shortcuts so that it's easy to add to any component

    combo="shift + a"
    label="Be awesome all the time"
    onKeyDown={() => console.log("Awesome!")}

Large trees (with drag'n'drop)

πŸ‘‰ Virtualization again!


Levels rendering or visualizations

πŸ‘‰ The DOM is not powerful enough here

πŸ‘‰ The DOM can still be good enough

    points={ =>
    ).join(' ')}
  {, j) => (

When things aren't native enough

πŸ‘‰ Build abstractions over Electron APIs

export default class ElectronMenuImplementation {
  buildFromTemplate(template) {
    this.menuTemplate = template;
    return undefined;

  showMenu(dimensions) {
    if (!electron) return;

    const { Menu } = electron.remote;
    const browserWindow = electron.remote.getCurrentWindow(); = Menu.buildFromTemplate(this.menuTemplate);{
      window: browserWindow,
      x: Math.round(dimensions.left),
      y: Math.round( + dimensions.height),
      async: true, // Ensure the UI is not blocked on macOS.
class MaterialUIContextMenu extends React.Component { ... }
class ElectronContextMenu extends React.Component { ... }
export default class MaterialUIMenuImplementation {
  buildFromTemplate(template) {
    return, id) => {
      if (item.type === 'separator') {
        return <Divider key={'separator' + id} />;
      } else if (item.type === 'checkbox') {
        return (
      } else {
        return (

  showMenu() { ... }

When things aren't fast enough

πŸ‘‰ Double check rendering of your components

  • why-did-you-update

Don't forget to measure performance in production (React development build is way slower)

  • React 16.5 profiler
  • shouldComponentUpdate is often the answer

πŸ‘‰ Inspect calls to Emscripten objects

  • Beware of overhead in binding code/wasm call when calling functions, in particular if renderings
objects.filter(object => object.getType() !== "") // n wasm calls + string conversion
  .map(object => object.getVariables(project));   // n wasm calls + string conversion

Won't be noticeable in most UI situations, but can be if used in renderings or long lists/trees

  • It might be useful to store strings/values on the JS side if you know they won't change.


Ultra fast iteration and testing

Feedback loop is a few seconds while developing


  • Storybook
  • React Styleguidist

Modular development of UI components

  • electron-builder

Auto-updates and packaging...

...and code signing, and upload, and creating installers for Windows, .DMG for macOS, .tar.gz/AppImage for Linux.

Since day one, GDevelop 5 is auto-updated.

Time between releases moved from months to weeks or days.

  • More contributors than before
  • Startup time is similar (even faster as a web-app, ~3seconds)
  • People can try the software from their browsers in seconds without downloading anything
  • Documentation can link to the editor and examples can be opened directly in browser
  • Can be later ported to iOS/Android tablets, even phones
  • Integration with Dropbox, Google Drive...

And more

Plot twist

It's (still) JavaScript!

WebAssembly is on average 33.7% faster than asm.js, with validation taking only 3% of the time it does for asm.js

- Bringing the web up to speed with WebAssemblyΒ Haas et al., PLDI 2017

It's good enough

but WebAssembly will make it even faster


What are users saying?

Wow, what a difference a year can makeΒ 

but I have to admit, once you worked a bit with it, it could be really more productive on many aspects than GD4

Amazing how much easier and streamlined the engine has become



Made with