Supporting ancient browsers using modern Web-Tooling
In recent years, IE has become deprecated and Web-Developers often don't need to worry about backward-compatibility much anymore.
However, there are still scenarios where supporting older browsers is required, especially in industrial settings.
I recently encountered this challenge while developing a web-based Human-Machine Interface (HMI) for a client.
The requirement? Make it work with an old Qt-Embedded Browser based on Chromium 56, a browser-engine released in January 2017.
Yes, you read that right – that's over seven years old!
This presented unique challenges, as the baseline for modern tooling doesn't account for browsers that old anymore.
However, with just a tiny amount of work, it's surprisingly manageable to support these ancient platforms with modern tools.
TL;DR
For those who want to skip the details, here are the key tools:
- Vite-Plugin-Legacy - For ES5 conversion and polyfilling
- PostCSS Preset Env - For using modern CSS features
- (For Ancient Browsers) Avoid using CSS-Grid, opt for Flexbox instead - (If needed) Patch dependencies with PNPM-Patch (or similar tools)
The Challenges of Supporting Chromium 56
Before we dive into solutions, let's outline the main hurdles:
- ES5-only JavaScript: No support for ES6+ features
- No ES Modules: The baseline for modern JavaScript tooling is not available.
- Limited CSS Support: Many modern CSS features are not supported
- No BigInt: The DataType, needed by a dependecy of MQTT.js, is not available.
Solution 1: Converting to ES5 and Polyfilling with Vite
Vite has become pretty much the norm for modern web development, but did you know it can also help support older browsers? Enter vite-plugin-legacy.
This plugin automatically converts modern JavaScript and adds necessary polyfills based on Browserlist-targets using Babel.
It's extremely easy to set up:
import { defineConfig } from 'vite';
import legacy from '@vitejs/plugin-legacy';
export default defineConfig({
plugins: [
// ... your other plugins,
legacy({
targets: ['defaults', 'chrome 56'],
}),
]
});
With this configuration, Vite will generate two bundles: a modern one for newer browsers and a legacy one for older browsers like Chromium 56.
It also includes a runtime script that loads the appropriate bundle based on the user's browser.
Solution 2: Enhancing CSS Support with PostCSS Preset Env
To bring modern CSS features to older browsers, we can use postcss-preset-env. PostCSS allows you to use modern and or future CSS features and automatically adds prefixes and, most importantly, polyfills as needed.
If you don't know PostCSS, you probably already heard of Tailwind, right? It's just another PostCSS-Plugin ;)
As Vite has support for PostCSS included, all one has to do is to install the preset-env plugin and create a postcss.config.js
-file
/** @type {import('postcss-load-config').Config} */
import presetEnv from 'postcss-preset-env';
const config = {
plugins: [
presetEnv({
autoprefixer: true,
stage: 3,
features: {
'nesting-rules': true,
'has-pseudo-class': true,
},
browserslist: ['defaults and chrome >= 56'],
}),
],
};
export default config;
This setup allows you to use many modern CSS features while ensuring compatibility with your Browserlist-targets, like Chromium 56.
Gotchas and Workarounds
While the above solutions solve many issues, there are still some gotchas to be aware of:
CSS Grid: Polyfills for CSS Grid and gap properties exist, but are not very performant or well maintained...
For ancient browsers, it's often better to fall back to Flexbox with margins.
It might not be as elegant, but it gets the job done without significant performance hits.
BigInt Issues: Some libraries (like MQTT.js in my case) have dependencies that use BigInt, which isn't available in older browsers. Babel doesn't provide a BigInt polyfill, so we need to get creative.
Solution 3: Patching Dependencies
When all else fails, you might need to patch your dependencies. I encountered this with MQTT.js, where a dependency was using BigInt (in my case unnecessarily). Here's how I solved it using pnpm's patch feature:
- Run
pnpm patch <package-name>
to create a temporary folder with the package's source code. - Edit the files in this temporary folder, replacing or removing all occurrences of BigInt.
- Apply the patch with
pnpm patch-commit <temporary-folder>
.
This creates a .pnpm-patches
folder in your project, and the patch will be automatically applied whenever you run pnpm install
.
You could provide a BigInt polyfill and replace the native BigInt with it.
In most cases, this is probably the better solution - In my specific case however, that wasn't necessary, so I just opted to remove it.
Conclusion
Supporting ancient browsers doesn't mean you have to give up on modern development tools and frameworks.
With tools like Vite, PostCSS, and clever use of dependency patching, you can very easily develop without too much thought of platforms from yesteryear.