diff --git a/.eslintrc.js b/.eslintrc.js index 12d6f14fa86e..00e3094b0634 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -324,6 +324,7 @@ module.exports = { 'packages/react-devtools-shared/**/*.js', 'packages/react-noop-renderer/**/*.js', 'packages/react-refresh/**/*.js', + 'packages/react-server-dom-esm/**/*.js', 'packages/react-server-dom-webpack/**/*.js', 'packages/react-test-renderer/**/*.js', 'packages/react-debug-tools/**/*.js', diff --git a/fixtures/flight-esm/.gitignore b/fixtures/flight-esm/.gitignore new file mode 100644 index 000000000000..89967363fce2 --- /dev/null +++ b/fixtures/flight-esm/.gitignore @@ -0,0 +1,20 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/fixtures/flight-esm/loader/package.json b/fixtures/flight-esm/loader/package.json new file mode 100644 index 000000000000..3dbc1ca591c0 --- /dev/null +++ b/fixtures/flight-esm/loader/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/fixtures/flight-esm/loader/region.js b/fixtures/flight-esm/loader/region.js new file mode 100644 index 000000000000..2f72a1dc7b2d --- /dev/null +++ b/fixtures/flight-esm/loader/region.js @@ -0,0 +1,52 @@ +import { + resolve, + load as reactLoad, + getSource as getSourceImpl, + transformSource as reactTransformSource, +} from 'react-server-dom-esm/node-loader'; + +export {resolve}; + +async function textLoad(url, context, defaultLoad) { + const {format} = context; + const result = await defaultLoad(url, context, defaultLoad); + if (result.format === 'module') { + if (typeof result.source === 'string') { + return result; + } + return { + source: Buffer.from(result.source).toString('utf8'), + format: 'module', + }; + } + return result; +} + +export async function load(url, context, defaultLoad) { + return await reactLoad(url, context, (u, c) => { + return textLoad(u, c, defaultLoad); + }); +} + +async function textTransformSource(source, context, defaultTransformSource) { + const {format} = context; + if (format === 'module') { + if (typeof source === 'string') { + return {source}; + } + return { + source: Buffer.from(source).toString('utf8'), + }; + } + return defaultTransformSource(source, context, defaultTransformSource); +} + +async function transformSourceImpl(source, context, defaultTransformSource) { + return await reactTransformSource(source, context, (s, c) => { + return textTransformSource(s, c, defaultTransformSource); + }); +} + +export const transformSource = + process.version < 'v16' ? transformSourceImpl : undefined; +export const getSource = process.version < 'v16' ? getSourceImpl : undefined; diff --git a/fixtures/flight-esm/package.json b/fixtures/flight-esm/package.json new file mode 100644 index 000000000000..8188df5abbd5 --- /dev/null +++ b/fixtures/flight-esm/package.json @@ -0,0 +1,28 @@ +{ + "name": "flight-esm", + "type": "module", + "version": "0.1.0", + "private": true, + "dependencies": { + "body-parser": "^1.20.1", + "browserslist": "^4.18.1", + "busboy": "^1.6.0", + "compression": "^1.7.4", + "concurrently": "^7.3.0", + "nodemon": "^2.0.19", + "prompts": "^2.4.2", + "react": "experimental", + "react-dom": "experimental", + "undici": "^5.20.0" + }, + "scripts": { + "predev": "cp -r ../../build/oss-experimental/* ./node_modules/", + "prestart": "cp -r ../../build/oss-experimental/* ./node_modules/", + "dev": "concurrently \"npm run dev:region\" \"npm run dev:global\"", + "dev:global": "NODE_ENV=development BUILD_PATH=dist node server/global", + "dev:region": "NODE_ENV=development BUILD_PATH=dist nodemon --watch src --watch dist -- --experimental-loader ./loader/region.js --conditions=react-server server/region", + "start": "concurrently \"npm run start:region\" \"npm run start:global\"", + "start:global": "NODE_ENV=production node server/global", + "start:region": "NODE_ENV=production node --experimental-loader ./loader/region.js --conditions=react-server server/region" + } +} diff --git a/fixtures/flight-esm/public/favicon.ico b/fixtures/flight-esm/public/favicon.ico new file mode 100644 index 000000000000..5c125de5d897 Binary files /dev/null and b/fixtures/flight-esm/public/favicon.ico differ diff --git a/fixtures/flight-esm/server/global.js b/fixtures/flight-esm/server/global.js new file mode 100644 index 000000000000..d6aaf4cc8ca1 --- /dev/null +++ b/fixtures/flight-esm/server/global.js @@ -0,0 +1,137 @@ +'use strict'; + +// This is a server to host CDN distributed resources like module source files and SSR + +const path = require('path'); +const url = require('url'); + +const fs = require('fs').promises; +const compress = require('compression'); +const chalk = require('chalk'); +const express = require('express'); +const http = require('http'); + +const {renderToPipeableStream} = require('react-dom/server'); +const {createFromNodeStream} = require('react-server-dom-esm/client'); + +const moduleBasePath = new URL('../src', url.pathToFileURL(__filename)).href; + +const app = express(); + +app.use(compress()); + +function request(options, body) { + return new Promise((resolve, reject) => { + const req = http.request(options, res => { + resolve(res); + }); + req.on('error', e => { + reject(e); + }); + body.pipe(req); + }); +} + +app.all('/', async function (req, res, next) { + // Proxy the request to the regional server. + const proxiedHeaders = { + 'X-Forwarded-Host': req.hostname, + 'X-Forwarded-For': req.ips, + 'X-Forwarded-Port': 3000, + 'X-Forwarded-Proto': req.protocol, + }; + // Proxy other headers as desired. + if (req.get('rsc-action')) { + proxiedHeaders['Content-type'] = req.get('Content-type'); + proxiedHeaders['rsc-action'] = req.get('rsc-action'); + } else if (req.get('Content-type')) { + proxiedHeaders['Content-type'] = req.get('Content-type'); + } + + const promiseForData = request( + { + host: '127.0.0.1', + port: 3001, + method: req.method, + path: '/', + headers: proxiedHeaders, + }, + req + ); + + if (req.accepts('text/html')) { + try { + const rscResponse = await promiseForData; + + const moduleBaseURL = '/src'; + + // For HTML, we're a "client" emulator that runs the client code, + // so we start by consuming the RSC payload. This needs the local file path + // to load the source files from as well as the URL path for preloads. + const root = await createFromNodeStream( + rscResponse, + moduleBasePath, + moduleBaseURL + ); + // Render it into HTML by resolving the client components + res.set('Content-type', 'text/html'); + const {pipe} = renderToPipeableStream(root, { + // TODO: bootstrapModules inserts a preload before the importmap which causes + // the import map to be invalid. We need to fix that in Float somehow. + // bootstrapModules: ['/src/index.js'], + }); + pipe(res); + } catch (e) { + console.error(`Failed to SSR: ${e.stack}`); + res.statusCode = 500; + res.end(); + } + } else { + try { + const rscResponse = await promiseForData; + // For other request, we pass-through the RSC payload. + res.set('Content-type', 'text/x-component'); + rscResponse.on('data', data => { + res.write(data); + res.flush(); + }); + rscResponse.on('end', data => { + res.end(); + }); + } catch (e) { + console.error(`Failed to proxy request: ${e.stack}`); + res.statusCode = 500; + res.end(); + } + } +}); + +app.use(express.static('public')); +app.use('/src', express.static('src')); +app.use( + '/node_modules/react-server-dom-esm/esm', + express.static('node_modules/react-server-dom-esm/esm') +); + +app.listen(3000, () => { + console.log('Global Fizz/Webpack Server listening on port 3000...'); +}); + +app.on('error', function (error) { + if (error.syscall !== 'listen') { + throw error; + } + + switch (error.code) { + case 'EACCES': + console.error('port 3000 requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error('Port 3000 is already in use'); + process.exit(1); + break; + default: + throw error; + } +}); diff --git a/fixtures/flight-esm/server/package.json b/fixtures/flight-esm/server/package.json new file mode 100644 index 000000000000..5bbefffbabee --- /dev/null +++ b/fixtures/flight-esm/server/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/fixtures/flight-esm/server/region.js b/fixtures/flight-esm/server/region.js new file mode 100644 index 000000000000..c7e8d9aad33c --- /dev/null +++ b/fixtures/flight-esm/server/region.js @@ -0,0 +1,140 @@ +'use strict'; + +// This is a server to host data-local resources like databases and RSC + +const path = require('path'); +const url = require('url'); + +if (typeof fetch === 'undefined') { + // Patch fetch for earlier Node versions. + global.fetch = require('undici').fetch; +} + +const express = require('express'); +const bodyParser = require('body-parser'); +const busboy = require('busboy'); +const app = express(); +const compress = require('compression'); +const {Readable} = require('node:stream'); + +app.use(compress()); + +// Application + +const {readFile} = require('fs').promises; + +const React = require('react'); + +const moduleBasePath = new URL('../src', url.pathToFileURL(__filename)).href; + +async function renderApp(res, returnValue) { + const {renderToPipeableStream} = await import('react-server-dom-esm/server'); + const m = await import('../src/App.js'); + + const App = m.default; + const root = React.createElement(App); + // For client-invoked server actions we refresh the tree and return a return value. + const payload = returnValue ? {returnValue, root} : root; + const {pipe} = renderToPipeableStream(payload, moduleBasePath); + pipe(res); +} + +app.get('/', async function (req, res) { + await renderApp(res, null); +}); + +app.post('/', bodyParser.text(), async function (req, res) { + const { + renderToPipeableStream, + decodeReply, + decodeReplyFromBusboy, + decodeAction, + } = await import('react-server-dom-esm/server'); + const serverReference = req.get('rsc-action'); + if (serverReference) { + // This is the client-side case + const [filepath, name] = serverReference.split('#'); + const action = (await import(filepath))[name]; + // Validate that this is actually a function we intended to expose and + // not the client trying to invoke arbitrary functions. In a real app, + // you'd have a manifest verifying this before even importing it. + if (action.$$typeof !== Symbol.for('react.server.reference')) { + throw new Error('Invalid action'); + } + + let args; + if (req.is('multipart/form-data')) { + // Use busboy to streamingly parse the reply from form-data. + const bb = busboy({headers: req.headers}); + const reply = decodeReplyFromBusboy(bb, moduleBasePath); + req.pipe(bb); + args = await reply; + } else { + args = await decodeReply(req.body, moduleBasePath); + } + const result = action.apply(null, args); + try { + // Wait for any mutations + await result; + } catch (x) { + // We handle the error on the client + } + // Refresh the client and return the value + renderApp(res, result); + } else { + // This is the progressive enhancement case + const UndiciRequest = require('undici').Request; + const fakeRequest = new UndiciRequest('http://localhost', { + method: 'POST', + headers: {'Content-Type': req.headers['content-type']}, + body: Readable.toWeb(req), + duplex: 'half', + }); + const formData = await fakeRequest.formData(); + const action = await decodeAction(formData, moduleBasePath); + try { + // Wait for any mutations + await action(); + } catch (x) { + const {setServerState} = await import('../src/ServerState.js'); + setServerState('Error: ' + x.message); + } + renderApp(res, null); + } +}); + +app.get('/todos', function (req, res) { + res.json([ + { + id: 1, + text: 'Shave yaks', + }, + { + id: 2, + text: 'Eat kale', + }, + ]); +}); + +app.listen(3001, () => { + console.log('Regional Flight Server listening on port 3001...'); +}); + +app.on('error', function (error) { + if (error.syscall !== 'listen') { + throw error; + } + + switch (error.code) { + case 'EACCES': + console.error('port 3001 requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error('Port 3001 is already in use'); + process.exit(1); + break; + default: + throw error; + } +}); diff --git a/fixtures/flight-esm/src/App.js b/fixtures/flight-esm/src/App.js new file mode 100644 index 000000000000..161776eddd61 --- /dev/null +++ b/fixtures/flight-esm/src/App.js @@ -0,0 +1,92 @@ +import * as React from 'react'; + +import Button from './Button.js'; +import Form from './Form.js'; + +import {like, greet} from './actions.js'; + +import {getServerState} from './ServerState.js'; + +const h = React.createElement; + +const importMap = { + imports: { + react: 'https://esm.sh/react@experimental?pin=v124&dev', + 'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev', + 'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/', + 'react-server-dom-esm/client': + '/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js', + }, +}; + +export default async function App() { + const res = await fetch('http://localhost:3001/todos'); + const todos = await res.json(); + return h( + 'html', + { + lang: 'en', + }, + h( + 'head', + null, + h('meta', { + charSet: 'utf-8', + }), + h('meta', { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }), + h('title', null, 'Flight'), + h('link', { + rel: 'stylesheet', + href: '/src/style.css', + precedence: 'default', + }), + h('script', { + type: 'importmap', + dangerouslySetInnerHTML: { + __html: JSON.stringify(importMap), + }, + }) + ), + h( + 'body', + null, + h( + 'div', + null, + h('h1', null, getServerState()), + h( + 'ul', + null, + todos.map(todo => + h( + 'li', + { + key: todo.id, + }, + todo.text + ) + ) + ), + h(Form, { + action: greet, + }), + h( + 'div', + null, + h( + Button, + { + action: like, + }, + 'Like' + ) + ) + ), + // TODO: Move this to bootstrapModules. + h('script', {type: 'module', src: '/src/index.js'}) + ) + ); +} diff --git a/fixtures/flight-esm/src/Button.js b/fixtures/flight-esm/src/Button.js new file mode 100644 index 000000000000..90171e8e84ee --- /dev/null +++ b/fixtures/flight-esm/src/Button.js @@ -0,0 +1,37 @@ +'use client'; + +import * as React from 'react'; +import {experimental_useFormStatus as useFormStatus} from 'react-dom'; +import ErrorBoundary from './ErrorBoundary.js'; + +const h = React.createElement; + +function ButtonDisabledWhilePending({action, children}) { + const {pending} = useFormStatus(); + return h( + 'button', + { + disabled: pending, + formAction: action, + }, + children + ); +} + +export default function Button({action, children}) { + return h( + ErrorBoundary, + null, + h( + 'form', + null, + h( + ButtonDisabledWhilePending, + { + action: action, + }, + children + ) + ) + ); +} diff --git a/fixtures/flight-esm/src/ErrorBoundary.js b/fixtures/flight-esm/src/ErrorBoundary.js new file mode 100644 index 000000000000..d76b82c670fa --- /dev/null +++ b/fixtures/flight-esm/src/ErrorBoundary.js @@ -0,0 +1,20 @@ +'use client'; + +import * as React from 'react'; + +export default class ErrorBoundary extends React.Component { + state = {error: null}; + static getDerivedStateFromError(error) { + return {error}; + } + render() { + if (this.state.error) { + return React.createElement( + 'div', + {}, + 'Caught an error: ' + this.state.error.message + ); + } + return this.props.children; + } +} diff --git a/fixtures/flight-esm/src/Form.js b/fixtures/flight-esm/src/Form.js new file mode 100644 index 000000000000..75f874d6e999 --- /dev/null +++ b/fixtures/flight-esm/src/Form.js @@ -0,0 +1,45 @@ +'use client'; + +import * as React from 'react'; +import {experimental_useFormStatus as useFormStatus} from 'react-dom'; +import ErrorBoundary from './ErrorBoundary.js'; + +const h = React.createElement; + +function Status() { + const {pending} = useFormStatus(); + return pending ? 'Saving...' : null; +} + +export default function Form({action, children}) { + const [isPending, setIsPending] = React.useState(false); + return h( + ErrorBoundary, + null, + h( + 'form', + { + action: action, + }, + h( + 'label', + {}, + 'Name: ', + h('input', { + name: 'name', + }) + ), + h( + 'label', + {}, + 'File: ', + h('input', { + type: 'file', + name: 'file', + }) + ), + h('button', {}, 'Say Hi'), + h(Status, {}) + ) + ); +} diff --git a/fixtures/flight-esm/src/ServerState.js b/fixtures/flight-esm/src/ServerState.js new file mode 100644 index 000000000000..3d4c7162262d --- /dev/null +++ b/fixtures/flight-esm/src/ServerState.js @@ -0,0 +1,9 @@ +let serverState = 'Hello World'; + +export function setServerState(message) { + serverState = message; +} + +export function getServerState() { + return serverState; +} diff --git a/fixtures/flight-esm/src/actions.js b/fixtures/flight-esm/src/actions.js new file mode 100644 index 000000000000..3d26189979c2 --- /dev/null +++ b/fixtures/flight-esm/src/actions.js @@ -0,0 +1,20 @@ +'use server'; + +import {setServerState} from './ServerState.js'; + +export async function like() { + setServerState('Liked!'); + return new Promise((resolve, reject) => resolve('Liked')); +} + +export async function greet(formData) { + const name = formData.get('name') || 'you'; + setServerState('Hi ' + name); + const file = formData.get('file'); + if (file) { + return `Ok, ${name}, here is ${file.name}: + ${(await file.text()).toUpperCase()} + `; + } + return 'Hi ' + name + '!'; +} diff --git a/fixtures/flight-esm/src/index.js b/fixtures/flight-esm/src/index.js new file mode 100644 index 000000000000..30d060af6342 --- /dev/null +++ b/fixtures/flight-esm/src/index.js @@ -0,0 +1,46 @@ +import * as React from 'react'; +import {use, Suspense, useState, startTransition} from 'react'; +import ReactDOM from 'react-dom/client'; +import {createFromFetch, encodeReply} from 'react-server-dom-esm/client'; + +const moduleBaseURL = '/src/'; +let updateRoot; +async function callServer(id, args) { + const response = fetch('/', { + method: 'POST', + headers: { + Accept: 'text/x-component', + 'rsc-action': id, + }, + body: await encodeReply(args), + }); + const {returnValue, root} = await createFromFetch(response, { + callServer, + moduleBaseURL, + }); + // Refresh the tree with the new RSC payload. + startTransition(() => { + updateRoot(root); + }); + return returnValue; +} + +let data = createFromFetch( + fetch('/', { + headers: { + Accept: 'text/x-component', + }, + }), + { + callServer, + moduleBaseURL, + } +); + +function Shell({data}) { + const [root, setRoot] = useState(use(data)); + updateRoot = setRoot; + return root; +} + +ReactDOM.hydrateRoot(document, React.createElement(Shell, {data})); diff --git a/fixtures/flight-esm/src/style.css b/fixtures/flight-esm/src/style.css new file mode 100644 index 000000000000..37ad792deda2 --- /dev/null +++ b/fixtures/flight-esm/src/style.css @@ -0,0 +1,3 @@ +body { + font-family: Helvetica; +} \ No newline at end of file diff --git a/fixtures/flight-esm/yarn.lock b/fixtures/flight-esm/yarn.lock new file mode 100644 index 000000000000..409a5592ba96 --- /dev/null +++ b/fixtures/flight-esm/yarn.lock @@ -0,0 +1,788 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.21.0": + version "7.22.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" + integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== + dependencies: + regenerator-runtime "^0.13.11" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.5: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@^1.20.1: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.18.1: + version "4.21.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551" + integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA== + dependencies: + caniuse-lite "^1.0.30001489" + electron-to-chromium "^1.4.411" + node-releases "^2.0.12" + update-browserslist-db "^1.0.11" + +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +caniuse-lite@^1.0.30001489: + version "1.0.30001492" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz#4a06861788a52b4c81fd3344573b68cc87fe062b" + integrity sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw== + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concurrently@^7.3.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.6.0.tgz#531a6f5f30cf616f355a4afb8f8fcb2bba65a49a" + integrity sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw== + dependencies: + chalk "^4.1.0" + date-fns "^2.29.1" + lodash "^4.17.21" + rxjs "^7.0.0" + shell-quote "^1.7.3" + spawn-command "^0.0.2-1" + supports-color "^8.1.0" + tree-kill "^1.2.2" + yargs "^17.3.1" + +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +date-fns@^2.29.1: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.411: + version "1.4.417" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.417.tgz#a0c7eb992e68287fa50c8da5a5238b01f20b9a82" + integrity sha512-8rY8HdCxuSVY8wku3i/eDac4g1b4cSbruzocenrqBlzqruAZYHjQCHIjC66dLR9DXhEHTojsC4EjhZ8KmzwXqA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-releases@^2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" + integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== + +nodemon@^2.0.19: + version "2.0.22" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.22.tgz#182c45c3a78da486f673d6c1702e00728daf5258" + integrity sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-dom@experimental: + version "0.0.0-experimental-018c58c9c-20230601" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-018c58c9c-20230601.tgz#2cc0ac824b83bab2ac1c6187f241dbd5dcd5201b" + integrity sha512-hwRsyoG1R3Tub0nUa72YvNcqPvU+pTcr9dadOnUCKKfSiYVbBCy7LxmkqLauCD8OjNJMlwtMgG4UAgtidclYGQ== + dependencies: + loose-envify "^1.1.0" + scheduler "0.0.0-experimental-018c58c9c-20230601" + +react@experimental: + version "0.0.0-experimental-018c58c9c-20230601" + resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-018c58c9c-20230601.tgz#ab04d1243c8f83b0166ed342056fa6b38ab2cd23" + integrity sha512-nSQIBsZ26Ii899pZ9cRt/6uQLbIUEAcDIivvAQyaHp4pWm289aB+7AK7VCWojAJIf4OStCuWs2berZsk4mzLVg== + dependencies: + loose-envify "^1.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +rxjs@^7.0.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scheduler@0.0.0-experimental-018c58c9c-20230601: + version "0.0.0-experimental-018c58c9c-20230601" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-018c58c9c-20230601.tgz#4f083614f8e857bab63dd90b4b37b03783dafe6b" + integrity sha512-otUM7AAAnCoJ5/0jTQwUQ7NhxjgcPEdrfzW7NfkpocrDoTUbql1kIGIhj9L9POMVFDI/wcZzRNK/oIEWsB4DPw== + dependencies: + loose-envify "^1.1.0" + +semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shell-quote@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + integrity sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +tslib@^2.1.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338" + integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici@^5.20.0: + version "5.22.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" + integrity sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw== + dependencies: + busboy "^1.6.0" + +unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js new file mode 100644 index 000000000000..a2307ca3155f --- /dev/null +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// This should really have a Node and a Browser fork but to avoid too many configs we limit this to build the same for both +export * from 'react-client/src/ReactFlightClientConfigBrowser'; +export * from 'react-server-dom-esm/src/ReactFlightClientConfigESMBundler'; +export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.dom-node-esm.js b/packages/react-reconciler/src/forks/ReactFiberConfig.dom-node-esm.js new file mode 100644 index 000000000000..4932b1a787bb --- /dev/null +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.dom-node-esm.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from 'react-dom-bindings/src/client/ReactFiberConfigDOM'; diff --git a/packages/react-server-dom-esm/README.md b/packages/react-server-dom-esm/README.md new file mode 100644 index 000000000000..71c61b345e32 --- /dev/null +++ b/packages/react-server-dom-esm/README.md @@ -0,0 +1,5 @@ +# react-server-dom-esm + +Experimental React Flight bindings for DOM using ESM. + +**Use it at your own risk.** diff --git a/packages/react-server-dom-esm/client.browser.js b/packages/react-server-dom-esm/client.browser.js new file mode 100644 index 000000000000..7d26c2771e50 --- /dev/null +++ b/packages/react-server-dom-esm/client.browser.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from './src/ReactFlightDOMClientBrowser'; diff --git a/packages/react-server-dom-esm/client.js b/packages/react-server-dom-esm/client.js new file mode 100644 index 000000000000..2dad5bb51387 --- /dev/null +++ b/packages/react-server-dom-esm/client.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from './client.browser'; diff --git a/packages/react-server-dom-esm/client.node.js b/packages/react-server-dom-esm/client.node.js new file mode 100644 index 000000000000..4f435353a20f --- /dev/null +++ b/packages/react-server-dom-esm/client.node.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from './src/ReactFlightDOMClientNode'; diff --git a/packages/react-server-dom-esm/esm/package.json b/packages/react-server-dom-esm/esm/package.json new file mode 100644 index 000000000000..3dbc1ca591c0 --- /dev/null +++ b/packages/react-server-dom-esm/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/react-server-dom-esm/esm/react-server-dom-esm-node-loader.production.min.js b/packages/react-server-dom-esm/esm/react-server-dom-esm-node-loader.production.min.js new file mode 100644 index 000000000000..b4b7cec77e9f --- /dev/null +++ b/packages/react-server-dom-esm/esm/react-server-dom-esm-node-loader.production.min.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from '../src/ReactFlightESMNodeLoader.js'; diff --git a/packages/react-server-dom-esm/index.js b/packages/react-server-dom-esm/index.js new file mode 100644 index 000000000000..05085b0ed023 --- /dev/null +++ b/packages/react-server-dom-esm/index.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +throw new Error('Use react-server-dom-esm/client instead.'); diff --git a/packages/react-server-dom-esm/npm/client.browser.js b/packages/react-server-dom-esm/npm/client.browser.js new file mode 100644 index 000000000000..992565cd6913 --- /dev/null +++ b/packages/react-server-dom-esm/npm/client.browser.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-esm-client.browser.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-esm-client.browser.development.js'); +} diff --git a/packages/react-server-dom-esm/npm/client.js b/packages/react-server-dom-esm/npm/client.js new file mode 100644 index 000000000000..89d93a7a7920 --- /dev/null +++ b/packages/react-server-dom-esm/npm/client.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./client.browser'); diff --git a/packages/react-server-dom-esm/npm/client.node.js b/packages/react-server-dom-esm/npm/client.node.js new file mode 100644 index 000000000000..e7ae5a5eb777 --- /dev/null +++ b/packages/react-server-dom-esm/npm/client.node.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-esm-client.node.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-esm-client.node.development.js'); +} diff --git a/packages/react-server-dom-esm/npm/esm/package.json b/packages/react-server-dom-esm/npm/esm/package.json new file mode 100644 index 000000000000..3dbc1ca591c0 --- /dev/null +++ b/packages/react-server-dom-esm/npm/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/react-server-dom-esm/npm/index.js b/packages/react-server-dom-esm/npm/index.js new file mode 100644 index 000000000000..60818c2dbd69 --- /dev/null +++ b/packages/react-server-dom-esm/npm/index.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +throw new Error('Use react-server-dom-esm/client instead.'); diff --git a/packages/react-server-dom-esm/npm/server.js b/packages/react-server-dom-esm/npm/server.js new file mode 100644 index 000000000000..13a632e64117 --- /dev/null +++ b/packages/react-server-dom-esm/npm/server.js @@ -0,0 +1,6 @@ +'use strict'; + +throw new Error( + 'The React Server Writer cannot be used outside a react-server environment. ' + + 'You must configure Node.js using the `--conditions react-server` flag.' +); diff --git a/packages/react-server-dom-esm/npm/server.node.js b/packages/react-server-dom-esm/npm/server.node.js new file mode 100644 index 000000000000..ec707a9db6a1 --- /dev/null +++ b/packages/react-server-dom-esm/npm/server.node.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react-server-dom-esm-server.node.production.min.js'); +} else { + module.exports = require('./cjs/react-server-dom-esm-server.node.development.js'); +} diff --git a/packages/react-server-dom-esm/package.json b/packages/react-server-dom-esm/package.json new file mode 100644 index 000000000000..34e7c04f40a2 --- /dev/null +++ b/packages/react-server-dom-esm/package.json @@ -0,0 +1,56 @@ +{ + "name": "react-server-dom-esm", + "description": "React Server Components bindings for DOM using ESM. This is intended to be integrated into meta-frameworks. It is not intended to be imported directly.", + "version": "18.2.0", + "keywords": [ + "react" + ], + "homepage": "https://reactjs.org/", + "bugs": "https://github.com/facebook/react/issues", + "license": "MIT", + "files": [ + "LICENSE", + "README.md", + "index.js", + "client.js", + "client.browser.js", + "client.node.js", + "server.js", + "server.node.js", + "cjs/", + "esm/" + ], + "exports": { + ".": "./index.js", + "./client": { + "node": "./client.node.js", + "default": "./client.browser.js" + }, + "./client.browser": "./client.browser.js", + "./client.node": "./client.node.js", + "./server": { + "react-server": "./server.node.js", + "default": "./server.js" + }, + "./server.node": "./server.node.js", + "./node-loader": "./esm/react-server-dom-esm-node-loader.production.min.js", + "./src/*": "./src/*.js", + "./package.json": "./package.json" + }, + "main": "index.js", + "repository": { + "type" : "git", + "url" : "https://github.com/facebook/react.git", + "directory": "packages/react-server-dom-esm" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "dependencies": { + "acorn-loose": "^8.3.0" + } +} diff --git a/packages/react-server-dom-esm/server.js b/packages/react-server-dom-esm/server.js new file mode 100644 index 000000000000..83d8b8a017ff --- /dev/null +++ b/packages/react-server-dom-esm/server.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +throw new Error( + 'The React Server cannot be used outside a react-server environment. ' + + 'You must configure Node.js using the `--conditions react-server` flag.', +); diff --git a/packages/react-server-dom-esm/server.node.js b/packages/react-server-dom-esm/server.node.js new file mode 100644 index 000000000000..7726b9bb929d --- /dev/null +++ b/packages/react-server-dom-esm/server.node.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from './src/ReactFlightDOMServerNode'; diff --git a/packages/react-server-dom-esm/src/ReactFlightClientConfigESMBundler.js b/packages/react-server-dom-esm/src/ReactFlightClientConfigESMBundler.js new file mode 100644 index 000000000000..55deba307367 --- /dev/null +++ b/packages/react-server-dom-esm/src/ReactFlightClientConfigESMBundler.js @@ -0,0 +1,103 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type { + Thenable, + FulfilledThenable, + RejectedThenable, +} from 'shared/ReactTypes'; + +export type SSRManifest = string; // Module root path + +export type ServerManifest = string; // Module root path + +export type ServerReferenceId = string; + +export opaque type ClientReferenceMetadata = [ + string, // module path + string, // export name +]; + +// eslint-disable-next-line no-unused-vars +export opaque type ClientReference = { + specifier: string, + name: string, +}; + +export function resolveClientReference( + bundlerConfig: SSRManifest, + metadata: ClientReferenceMetadata, +): ClientReference { + const baseURL = bundlerConfig; + return { + specifier: baseURL + metadata[0], + name: metadata[1], + }; +} + +export function resolveServerReference( + config: ServerManifest, + id: ServerReferenceId, +): ClientReference { + const baseURL: string = config; + const idx = id.lastIndexOf('#'); + const exportName = id.slice(idx + 1); + const fullURL = id.slice(0, idx); + if (!fullURL.startsWith(baseURL)) { + throw new Error( + 'Attempted to load a Server Reference outside the hosted root.', + ); + } + return {specifier: fullURL, name: exportName}; +} + +const asyncModuleCache: Map> = new Map(); + +export function preloadModule( + metadata: ClientReference, +): null | Thenable { + const existingPromise = asyncModuleCache.get(metadata.specifier); + if (existingPromise) { + if (existingPromise.status === 'fulfilled') { + return null; + } + return existingPromise; + } else { + // $FlowFixMe[unsupported-syntax] + const modulePromise: Thenable = import(metadata.specifier); + modulePromise.then( + value => { + const fulfilledThenable: FulfilledThenable = + (modulePromise: any); + fulfilledThenable.status = 'fulfilled'; + fulfilledThenable.value = value; + }, + reason => { + const rejectedThenable: RejectedThenable = (modulePromise: any); + rejectedThenable.status = 'rejected'; + rejectedThenable.reason = reason; + }, + ); + asyncModuleCache.set(metadata.specifier, modulePromise); + return modulePromise; + } +} + +export function requireModule(metadata: ClientReference): T { + let moduleExports; + // We assume that preloadModule has been called before, which + // should have added something to the module cache. + const promise: any = asyncModuleCache.get(metadata.specifier); + if (promise.status === 'fulfilled') { + moduleExports = promise.value; + } else { + throw promise.reason; + } + return moduleExports[metadata.name]; +} diff --git a/packages/react-server-dom-esm/src/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-esm/src/ReactFlightDOMClientBrowser.js new file mode 100644 index 000000000000..e3ebaf3fb1aa --- /dev/null +++ b/packages/react-server-dom-esm/src/ReactFlightDOMClientBrowser.js @@ -0,0 +1,110 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Thenable} from 'shared/ReactTypes.js'; + +import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient'; + +import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient'; + +import { + createResponse, + getRoot, + reportGlobalError, + processBinaryChunk, + close, +} from 'react-client/src/ReactFlightClient'; + +import { + processReply, + createServerReference, +} from 'react-client/src/ReactFlightReplyClient'; + +type CallServerCallback = (string, args: A) => Promise; + +export type Options = { + moduleBaseURL?: string, + callServer?: CallServerCallback, +}; + +function createResponseFromOptions(options: void | Options) { + return createResponse( + options && options.moduleBaseURL ? options.moduleBaseURL : '', + options && options.callServer ? options.callServer : undefined, + ); +} + +function startReadingFromStream( + response: FlightResponse, + stream: ReadableStream, +): void { + const reader = stream.getReader(); + function progress({ + done, + value, + }: { + done: boolean, + value: ?any, + ... + }): void | Promise { + if (done) { + close(response); + return; + } + const buffer: Uint8Array = (value: any); + processBinaryChunk(response, buffer); + return reader.read().then(progress).catch(error); + } + function error(e: any) { + reportGlobalError(response, e); + } + reader.read().then(progress).catch(error); +} + +function createFromReadableStream( + stream: ReadableStream, + options?: Options, +): Thenable { + const response: FlightResponse = createResponseFromOptions(options); + startReadingFromStream(response, stream); + return getRoot(response); +} + +function createFromFetch( + promiseForResponse: Promise, + options?: Options, +): Thenable { + const response: FlightResponse = createResponseFromOptions(options); + promiseForResponse.then( + function (r) { + startReadingFromStream(response, (r.body: any)); + }, + function (e) { + reportGlobalError(response, e); + }, + ); + return getRoot(response); +} + +function encodeReply( + value: ReactServerValue, +): Promise< + string | URLSearchParams | FormData, +> /* We don't use URLSearchParams yet but maybe */ { + return new Promise((resolve, reject) => { + processReply(value, '', resolve, reject); + }); +} + +export { + createFromFetch, + createFromReadableStream, + encodeReply, + createServerReference, +}; diff --git a/packages/react-server-dom-esm/src/ReactFlightDOMClientNode.js b/packages/react-server-dom-esm/src/ReactFlightDOMClientNode.js new file mode 100644 index 000000000000..d673c9d6e419 --- /dev/null +++ b/packages/react-server-dom-esm/src/ReactFlightDOMClientNode.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Thenable} from 'shared/ReactTypes.js'; + +import type {Response} from 'react-client/src/ReactFlightClient'; + +import type {Readable} from 'stream'; + +import { + createResponse, + getRoot, + reportGlobalError, + processBinaryChunk, + close, +} from 'react-client/src/ReactFlightClient'; +import {processStringChunk} from '../../react-client/src/ReactFlightClient'; + +function noServerCall() { + throw new Error( + 'Server Functions cannot be called during initial render. ' + + 'This would create a fetch waterfall. Try to use a Server Component ' + + 'to pass data to Client Components instead.', + ); +} + +export function createServerReference, T>( + id: any, + callServer: any, +): (...A) => Promise { + return noServerCall; +} + +function createFromNodeStream( + stream: Readable, + moduleRootPath: string, + moduleBaseURL: string, // TODO: Used for preloading hints +): Thenable { + const response: Response = createResponse(moduleRootPath, noServerCall); + stream.on('data', chunk => { + if (typeof chunk === 'string') { + processStringChunk(response, chunk, 0); + } else { + processBinaryChunk(response, chunk); + } + }); + stream.on('error', error => { + reportGlobalError(response, error); + }); + stream.on('end', () => close(response)); + return getRoot(response); +} + +export {createFromNodeStream}; diff --git a/packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js new file mode 100644 index 000000000000..8caedde6732b --- /dev/null +++ b/packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js @@ -0,0 +1,158 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type { + Request, + ReactClientValue, +} from 'react-server/src/ReactFlightServer'; +import type {Destination} from 'react-server/src/ReactServerStreamConfigNode'; +import type {ClientManifest} from './ReactFlightServerConfigESMBundler'; +import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig'; +import type {Busboy} from 'busboy'; +import type {Writable} from 'stream'; +import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes'; + +import { + createRequest, + startWork, + startFlowing, + abort, +} from 'react-server/src/ReactFlightServer'; + +import { + createResponse, + reportGlobalError, + close, + resolveField, + resolveFileInfo, + resolveFileChunk, + resolveFileComplete, + getRoot, +} from 'react-server/src/ReactFlightReplyServer'; + +import {decodeAction} from 'react-server/src/ReactFlightActionServer'; + +function createDrainHandler(destination: Destination, request: Request) { + return () => startFlowing(request, destination); +} + +type Options = { + onError?: (error: mixed) => void, + context?: Array<[string, ServerContextJSONValue]>, + identifierPrefix?: string, +}; + +type PipeableStream = { + abort(reason: mixed): void, + pipe(destination: T): T, +}; + +function renderToPipeableStream( + model: ReactClientValue, + moduleBasePath: ClientManifest, + options?: Options, +): PipeableStream { + const request = createRequest( + model, + moduleBasePath, + options ? options.onError : undefined, + options ? options.context : undefined, + options ? options.identifierPrefix : undefined, + ); + let hasStartedFlowing = false; + startWork(request); + return { + pipe(destination: T): T { + if (hasStartedFlowing) { + throw new Error( + 'React currently only supports piping to one writable stream.', + ); + } + hasStartedFlowing = true; + startFlowing(request, destination); + destination.on('drain', createDrainHandler(destination, request)); + return destination; + }, + abort(reason: mixed) { + abort(request, reason); + }, + }; +} + +function decodeReplyFromBusboy( + busboyStream: Busboy, + moduleBasePath: ServerManifest, +): Thenable { + const response = createResponse(moduleBasePath, ''); + let pendingFiles = 0; + const queuedFields: Array = []; + busboyStream.on('field', (name, value) => { + if (pendingFiles > 0) { + // Because the 'end' event fires two microtasks after the next 'field' + // we would resolve files and fields out of order. To handle this properly + // we queue any fields we receive until the previous file is done. + queuedFields.push(name, value); + } else { + resolveField(response, name, value); + } + }); + busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => { + if (encoding.toLowerCase() === 'base64') { + throw new Error( + "React doesn't accept base64 encoded file uploads because we don't expect " + + "form data passed from a browser to ever encode data that way. If that's " + + 'the wrong assumption, we can easily fix it.', + ); + } + pendingFiles++; + const file = resolveFileInfo(response, name, filename, mimeType); + value.on('data', chunk => { + resolveFileChunk(response, file, chunk); + }); + value.on('end', () => { + resolveFileComplete(response, name, file); + pendingFiles--; + if (pendingFiles === 0) { + // Release any queued fields + for (let i = 0; i < queuedFields.length; i += 2) { + resolveField(response, queuedFields[i], queuedFields[i + 1]); + } + queuedFields.length = 0; + } + }); + }); + busboyStream.on('finish', () => { + close(response); + }); + busboyStream.on('error', err => { + reportGlobalError(response, err); + }); + return getRoot(response); +} + +function decodeReply( + body: string | FormData, + moduleBasePath: ServerManifest, +): Thenable { + if (typeof body === 'string') { + const form = new FormData(); + form.append('0', body); + body = form; + } + const response = createResponse(moduleBasePath, '', body); + close(response); + return getRoot(response); +} + +export { + renderToPipeableStream, + decodeReplyFromBusboy, + decodeReply, + decodeAction, +}; diff --git a/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js new file mode 100644 index 000000000000..1e03a453474c --- /dev/null +++ b/packages/react-server-dom-esm/src/ReactFlightESMNodeLoader.js @@ -0,0 +1,478 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import * as acorn from 'acorn-loose'; + +type ResolveContext = { + conditions: Array, + parentURL: string | void, +}; + +type ResolveFunction = ( + string, + ResolveContext, + ResolveFunction, +) => {url: string} | Promise<{url: string}>; + +type GetSourceContext = { + format: string, +}; + +type GetSourceFunction = ( + string, + GetSourceContext, + GetSourceFunction, +) => Promise<{source: Source}>; + +type TransformSourceContext = { + format: string, + url: string, +}; + +type TransformSourceFunction = ( + Source, + TransformSourceContext, + TransformSourceFunction, +) => Promise<{source: Source}>; + +type LoadContext = { + conditions: Array, + format: string | null | void, + importAssertions: Object, +}; + +type LoadFunction = ( + string, + LoadContext, + LoadFunction, +) => Promise<{format: string, shortCircuit?: boolean, source: Source}>; + +type Source = string | ArrayBuffer | Uint8Array; + +let warnedAboutConditionsFlag = false; + +let stashedGetSource: null | GetSourceFunction = null; +let stashedResolve: null | ResolveFunction = null; + +export async function resolve( + specifier: string, + context: ResolveContext, + defaultResolve: ResolveFunction, +): Promise<{url: string}> { + // We stash this in case we end up needing to resolve export * statements later. + stashedResolve = defaultResolve; + + if (!context.conditions.includes('react-server')) { + context = { + ...context, + conditions: [...context.conditions, 'react-server'], + }; + if (!warnedAboutConditionsFlag) { + warnedAboutConditionsFlag = true; + // eslint-disable-next-line react-internal/no-production-logging + console.warn( + 'You did not run Node.js with the `--conditions react-server` flag. ' + + 'Any "react-server" override will only work with ESM imports.', + ); + } + } + return await defaultResolve(specifier, context, defaultResolve); +} + +export async function getSource( + url: string, + context: GetSourceContext, + defaultGetSource: GetSourceFunction, +): Promise<{source: Source}> { + // We stash this in case we end up needing to resolve export * statements later. + stashedGetSource = defaultGetSource; + return defaultGetSource(url, context, defaultGetSource); +} + +function addLocalExportedNames(names: Map, node: any) { + switch (node.type) { + case 'Identifier': + names.set(node.name, node.name); + return; + case 'ObjectPattern': + for (let i = 0; i < node.properties.length; i++) + addLocalExportedNames(names, node.properties[i]); + return; + case 'ArrayPattern': + for (let i = 0; i < node.elements.length; i++) { + const element = node.elements[i]; + if (element) addLocalExportedNames(names, element); + } + return; + case 'Property': + addLocalExportedNames(names, node.value); + return; + case 'AssignmentPattern': + addLocalExportedNames(names, node.left); + return; + case 'RestElement': + addLocalExportedNames(names, node.argument); + return; + case 'ParenthesizedExpression': + addLocalExportedNames(names, node.expression); + return; + } +} + +function transformServerModule( + source: string, + body: any, + url: string, + loader: LoadFunction, +): string { + // If the same local name is exported more than once, we only need one of the names. + const localNames: Map = new Map(); + const localTypes: Map = new Map(); + + for (let i = 0; i < body.length; i++) { + const node = body[i]; + switch (node.type) { + case 'ExportAllDeclaration': + // If export * is used, the other file needs to explicitly opt into "use server" too. + break; + case 'ExportDefaultDeclaration': + if (node.declaration.type === 'Identifier') { + localNames.set(node.declaration.name, 'default'); + } else if (node.declaration.type === 'FunctionDeclaration') { + if (node.declaration.id) { + localNames.set(node.declaration.id.name, 'default'); + localTypes.set(node.declaration.id.name, 'function'); + } else { + // TODO: This needs to be rewritten inline because it doesn't have a local name. + } + } + continue; + case 'ExportNamedDeclaration': + if (node.declaration) { + if (node.declaration.type === 'VariableDeclaration') { + const declarations = node.declaration.declarations; + for (let j = 0; j < declarations.length; j++) { + addLocalExportedNames(localNames, declarations[j].id); + } + } else { + const name = node.declaration.id.name; + localNames.set(name, name); + if (node.declaration.type === 'FunctionDeclaration') { + localTypes.set(name, 'function'); + } + } + } + if (node.specifiers) { + const specifiers = node.specifiers; + for (let j = 0; j < specifiers.length; j++) { + const specifier = specifiers[j]; + localNames.set(specifier.local.name, specifier.exported.name); + } + } + continue; + } + } + + let newSrc = source + '\n\n;'; + localNames.forEach(function (exported, local) { + if (localTypes.get(local) !== 'function') { + // We first check if the export is a function and if so annotate it. + newSrc += 'if (typeof ' + local + ' === "function") '; + } + newSrc += 'Object.defineProperties(' + local + ',{'; + newSrc += '$$typeof: {value: Symbol.for("react.server.reference")},'; + newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + exported) + '},'; + newSrc += '$$bound: { value: null }'; + newSrc += '});\n'; + }); + return newSrc; +} + +function addExportNames(names: Array, node: any) { + switch (node.type) { + case 'Identifier': + names.push(node.name); + return; + case 'ObjectPattern': + for (let i = 0; i < node.properties.length; i++) + addExportNames(names, node.properties[i]); + return; + case 'ArrayPattern': + for (let i = 0; i < node.elements.length; i++) { + const element = node.elements[i]; + if (element) addExportNames(names, element); + } + return; + case 'Property': + addExportNames(names, node.value); + return; + case 'AssignmentPattern': + addExportNames(names, node.left); + return; + case 'RestElement': + addExportNames(names, node.argument); + return; + case 'ParenthesizedExpression': + addExportNames(names, node.expression); + return; + } +} + +function resolveClientImport( + specifier: string, + parentURL: string, +): {url: string} | Promise<{url: string}> { + // Resolve an import specifier as if it was loaded by the client. This doesn't use + // the overrides that this loader does but instead reverts to the default. + // This resolution algorithm will not necessarily have the same configuration + // as the actual client loader. It should mostly work and if it doesn't you can + // always convert to explicit exported names instead. + const conditions = ['node', 'import']; + if (stashedResolve === null) { + throw new Error( + 'Expected resolve to have been called before transformSource', + ); + } + return stashedResolve(specifier, {conditions, parentURL}, stashedResolve); +} + +async function parseExportNamesInto( + body: any, + names: Array, + parentURL: string, + loader: LoadFunction, +): Promise { + for (let i = 0; i < body.length; i++) { + const node = body[i]; + switch (node.type) { + case 'ExportAllDeclaration': + if (node.exported) { + addExportNames(names, node.exported); + continue; + } else { + const {url} = await resolveClientImport(node.source.value, parentURL); + const {source} = await loader( + url, + {format: 'module', conditions: [], importAssertions: {}}, + loader, + ); + if (typeof source !== 'string') { + throw new Error('Expected the transformed source to be a string.'); + } + let childBody; + try { + childBody = acorn.parse(source, { + ecmaVersion: '2024', + sourceType: 'module', + }).body; + } catch (x) { + // eslint-disable-next-line react-internal/no-production-logging + console.error('Error parsing %s %s', url, x.message); + continue; + } + await parseExportNamesInto(childBody, names, url, loader); + continue; + } + case 'ExportDefaultDeclaration': + names.push('default'); + continue; + case 'ExportNamedDeclaration': + if (node.declaration) { + if (node.declaration.type === 'VariableDeclaration') { + const declarations = node.declaration.declarations; + for (let j = 0; j < declarations.length; j++) { + addExportNames(names, declarations[j].id); + } + } else { + addExportNames(names, node.declaration.id); + } + } + if (node.specifiers) { + const specifiers = node.specifiers; + for (let j = 0; j < specifiers.length; j++) { + addExportNames(names, specifiers[j].exported); + } + } + continue; + } + } +} + +async function transformClientModule( + body: any, + url: string, + loader: LoadFunction, +): Promise { + const names: Array = []; + + await parseExportNamesInto(body, names, url, loader); + + let newSrc = + "const CLIENT_REFERENCE = Symbol.for('react.client.reference');\n"; + for (let i = 0; i < names.length; i++) { + const name = names[i]; + if (name === 'default') { + newSrc += 'export default '; + newSrc += 'Object.defineProperties(function() {'; + newSrc += + 'throw new Error(' + + JSON.stringify( + `Attempted to call the default export of ${url} from the server` + + `but it's on the client. It's not possible to invoke a client function from ` + + `the server, it can only be rendered as a Component or passed to props of a` + + `Client Component.`, + ) + + ');'; + } else { + newSrc += 'export const ' + name + ' = '; + newSrc += 'Object.defineProperties(function() {'; + newSrc += + 'throw new Error(' + + JSON.stringify( + `Attempted to call ${name}() from the server but ${name} is on the client. ` + + `It's not possible to invoke a client function from the server, it can ` + + `only be rendered as a Component or passed to props of a Client Component.`, + ) + + ');'; + } + newSrc += '},{'; + newSrc += '$$typeof: {value: CLIENT_REFERENCE},'; + newSrc += '$$id: {value: ' + JSON.stringify(url + '#' + name) + '}'; + newSrc += '});\n'; + } + return newSrc; +} + +async function loadClientImport( + url: string, + defaultTransformSource: TransformSourceFunction, +): Promise<{format: string, shortCircuit?: boolean, source: Source}> { + if (stashedGetSource === null) { + throw new Error( + 'Expected getSource to have been called before transformSource', + ); + } + // TODO: Validate that this is another module by calling getFormat. + const {source} = await stashedGetSource( + url, + {format: 'module'}, + stashedGetSource, + ); + const result = await defaultTransformSource( + source, + {format: 'module', url}, + defaultTransformSource, + ); + return {format: 'module', source: result.source}; +} + +async function transformModuleIfNeeded( + source: string, + url: string, + loader: LoadFunction, +): Promise { + // Do a quick check for the exact string. If it doesn't exist, don't + // bother parsing. + if ( + source.indexOf('use client') === -1 && + source.indexOf('use server') === -1 + ) { + return source; + } + + let body; + try { + body = acorn.parse(source, { + ecmaVersion: '2024', + sourceType: 'module', + }).body; + } catch (x) { + // eslint-disable-next-line react-internal/no-production-logging + console.error('Error parsing %s %s', url, x.message); + return source; + } + + let useClient = false; + let useServer = false; + for (let i = 0; i < body.length; i++) { + const node = body[i]; + if (node.type !== 'ExpressionStatement' || !node.directive) { + break; + } + if (node.directive === 'use client') { + useClient = true; + } + if (node.directive === 'use server') { + useServer = true; + } + } + + if (!useClient && !useServer) { + return source; + } + + if (useClient && useServer) { + throw new Error( + 'Cannot have both "use client" and "use server" directives in the same file.', + ); + } + + if (useClient) { + return transformClientModule(body, url, loader); + } + + return transformServerModule(source, body, url, loader); +} + +export async function transformSource( + source: Source, + context: TransformSourceContext, + defaultTransformSource: TransformSourceFunction, +): Promise<{source: Source}> { + const transformed = await defaultTransformSource( + source, + context, + defaultTransformSource, + ); + if (context.format === 'module') { + const transformedSource = transformed.source; + if (typeof transformedSource !== 'string') { + throw new Error('Expected source to have been transformed to a string.'); + } + const newSrc = await transformModuleIfNeeded( + transformedSource, + context.url, + (url: string, ctx: LoadContext, defaultLoad: LoadFunction) => { + return loadClientImport(url, defaultTransformSource); + }, + ); + return {source: newSrc}; + } + return transformed; +} + +export async function load( + url: string, + context: LoadContext, + defaultLoad: LoadFunction, +): Promise<{format: string, shortCircuit?: boolean, source: Source}> { + const result = await defaultLoad(url, context, defaultLoad); + if (result.format === 'module') { + if (typeof result.source !== 'string') { + throw new Error('Expected source to have been loaded into a string.'); + } + const newSrc = await transformModuleIfNeeded( + result.source, + url, + defaultLoad, + ); + return {format: 'module', source: newSrc}; + } + return result; +} diff --git a/packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js b/packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js new file mode 100644 index 000000000000..3fc97067db0a --- /dev/null +++ b/packages/react-server-dom-esm/src/ReactFlightServerConfigESMBundler.js @@ -0,0 +1,83 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {ReactClientValue} from 'react-server/src/ReactFlightServer'; + +export type ClientManifest = string; // base URL on the file system + +export type ServerReference = T & { + $$typeof: symbol, + $$id: string, + $$bound: null | Array, +}; + +export type ServerReferenceId = string; + +// eslint-disable-next-line no-unused-vars +export type ClientReference = { + $$typeof: symbol, + $$id: string, +}; + +export type ClientReferenceMetadata = [ + string, // module path + string, // export name +]; + +export type ClientReferenceKey = string; + +const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference'); +const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference'); + +export function getClientReferenceKey( + reference: ClientReference, +): ClientReferenceKey { + return reference.$$id; +} + +export function isClientReference(reference: Object): boolean { + return reference.$$typeof === CLIENT_REFERENCE_TAG; +} + +export function isServerReference(reference: Object): boolean { + return reference.$$typeof === SERVER_REFERENCE_TAG; +} + +export function resolveClientReferenceMetadata( + config: ClientManifest, + clientReference: ClientReference, +): ClientReferenceMetadata { + const baseURL: string = config; + const id = clientReference.$$id; + const idx = id.lastIndexOf('#'); + const exportName = id.slice(idx + 1); + const fullURL = id.slice(0, idx); + if (!fullURL.startsWith(baseURL)) { + throw new Error( + 'Attempted to load a Client Module outside the hosted root.', + ); + } + // Relative URL + const modulePath = fullURL.slice(baseURL.length); + return [modulePath, exportName]; +} + +export function getServerReferenceId( + config: ClientManifest, + serverReference: ServerReference, +): ServerReferenceId { + return serverReference.$$id; +} + +export function getServerReferenceBoundArguments( + config: ClientManifest, + serverReference: ServerReference, +): null | Array { + return serverReference.$$bound; +} diff --git a/packages/react-server/src/forks/ReactFizzConfig.dom-node-esm.js b/packages/react-server/src/forks/ReactFizzConfig.dom-node-esm.js new file mode 100644 index 000000000000..71c6ab5a5586 --- /dev/null +++ b/packages/react-server/src/forks/ReactFizzConfig.dom-node-esm.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ +import {AsyncLocalStorage} from 'async_hooks'; + +import type {Request} from 'react-server/src/ReactFizzServer'; + +export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; + +export const supportsRequestStorage = true; +export const requestStorage: AsyncLocalStorage = + new AsyncLocalStorage(); diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js new file mode 100644 index 000000000000..d52bf8ad7451 --- /dev/null +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-node-esm.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ +import {AsyncLocalStorage} from 'async_hooks'; + +import type {Request} from 'react-server/src/ReactFlightServer'; + +export * from 'react-server-dom-esm/src/ReactFlightServerConfigESMBundler'; +export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM'; + +export const supportsRequestStorage = true; +export const requestStorage: AsyncLocalStorage = + new AsyncLocalStorage(); diff --git a/packages/react-server/src/forks/ReactServerStreamConfig.dom-node-esm.js b/packages/react-server/src/forks/ReactServerStreamConfig.dom-node-esm.js new file mode 100644 index 000000000000..1a3871aef2e8 --- /dev/null +++ b/packages/react-server/src/forks/ReactServerStreamConfig.dom-node-esm.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from '../ReactServerStreamConfigNode'; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 9a54e7398341..58ae58d9bd44 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -450,6 +450,45 @@ const bundles = [ externals: ['url', 'module'], }, + /******* React Server DOM ESM Server *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-esm/server.node', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'util', 'async_hooks', 'react-dom'], + }, + + /******* React Server DOM ESM Client *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD, ESM_DEV, ESM_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-esm/client.browser', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'react-dom'], + }, + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-server-dom-esm/client.node', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['react', 'react-dom', 'util'], + }, + + /******* React Server DOM ESM Node.js Loader *******/ + { + bundleTypes: [ESM_PROD], + moduleType: RENDERER_UTILS, + entry: 'react-server-dom-esm/node-loader', + global: 'ReactServerESMNodeLoader', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: false, + externals: ['acorn'], + }, + /******* React Suspense Test Utils *******/ { bundleTypes: [NODE_ES2015], diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index 70af232fc1c5..2cb934d862e7 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -155,6 +155,39 @@ module.exports = [ isFlowTyped: true, isServerSupported: true, }, + { + shortName: 'dom-node-esm', + entryPoints: [ + 'react-server-dom-esm/client.browser', + 'react-server-dom-esm/server.node', + 'react-server-dom-esm/client.node', + ], + paths: [ + 'react-dom', + 'react-dom-bindings', + 'react-dom/client', + 'react-dom/server', + 'react-dom/server.node', + 'react-dom/static', + 'react-dom/static.node', + 'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node + 'react-dom/src/server/ReactDOMFizzStaticNode.js', + 'react-server-dom-esm', + 'react-server-dom-esm/client.browser', + 'react-server-dom-esm/client.node', + 'react-server-dom-esm/server', + 'react-server-dom-esm/server.node', + 'react-server-dom-esm/src/ReactFlightDOMServerNode.js', // react-server-dom-webpack/server.node + 'react-devtools', + 'react-devtools-core', + 'react-devtools-shell', + 'react-devtools-shared', + 'react-interactions', + 'shared/ReactDOMSharedInternals', + ], + isFlowTyped: true, + isServerSupported: true, + }, { shortName: 'dom-legacy', entryPoints: [