ractivejs / ractive

Next-generation DOM manipulation
http://ractive.js.org
MIT License
5.94k stars 396 forks source link

Ractive js not working on iOS 9 Safari - iPad & iPhone #2193

Closed lubomirblazekcz closed 8 years ago

lubomirblazekcz commented 9 years ago

Hello,

I found a critical bug with ractive js on iOS 9 Safari. The problem is that the page can't be loaded and keeps refreshing until Safari get message "A problem repeatedly occurred on page"

Try some of the examples on http://examples.ractivejs.org/, some of them have problems with loading, eg.: http://examples.ractivejs.org/illustration

This problem occurs on heavy load applications with lot of code in template. Our application has 1110 rows in template and it fails to load it. But if we limit it on 300 it works.

Any help is appreciated, I spent some time on debuging this and it's only happening on iOS 9 devices. On iOS 8 devices it's working, but the loading is extremely long.

MartinKolarik commented 9 years ago

Indeed, the example page doesn't load in Safari, and if opened in Chrome, it crashes the whole browser.

evs-chris commented 9 years ago

I have no Apple things with which to test (:), but from googling around a bit, it looks like pages that use lots of memory are known to crash iOS Safari, especially on older devices with less memory. If you add in pagination does it keep it from crashing?

lubomirblazekcz commented 9 years ago

Well I'm afraid that pagination is out of question for our kind of aplication. Anyway we tested this on iPhone 4S and iPad mini with iOS9 and there was the problem. But on old iPhone 4 and old iPad with older iOS it worked, but the loading was longer.

But I figured that the same problem was happening also with the oficial examples, so the problem is either in iOS 9 or some problem in ractive.

fskreuz commented 9 years ago

Was waiting for someone else to suspect iOS9. Throwing this info for what it's worth. Maybe iOS9 has messed up timing or something. Angular (I believe we use 1.2.x) also has problems with iOS9, where apps end up in an infinite digest cycle before Angular bails out and shows an error dialog. This may also be the case with Ractive, as indicated by the repeated errors and long load times.

skeptic35 commented 9 years ago

Sorry, can't hold it back: Not running on iOS9 Safari is imho entirely forgivable ;)

evs-chris commented 9 years ago

I just ran into this with a co-worker's iThing. It works just fine in iOS8, but some heavier pages just completely bail on iOS9. It gives some error about the page failed to run multiple times so it's giving up. Let me just pull up the handy mobile developer console oh wait that's not a thing that would be totally useful.

heavyk commented 9 years ago

you have to connect over cable to get the console on your MacbookPro

JonDum commented 9 years ago

you have to connect over cable to get the console on your MacbookPro

and only on safari lol

Rich-Harris commented 9 years ago

Some breadcrumbs via Twitter: Angular's problem was apparently related to the use of window.location.hash, and they worked around it like so: https://gist.github.com/IgorMinar/863acd413e3925bf282c. I haven't had a chance to look into whether something similar would fix this issue (it seems that it only affects real devices, and not the iOS simulator, which will make debugging lots of fun), but it's a starting point...

evs-chris commented 9 years ago

I did some tracking on this in my app today using alerts peppered through Ractive's init (ugh) and it looks like it's going to lunch somewhere during the initial parse of the template in 0.7.3. My captive test device had to go home for the day, but I'll try to track it down within the parser tomorrow.

evs-chris commented 9 years ago

I have now tracked this down to iOS 9 Safari not being able to parse giant-ish templates. For instance, I had one with a ton (~30) of partials in it that was a total of ~1400 lines. Splitting them out into their own individual script tags made the problem go away. I suspect it's got something to do with increased OS resource usage and/or a change in how substrings are handled, since the parser substrings a lot.

martypdx commented 9 years ago

@evs-chris just to be clear, it's not rendering. If you pre-parse the template then it works?

evs-chris commented 9 years ago

Correct. It's just parsing that kills it. Parsing the same amount of template in smaller chunks works fine. While I didn't try it specifically here, I use equally large or larger templates pre-parsed elsewhere, and they work as well.

evs-chris commented 9 years ago

I hacked the parser to avoid substrings (see the substr branch), and it does indeed work on iOS9 devices. Unfortunately it also increases the parse time by an order of magnitude. If anyone else would like to hack on that branch, please feel free to do so. I'm inclined to say close this as cant-fix, since it's not our bug. Do we have a known issues spot in the docs somewhere?

