React Labs: What We’ve Been Working On – June 2022

React 18 was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring.

We typically have a number of projects being worked on at any time, ranging from the more experimental to the clearly defined. Looking ahead, we’d like to start regularly sharing more about what we’ve been working on with the community across these projects.

To set expectations, this is not a roadmap with clear timelines. Many of these projects are under active research and are difficult to put concrete ship dates on. They may possibly never even ship in their current iteration depending on what we learn. Instead, we want to share with you the problem spaces we’re actively thinking about, and what we’ve learned so far.

Server Components

We announced an experimental demo of React Server Components (RSC) in December 2020. Since then we’ve been finishing up its dependencies in React 18, and working on changes inspired by experimental feedback.

In particular, we’re abandoning the idea of having forked I/O libraries (eg react-fetch), and instead adopting an async/await model for better compatibility. This doesn’t technically block RSC’s release because you can also use routers for data fetching. Another change is that we’re also moving away from the file extension approach in favor of annotating boundaries.

We’re working together with Vercel and Shopify to unify bundler support for shared semantics in both Webpack and Vite. Before launch, we want to make sure that the semantics of RSCs are the same across the whole React ecosystem. This is the major blocker for reaching stable.

Asset Loading

Currently, assets like scripts, external styles, fonts, and images are typically preloaded and loaded using external systems. This can make it tricky to coordinate across new environments like streaming, server components, and more.
We’re looking at adding APIs to preload and load deduplicated external assets through React APIs that work in all React environments.

We’re also looking at having these support Suspense so you can have images, CSS, and fonts that block display until they’re loaded but don’t block streaming and concurrent rendering. This can help avoid “popcorning“ as the visuals pop and layout shifts.

Static Server Rendering Optimizations

Static Site Generation (SSG) and Incremental Static Regeneration (ISR) are great ways to get performance for cacheable pages, but we think we can add features to improve performance of dynamic Server Side Rendering (SSR) – especially when most but not all of the content is cacheable. We’re exploring ways to optimize server rendering utilizing compilation and static passes.

React Optimizing Compiler

We gave an early preview of React Forget at React Conf 2021. It’s a compiler that automatically generates the equivalent of useMemo and useCallback calls to minimize the cost of re-rendering, while retaining React’s programming model.

Recently, we finished a rewrite of the compiler to make it more reliable and capable. This new architecture allows us to analyze and memoize more complex patterns such as the use of local mutations, and opens up many new compile-time optimization opportunities beyond just being on par with memoization hooks.

We’re also working on a playground for exploring many aspects of the compiler. While the goal of the playground is to make development of the compiler easier, we think that it will make it easier to try it out and build intuition for what the compiler does. It reveals various insights into how it works under the hood, and live renders the compiler’s outputs as you type. This will be shipped together with the compiler when it’s released.

Offscreen

Today, if you want to hide and show a component, you have two options. One is to add or remove it from the tree completely. The problem with this approach is that the state of your UI is lost each time you unmount, including state stored in the DOM, like scroll position.

The other option is to keep the component mounted and toggle the appearance visually using CSS. This preserves the state of your UI, but it comes at a performance cost, because React must keep rendering the hidden component and all of its children whenever it receives new updates.

Offscreen introduces a third option: hide the UI visually, but deprioritize its content. The idea is similar in spirit to the content-visibility CSS property: when content is hidden, it doesn’t need to stay in sync with the rest of the UI. React can defer the rendering work until the rest of the app is idle, or until the content becomes visible again.

Offscreen is a low level capability that unlocks high level features. Similar to React’s other concurrent features like startTransition, in most cases you won’t interact with the Offscreen API directly, but instead via an opinionated framework to implement patterns like:

Instant transitions. Some routing frameworks already prefetch data to speed up subsequent navigations, like when hovering over a link. With Offscreen, they’ll also be able to prerender the next screen in the background.

Reusable state. Similarly, when navigating between routes or tabs, you can use Offscreen to preserve the state of the previous screen so you can switch back and pick up where you left off.

Virtualized list rendering. When displaying large lists of items, virtualized list frameworks will prerender more rows than are currently visible. You can use Offscreen to prerender the hidden rows at a lower priority than the visible items in the list.

