Web dev for old, embedded browsers

As painless as possible


Context

I work for a company that, among other things, makes software intended to be run on smart TV's. These TV's (from Samsung and LG) run their JS-based apps on old, non-upgradable, embedded browsers. As an example, a WebOS 3 TV runs its apps on a Chromium 38-based monster browser 1, with incomplete API support 2. On top of that, they often come with barely any RAM memory, which creates an awful user experience 3. And in case you're wondering, yes, they're still very much in use in the industry, due to how expensive it is to replace hundreds of Signage-grade TV's every couple of years.

Long story short, I was tasked with a rewrite of one such app, that had grown way too large and complex, to the point where adding new features was impossible. It's not really relevant what the app does exactly, but the main requirement was it being multiplatform, and being more maintainable than its predecessor. It needs to run on LG and Samsung TV's (both new and old ones), Ubuntu (and potentially other Linux distros), Windows, and Android. The previous app was written in AngularJS which, with all of its problems, proved to have some unexpected advantages. So for the rewrite, we considered the following web frameworks: AngularJS, React, SolidJS, Svelte. I will briefly explain the pros and cons of each of them for this use case, and what our decision ended up being. To run them on desktop and Android, we opted for using Tauri, to compile this web app into an executable.

The options

AngularJS

Pros

  • Familiarity: it is already in use in this and other projects
  • No compilation step: this makes it easier to debug in older TV's, since you can't use the "dev" builds of other frameworks due to browser limitations
  • Designed for older browsers, with no use of newer API's
  • Simplicity: I'm not going to defend every design choice of the framework, but I have to admit it's simple enough that you can always know exactly what's going on under the hood. Though this may be biased by the fact that I'm very familiar with it.

Cons

  • Not super ergonomic abstractions. Easy to make some nasty mistakes
  • No modern comforts & tooling. Linters and LSP servers struggle to be useful, no hot module replacement 4, etc
  • Hit EOL over three years ago, at the time of writing
  • Dying modules ecosystem
  • Hard to hire "AngularJS devs" nowadays
  • Not super fast or memory efficient, though generally not awful either

React

Pros

  • Familiarity: it is already in use in other projects in the company
  • Everyone knows React, easy to hire
  • Lots of tooling and modern comforts
  • Extensive module ecosystem

Cons

  • Due to the specifics of this project, having a good module ecosystem is not that relevant
  • Not super performant or memory efficient, compared to the alternatives. Huge bundle sizes
  • Compilation step makes it hard to debug on older TV's
  • Requires babel-ing and polyfills

SolidJS

Pros

  • Familiar syntax, though often with somewhat different semantics
  • Super lightweight and efficient
  • Lots of tooling and modern comforts
  • With the solid/no-proxy-apis eslint rule, it can warn you if you're using any framework features that require using Proxies (which are one of, if not the only thing the framework uses that cannot be polyfilled/transpiled to run on older browsers)
  • Additionally, there's some documentation as to which features require Proxies, which I think shows some commitment to supporting older platforms.

Cons

  • Not as popular as React
  • Compilation step makes it hard to debug on older TV's
  • Requires babel-ing and polyfills

Svelte

Pros

  • Super lightweight and efficient
  • Lots of tooling and modern comforts
  • I quite like it :p

Cons

  • Svelte 5's main feature is "runes", which completely changes the way it manages reactivity into a 100% Proxy-based paradigm. This is incompatible with older browsers. While the previous method can still be used for backwards-compatibility reasons, I think this shows that the Svelte team is not focused on supporting this kind of use case, and it worries me it will stop being supported in future versions. This was enough to disqualify it.

The choice

After considering our options and doing some testing, we ended up going for SolidJS. It seems to be the most likely to be able to handle the performance and memory requirements of the app, it has a lot of modern comforts, and not only does it run on older platforms, but it also seems to be committed to keep supporting them.

To get it working, I just needed to configure the @vitejs/plugin-legacy plugin properly in your vite.config.ts :

export default defineConfig({
  base: './', // IMPORTANT for webOS
  plugins: [
    solid(),
    legacy({
      targets: ['chrome 38'], // Change depending on your needs
      additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
      polyfills: true // Include all polyfills needed for your target
                      // This can also be a string[],
                      // defining which polyfills you want to include
    })
  ],
  ...

And you should also consider using the solid/no-proxy-apis lint, to avoid silly mistakes, adding this to your .eslintrc.json :

{
  "extends": [
    "eslint:recommended",
    "plugin:solid/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": [
    "solid"
  ],
  "rules": {
    "solid/no-proxy-apis": "error"
  }
}

  • 3 When memory runs out, the TV shows an error message and restarts
  • 4 It may not work on older TV's, but on modern TV's and on other platforms it'd be awesome to have