evs-chris commented 9 years ago

I just re-read the original post, and it seems in my various alert shotgunnings and head-to-desk interactions I forgot about the part of this issue that involved rendering a large number of rows. I haven't run into that part of this issue yet, but the most I've had to render at a time so far has been ~150 items.

@evromalarkey by rows in the template, do you mean items in an array that are being used in an {{#each}}? Can you give me a failing example to work with that doesn't involve the parsing issue? Also, edge seems to be considerably less resource intensive than 0.7.3. Have you tried your code on edge yet? edge seems to have stabilized to the point that I'm not longer finding bugs from my various apps, for whatever that's worth.

ceremcem commented 9 years ago

@evs-chris So do we have a workaround by preparsing? How could we preparse?

What do you mean by saying

Splitting them out into their own individual script tags made the problem go away.

Did you convert your partials into components?

martypdx commented 9 years ago

@ceremcem It means instead of passing in <p>Hello World</p> you run Ractive.parse('<p>Hello World</p>') at build time and the result of that call is what is delivered to the browser. Implementation details are specific to your build/delivery process.

ceremcem commented 9 years ago

@martypdx I didn't follow the documentation. OK, I understand we get an object, like

[{"t":7,"e":"div","a":{"class":"gallery"},"f":[{"t":4,"r":"items","i":"i","f":[" ",{"t":7,"e":"figure","a":{"intro":"staggered"},"f":[{"t":7,"e":"img","a":{"class":"thumbnail","src":["assets/images/",{"t":2,"r":"id","p":4},".jpg"]}}," ",{"t":7,"e":"figcaption","f":[{"t":2,"x":{"r":["i"],"s":"❖0+1"},"p":4},": ",{"t":2,"r":"description","p":4}]}],"v":{"tap":"select"}}," "],"p":1}]}]

Can you have a chance to give steps to implement this by hand? For example, I can run console.log(Ractive.parse(THE CONTENT)), get this result to the console, copy and paste into a javascript file and assign it to a variable. What do I need to do after that?

JonDum commented 9 years ago

You pass that object directly into the template property instead of the string.

new Ractive({
   el: 'app',
   template: [{ ... }]
})

But seriously, use a module system @ceremcem. ractive-loader makes it dead easy to use templates and have them parsed automatically. rvc can do this too I think. There's a bunch. Since you're just calling Ractive.parse() it's real easy to add that into a build system so you don't have to copy and paste like that. If you need a full example, I keep up to date a little boilerplate for myself and coworkers as a starting point https://github.com/JonDum/Web-Boilerplate for new projects.

ceremcem commented 9 years ago

@JonDum I'm convinced that I need to try switching to webpack, but I need a very quick workaround today. Now I'm splitting the single page web application into different html files (it works). Our project uses brunch.io, so I don't know how to benefit from ractive-loader. rvc seems to be a little bit complex to me at this time. Isn't there any simple manual way?

MartinKolarik commented 9 years ago

@ceremcem

Isn't there any simple manual way?

As @JonDum pointed out, you just need to pass the output of Ractive.parse() to new Ractive()

new Ractive({ template: /* output of Ractive.parse() goes here */ });
martypdx commented 9 years ago

@ceremcem:

var fs = require( 'fs' );
var path = require( 'path' );
var Ractive = require( 'ractive' );

// this assumes getting your templates from a directory
var dir = './templates';
var templates = {};

fs.readdirSync( dir ).forEach( function( file ){
    var name = path.basename( file, '.html' ); //assuming templates are .html
    var filePath = path.join( dir, file );
    var unparsed = fs.readFileSync( filePath ).toString();
    templates[ name ] = Ractive.parse( unparsed );
});

var js = 'var templates = ' + JSON.stringify( templates /*, true, 2*/ ) + ';'
fs.writeFileSync( 'templates.js', js );

Then if you include that file via <script src='templates.js'></script> you can access:

new Ractive({
    template: templates.foo  //name of template file
});
ceremcem commented 9 years ago

@martypdx Thank you for Node.js sample. I edited your sample script accordingly and it worked out:


var inputHtml =  './public/index.html'; 
var preparsedOutput = './public/javascripts/preparsed.js';
var tmpFile = './public/ractive.html';
var templateId = "#app";   // the ractive template id in HTML file 

var fs = require( 'fs' );
var path = require( 'path' );
var Ractive = require( 'ractive' );
var cheerio = require( 'cheerio' );

var content = fs.readFileSync(inputHtml).toString();
var $ = cheerio.load(content);
var template = $(templateId).html();
fs.writeFileSync( tmpFile, template);

content = fs.readFileSync(tmpFile).toString();
var parsed = Ractive.parse(content);
var parsedStr = JSON.stringify(parsed);

console.log("length: ", parsedStr.length);

var js = 'var preparsed = ' + parsedStr + ';'
fs.writeFileSync( preparsedOutput, js );

Tomorrow I'll find an iPhone-5 and test it. Thank you both.

martypdx commented 9 years ago

@ceremcem :+1:

ceremcem commented 9 years ago

Yess! Preparsing made the ractive application work correctly on iOS9. It continues working on desktop browsers and Android 4/5 phones; but the weird result is that it stopped working on iPhone 4S (iOS7), older Android devices, new Android tablets with Android 5. Why preparsing might cause any trouble? The application is online at this address.

martypdx commented 9 years ago

@ceremcem when I first loaded the page into chrome it didn't work and had a "couldn't load module /" error. On refresh it worked. Could it be a timing issue with how your loading modules that shows up on some devices? Just a wild guess...

ceremcem commented 9 years ago

@martypdx I don't know if it is a timing issue or not, but I started by trying other ways to load the preparsed object, starting from your original suggestion: I dropped usage of require, loaded the preparsed object externally with a script tag as you mentioned earlier. As far as I can test, it works on Safari and Chrome on iPhone4S (which was broken at the previous version), on Iceweasel on Linux desktop (continued working), on Chrome and Dolphin on Galaxy S4 (which was broken as well). I'll check if it works on iOS9 devices as soon as I can. Thank you again and again and again :)

Edit

Preparsing works on iOS9 devices as well.

jmellicker commented 9 years ago

We had the "A problem repeatedly occurred on page" error in iOS Safari with a project that loaded about 34 components up front using Ractive.load.

We solved this by creating a structure where each page route loads only the needed components (anywhere from 1 to 9). The app works great on iOS now, we haven't seen that error since.

zendar commented 8 years ago

We also solved our problems with iOS9 by pre-parsing the template. It isn't an optimal solution, but it works. :-)

lubomirblazekcz commented 8 years ago

I just get chance to look into this problem again on my project and I can confirm that preparsing indeed fix this issue! Thanks to everyone

Madgvox commented 8 years ago

Fantastic! :+1: I'll close this issue then as a solution has been found.

keith5000 commented 8 years ago

I disagree that this issue should be closed. The problem still exists in Ractive. Preparsing is just a workaround and will not work for everybody.

evs-chris commented 8 years ago

To be fair, the problem exists in Safari. I actually made a branch that didn't substring for every parser op, and it did work on Safari on iOS. It also slowed down parsing so much that it took roughly 135x longer.

Preparsing isn't the only solution though, as you can also split large templates into multiple partials and Safari will chug right on through it. The arbitrary limit that worked for my app was ~100 lines of template.

keith5000 commented 8 years ago

Does Apple have any plans to fix this in Safari? (Or is the bug in Webkit since Chrome on iOS also has this bug?)

evs-chris commented 8 years ago

I've no idea what Apple plans to do, or even if they have a bug they are tracking for this. I'm not an Apple user or developer, so i don't think I could report or even inquire. Improvements in their js engine should eventually resolve the issue, since it looks like a memory leak/resource exhaustion bug introduced by their last round of performance enhancements for Safari.

There is only Safari on iOS wrapped in different guises, so yes, this affects all browsers on iDevices. In fact, Chrome on iOS dies horribly when it encounters this bug.

keith5000 commented 8 years ago

That's very discouraging. Considering iOS 9 has been out for several months now with several updates thus far without addressing the memory leak, I have to guess that doing so is not high on Apple's list of priorities. If Ractive can't work around this problem then it is a very limited solution. I recently (pre-iOS 9) rolled out a large web application using Ractive and now I'm in the unfortunate position of having to completely rewrite it to work around the iOS 9 bug. Based on the Ractive team's reluctance to address this issue, I have to consider dropping Ractive and use something else that can scale.

At the very least, I suggest that you state the iOS 9 limitation somewhere in the documentation.

MartinKolarik commented 8 years ago

@keith5000 you can work around this problem by pre-parsing the template, which is recommended when running in production anyway.

ceremcem commented 8 years ago

We are on our way to switch to webpack, as recommended before. So, as no one look at the html source code, who cares if we preparsed or not. That seems an appropriate solution to me. By the way, doesn't preparsing give us a faster application? If so, I may thank Apple for introducing such a bug which make me meet with the preparsing option.

2016-01-13 16:37 GMT+02:00 Martin Kolárik notifications@github.com:

@keith5000 https://github.com/keith5000 you can work around this problem by pre-parsing the template, which is recommended when running in production anyway.

— Reply to this email directly or view it on GitHub https://github.com/ractivejs/ractive/issues/2193#issuecomment-171310091.

fskreuz commented 8 years ago

By the way, doesn't preparsing give us a faster application?

Yes. Avoids having Ractive do parsing on runtime, the part that chokes an iDevice. Preparsing also makes your components smaller (thus load faster) since the compiled form isn't markup anymore, but a compact AST.

If so, I may thank Apple for introducing such a bug which make me meet with the preparsing option.

We can all thank Apple and this bug for reminding us to deploy optimized code to production, and uncovering Ractive users who target constrained systems (cough iOS cough :trollface: ).

If only we had iOS devices handy to test these things... (sorry, Android user here)

martypdx commented 8 years ago

I'm, going to reopen this issue, as I suspect it will be easier to find for devs searching gh issues if we keep it open.

Priority is low because it's high effort and there's a clear work-around that's "best practices" anyway.

But for the developer, it is a bug regardless of whether it is in Ractive or iOS Safari.

martypdx commented 8 years ago

rolled out a large web application using Ractive and now I'm in the unfortunate position of having to completely rewrite it

@keith5000 how do you currently write and deliver your app? Are the templates script tags in the html page(s)?

evs-chris commented 8 years ago

I was also going to add a known issues page to the docs and link to it on the welcome page.

djp9803 commented 8 years ago
I am using templates in script tags within html pages (bottle templates). I love ractive, but as others have asked, is there no SIMPLE way to parse the templates without having to learn npm, grunt, gulp and other tools first? I don't use any of those tools or "build" my application. Even a method to get the parsed templates (as @ceremcem commented) to cut and paste would be helpful. Thanks.
ceremcem commented 8 years ago

@djp9803 Doesn't the nodejs script work for you?

djp9803 commented 8 years ago

@ceremcem Yes, I have it working. Thanks.

ceremcem commented 8 years ago

@evs-chris closing this bug causes false sense that "Ractivejs has solved this problem". Would you consider leaving it open?

evs-chris commented 8 years ago

Ractive can't solve this problem, though. There's a warning about large-ish templates in the docs that suggests using pre-parsed templates if you have them and support safari on ios, and I was trying to clean up some of our giant backlog of issues that are handled to the extent that we can.

In the absence of a fix for safari on ios dying with a bunch of substringing, you can work around the safari bug by splitting large-ish templates into smaller <script> partials if you can't pre-parse templates. If you can pre-parse templates, that's even better, as it's faster.

I did another test with the parser replacing substrings with regexes adjusted to start at the correct position with lastIndex twiddling. It is still abysmally slow.

Given all of that, should this still be left open?

fskreuz commented 8 years ago

This could be left open, but then... who's still on iOS 9? I'm under the assumption that iDevices get updated regularly and iOS 10 has been out for quite a while now. There might be little need to look into this unless it still happens on iOS 10.

Disclaimer: Android user here.

ceremcem commented 8 years ago

For those who use Browserify and want to preparse the templates continuously (in .html or .pug/.jade format) may use this browserify transform.

Enable this transform in your Gulp file and then:

Replace template: '#my-template' with

You may take a look at the components used in Scada.js for more examples. Briefly:

In the main ractive instance:

ractive = new Ractive do
    el: '#main-output'
    template: RACTIVE_PREPARSE('base.pug')

In a component file:

Ractive.components.checkbox = Ractive.extend do
    template: RACTIVE_PREPARSE('index.pug')
    isolated: yes 

TODO

Since altering a template file does not change its appropriate .js file, using cache with browserify fails to update while in continuous build mode. Till this problem is resolved we need to disable browserify caching.

ceremcem commented 7 years ago

I don't want to put the clock back for this issue, but probably the same problem bite me again. Page is not displayed on specifically IPhone 5 models. I'm preparsing the templates, there is nothing left for the browser to do. The only resort I could think of is sending components one after another, after page loaded and new Ractive completed. I don't know how to make this happen though.

Is there any advice to follow?