How to solve "An invalid or illegal string was specified" error with Svelte 3 and Cordova

August 19, 2019

The problem

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
 

error

Deciphering the message

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!

What’s happening

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).

caniuse1 caniuse2

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 solution

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!

Further reading


Written by@Jonathan Perry
Fullstack dev - I like making products fast

GitHubMediumTwitter