Backgrounded content. We’re also exploring a related feature for deprioritizing content in the background without hiding it, like when displaying a modal overlay.

Transition Tracing

Currently, React has two profiling tools. The original Profiler shows an overview of all the commits in a profiling session. For each commit, it also shows all components that rendered and the amount of time it took for them to render. We also have a beta version of a Timeline Profiler introduced in React 18 that shows when components schedule updates and when React works on these updates. Both of these profilers help developers identify performance problems in their code.

We’ve realized that developers don’t find knowing about individual slow commits or components out of context that useful. It’s more useful to know about what actually causes the slow commits. And that developers want to be able to track specific interactions (eg a button click, an initial load, or a page navigation) to watch for performance regressions and to understand why an interaction was slow and how to fix it.

We previously tried to solve this issue by creating an Interaction Tracing API, but it had some fundamental design flaws that reduced the accuracy of tracking why an interaction was slow and sometimes resulted in interactions never ending. We ended up removing this API because of these issues.

We are working on a new version for the Interaction Tracing API (tentatively called Transition Tracing because it is initiated via startTransition) that solves these problems.

New React Docs

Last year, we announced the beta version of the new React documentation website. The new learning materials teach Hooks first and has new diagrams, illustrations, as well as many interactive examples and challenges. We took a break from that work to focus on the React 18 release, but now that React 18 is out, we’re actively working to finish and ship the new documentation.

We are currently writing a detailed section about effects, as we’ve heard that is one of the more challenging topics for both new and experienced React users. Synchronizing with Effects is the first published page in the series, and there are more to come in the following weeks. When we first started writing a detailed section about effects, we’ve realized that many common effect patterns can be simplified by adding a new primitive to React. We’ve shared some initial thoughts on that in the useEvent RFC. It is currently in early research, and we are still iterating on the idea. We appreciate the community’s comments on the RFC so far, as well as the feedback and contributions to the ongoing documentation rewrite. We’d specifically like to thank Harish Kumar for submitting and reviewing many improvements to the new website implementation.

Thanks to Sophie Alpert for reviewing this blog post!

Flatlogic Admin Templates banner

Build Your Own Little Menu Bar App for All Those Things you Forget

I have a little Pinned Pen of Things I Forget that I revisit quite a bit… when… I forget… things. It occurred to me the other day that it would be even more convenient as a menu bar app (macOS) than a Pen. Maybe an excuse to learn Swift? Nahhh. There’s always an app for that. I know Fluid can do that, but I wasn’t 100% sure if that’s up to date anymore or what. As I happen to be a Setapp subscriber, I know they have Unite as well, which is very similar.

So let’s do this! (This is a macOS thing, for the record.)

1) Make a little website of Things You Forget that allows you to one-click copy to clipboard.

You don’t need a special URL or anything for it. And good news, CodePen Projects can deploy your Project to a URL with a click.

Here’s my example Project.

2) Deploy it

Now I click that Deploy button in the footer and can kick it out to a live-on-the-internet URL easily.

3) Turn it into a Menu Bar App with Unite

Then I launch Unite and give it the barebones information it needs:

That’ll instantly make the app. It takes like 2 seconds. From here, two things:

Paste a better image into the Get Info window so you can have a cool icon.
If you’re on an M1 Mac, you might have to click the “Open using Rosetta” icon in order for it to work.

That second one is a little unfortunate. Makes me wish I tried Fluid, but hey, this ultimately worked. You’ll know you need to do it if you see this:

4) Enjoy

A totally custom-designed app to help your own brain!

The post Build Your Own Little Menu Bar App for All Those Things you Forget appeared first on CodePen Blog.

Flatlogic Admin Templates banner

Implement a PWA using Blazor with BFF security and Azure B2C

The article shows how to implement a progressive web application (PWA) using Blazor which is secured using the backend for frontend architecture and Azure B2C as the identity provider.

Code https://github.com/damienbod/PwaBlazorBffAzureB2C

Setup and challenges with PWAs

