JUCE 8 comes with a new set of tooling to help developers build UIs for audio applications and plugins with web technologies such as React, Vue, or even plain HTML, CSS and javascript.
Building with WebViews delivers many advantages:
This is a new software paradigm for music software with best practices that will evolve over time. Please note:
convertTo0To1 and convertFrom0To1, stringFromValue, etc backend functions.WebViews are embeddable browsers that mobile and desktop apps use to show you webpages without leaving the app.
The actual WebView software component is native to an end user's operating system. It's not something that's bundled and shipped in the application/plugin binary.
On MacOS, it's WebKit, on Windows is Edge (Chromium based), and Linux is GTK WebKit2.
A note on Windows compatibility: All Windows 11 machines have the WebView2 runtime pre-installed. The "vast majority" of Windows 10 recevied an update in mid-2022 to rollout runtimes, more details here.
There are 3 big changes:
WebBrowserComponent has been updated with new low level functions that supports communication between C++ and javascript running in the browser. New options can be supplied to WebBrowserComponent::OptionsJUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKINGIn addition, there's a new WebViewPluginDemo which demonstrates a JUCE plugin with a React frontend.
The WebBrowserComponent::pageAboutToLoad() function on Android now only
receives callbacks for entire page navigation events, as opposed to every
resource fetch operation. See BREAKING_CHANGES.md for more detail.
The WebView functionality is found in the juce_gui_extra module.
On Windows, it is now possible to statically link the WebView2 loader library.
In CMake use the NEEDS_WEBVIEW2 TRUE option with the juce_add_gui_app or juce_add_plugin function. This will make JUCE search for the WebView2 Nuget package in a default location, and emit hints if it cannot find it. If you installed the WebView2 package in a non-standard location you can use the JUCE_WEBVIEW2_PACKAGE_LOCATION CMake variable to add it to the search.
Additionally, use target_compile_definitions to ensure the following definitions have the right value.
JUCE_WEB_BROWSER=1 # This defaults to 1 unless explicitly specified
JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING=1For Projucer, ensure these two flags are set to enabled in the settings under Modules > juce_gui_extra.
On Windows, JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING ensures you are in line with Windows best practices to static link the WebView loader with your binary. Note this is not the actual WebView binary. It's just a small dll that talks to Windows and asks it to acquire a WebView.
JUCE will warn you if your build machine doesn't have WebView installed and prompt you to install.
The following illustrates the high level overview of how WebView integrates into the JUCE stack.

