Update 2026: Missing images

I found this blogpost sitting in an export file from Ghost, which was what I used to publish those blogs back in the day. The content is still there, but all the images are lost. I’ll replace those images with some descriptions because I don’t expect anybody to follow what I did then anymore, now that Bubble.io is practically dead.

CitizenDev had released about 20 plugins on to Bubble.io Marketplace. Bubble give us a “working” SDK to develop custom elements, actions, in order to make up for the lack thereof natively in Bubble. However, a “working” SDK is what it is, and working with this SDK is a horrible development experience by 2023 standard.

  • The editor is terrible. You can’t even search/scroll normally.
  • The preview section of the element is a separated, isolated iframe. Which means I have to duplicate all the code inside this section to provide a truly WYSIWYG view
  • jQuery only. (No reactive goodness)

Don’t want to read this whole write up and just want to jump straight to the good action? Check out the POC Github Repo: https://github.com/citizendev-io/preact-bubble-plugin-poc

Approach

I have been thinking long about integrating some new JS framework into Bubble’s plugin. The main problems are:

  • Most new JS frameworks now require you to have a build step. You can still (probably) write React using their olden, non-JSX syntax, but I doubt it would be any better than wrestling with jQuery.
  • However Babel allows building JSX code directly on Browser, using their standalone package. This may solves the problem of running React code on Browser directly, but this is going to incur a lot of overhead.

The best way forward with developing (large) component would be ditching the whole Bubble’s plugin editor setup. We need to just load a prebuilt, minified element code into the Bubble’s plugin editor. This is where WebComponent going to shine, because you can use React, Vue to build a totally isolated component that can be loaded with zero dependency on Bubble.

Ideas

Here are several ideas, which we will visit one-by-one when we test out the process

  • Use Preact as an alternative for React. Preact support building WebComponent natively with their thin WebComponent wrapper
  • Use Vite dev server to serve the JS, CSS bundle on localhost.
  • In Bubble’s element editor, load the JS & CSS bundle from localhost and Develop
  • When publishing for livemode, upload the JS & CSS to Bubble’s plugin file, then point the loader to load from those source

The setup

The goal is to setup a system that allow efficient development while still able to run actual system code.

This is an interplay between two system: Bubble.io and the dev server on our machine. Let’s setup.

Vite + Preact

First we start with a Vite + Preact project, which is already covered by the Preact documents. Once the setup is done we get a clean project for writing Preact code.

There used to be an image here

It is probably showing a project layout, scaffolded by Vite + Preact

One important thing that we need to do is setup proper TypeScript support (it’s 2023). Follow this section of the document and we are golden.

There used to be an image here

Old caption: “Don’t you just love static type checking and their typehint.”

It is probably me showing an autocomplete dropdown from VSCode or something similar, where all the object properties are showing. After all I’m a sucker for typed language

This codebase give us also a functional Enzyme + Jest test suite, but for the sake of brevity in this guide we will skip using it.

Let’s throw in a simple counter:

import { h, VNode } from "preact";
import { useState } from "preact/hooks";

interface BubbleProperties {
  name?: string;
}
interface CounterProps {
  properties?: BubbleProperties;
}

export default function Counter(props: CounterProps): VNode {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Hi {props.properties?.name ?? "anonymous"}.</h1>
      <div>
        This counter is written fully in Preact. This element can also load
        properties from Bubble.io
        <br />
        The current count is: {count}
      </div>
      <button onClick={() => setCount(count + 10)}>Plus 10</button>
      <button onClick={() => setCount(count - 1)}>Minus</button>
    </div>
  );
}

Good olde Counter. With a Properties object that we will hopefully get from Bubble.

Next step we make this codebase emit a library. This means we need to:

  • Reconfigure Vite to build in library mode. Vite has a section in their document for this.
  • We configure Vite to build from a src/index.tsx entrypoint. This will be the core component code. Then we create that src/index.tsx file.
  • We need to remove the "type": "module" directive in package.json. This will allow us to emit an index.umd.js file which can be loaded safely on Browser. Read more about the UMD, ESM, CJS shenanigans here.

Vite devserver won’t emit a compiled version of the src/index.tsx file. Vite’s code revolve around the index.html file, with a hot reloaded connection to the JS/TS files in question. We instead ditch the Vite devserver, and run a modified script.

{
  "scripts": {
    "dev": "concurrently \"vite build --watch\" \"serve dist --cors\"",
  },
  "devDependencies": {
    "serve": "^14.0.0",
    "concurrently": "^8.0.0"
  }
}

In this script, we run the vite build command, with the --watch option so it will automatically rebuild the JS bundle. Then we run serve of the whole JS bundle.

At the end of this step, we have this working URL localhost:3000/index.umd.js

There used to be an image here

Old caption: “A beautiful executable soup.”

I’m not particularly sure what I would have shown here, but I’m thinking it me opening localhost:3000/index.umd.js in the browser or curl-ing it, to show the JS file to be loaded

Loading code

Now that we have sort of executable JS thing going, we can proceed with creating a new Bubble plugin.

There used to be an image here

Old caption: “We start with an aptly named Counter element.”

This probably showed a Bubble element entry, in their plugin editor.

First thing we need to do is to import the code. I added a header script reference:

There used to be an image here

Old caption: “Load the script”

This probably showed me adding a <script> tag in the header section

Load the script React component

Spin up a test Bubble app, put the test element in and go:

There used to be an image here

Old caption: “Uhhhh”

This was probably me showing an error, where the element doesn’t render

As it turns out, Preact still run its element in its own Virtual DOM. For the uninformed (me included), this mean we need to run its element inside a special environment, like its preact-habitat. But we have a better trick up our sleeve: web-component

Preact give us a nice wrapper called preact-custom-element that gives us a thinly wrapped Preact element that can be added directly as HTML tag onto the website. Configuration of this element is really simple. We only need to add a line of code to register.

import register from "preact-custom-element";

export default function Counter(props: CounterProps): VNode {
   // ...something
}
register(Counter, "custom-counter", ["properties"]);
    

Now we can load the element like this:

There used to be an image here

This was probably me showing the HTML markup where I load a custom element. It probably just had the line <custom-counter/> in it

Note here we are attaching the properties sent from Bubble to the element. The Preact Web Component wrapper will ensure the element is re-rendered when we update the properties attribute.

The Bubble element receive a name field:

There used to be an image here.

It probably showed a name field in the Bubble editor for the element

Pass in the name field (I choose Tyler Durden), then open the preview of the test app.

There used to be an image here.

It probably showed me typing “Tyler Durden” into the name field

There used to be an image here

Old caption: “Adding the Element”

This is probably me adding that element in the Bubble WYSIWYG area

There used to be an image here

Old caption: “In preview mode”

This definitely showed the preview page with a working Preact component

Too much details?

Yeah I know this is too much word. Consider this as an excited rant. If you just want to test this approach out, check out this Github repo: https://github.com/citizendev-io/preact-bubble-plugin-poc

What’s next?

  • WYSIWYG. We need to be able to load the element inside preview as well.
  • Setup a project template.
  • Really build something out of this.