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?
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 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
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.
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.
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.
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!
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.
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!
- Use the built-in Chat window in React Studio
- Join us on the React Studio Slack channel
- Get in touch privately at hello @ neonto.com
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