The application is setup to implement all security in the trusted backend and reduce the security risks of the overall software. We use Azure B2C as an identity provider. When implementing and using BFF security architecture, cookies are used to secure the Blazor WASM UI and its backend. Microsoft.Identity.Web is used to implement the authentication as recommended by Microsoft for server rendered applications. Anti-forgery tokens as well as all the other cookie protections can be used to reduce the risk of CSRF attacks. This requires that the WASM application is hosted in an ASP.NET Core razor page and the dynamic data can be added. With PWA applications, this is not possible. To work around this, CORS preflight and custom headers can be used to protect against this as well as same site. The anti-forgery cookies need to be removed to support PWAs. Using CORS preflight has some disadvantages compared to anti-forgery cookies but works good.

Setup Blazor BFF with Azure B2C for PWA

The application is setup using the Blazor.BFF.AzureB2C.Template Nuget package. This uses anti-forgery cookies. All of the anti-forgery protection can be completely removed. The Azure App registrations and the Azure B2C user flows need to be setup and the application should work (without PWA support).

To setup the PWA support, you need to add an index.html file to the wwwroot of the Blazor client and a service worker JS script to implement the PWA. The index.html file adds what is required and the serviceWorkerRegistration.js script is linked.

<!DOCTYPE html>
<html>
<!– PWA / Offline Version –>
<head>
<meta charset=”utf-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no” />
<base href=”/” />
<title>PWA Blazor Azure B2C Cookie</title>
<base href=”~/” />
<link rel=”stylesheet” href=”css/bootstrap/bootstrap.min.css” />
<link href=”css/app.css” rel=”stylesheet” />
<link href=”BlazorHosted.Client.styles.css” rel=”stylesheet” />
<link href=”manifest.json” rel=”manifest” />
<link rel=”apple-touch-icon” sizes=”512×512″ href=”icon-512.png” />

<body>
<div id=”app”>
<div class=”spinner d-flex align-items-center justify-content-center spinner”>
<div class=”spinner-border text-success” role=”status”>
<span class=”sr-only”>Loading…</span>
</div>
</div>
</div>

<div id=”blazor-error-ui”>
An unhandled error has occurred.
<a href=”” class=”reload”>Reload</a>
<a class=”dismiss”>🗙</a>
</div>

<script src=”_framework/blazor.webassembly.js”></script>
<script src=”serviceWorkerRegistration.js”></script>
</body>

</html>

The serviceWorker.published.js script is pretty standard except that the OpenID Connect redirects and signout URLs need to be excluded from the PWA and always rendered from the trusted backend. The registration script references the service worker so that the inline Javascript is removed from the html because we do not allow unsafe inline scripts anywhere in an application if possible.

navigator.serviceWorker.register(‘service-worker.js’);

The service worker excludes all the required authentication URLs and any other required server URLs. The published script registers the PWA.

Note: if you would like to test the PWA locally without deploying the application, you can reference the published script directly and it will run locally. You need to be carefully testing as the script and the cache needs to be emptied before testing each time.

// Caution! Be sure you understand the caveats before publishing an application with
// offline support. See https://aka.ms/blazor-offline-considerations

self.importScripts(‘./service-worker-assets.js’);
self.addEventListener(‘install’, event => event.waitUntil(onInstall(event)));
self.addEventListener(‘activate’, event => event.waitUntil(onActivate(event)));
self.addEventListener(‘fetch’, event => event.respondWith(onFetch(event)));

const cacheNamePrefix = ‘offline-cache-‘;
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [/.dll$/, /.pdb$/, /.wasm/, /.html/, /.js$/, /.json$/, /.css$/, /.woff$/, /.png$/, /.jpe?g$/, /.gif$/, /.ico$/, /.blat$/, /.dat$/];
const offlineAssetsExclude = [/^service-worker.js$/];

async function onInstall(event) {
console.info(‘Service worker: Install’);

// Fetch and cache all matching items from the assets manifest
const assetsRequests = self.assetsManifest.assets
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
.map(asset => new Request(asset.url, { integrity: asset.hash, cache: ‘no-cache’ }));

await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}

async function onActivate(event) {
console.info(‘Service worker: Activate’);

// Delete unused caches
const cacheKeys = await caches.keys();
await Promise.all(cacheKeys
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
.map(key => caches.delete(key)));
}

