The JavaScript SPA ecosystem
In the ever-evolving JavaScript (Web) ecosystem, a select group of Single Page Application (SPA) frameworks claim the majority of developer mindshare and shape the trajectory of modern web development. Meanwhile, emerging contenders aim to challenge existing paradigms or introduce unique features, often focused on enhancing performance or developer experience. Some noteworthy ones include:
- React: a library (not a framework) developed by Facebook. It utilizes a component-based architecture, enabling developers to create reusable UI elements.
- Vue: a framework that provides a reactive and component-based structure similar to React. It also allows developers to incrementally adopt its features into existing JavaScript projects.
- Angular: a framework maintained by Google.
- Svelte: a novel approach to building user interfaces by shifting much of the work to compile time. Instead of shipping a framework to the browser, Svelte compiles components into highly optimized JavaScript during the build process.
- Solid: a framework that feels similar to React when you compare its syntax to Functional Components and Hooks from React. Component functions are called only once at component construction and reactivity is facilitated by signals.
Two things that are common across these frameworks are that they are all manipulating HTML and have some sort of reactivity model (change HTML when something happens/changes). It’s common in building modern applications with SPAs to make sets of elements and units of logic reusable. This is often done by breaking code into components (leaning to separation by presentation) or somehow breaking logic out (reactivity logic included) into an importable module. Most SPA frameworks support some sort of component structure and something akin to React Hooks for sharing logic.
Additionally, there are meta frameworks that provide features and capabilities beyond what an SPA framework does out of the box. This can range from full-stack functionality (front to back) and server-side rendering (SSR) to other advanced features like data caching, routing, authentication, and authorization. Meta-frameworks tend to be built on top of an existing SPA framework. Some popular meta frameworks include:
- Next.js: a React framework.
- Nuxt.js: a Vue.js framework.
- SvelteKit: a Svelte framework.
- SolidStart: a framework for Solid that is currently in beta.
- Astro: a framework for content-driven websites. Astro is UI-agnostic, meaning you can bring your own framework. React, Vue, Angular, Svelte, and Solid are all officially supported in Astro.
Reducing friction of using PowerSync for SPAs
Of the above-mentioned SPA frameworks, PowerSync previously only had a wrapper package for React. It uses React Hooks around the PS common SDK package to support reactivity/live query integration for PowerSync functionality — making it easy for developers using React in their project to leverage PowerSync. This didn’t mean that non-React based web projects could not use PowerSync, but that the barrier to entry was a bit higher than if they were to use React.
Today, we announced the alpha release of a Vue Composables package for PowerSync. In this post, we will provide some background on its development.
Where we started: Porting React implementation to Vue
For adding the Vue reactivity wrapper, our first stab was to take the React wrapper and convert the React-specific code directly. The plan was to implement the wrapper code as composables which work similar to Hooks. Consider a quick and dirty port of one of the React Hooks:
React: [.inline-code-snippet]usePowerSyncWatchedQuery.ts[.inline-code-snippet]
Vue: [.inline-code-snippet]usePowerSyncWatchedQuery.ts[.inline-code-snippet]
We also identified the possibility to improve on the signatures of the [.inline-code-snippet]usePowerSyncQuery[.inline-code-snippet] and [.inline-code-snippet]usePowerSyncWatchedQuery[.inline-code-snippet] composables to return a reactive error and loading state which developers can hook onto.
After the reactivity was proven to work with a demo project (a Vue demo project derived from our react-supabase-todolist example), the next step was to include the Vue wrapper and demo project in the powersync-js repo.
Unknowns at the time of initial design
- How to map the React context/provider mechanism to Vue such that the same configuration is possible. With the React wrapper and React example project, context/providers are used to configure children of a top-level component such that all children are configured with a certain configuration.
Vuetify, Pinia, and Vue Router (well-known Vue libraries) use Vue’s plugin mechanism which allows a library to have app-wide configuration installed on the Vue instance during instantiation — often propagating the configuration with provide/inject.
- For the quick port above, parameters of the composable weren’t reactive. To do that in Vue you would need to wrap them with some help from Vue. For context, a Vue composable/component only executes once, whereas a React function/component executes multiple times — which is where we saw that parameter reactivity breaking in the Vue version.
A first attempt was to make the composables take in both primitives and wrapped versions of the parameters. See MaybeRef<T>.
Update post-POC: Alpha implementation for Vue composables
This is what we came up with after some experimentation: The parameters to the composable can either be normal JavaScript variables or refs, which on change will cause the query to refresh automatically. This is using the newly added [.inline-code-snippet]watch[.inline-code-snippet] with callback approach instead of [.inline-code-snippet]AsyncIterator[.inline-code-snippet]. Note that the implementation below might differ a bit from the released version.
[.inline-code-snippet]usePowerSyncWatchedQuery.ts[.inline-code-snippet]
See below for an example of this new composable in action. The query will refresh as the user changes the SQL in the input field, and any errors generated while typing will be shown.
[.inline-code-snippet]TodoListDisplayWatchedQuery.vue[.inline-code-snippet]
Bonus: for those unfamiliar with all the popular JavaScript options
Careful, this guy has opinions.