Meet the Design Compiler

How plugins work in React Studio, and what design-guided metaprogramming can do for you

In this post, we’d like to introduce you to the heart of React Studio: the design compiler. We’ll take a look at how React Studio plugins work and how Neonto’s design compiler can intelligently bring together your code and visual designs to construct React projects.

More fundamentally, this is about new abstractions for programming. The design compiler is actually an evolutionary step beyond libraries and frameworks. On a quick glance at React Studio, you might think “just another GUI builder”… But you’d be missing something essential. There is a whole level of metaprogramming capabilities in React Studio that makes it so much more!

You’ve probably seen code generators and declarative UI design tools before, but React Studio is neither. It combines features of both of those approaches into a single metaprogramming tool — an “application that writes applications” as guided by your inputs.

There are two significant practical benefits to this approach. The first is encapsulation safety. This could be compared to type safety: in a programming language with static types, the compiler can catch many kinds of errors before you even run the program. The design compiler does something similar — except for entire code fragments, not just individual variables.

The other benefit is framework independence. There are many frameworks out there that promise some kind of platform independence (“write once in language X, run anywhere!”)… But the design compiler approach takes it one level higher. You can actually retarget your app design to a different programming language and framework, just like you can compile a C++ program for a different CPU and operating system. (This is not just a theoretical possibility; at the end of this post you can find non-React examples of the Neonto design compiler in action.)

On the process level, the design compiler enables a new kind of software development workflow that we are calling Design-Accelerated Development. (That acronym is quite a “dad joke” in itself, isn’t it…?) In this post, we won’t go into details on that because focus here is on the technical aspects of the design compiler and its plugin interfaces. But we are going to tell you about our ideas for Design-Accelerated Development workflow in another series of posts soon. (Don’t miss that: follow us here on Medium)

“Peak Framework” and the limits of runtime

We wrote a separate post (link below) about the thinking that led to the design compiler approach. In brief, the software industry has reached “Peak Framework”. Buildtime tools is where the action is today. But do we have the right abstractions?

Read “Peak Framework” and the runtime barrier: why libraries and frameworks are exhausted

Modern build tools could be so much more than text editors and terminal commands — and that’s where React Studio comes in.

Inputs and outputs of the design compiler

A picture speaks more than a thousand words, so here’s a diagram. (Well, this picture does have quite a few words in it, so maybe it’s cheating…?)

The output is always a React project using Facebook’s create-react-app toolchain. This is a very solid base for modern web app development: you get a prepackaged build system with everything needed both for local testing and deployment builds, and the language environment is set up to support latest best practices (e.g. new JavaScript features that make React/JSX code more concise). If your needs later diverge from Facebook’s defaults, you can “eject” the project and modify it manually.

The inputs are what feed the design compiler. These are both visual (layouts, drawings and configuration made within the React Studio GUI) as well as programmatic in the form of plugins. The plugin system provides an advanced set of “hooks” into both the design compiler and the Studio GUI.

The default installation of React Studio 1.0 includes a few plugins: two web service plugins (a.k.a. data plugins), two component plugins, and defaults for the UI framework and navigation plugins.

The default plugins are useful as starting points for your own plugin development. There are really two ways to make a plugin: 1) take your own custom code work and wrap it into a plugin, or 2) take an existing npm package and make it available as a plugin in React Studio. We’ll look at examples of both.

Structure of plugins, and where to find them

Camera plugin

The default plugins were installed by React Studio in the folder:

~/Library/Application Support/React Studio

The ~ symbol means your home folder. You can get into the ~/Library folder easily in Finder using a hidden shortcut: open the Go menu, press down Alt key, and the Library item appears.

Plugins are macOS bundles. This structure is commonly used on both macOS and iOS, and it’s a very practical way to package together code and resources. Bundles are really folders that just appear like single files to the Finder. To read and modify the contents of a plugin, right-click on it in Finder and choose Show Package Contents.

Open Plugins folder by clicking the“Show Plugin folder in Finder” button

Some notes on plugins:

  • If you have a .plugin file, you don’t have to go to the Library folder to install it — you can do that in React Studio by using the Plugin Manager (found in the Plugins menu).
  • It’s possible to keep plugins in a project-specific location. This is very useful for two things: testing your plugins while developing, and managing advanced projects where you want to make sure that your project and its source plugins stay in sync. There’s even a Reload command that lets you load project-specific plugins without having to restart React Studio — highly convenient for development. You can find this in Project Map > App Settings block > Plugin settings.

Starting a new plugin

It’s easiest to start a plugin by modifying one of the default ones. First make a copy of the plugin in Finder and give it a new name.

To really rename the plugin, it’s not enough to just rename it in Finder. You must also edit Contents/Info.plist and replace the plugin name in both the “bundle name” and “bundle identifier” fields. (The bundle identifier is an internal package id that is used by the operating system; the “bundle name” is a human-readable version of your plugin’s name.)

Code and assets

The plugin’s main code is in Contents/Executables/Main.js. This contains the definitions and functions that are called by React Studio to perform tasks like creating a UI for the plugin and processing code fragments for the design compiler.

Camera plugin React code template

