-
Notifications
You must be signed in to change notification settings - Fork 246
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
WIP: Greenfield web app #5162
WIP: Greenfield web app #5162
Conversation
Thanks, Alex! Look forward to digging into all of this. |
for future pull requests, can you open a PR from your forked repository? We like to keep the main repo free from non-released branches. |
Yep |
…environment variables. The commited env-example contains all public env variables, namely our auth0 client config. basic popup login working; this is apparently faster (appears faster) than redirect-based
…pt to handle the redirect; can be served statically from nginx
…cleanup, add server-side auth state
…eed to perform fetch requests, or otherwise use auth state, can operate in ssr mode; synchronization of state after this happens naturally by way of the cookie that contains it
…er.scss; proper dark mode on profile menu
@@ -0,0 +1,23 @@ | |||
import { PureComponent } from 'react'; | |||
import auth from '../libs/auth'; |
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.
Because of the Webpack config, auth can be imported multiple times across components, while also being included only once across the final collection of bundles.
The current Webpack config handles the mapping from components to flat bundles quite well.
…eanup, autoprefixer to normalize browser behavior
…f whitelisting for now
…cularly well suited for scorecard, bringing render time down to ~1.5ms + network latency
…eing somewhat stale
…ould inherit color as well
…tor into separate referrer-cookie package; factor out all calls to check if on server or browser into utils library
…rver/client; remove fast-url-parser, as not used. fix mobile style: font-size, initial content-width
… better typescript organization (first attempt at importing d.ts failed)
…o not block page navigation when data unavailable; next step is to prefetch scorecard data if not on a scoracrd page
@cseed As I briefly mentioned in an email on Saturday, I moved notebook loading to a prefetched model. Data and page loading are decoupled: When one clicks on the Notebook link, routing to the page happens without waiting for data to come in from notebook-api.hail.is. If data is available, it is shown, else a loading indicator. To avoid loading screens, I call notebook-api.hail.is asynchronously immediately after a user hits app.hail.is, whether they're on the Notebook page or not. Therefore, typically a user will never see a Notebook page loading indicator, when they click from one page to Notebook (say they land on the home page: by the time they click on Notebook, the data is fetched, so no loading screen). Furthermore, web sockets keep that Notebook data up to date, again regardless of whether a user is on the Notebook page. So in all instances except when a user lands on Notebook directly, the time it takes to reach the Notebook page is << 16ms (seemingly ~3ms). A similar approach is now used for scorecard, except Scorecard will render data in the SSR phase (because I consider scorecard less important, and because most users of the web app won't need it), and will not prefetch data when on another page. However, clicking on "Scorecard" from another page will not cause routing to block while waiting for the https://scorecard.hail.is/json response; instead a loading indicator will be shown. In practice I find this preferable, because the GUI feels more responsive, and users get more feedback. This demonstrates the core benefit of universal rendering approaches, and how they can improve page loading times over even the leanest server-side rendered application. |
…erns in a more concise manner
Let's build it from scratch, but better, faster, ...
Philosophy: Minimal magic, minimal reliance on outside work, don't use it unless we understand it.
Goal: <16ms interactions, including <16ms page transitions. Should feel identical to a desktop app in terms of performance, but maintain state like a website (i.e
get
variables).TODO:
To run:
then navigate to
http://localhost:3000
# lines: Most come from the package.json.lock files. These maintain versioning information.
Documentation
JS
https://javascript.info
We use the subset termed ES2018.
Compatibility across all browsers is ensured by transpilation using BabelJS, to some lower JS target. Polyfills should not be used, except when impossible to support a browser (this is configurable).
I mostly don't care about anything that isn't an evergreen browser, so I think we should support: Edge, Safari, Chrome, Firefox.
Among those we may want to care about, IE11 has ~2% global use (more if only desktop browsers)
NodeJS
We use 10.15. This is the latest LTS release
Versioning / dependency management
TL;DR:
npm
package.json
The file that tracks dependencies, and their semantic versioning numbers
Shape:
npm run
. Ex:npm run dev
Async, Await, Promises and callback (WIP)
Javascript is async-first. This is most obvious in Node.js, which is the most popular library for server-side JS.
At a high level, a function that defines a callback will return immediately. The callback is pushed on to the event-loop stack, and on each tick, is checked to determine whether it has returned or not.
Blocking operations within the callbacks will block the event loop. This is how CPU viruses, like blockchain manage to slow down web pages that are hijacked to include some mining script: hashing something 30 million times, takes a long time, and JS cannot do anything besides waiting for those operations to finish in a synchronous fashion.
Luckily, asynchronous functions are the norm in the JS ecosystem, such that both in the browser, and nodejs, IO functions are (mostly?) asynchronous.
Browsers and NodeJS use different event loops:
NodeJS: libuv event loop
Web: depends on the underlying Javascript Engine
Using callbacks
Using async/await
Deeply nested callbacks are hard to follow. This is called "callback hell". To help combat this, JS, in both NodeJS and Web context, developed Promises. Promises flatten the callback tree.
This has one problem. Chaining promises leads to a potentially hard to follow chain of
.then
.catch
. As in many other languages, the solution to "transforming" async call syntax to sync ones, is to color async functions with a "async" and "await" clauses.This can be used with any functions that return promises (but not those that just return a callback). Luckily again, JS libraries have been moving towards the Promise-land (sorry) for ~5 years, before Promises were in stdlib (bluebird).
React
What is a react component? A function that returns JSX.
React components accept props (HTML attributes
<Component propName={propValue} />
)Stateless vs stateful components
Stateless ones are typically cheaper, but not necessarily:
JSX differences from html
className
: "class" is a reserved word in JSX; used to specify the component class (every HTML element is modeled as an object). This will go away in 2019, maybeTo get around this, wrap in a "", an html element, or array. Basically, react wants a top level / root object. https://reactjs.org/docs/fragments.html, https://pawelgrzybek.com/return-multiple-elements-from-a-component-with-react-16/
JSX naming conventions
<span>
is a an HTML<span>
on output.You can pass state to these user-defined components, much like you would in HTML, using attributes. These attributes can have arbitrary names, except they must start with a lowercase letter, and follow camel-case convention. These attributes are called
props
PureComponent / shallow watch
React's reconciler is triggered whenever this.setState is called, resulting in a walk down the descendent nodes, based on either the presence of that state variable as a "prop" (i.e
<MyComponent name={this.state.name}/>
), or its use directly within the component (i.e `{this.state.name === 'Alex' ?To give the reconciler less work to do, when accepting objects as props, use a
<PureComponent>
. This will tell React to check the reference for diff, rather than deep value compare. Obviously much faster to do the latter.You can do even better than
PureComponent
. Use a regularComponent
, and specific ashouldComponentUpdate() { }
method in that component. Within that method, write whatever checks needed, so that when a prop, or state changes, you returntrue
, otherwisefalse
. When true, the component will re-render. However, this allows you to react in a more fine-grained way, i.e instead of checking reference, check for the update of a specific property, or don't react to that object changing at all.Behind the scenes, PureComponent is in effect implementing a shouldComponentUpdate that checks reference equality (prevProp !== currentProp).
References:
Memo stateless components
Stateless/functional components (i.e those than don't extend React.Component or React.PureComponent, i.e
(props) => <div>Hello {props.name}</div>
), can be memoized. As in a typical memoized function, given one set of input (props), the result is cached, and the cached result is returned for n + 1 calls.References
Typescript
And React Component prop definitions
https://levelup.gitconnected.com/ultimate-react-component-patterns-with-typescript-2-8-82990c516935
NextJS
https://nextjs.org/docs/
Next has 4 deviations from normal react:
it has this shape:
_document.js: Optional. Rendered only on the server, exactly one time. Wraps _app. Good place to define external resource you want to load, such as some external stylesheet, font, whatever.
getInitialProps
: a lifecycle method that is only available to components in thepages/
folder.getInitialProps
runs once during server-side rendering, and again if you navigate to the page that defines it. Only components in pages can specify this property. This is because it is effectively a function triggered during routing and:getInitialProps
is of course only available if you define a stateful component. See functional components (just JSX wrapped in a function, rather than a class)pages/
, with index.js mapping to/
For instance, to navigate to
domain.com/scorecard/users
, you'd make the folder structure:pages/
These 'pages' components are just like normal react components, except they expose
getInitialProps
, described above. Each page file must export 1 default component:There is nothing else to do to get routing to work, a quite nice solution.
JS pragma
this
is different than in most (every?) other language. scope of this is bound to caller, not object containing the methodTips
Client-side routing
Wrap a normal anchor tag in
<Link ></Link>
ex:
This simply adds the client-side routing logic, and passes the href to <a href=.
Prefetching
One of the neat things about Next is how easy it makes prefetching pages. This allows perceived page loading times on the order of 5ms, even when the page requires very complex state (say a GraphQL or series of REST calls with large responses).
Make your app do ONLY server-side routing
Meaning every time you click on a link in your page, you hit the server, just like the first visited page.
Simply use
<a>
directly.Caching and sidecar requests
Broadly, there are three strategies: browser caching, server caching, and service-worker caching.
In this project we will likely use all three. Server caching is an excellent strategy for pages that serve only public data. In this strategy we pre-generate the static html, serve that, and invalidate the cache once in a while. An example of this can be found in e131a93
Styleguide
Performance
In practice, React in 2019 will likely be the best performing UI solution available, with of course the exception of some very well optimized JS. This is because of React Fiber's time slice mode, which will effectively allow UI operations, like user input, to preempt other operations
How React works
React Fiber, the new reconciling/scheduling algorithm: https://github.com/acdlite/react-fiber-architecture