- Mar 2023
-
blog.cloudflare.com blog.cloudflare.com
-
www.cloudflare.com www.cloudflare.com
-
alistapart.com alistapart.com
-
jakearchibald.com jakearchibald.com
Tags
Annotators
URL
-
-
www.youtube.com www.youtube.com
Tags
Annotators
URL
-
-
www.youtube.com www.youtube.com
Tags
Annotators
URL
-
-
blog.bitsrc.io blog.bitsrc.io
-
```js // FromStream component
import { PureComponent } from 'react';
export default class FromStream extends PureComponent { constructor(props) { super(props); this.state = { value: false }; }
componentDidMount() { this.initStream(); }
componentDidUpdate(prevProps) { if (prevProps.stream !== this.props.stream) { this.initStream(); } }
componentWillUnmount() { if (this.unSubscribe) { this.unSubscribe(); } }
initStream() { if (this.unSubscribe) { this.unSubscribe(); this.unSubscribe = null; }
if (this.props.stream) { const onValue = (value) => { this.setState(() => ({ value: map(value) })); }; this.props.stream.onValue(onValue); this.unSubscribe = () => stream.offValue(onValue); }
}
render() { return this.props.children(this.state && this.state.value); } } ```
```js // Date/Time import React from 'react'; import Kefir from 'kefir'; import FromStream from './FromStream';
const dateStream = Kefir.interval(1000).map(() => new Date().toString());
export default () => ( <FromStream stream={dateStream}> {(dateString) => dateString} </FromStream> ); ```
```js // Scroll import React from 'react'; import Kefir from 'kefir'; import FromStream from './FromStream';
const scrolledPercentStream = Kefir.fromEvents(document, 'scroll').map((e) => { const scrollY = window.document.pageYOffset; const scrollHeight = e.target.body.scrollHeight; return scrollY / (scrollHeight - windiw.innerHeight) * 100; });
export default () => ( <FromStream stream={scrolledPercentStream}> {(percent) => ( <div className="bar" style={{ top: <code>${percent}% }}></div> )} </FromStream> ); ```
-
-
marconijr.com marconijr.com
-
```js import { useState, useEffect } from 'react';
interface StreamState { data: Uint8Array | null; error: Error | null; controller: AbortController; }
const useAbortableStreamFetch = (url: string, options?: RequestInit): { data: Uint8Array | null, error: Error | null, abort: () => void, } => {
const [state, setState] = useState<StreamState>({ data: null, error: null, controller: new AbortController(), });
useEffect(() => { (async () => { try { const resp = await fetch(url, { ...options, signal: state.controller.signal, }); if (!resp.ok || !resp.body) { throw resp.statusText; }
const reader = resp.body.getReader(); while (true) { const { value, done } = await reader.read(); if (done) { break; } setState(prevState => ({ ...prevState, ...{ data: value } })); } } catch (err) { if (err.name !== 'AbortError') { setState(prevState => ({ ...prevState, ...{ error: err } })); } } })(); return () => state.controller.abort();
}, [url, options]);
return { data: state.data, error: state.error, abort: () => state.controller && state.controller.abort(), }; };
export default useAbortableStreamFetch; ```
-
-
nodesource.com nodesource.com
-
-
-
getquick.link getquick.link
-
-
stackoverflow.com stackoverflow.com
-
donavon.com donavon.com
Tags
Annotators
URL
-
-
sergiodxa.com sergiodxa.com
-
Send the 304 Not Modified response
```js import etag from "etag"; import { renderToString } from "react-dom/server"; import type { EntryContext, HandleDataRequestFunction } from "remix"; import { RemixServer } from "remix";
export default function handleRequest( request: Request, status: number, headers: Headers, remixContext: EntryContext ) { let markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );
headers.set("Content-Type", "text/html"); headers.set("ETag", etag(markup));
// check if the
If-None-Match
header matches the ETag if (request.headers.get("If-None-Match") === headers.get("ETag")) { // and send an empty Response with status 304 and the headers. return new Response("", { status: 304, headers }); }return new Response("<!DOCTYPE html>" + markup, { status, headers }); }
export let handleDataRequest: HandleDataRequestFunction = async ( response: Response, { request } ) => { let body = await response.text();
if (request.method.toLowerCase() === "get") { response.headers.set("etag", etag(body)); // As with document requests, check the
If-None-Match
header // and compare it with the Etag, if they match, send the empty 304 Response if (request.headers.get("If-None-Match") === response.headers.get("ETag")) { return new Response("", { status: 304, headers: response.headers }); } }return response; }; ```
-
All Together
```js import etag from "etag"; import { renderToString } from "react-dom/server"; import type { EntryContext, HandleDataRequestFunction } from "remix"; import { RemixServer } from "remix";
export default function handleRequest( request: Request, status: number, headers: Headers, remixContext: EntryContext ) { let markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );
headers.set("Content-Type", "text/html"); headers.set("ETag", etag(markup));
return new Response("<!DOCTYPE html>" + markup, { status, headers }); }
export let handleDataRequest: HandleDataRequestFunction = async ( response: Response ) => { let body = await response.text(); response.headers.set("etag", etag(body)); return response; }; ```
-
Using ETags for document requests
```js import etag from "etag"; import { renderToString } from "react-dom/server"; import type { EntryContext } from "remix"; import { RemixServer } from "remix";
export default function handleRequest( request: Request, status: number, headers: Headers, remixContext: EntryContext ) { let markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );
headers.set("Content-Type", "text/html"); // add the Etag header using the markup as value headers.set("ETag", etag(markup));
return new Response("<!DOCTYPE html>" + markup, { status, headers }); } ```
-
Using ETags for data requests
```js import etag from "etag"; import type { HandleDataRequestFunction } from "remix";
export let handleDataRequest: HandleDataRequestFunction = async ( response: Response, { request } ) => { let body = await response.text(); // parse the response body as text
// only add the ETag for GET requests if (request.method.toLowerCase() === "get") { response.headers.set("etag", etag(body)); // and use it to create the ETag }
return response; // return the response }; ```
-
-
-
www.npmjs.com www.npmjs.com
-
```js import { renderToString } from "react-dom/server"; import { RemixServer } from "remix"; import type { EntryContext } from "remix"; import { etag } from 'remix-etag';
export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext ) { const markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );
responseHeaders.set("Content-Type", "text/html");
const response = new Response("<!DOCTYPE html>" + markup, { status: responseStatusCode, headers: responseHeaders, }); return etag({ request, response }); } ```
Tags
Annotators
URL
-
-
support.cloudflare.com support.cloudflare.com
-
developer.mozilla.org developer.mozilla.orgETag1
-
Tags
Annotators
URL
-
-
www.bobdc.com www.bobdc.com
-
```sparql PREFIX wd: http://www.wikidata.org/entity/ PREFIX gist: https://ontologies.semanticarts.com/gist/ PREFIX dcterms: http://purl.org/dc/terms/
SELECT DISTINCT ?commitTitle ?commitTime ?filename ?textLine WHERE {
?commit a wd:Q20058545 ; # it's a commit gist:hasPart ?part ; dcterms:subject ?commitSubject ; gist:atDateTime ?commitTime .
?commitSubject dcterms:title ?commitTitle .
?part gist:produces ?contiguousLines .
?contiguousLines gist:occursIn ?file ; http://example.com/containedTextContainer ?textContainer .
?file gist:name ?filename . ?textContainer ?line ?textLine .
FILTER(contains(?textLine,"music")) } ```
Tags
Annotators
URL
-
-
Tags
Annotators
URL
-
-
ci.mines-stetienne.fr ci.mines-stetienne.fr
Tags
Annotators
URL
-
-
tkdodo.eu tkdodo.eu
Tags
Annotators
URL
-
-
github.com github.com
Tags
Annotators
URL
-
-
developer.mozilla.org developer.mozilla.orgTrailer1
-
```http HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked Trailer: Expires
7\r\n Mozilla\r\n 9\r\n Developer\r\n 7\r\n Network\r\n 0\r\n Expires: Wed, 21 Oct 2015 07:28:00 GMT\r\n \r\n ```
-
-
jimpster.bandcamp.com jimpster.bandcamp.com
-
github.com github.com
-
```js import { renderToReadableStream } from 'react-dom/server'; import type { EntryContext } from '@remix-run/cloudflare'; import { RemixServer } from '@remix-run/react'; import { renderHeadToString } from 'remix-island'; import { Head } from './root';
const readableString = (value: string) => { const te = new TextEncoder(); return new ReadableStream({ start(controller) { controller.enqueue(te.encode(value)); controller.close(); }, }); };
export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, ) { const { readable, writable } = new TransformStream(); const head = readableString(
<!DOCTYPE html><html><head>${renderHeadToString({ request, remixContext, Head, })}</head><body><div id="root">
, ); const end = readableString(</div></body></html>
);const body = await renderToReadableStream( <RemixServer context={remixContext} url={request.url} />, );
Promise.resolve() .then(() => head.pipeTo(writable, { preventClose: true })) .then(() => body.pipeTo(writable, { preventClose: true })) .then(() => end.pipeTo(writable));
responseHeaders.set('Content-Type', 'text/html');
return new Response(readable, { status: responseStatusCode, headers: responseHeaders, }); } ```
Tags
Annotators
URL
-
-
prim.iledefrance-mobilites.fr prim.iledefrance-mobilites.fr
-
Tags
Annotators
URL
-
-
fonts.adobe.com fonts.adobe.com
Tags
Annotators
URL
-
-
www.dafontfree.net www.dafontfree.net
Tags
Annotators
URL
-
-
typofonderie.com typofonderie.com
-
-
```js import React, { Component } from 'react'; import './style.css'; import ndjsonStream from 'can-ndjson-stream';
class App extends Component { constructor(props) { super(props);
this.state = { todos: [] };
}
componentDidMount(){ fetch('http://localhost:5000/api/10', { method: 'get' }).then(data => { return ndjsonStream(data.body); }).then((todoStream) => { const streamReader = todoStream.getReader(); const read = result => { if (result.done) return;
this.setState({ todos: this.state.todos.concat([result.value.user]) }); streamReader.read().then(read); }; streamReader.read().then(read); }).catch(err => { console.error(err) });
}
render() { return ( <div className="App">
React + NDJSON Stream Demo
-
{this.state.todos.map((todo, i) =>
- {todo} )}
export default App; ```
-
-
dbconvert.com dbconvert.com
Tags
Annotators
URL
-
-
andreitopli.medium.com andreitopli.medium.com
-
Tags
Annotators
URL
-
-
datatracker.ietf.org datatracker.ietf.org
Tags
Annotators
URL
-
-
dataprotocols.org dataprotocols.org
Tags
Annotators
URL
-
-
exploringjs.com exploringjs.com
-
-
Streaming across worker threads
```js import { ReadableStream } from 'node:stream/web'; import { Worker } from 'node:worker_threads';
const readable = new ReadableStream(getSomeSource());
const worker = new Worker('/path/to/worker.js', { workerData: readable, transferList: [readable], }); ```
```js const { workerData: stream } = require('worker_threads');
const reader = stream.getReader(); reader.read().then(console.log); ```
-
Consuming web streams
```js import { arrayBuffer, blob, buffer, json, text, } from 'node:stream/consumers';
const data1 = await arrayBuffer(getReadableStreamSomehow());
const data2 = await blob(getReadableStreamSomehow());
const data3 = await buffer(getReadableStreamSomehow());
const data4 = await json(getReadableStreamSomehow());
const data5 = await text(getReadableStreamSomehow()); ```
-
Adapting to the Node.js Streams API
```js /* * For instance, given a ReadableStream object, the stream.Readable.fromWeb() method * will create an return a Node.js stream.Readable object that can be used to consume * the ReadableStream's data: / import { Readable } from 'node:stream';
const readable = new ReadableStream(getSomeSource());
const nodeReadable = Readable.fromWeb(readable);
nodeReadable.on('data', console.log); ```
```js /* * The adaptation can also work the other way -- starting with a Node.js * stream.Readable and acquiring a web streams ReadableStream: / import { Readable } from 'node:stream';
const readable = new Readable({ read(size) { reader.push(Buffer.from('hello')); } });
const readableStream = Readable.toWeb(readable);
await readableStream.read();
```
Tags
Annotators
URL
-
-
Tags
Annotators
URL
-
-
2ality.com 2ality.com
-
-
blog.mmyoji.com blog.mmyoji.com
-
deno.com deno.com
Tags
Annotators
URL
-
-
blog.k-nut.eu blog.k-nut.eu
Tags
Annotators
URL
-
-
codepen.io codepen.io
Tags
Annotators
URL
-
-
twitter.com twitter.com
Tags
Annotators
URL
-
-
developer.mozilla.org developer.mozilla.org
-
developers.cloudflare.com developers.cloudflare.com
-
-
Pitfall #1: Server-Side Rendering Attacker-Controlled Initial State
```html
<script>window.__STATE__ = ${JSON.stringify({ data })}</script>```
-
Pitfall #3: Misunderstanding What it Means to Dangerously Set
-
Pitfall #2: Sneaky Links
-
-
-
thomasnguyen.site thomasnguyen.site
-
One option is to use the serialize-javascript NPM module to escape the rendered JSON.
html { username: "pwned", bio: "</script><script>alert('XSS Vulnerability!')</script>" }
-
This is risky because JSON.stringify() will blindly turn any data you give it into a string (so long as it is valid JSON) which will be rendered in the page. If { data } has fields that un-trusted users can edit like usernames or bios, they can inject something like this:
json { username: "pwned", bio: "</script><script>alert('XSS Vulnerability!')</script>" }
-
Sometimes when we render initial state, we dangerously generate a document variable from a JSON string. Vulnerable code looks like this:
```html
<script>window.__STATE__ = ${JSON.stringify({ data })}</script>```
-
Server-side rendering attacker-controlled initial state
-
-
yezyilomo.github.io yezyilomo.github.io
Tags
Annotators
URL
-
-
www.freecodecamp.org www.freecodecamp.org
-
To pass along the state, the template attaches state to window.__STATE__ inside a <script> tag.Now you can read state on the client side by accessing window.__STATE__.
-
-
www.youtube.com www.youtube.com
-
-
www.youtube.com www.youtube.com
Tags
Annotators
URL
-
-
codesandbox.io codesandbox.io
Tags
Annotators
URL
-
-
tanstack.com tanstack.com
-
ogzhanolguncu.com ogzhanolguncu.com
Tags
Annotators
URL
-
-
dev.to dev.to
-
tanstack.com tanstack.com
Tags
Annotators
URL
-
-
react-query-v3.tanstack.com react-query-v3.tanstack.comSSR1
Tags
Annotators
URL
-
-
soundcloud.com soundcloud.com
-
www.imdb.com www.imdb.com
-
gist.github.com gist.github.com
Tags
Annotators
URL
-
-
glitch.com glitch.com
Tags
Annotators
URL
-
-
developer.mozilla.org developer.mozilla.org
-
Async iteration of a stream using for await...ofThis example shows how you can process the fetch() response using a for await...of loop to iterate through the arriving chunks.
```js const response = await fetch("https://www.example.org"); let total = 0;
// Iterate response.body (a ReadableStream) asynchronously for await (const chunk of response.body) { // Do something with each chunk // Here we just accumulate the size of the response. total += chunk.length; }
// Do something with the total console.log(total); ```
Tags
Annotators
URL
-
-
developer.mozilla.org developer.mozilla.org
-
The body read-only property of the Response interface is a ReadableStream of the body contents.
Tags
Annotators
URL
-
-
twitter.com twitter.com
Tags
Annotators
URL
-
-
twitter.com twitter.com
Tags
Annotators
URL
-
-
www.adaptivecards.io www.adaptivecards.io
Tags
Annotators
URL
-
-
learn.microsoft.com learn.microsoft.com
-
www.proposals.es www.proposals.es
-
-
developer.apple.com developer.apple.com
-
www.bortzmeyer.org www.bortzmeyer.org
Tags
Annotators
URL
-
-
datatracker.ietf.org datatracker.ietf.orgrfc82161
-
www.allocine.fr www.allocine.frLiaison1
-
www.youtube.com www.youtube.com
-
-
codesandbox.io codesandbox.io
Tags
Annotators
URL
-
-
gatewayapps.github.io gatewayapps.github.io
-
www.npmjs.com www.npmjs.com
-
microsoft.github.io microsoft.github.io
-
www.madewithcards.io www.madewithcards.io
-
-
adaptivecards.io adaptivecards.io
Tags
Annotators
URL
-
-
www.w3.org www.w3.org
-
-
microsoftedge.github.io microsoftedge.github.iopwamp1
Tags
Annotators
URL
-
-
www.proposals.es www.proposals.es
-
js match (res) { if (isEmpty(res)) { … } when ({ pages, data }) if (pages > 1) { … } when ({ pages, data }) if (pages === 1) { … } else { … } }
js match (arithmeticStr) { when (/(?<left>\d+) \+ (?<right>\d+)/) as ({ groups: { left, right } }) { process(left, right); } when (/(?<left>\d+) \+ (?<right>\d+)/) { process(left, right); } // maybe? else { ... } }
```js class Name { static Symbol.matcher { const pieces = matchable.split(' '); if (pieces.length === 2) { return { matched: true, value: pieces }; } } }
match ('Tab Atkins-Bittner') { when (^Name with [first, last]) if (last.includes('-')) { … } when (^Name with [first, last]) { … } else { ... } } ```
js const res = await fetch(jsonService) match (res) { when ({ status: 200, headers: { 'Content-Length': s } }) { console.log(`size is ${s}`); } when ({ status: 404 }) { console.log('JSON not found'); } when ({ status }) if (status >= 400) { throw new RequestError(res); } };
```js function todoApp(state = initialState, action) { return match (action) { when ({ type: 'set-visibility-filter', payload: visFilter }) { ({ ...state, visFilter }); } when ({ type: 'add-todo', payload: text }) { ({ ...state, todos: [...state.todos, { text, completed: false }] }); } when ({ type: 'toggle-todo', payload: index }) { const newTodos = state.todos.map((todo, i) => { return i !== index ? todo : { ...todo, completed: !todo.completed }; });
({ ...state, todos: newTodos, }); } else { state } // ignore unknown actions
} } ```
jsx <Fetch url={API_URL}> {props => match (props) { when ({ loading }) { <Loading />; } when ({ error }) { <Error error={error} />; } when ({ data }) { <Page data={data} />; } }} </Fetch>
Tags
Annotators
URL
-
-
hackmd.io hackmd.io
Tags
Annotators
URL
-
-
docs.google.com docs.google.com
-
developer.mozilla.org developer.mozilla.org
-
A FinalizationRegistry object lets you request a callback when an object is garbage-collected.
-
-
rob-blackbourn.github.io rob-blackbourn.github.io
-
javascript.plainenglish.io javascript.plainenglish.io
-
gist.github.com gist.github.com
-
mrscruff.bandcamp.com mrscruff.bandcamp.com
-
ubiquitycompilations.bandcamp.com ubiquitycompilations.bandcamp.com
-
soundcloud.com soundcloud.com
-
soundcloud.com soundcloud.com
-
soundcloud.com soundcloud.com
-
fullblastradio.bandcamp.com fullblastradio.bandcamp.com
-
soundcloud.com soundcloud.com
-
ubiquitycompilations.bandcamp.com ubiquitycompilations.bandcamp.com
-
ubiquitycompilations.bandcamp.com ubiquitycompilations.bandcamp.com
-
www.wikidata.org www.wikidata.orgISSN-L1
Tags
Annotators
URL
-
-
www.zataz.com www.zataz.comZATAZ1
Tags
Annotators
URL
-
-
kyleshevlin.com kyleshevlin.com
-
-
datatracker.ietf.org datatracker.ietf.org
-
addons.mozilla.org addons.mozilla.org
-
-
-
www.smashingmagazine.com www.smashingmagazine.com
-
www.keycdn.com www.keycdn.com
Tags
Annotators
URL
-
-
www.youtube.com www.youtube.com
-
frederik-braun.com frederik-braun.com
-
developer.mozilla.org developer.mozilla.org
-
www.w3.org www.w3.org
Tags
Annotators
URL
-
-
Tags
Annotators
URL
-
-
developers.cloudflare.com developers.cloudflare.com
-
developer.mozilla.org developer.mozilla.org
-
gist.github.com gist.github.com
-
You'll notice that for the app/routes/jokes/$jokeId.tsx route in addition to Cache-Control we've also set Vary header to Cookie. This is because we're returning something that's specific to the user who is logged in. So we want the cache to associated to that particular Cookie value and not shared with different users, so the browser and CDN will not deliver the cached value if the cookie is different from the cached response's cookie.
-
-
-
Tags
Annotators
URL
-
-
www.youtube.com www.youtube.com
-
-
-
```js import parseCacheControl from "parse-cache-control";
export function headers({ loaderHeaders, parentHeaders, }: { loaderHeaders: Headers; parentHeaders: Headers; }) { const loaderCache = parseCacheControl( loaderHeaders.get("Cache-Control") ); const parentCache = parseCacheControl( parentHeaders.get("Cache-Control") );
// take the most conservative between the parent and loader, otherwise // we'll be too aggressive for one of them. const maxAge = Math.min( loaderCache["max-age"], parentCache["max-age"] );
return { "Cache-Control":
max-age=${maxAge}
, }; } ```
```js import { renderToString } from "react-dom/server"; import { RemixServer } from "@remix-run/react"; import type { EntryContext } from "@remix-run/node"; // or cloudflare/deno
export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext ) { const markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );
responseHeaders.set("Content-Type", "text/html"); responseHeaders.set("X-Powered-By", "Hugs");
return new Response("<!DOCTYPE html>" + markup, { status: responseStatusCode, headers: responseHeaders, }); } ```
Tags
Annotators
URL
-
-
developer.mozilla.org developer.mozilla.org
-
developer.mozilla.org developer.mozilla.orgWeakMap1
-
developer.mozilla.org developer.mozilla.org
-
jayconrod.com jayconrod.com
Tags
Annotators
URL
-
-
javascript.info javascript.info
-
-
javascript.info javascript.info
Tags
Annotators
URL
-
-
activitypods.org activitypods.org
Tags
Annotators
URL
-
- Feb 2023
-
betterprogramming.pub betterprogramming.pub
-
www.youtube.com www.youtube.com
-
-
www.aboutmonica.com www.aboutmonica.com
-
patterns.dev patterns.dev
Tags
Annotators
URL
-
-
patterns.dev patterns.dev
Tags
Annotators
URL
-
-
patterns.dev patterns.dev
Tags
Annotators
URL
-
-
patterns.dev patterns.dev
-
-
beta.reactjs.org beta.reactjs.org
-
github.com github.com
-
```js import type { EntryContext } from "@remix-run/cloudflare"; import { RemixServer } from "@remix-run/react"; import isbot from "isbot"; import { renderToReadableStream } from "react-dom/server";
const ABORT_DELAY = 5000;
const handleRequest = async ( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext ) => { let didError = false;
const stream = await renderToReadableStream( <RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />, { onError: (error: unknown) => { didError = true; console.error(error);
// You can also log crash/error report }, signal: AbortSignal.timeout(ABORT_DELAY), }
);
if (isbot(request.headers.get("user-agent"))) { await stream.allReady; }
responseHeaders.set("Content-Type", "text/html"); return new Response(stream, { headers: responseHeaders, status: didError ? 500 : responseStatusCode, }); };
export default handleRequest; ```
-
-
developer.chrome.com developer.chrome.com
Tags
Annotators
URL
-
-
chromestatus.com chromestatus.com
Tags
Annotators
URL
-