-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lazy serve: Track opened pages for dependency changes #1513
Lazy serve: Track opened pages for dependency changes #1513
Conversation
Hi @ryoarmanda! Just some personal feedback about how to improve user experience. Suppose we have a scenario where I have visited >5 pages, with >5 tabs open and I can't recall which pages are the top 5 most recently visited. And suppose I happen to modify the content of the page which it is not in the top 5 most recently visited list, without being aware of it. This may confuse the user as to why the page wasn't updated. Furthermore, the user has no easy way to find out which of the pages are currently being tracked for reloading (in the top 5 list). Thus, I think it may be a good idea to have a logging feature to update the user of the updated list of top 5 most recently visited pages; logged in the console whenever the user visits a new page, or when he modifies any pages (just to make sure that the list of top 5 most recently visited pages doesn't get lost in the logs). Also can I check what are the benefits of having a limit on the most recently visited pages? I was thinking what if we explore not having a limit on the most recently visited pages -- so when a page will be tracked for rebuilding if the user has visited it before? What do you think about this? @ryoarmanda :-) |
Yeah I agree with the concern of identifying the recently visited pages too. I will add the log for developer convenience.
I essentially followed how While we don't know the real intent of the user (i.e. how do we know when the user wants this page to be rebuilt?), I argue we can sort of make a guess on their "current workspace" based on their several most recently visited pages, hence why I put a limit to know which of the visited pages are still relevant to the user right now. A more accurate way to gauge the workspace is by checking out the opened tabs of the browser, but so far I don't see any good way to do that. |
Is this going to affect the performance of the live preview e.g., the speed of rebuilding? |
I don't think there's any performance difference in the rebuilding side as I didn't modify any of the page generation functions. The changes here is more on the part which identifies whether a page should be rebuilt when there are file changes detected, in which I feel is only a slight increase if there's any (the original compare against 1 page path vs the new compare against up to 5 page paths) |
Thanks! @ryoarmanda
quick comment on this aspect first (reviews from other folks still very welcome): it has a rather noticeable effect (try editing you'll likely need to make some changes + additions to the page generation queueing logic to fufill the second part in #1405 as well to avoid this (note simply ordering according to
|
Sorry, can I clarify whether you mean that the performance is off compared to master's performance? 😅 My earlier comment was about that, in the sense that there should be no noticeable change compared to the current performance as my change was just revolving on the decision whether to rebuild and not rebuilding itself (though by the comment you made that reordering does not matter actually realized that my reordering part is redundant =x I misunderstood the comment on If you do mean that the performance is off, I just tried benchmarking the changes here against master and the timing is pretty much the same. On editing |
I'm currently thinking that as we want to achieve sequential generation for recently viewed pages (so it generates in order from most-to-least recent), and async generation on pages that do not care about order of generation (to leverage the async operations), I might be able to modify the |
Ahh, thanks for clarifying. I was commenting on this part in particular, but I may have misinterpreted it - in that building 5 pages simultaneously would simply be less performant than one. You'll need to do
the ordering you have here already isn't redundant, just needs to be further built upon
You'll need to visit (start tracking) all the pages which has |
My worry is that this will slow down for live preview experience for authors across the board. Some of the pages in my MarkBind sites are quite heavy, and building 5 instead of 1 can slow me down noticeably. |
It should be ok if we adopt sequential generation of those 5 pages (so the most recent gets updated first, no slow down in how fast it takes for the updated version to show on the most recent tab). Its also quite likely not all of the 5 most recently visited pages depend on the file that updated, so in practice only 2 / 3 pages should be sequentially built most of the time. (assuming you open multiple tabs to work on mostly "closely related" pages - which may not even be true) The overall system performance would still take a hit, which is unavoidable. It can be mitigated by some smarter scheduling (second part of #1405), but that's a substantial change we can put that off to another PR, unless @ryoarmanda is up for integrating that here right away. 😁 (sequential generation should really suffice for now though)
5 sounds good to me. Any thoughts? @damithc |
Yes, we can go with 5 and see how it performs in practice.
Got it. Thanks for the explanation @ang-zeyu |
Oh I wasn't meaning to say that 😅 what I meant was that now there's a comparison against (up to) 5 page paths on determining whether a page should be rebuilt, from the original check that compares against 1 path (the
Yeah performance is a bit varied now with sequential generation, sometimes better and other times slower than the |
There's a hiccup on maintaining the recently viewed pages list which I have added the countermeasure just now. From how the My best idea for a countermeasure is that we classify it based on time since the last request. My observation is that reload spam requests tend to be very close with each other (only around But admittedly it's not perfect. For example, it is hard to distinguish a request that came from As far as I can think of, I can't distinguish the two from what we have right now 😓. Tried an idea of having a flag that is set to true whenever the file handlers are fired, but we don't know how long the reload spam is going to take, so don't know when to set down the flag again. If anyone has any other ways to handle this, I'm all ears :) Aside: This prompts me to look for the implementation of |
Thanks for investigating this. As you highlighted, I don't think we should be relying on indeterministic time-sensitive behaviour for the end user, which can vary very greatly from system to system, background processes, ... Would it be possible to do a simple patch for the purposes here? |
I think I might have to do a fork, as for the reloads and the local websocket storage is directly implemented in the (For reference, the websocket management part on the |
5aa76ac
to
24addce
Compare
@ang-zeyu @wxwxwxwx9 @damithc I just updated the strategy of determining recently viewed pages by patching the This list will be updated whenever a request has finished and is not a request that comes from live reload (the patch also exposes this utility function to test whether a given request URL corresponds to a live reload). In other words, the list will be updated whenever a new page is visited by user, either by intention or redirect. Also as we have the exact measure of the user's workspace, there is no need to cap the amount of pages to be considered recently viewed. There are lots of edge cases that can happen here mainly due to asynchronicity of requests and connection closes, and I feel like I have addressed most of them here, but I am not sure if I have caught all the cases. I'll try documenting all the cases I have found later on, but if anyone is available to play around that will be very helpful :) |
6114ebb
to
3a79423
Compare
Just for documenting for anyone who wants to test the opened pages list, here are the cases I discovered and addressed:
Note: On the live-server side, tab/connection close is recognized when the client socket established is closed, but this socket communication flow is established after the request has finished, as it is the product of the library's injected script in the sent HTML. MarkBind can only listen to as far as request finish, so as far as I can research MarkBind cannot actually react to a tab/connection close event in real-time. Aside from that, when a tab closes, the live-server patch will handle accordingly so that when MarkBind calls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! 👍
Let me confirm my understanding of your patch here first:
activeTabs
is only for page regeneration
- if there is a non-live reload request, push a new
activetab
-this is for new page navigations- done here instead of at socket time since you can't open the socket without the page built (on the earlier point for regeneration, if so, could this stay at socket initialisation time? (3))
- On file change / addition / removal (caused by a page rebuilt, new page built, page deleted), flag the
activetab
object asisReloading
, "update"client / prevClient
, send the 'reload' /refreshcss
event as per before to all tabs- for the
reload
event, it results in the page reloading, so the old websocket pair is closed; a new one is opened, as per before - unlike before though, closing the websocket now only removes from
activeTabs
theactiveTab
s which haveclient === theClosedSocket
- i.e. only socket closures not resulting from live reloads would remove the active tab (let's document it if so)
- for the
- On socket establishment, update the corresponding
activeTab
from (1) with the client- is it possible for the
tab
to ever not exist? (in which caseif (tab) {
is unnecessary)
- is it possible for the
- When any request-response finishes, update
Site
's "copy ofactiveTabs
"- new page builds (page navigations) is still handled by the old code
require('colors'); | ||
|
||
// CHANGED: added relative path that directs to the live-server directory from here | ||
const relativePathToLiveServer = "../../../../../node_modules/live-server"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use require.resolve
here so it works when published too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, will do.
* This patch allows us to gain access to the information that can be gathered with the client | ||
* websockets, which in turn can enables the support for multiple-tab development. | ||
* | ||
* Patch is written against live-server v1.2.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's fix the version in package.json
just in case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing
bf05acb
to
37d1f4a
Compare
37d1f4a
to
b026203
Compare
yup, just the spinning wheel builds.
In that case, is the
Thanks for the clarification. 👍 Let's try to make sure this is as valid as best as possible since this is pretty complex. To confirm again, so this is to take care of the case the user edits the same file they just navigated to between the considerable time lag of a request <-> socket establishment? Would this cause issues if:
👍 aside: do push incrementally so its easy to see what's changed, or with a fixup strategy if you want to keep your working commits tidy (#1478 (comment)) 😅 |
Initially I did it that way so MarkBind can grab the active urls to be able to have a real-time report of the current opened pages to the user when they visited a page (inspired by the earlier comment in this PR #1513 (comment) about the risk of authors' difficulty to precisely know what pages are the recently visited), but it also goes along with the fact that there is a gap between request and socket establishment, so this way it will properly record the page is opened even though the live reload has not been established. Aside, not sure if real-time feedback is really necessary as we will sync and log the opened pages list during MarkBind's fs event handlers anyway, what do you think? If it is unnecessary, I think we can remove the
Yes, this can unfortunately happen, the Off the top of my head, I think it's also quite hard to find a way minimize the impact of this edge case without causing other unintended effects. Thought of filtering out Would it be okay for us to have the limitation as you described? |
Yes, let's remove as mentioned ^. Shouldn't be necessary with the syncing on fs events.
Don't think its worth the effort currently either. Should we document this as a todo though? Maybe someone can find a way to fix this up later. =P |
packages/core/src/utils/index.js
Outdated
for (let i = 0; i < array.length; i += 1) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await func(array[i]); | ||
// eslint-enable-next-line no-await-in-loop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think this is kind of unavoidable. We need to await the current async function to finish before moving onwards to the next element so that the async functions is executed in order. forEach
is not able to support this kind of behaviour so I need to create a new approach. Had to make do with disabling that eslint rule for my approach.
Reference:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
referring to the eslint-enable-next-line
in particular
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh god haha my mistake 😅 TIL I don't need to do that
Certainly, hope someone comes around and found a solution to this 😄 I'll write a todo at the |
796c67c
to
7eb57c3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, nice work on documenting the test cases as well.
Couple last ones:
* @param {Array<string>} normalizedUrls Collection of normalized url of pages taken from the clients | ||
* ordered from most-to-least recently opened | ||
*/ | ||
changeCurrentOpenedPages(normalizedUrls) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs a guard against non-lazy serve mode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
||
// CHANGED: Added line to create new entry on non-live-reload requests | ||
const reqUrl = req.originalUrl; | ||
if (!LiveServer.isLiveReloadRequest(reqUrl)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs one more guard against xx._include_.html
types of files (dynamically loaded ones used in pages but not actually pages)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right, will do.
packages/core/src/utils/index.js
Outdated
for (let i = 0; i < array.length; i += 1) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await func(array[i]); | ||
// eslint-enable-next-line no-await-in-loop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
referring to the eslint-enable-next-line
in particular
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm 👍
Commit message needs an update though |
Right, I have updated the commit message in the original PR comment 👍 |
@ryoarmanda just noticed this Reproduction:
We might have to shift the active tab initialisation to socket initialisation time after all. PS: I didn't quite get the part above in bold earlier though (my pov is the things you added under
There is this, but just realised shouldn't need to be concerned with this case as well, because the socket is also initialised while the page is building (in the spinning wheel page).
on the same lines, since the socket is almost instaneously initialised, shouldn't be necessary |
@ang-zeyu Thanks for catching the back edge case! I will investigate it and see which part affects this shortly |
@ryoarmanda Just noticed one more edge case, but not too critical: the pages rebuilt in |
Ah right, need to adapt the corresponding methods as well. Thanks for the heads up! I'm working on it 👍 |
What is the purpose of this pull request?
Addresses the first enhancement proposal on #1405
Overview of changes:
live-server
library to expose current active tabs' urls, in order from most-to-least recently visited.currentOpenedPages
to theSite
class, which is an array that will store the absolute extension-less path to the current opened pages, ordered in the most-to-least recently visited.Site
methodchangeCurrentOpenedPages
to change the current opened pages list according to the given array of normalized URLs, filtering out duplicates and keeping the first match (i.e. the most recently visited among the duplicates)regenerateAffectedPages
to check whether the page is on the current opened pages list. Also modified page regeneration by prioritizing opened pages, of which is ordered in the same order as the current opened pages list.live-server
on file add/change/remove handlers.Anything you'd like to highlight / discuss:
Testing instructions:
Test on the
docs
folder for convenience. Refer to the case table on the below comment #1513 (comment).Proposed commit message: (wrap lines at 72 characters)
Lazy serve: Track opened pages for dependency changes
The lazy serve option tracks only the last visited page for changes
in its dependencies. Users editing multiple files must manually
reload each page to keep the browser synchronized with the content.
Let's track the opened pages instead, and rebuild them when any of
its dependencies have changed.
Checklist: ☑️