Let's walk through each of these pieces.
Although one could create a WebBrowserComponent in JUCE 7, in JUCE 8 it has been supercharged with an array of new functions and options.
New low level functions added in JUCE 8:
evaluateJavascript() evaluates a string as Javascript in the WebView context, allowing for low level, direct manipulation of the javascript stateemitEventIfBrowserIsVisible() emits javascript events that javascript code inside the WebView can listen toNew options for WebBrowserComponent::Options have been built on top of these low level functions:
withInitialisationData lets you specify key value pairs accessible by javascript.withUserScript lets you run javascript code before anything else is loaded in the WebView.withNativeIntegrationEnabled injects JUCE's javascript shim client-side. This provides the foundation for communication between javascript and C++.withNativeFunction lets you expose a named function in javascript that will asynchronously call a C++ function callback.withEventListener will listen to a named event in the WebView and call a C++ function callback.withResourceProvider is a callback the WebView will call whenever a web document requests a resource url (for an html document, image, css, etc). This allows you to serve data directly to the WebView from C++.When working with WebViews, remember to specify the following options. The WebView2 backend is not yet the Windows default and plugins require a User Data Folder specified for WebViews to be happy:
.withBackend (WebBrowserComponent::Options::Backend::webview2)
.withWinWebView2Options (WebBrowserComponent::Options::WinWebView2{}
.withUserDataFolder (File::getSpecialLocation (File::SpecialLocationType::tempDirectory)))The only other thing left to do is load a page!
To serve from a local server (for example in Debug mode) you can call the following to load a page over the network:
webComponent.goToURL ("localhost:3000");To load from C++ (for example in Release mode), do something like the following:
webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());By specifying withResourceProvider, you can hook into requests that the WebView makes. Essentially this callback can behave as a lightweight web server.
For example, in Release builds, you will probably want to serve most of the html, js, css and images from BinaryData via the resource provider.
In Debug builds, even though you might be hitting a development server on localhost, you still might want to grab data from the C++ backend via the resource provider. For example, the demo grabs spectrum data from C++ for a visualization.
Note that if you are loading scripts from a development server on a localhost address like localhost:3000, you'll need to supply this to withResourceProvider's optional parameter allowsOriginIn.
To reach the resource provider manually from javascript you can navigate to to juce.getBackendResourceAddress (path).
On MacOS, iOS and Linux this resolves to juce://juce.backend/ + path. On Windows and Android it will resolve to https://juce.backend/ + path.
This lets you expose a native C++ function to your javascript frontend.
For example, lets say you want to have a "Load Preset" button in your html where you can specify a preset with an integer number:
.withNativeFunction ("loadPreset", [](auto& var, auto complete)
{
loadPresetID (var[0]); // actually load the preset
complete ("Preset Loaded: " + var[0].toString());
})On the javascript side, with the JUCE frontend library imported in the Juce namespace, you can now call this when a button is clicked:
const loadPreset = Juce.getNativeFunction("loadPreset");
loadPreset(45).then((result => {
console.log(result);
});
Note by the usage of javascript Promises that this API is asynchronous.
JUCE 8 comes with a new javascript frontend library, located at modules/juce_gui_extra/native/javascript/index.js.
The library will run happily without errors regardless of whether the backend is present. This is by design to allow the frontend to be developed and visually tested without any need for C++ machinery.
Just like you can attach a parameter to a C++ slider, you can connect a C++ JUCE audio parameter to a javascript state object.
The following attachment types are provided out of the box: WebSliderParameterAttachment, WebToggleButtonParameterAttachment, WebComboBoxParameterAttachment.
They have corresponding Web Relays. These establish event listeners on the Javascript side.
This makes wiring up parameters to your Web frontend straightforward. For example, assuming you have a gain parameter — you'd need both an Attachment and a Relay for the parameter, like so:
WebSliderRelay gainRelay { browser, "gain" };
WebSliderParameterAttachment gainWebAttachment { *processor.parameters.getParameter ("gain"), gainRelay, nullptr };You'll pass the relay to WebBrowserComponent's constructor and setup the javascript side by specifying .withOptionsFrom
.withOptionsFrom (gainRelay)On the javascript side, you can access the javascript's representation of the parameter via getSliderState:
Juce.getSliderState("gain");You can wire up an arbitrary piece of UI to the JUCE backend using this slider state. In raw javascript, assuming you have a html range input with id slider, you could use event listeners:
const slider = document.getElementById("slider");
const sliderState = Juce.getSliderState("gain");
// when the backend (like the DAW) changes the value, update our slider
sliderState.valueChangedEvent.addListener(() => {
slider.value = sliderState.getNormalisedValue() * slider.max;
});
slider.addEventListener("mousedown", (event) => {
sliderState.sliderDragStarted();
});
slider.addEventListener("mouseup", (event) => {
sliderState.sliderDragEnded();
});
// when our slider changes value, update the DAW
slider.addEventListener("input", (event) => {
sliderState.setNormalisedValue(slider.value / slider.max);
});JUCE 8 comes with a WebView demo, showing how one might interact with a Web frontend from an audio plugin, including feeding the WebView UI audio spectrum data.
Because the demo is a plugin, you won't find it in the DemoRunner app. Instead, you can Open Example in the Projucer and export a project for your IDE.

Or configure the plugin with CMake. For example, on MacOS run this from JUCE's root directory:
cmake . -B cmake-build -DJUCE_BUILD_EXAMPLES=ON -GXcodeThis will produce cmake-build/JUCE.xcodeproj which you can then open and select the WebViewPluginDemo_AU schema to build the AU plugin.
After doing so, you should see the plugin at cmake-build/examples/GUI/WebViewPluginDemo_artefacts/Debug/AU/WebViewPluginDemo.component. Manually mv this into an AU folder like ~/Library/Audio/Plug-Ins/Components/ and open in your favorite DAW.
In your DAW, it will appear as an audio effect under the JUCE manufacturer:

You will see the following "fallback" html page until we build the frontend:

After installing node and running the above npm commands, the file examples/Assets/webviewplugin-gui_1.0.0.zip will be present, which is what the Demo will load as the frontend.
Opening the plugin again, you will see a React application with a slider that manipulates a lowpass filter as well as a visualization of the audio's spectrum.

So far we've built a static zip file for the demo. This is similar to a process you might use for deploying a Release build of your application (although you might choose to just put the files in BinaryData vs. zip them up).
A lot of the power of working with Web UIs comes from the ability to edit the UI on the fly, without recompiling. You can try this out in the demo by uncommenting the line that instructs it to go to localDevServerAddress.
webComponent.goToURL (localDevServerAddress);
// webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());
You can issue the following commands in the examples/Plugins/WebViewPluginDemoGUI frontend project directory to run the node project locally.
npm install; npm start
Load the project in an IDE like VS Code, make some modifications, hit save, and the WebView contents will automatically reload.