Closed mgol closed 6 years ago
Another note to self: we still have the logic to handle multiple app roots in the recorder code; it should be changed to only work on one app root. Otherwise we have an O(n^2)
complexity where n
is the number of app roots, not to mention duplicate event registration.
PR updated; I still need to fix IE & refactor Preboot further a little so that it still works fine for multiple app roots but I addressed most comments otherwise.
PR rebased & updated; the logic has been rewritten, all it needs is tests & bug reports!
@CaerusKaru Feedback addressed. I also changed the commit message to mention the changed function names (without the PrebootCode
infixes).
Please check if the PR fulfills these requirements
[x] The commit message follows our guidelines: https://github.com/angular/preboot/blob/master/CONTRIBUTING.md#commit-message-format
[ ] Tests for the changes have been added (for bug fixes / features)
[ ] Docs have been added / updated (for bug fixes / features)
What modules are related to this pull-request
[x] server side
[x] client side
[x] inline
[ ] build process
[ ] docs
[ ] tests
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...) remove unused proxy imports from some test files
A bug fix via a refactor.
See #82.
There should be no race conditions.
Yes, at least in the inline code handling as it no longer waits for the app root to be available so it cannot be fully put in
<head>
.Currently Preboot suffers from various race conditions:
It waits for the
document.body
to be available and then applies its logic. However, the application root may not be available at this point - see the issue72.
Even if the application root exists when Preboot starts listening for events the server node may not be available in full yet. Since Preboot searches for nodes matching desired selectors inside of the existing
serverNode
, it may skip some of them if the server node hasn't loaded fully. This is especially common if the internet connection is slow.Preboot waits for
body
in a tight loop, checking every 10 milliseconds. This starves the browser but may also be clamped by it to a larger delay (around 1 second); that's what happens in modern browsers if the tab where the page loads is inactive which is what happens if you open a link in a new tab in Chrome or Firefox. This means Preboot may start listening for events after Angular reports the app is stable and thePrebootComplete
event fires. This then leaves the server node active, never transferring to the client one which makes for a broken site.To solve it, we're doing the following:
Since we want to attach event listeners as soon as possible when the server node starts being available (so that it can be shallow-cloned) we cannot wait for it to be available in full. Therefore, we switched to delegated event handlers on each of the app roots instead of directly on specific nodes.
As we support multiple app roots, to not duplicate large inline preboot code, we've split
getInlinePrebootCode
into two parts: function definitions to be put in<head>
and the invocation separate for each app root put just after the app root opening tag.To maintain children offset numbers (otherwise selectors won't match between the client & the server) we've removed the in-app-root script immediately when it starts executing; this won't stop the execution.
document.currentScript
is a good way to find the currently running script but it doesn't work in IE. A fallback behavior is applied to IE that leverages the fact that start scripts are invoked synchronously so the current script is the last one currently in the DOM.We've removed
waitUntilReady
as there's no reliable way to wait; we'll invoke the init code immediately instead.We've switched the overlay used for freeing the UI to attach to
document.documentElement
instead ofdocument.body
so that we don't have to wait forbody
.The mentioned overlay is now created for each app root separately to avoid race conditions.
Fixes #82 Fixes #72
BREAKING CHANGES:
getInlineDefinition
andgetInlineInvocation
; the previously definedgetInlinePrebootCode
has been removed. ThegetInlineDefinition
output needs to be placed before any content user might interactive with, preferably in . ThegetInlineInvocation
output needs to be put just after the opening tag of each app root. OnlygetInlineDefinition
needs to have options passed. An example expected (simplified) layout in EJS format:TODO:
document.currentScript
.Maybe switch the selectors to be scoped to the concrete app root so that they don't interfere with each other?(we're now attaching delegated handlers to the server node, not to document). This may be an issue as we now have separate code for each app root. We need to switch the logic to not use theappRoots
array as each of the app roots is now handled separately.getInlinePrebootCode
.