async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === ‘GET’) {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === ‘navigate’
&& !event.request.url.includes(‘/signin-oidc’)
&& !event.request.url.includes(‘/signout-callback-oidc’)
&& !event.request.url.includes(‘/api/Account/Login’)
&& !event.request.url.includes(‘/api/Account/Logout’)
&& !event.request.url.includes(‘/HostAuthentication/’);

const request = shouldServeIndexHtml ? ‘index.html’ : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}

return cachedResponse || fetch(event.request, { credentials: ‘include’ });
}

The ServiceWorkerAssetsManifest definition needs to be added to the client project.

<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>

Now the PWA should work. The next step is to add the extra CSRF protection.

Setup CSRF protection using CORS preflight

CORS preflight can be used to protect against CSRF as well as same site. All API calls should include a custom HTTP header and this needs to be controlled on the APIs that the header exists.

The can be implemented in the Blazor WASM client by using a CSRF middleware protection.

public class CsrfProtectionCorsPreflightAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var header = context.HttpContext
.Request
.Headers
.Any(p => p.Key.ToLower() == “x-force-cors-preflight”);

if (!header)
{
// “X-FORCE-CORS-PREFLIGHT header is missing”
context.Result = new UnauthorizedObjectResult(“X-FORCE-CORS-PREFLIGHT header is missing”);
return;
}
}
}

In the Blazor client, the middleware can be added to all HttpClient instances used in the Blazor WASM.

builder.Services.AddHttpClient(“default”, client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue(“application/json”));

}).AddHttpMessageHandler<CsrfProtectionMessageHandler>();

builder.Services.AddHttpClient(“authorizedClient”, client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue(“application/json”));

}).AddHttpMessageHandler<AuthorizedHandler>()
.AddHttpMessageHandler<CsrfProtectionMessageHandler>();

The CSRF CORS preflight header can be validated using an ActionFilter in the ASP.NET Core backend application. This is not the only way of doing this. The CsrfProtectionCorsPreflightAttribute implements the ActionFilterAttribute so only the OnActionExecuting needs to be implemented. The custom header is validated and if it fails, an unauthorized result is returned. It does not matter if you give the reason why, unless you want to obfuscate this a bit.

public class CsrfProtectionCorsPreflightAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var header = context.HttpContext
.Request
.Headers
.Any(p => p.Key.ToLower() == “x-force-cors-preflight”);

if (!header)
{
// “X-FORCE-CORS-PREFLIGHT header is missing”
context.Result = new UnauthorizedObjectResult(“X-FORCE-CORS-PREFLIGHT header is missing”);
return;
}
}
}

The CSRF can then be applied anywhere this is required. All secured routes where cookies are used should enforce this.

[CsrfProtectionCorsPreflight]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[ApiController]
[Route(“api/[controller]”)]
public class DirectApiController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new List<string> { “some data”, “more data”, “loads of data” };
}
}

Now the PWA works using the server rendered application and protected using BFF with all security in the trusted backend.

Problems with this solution and Blazor

The custom header cannot be applied and added when sending direct links, redirects or forms which don’t used Javascript. Anywhere a form is implemented and requires the CORS preflight protection, a HttpClient which adds the header needs to be used.

This is a problem with the Azure B2C signin and signout. The signin redirects the whole application, but this is not so much a problem because when signing in, the identity has no cookie with sensitive data, or should have none. The signout only works correctly with Azure B2C with a form request from the whole application and not HttpClient API call using Javascript. The CORS preflight header cannot be applied to an Azure B2C identity provider signout request, if you require the session to be ended on Azure B2C. If you only require a local logout, then the HttpClient can be used.

Note: Same site protection also exists for modern browsers, so this double CSRF fallback is not really critical, if the same site is implemented correctly and using a browser which enforces this.

Links

https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/graph-api

Managing Azure B2C users with Microsoft Graph API

https://docs.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#client-credentials-provider

https://github.com/search?q=Microsoft.Identity.Web

https://github.com/damienbod/Blazor.BFF.AzureB2C.Template