In Contents/Resources, you can find templates that provide React JSX code. (For plugins that just wrap an npm package, there may not be any need for a template, but more complex plugins will have something here.) Our internal practice has been to use the Mustache templating language, but this is not set in stone — it’s not a fixed feature of React Studio, but just a JavaScript dependency that gets included in each plugin via a setting in Info.plist. You’re completely free to use some other library or method for your templating needs, if you prefer.

Component plugin: Camera

This plugin is a compact example of a self-contained component plugin (i.e. all of the code is in the plugin, nothing is loaded at buildtime using the npm package manager).

The Main.js file is reasonably well commented, and contains the code for all the entry points called by React Studio. All of them are declared in the context of a “this” object — this represents the plugin instance (i.e. when you drag the Camera element into a canvas, an instance gets created, and the “this” object will point to that). You can also place private data in the “this” object; the convention is that such private data is prefixed with an underscore. In Camera, the only such declaration is “this._data”. (This object gets saved within the React Studio project thanks to the implementation of the persist/unpersist functions.)

The real beef of the plugin is in the exportAsReactWebComponent function. The arguments are a class name and an exporter object, which can be used to access React Studio’s design compiler state. Camera’s implementation of this function loads a template file from its Resources folder, then uses the Mustache template engine to fill out some values within the template code.

Another interesting method is writeReactWebCodeForPublishedInteractAction. This lets you write the JavaScript code for a previously declared interaction. Camera provides an action named “Shoot”, which lets the user take a picture and save the image data into a data slot within the React Studio project. (Actions can be chained in the React Studio UI, so typically the “shoot” action might be the first one in a chain where the image gets eventually sent to a server.)

Component plugin: Rating (wraps an existing npm package)

This plugin is interesting because it wraps an existing package from npm, one called react-rating.

The implementation here looks quite similar to Camera, but there is no template in the Resources folder — we don’t need one because the component code comes from the npm package.

Instead, the plugin declares its dependencies using the getReactWebPackages hook. Rather than write a component class, the plugin declares that it doesn’t need one (using this.writesCustomReactWebComponent = false), and implements a getReactWebJSXCode function. In this function, the plugin just returns an instance of the Rating component configured according to user-settable values from the React Studio UI.

Data plugin: Generic JSON

The Generic JSON plugin shares many similarities with the component plugins seen earlier. The type of plugin is different; this is declared in Info.plist using the key NeontoPluginTypeId. (The Info.plist file is a required feature of the Apple bundle format; all the keys with a “CF” prefix are defined by Apple. The only ones you typically need to edit are bundle version, name and identifier.)

Generic JSON’s Main.js has some of the same parts as the component plugins. There’s a describePlugin function, some private data, an inspector UI declaration and action methods, and rendering an icon for display (which just fetches an image from resources).

The readDataSheetFromServiceAsync function is used to load data inside React Studio. This allows your plugin to load data to be used at design-time — very useful!

For the output, there’s a writeReactWebCodeForDataSheetLoad function. As with the Camera plugin, it uses a Mustache template as the basis for the JavaScript code.

Data plugin: RSS Feed

RSS Feed is similar to Generic JSON above, but has two distinguishing features.

For the design-time preview, the data is loaded using an XML parser. The library for this is included in Resources/js/sax.js. This library file needs to be declared in the plugin’s Info.plist (key NeontoPluginDependencies), so that React Studio loads the library code into the plugin instance.

The actual exported JavaScript code has a peculiarity: it performs an AJAX-style to load the RSS feed, but it doesn’t call the server directly. Instead it proxies the request through Neonto’s “API Bridge” server. (You can see this at the start of the Resources/templates-web/DataSheetLoad.js file.) This proxying is necessary because RSS feeds can’t be directly loaded: they are typically served by plain-HTTP servers, but modern web browsers require HTTPS for all requests. Also there is a question of cross-origin requests being allowed — most servers that offer an RSS feed don’t allow AJAX loads of the feed. For these reasons, Neonto offers the API Bridge.

As part of your React Studio subscription, you have access to the API Bridge. Initially we offer Google Sheets and RSS, but new services can be added easily — let us know what you’d like to see on this front!

Plugin development questions?

We’d love to help you make your own plugins. There are a number of ways to get in touch, choose whichever is most convenient for you!

Neonto’s design compiler for other platforms

So far we have looked at examples of plugins that specifically output React web code, JSX and CSS… However Neonto’s design compiler supports other targets too!

Neonto Native Studio is an enterprise-ready product for native iOS and Android development. It uses the same design compiler core to construct truly native (“frameworkless”) projects for iOS and Android. It’s being used to produce hundreds of native apps already. If your interests are more in the native mobile space, check it out! There is a free 14-day trial available.

The default installation of Native Studio includes a Camera plugin that is very similar to the one you find in React Studio. (In fact, they could be the same plugin: you could have both the React and native implementations inside a single Camera.plugin … We didn’t do this just because we don’t want to confuse you with the implementations for non-React targets.)

If you’re looking to understand how Neonto’s design compiler can produce native code for multiple platforms from the same design inputs, the Camera plugins are a good place to start. You can then explore further with advanced APIs at the Native Studio developer site.

Download React Studio from www.neonto.com/reactstudio