Once you’re done configuring babel for your svelte cordova app, and you have your svelte code all transpiled, you might still notice a cryptic error on old Android devices (6 and below):
code: 12
message: "An invalid or illegal string was specified."
name: "SyntaxError"
stack: "Error: An invalid or illegal string was specified.↵ at Uo (file:///android_asset/www/bundle.js:1:38541)↵ at c (file:///android_asset/www/bundle.js:1:40448)↵ at Object.start (file:///android_asset/www/bundle.js:1:40741)↵ at file:///android_asset/www/bundle.js:1:221013↵ at na (file:///android_asset/www/bundle.js:1:39512)↵ at file:///android_asset/www/bundle.js:1:26428↵ at MutationObserver.br (file:///android_asset/www/bundle.js:1:24691)"
__proto__: DOMException
Digging into the compiled bundle.js
file, we’ll follow the error position to this line (it’s all one line but you know what I mean) to find the culprit:
mo.insertRule("@keyframes "
Aha!
The @keyframes
CSS rule is being injected as part of svelte’s transitions. If you’re writing a svelte app, there’s 90% chance you’re using some transition.
According to caiuse.com, @keyframes
is supported since chrome 4, but only since chrome 43 is it supported unprefixed (that is without the -webkit-
prefix).
Usually the fix would be simple, just use @-webkit-keyframes
right?
But you can’t use that because Svelte is injecting the rule dynamically via JS in the compiled bundle.js
file with @keyframes
instead of @-webkit-keyframes
.
Is this a svelte bug? According to Rich Harris (creator of svelte) in this GitHub comment, this should be a workaround, rather than have svelte support ancient browsers.
The simplest solution that works is to override the insertRule function (thanks to JS being a dynamic language), as suggented in this comment. As we’re targeting Android and iOS here, these are both browsers (WebViews) that use the WebKit engine, so we can just replace everything with the webkit prefix.
So add a file called polyfill.js
in your public
folder, containing the following code:
(function () {
if (!(CSSStyleSheet && CSSStyleSheet.prototype.insertRule)) {
return;
}
var style = document.createElement('style');
var shouldPrefixKeyframes = false;
document.body.appendChild(style);
try {
style.sheet.insertRule('@keyframes _ {}');
} catch (err) {
shouldPrefixKeyframes = true;
}
document.body.removeChild(style);
if (!shouldPrefixKeyframes) {
return;
}
var originalInsertRule = CSSStyleSheet.prototype.insertRule;
CSSStyleSheet.prototype.insertRule = function (rule, index) {
if (rule.indexOf('@keyframes') === 0) {
rule = rule.replace('@keyframes', '@-webkit-keyframes');
try {
originalInsertRule.call(this, rule, index);
} catch (err) {}
} else {
originalInsertRule.call(this, rule, index);
}
};
})();
Now we’ll load this script before loading bundle.js
. In index.html
add <script src='polyfill.js'></script>
before <script src='bundle.js'></script>
.
Fixed!