diff --git a/.gitignore b/.gitignore index e28c4dbde..7f2b1c23d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ npm-debug.* yalc.lock .byebug_history + +# IDE +.idea/ diff --git a/jest.config.js b/jest.config.js index ee6fa2d66..09319b866 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,5 @@ module.exports = { preset: 'ts-jest/presets/js-with-ts', testEnvironment: 'jsdom', + setupFiles: ['/node_package/tests/jest.setup.js'], }; diff --git a/lib/react_on_rails/helper.rb b/lib/react_on_rails/helper.rb index f354f2615..86c685fd2 100644 --- a/lib/react_on_rails/helper.rb +++ b/lib/react_on_rails/helper.rb @@ -91,6 +91,39 @@ def react_component(component_name, options = {}) end end + def rsc_react_component(component_name, options = {}) + rendering_fiber = Fiber.new do + res = internal_rsc_react_component(component_name, options) + res.each_chunk do |chunk| + Fiber.yield chunk + end + Fiber.yield nil + end + rendering_fiber + end + + def stream_react_component(component_name, options = {}) + rendering_fiber = Fiber.new do + stream = stream_react_component_internal(component_name, options) + stream.each_chunk do |chunk| + Fiber.yield chunk + end + Fiber.yield nil + end + + if @rorp_rendering_fibers.nil? + raise ReactOnRails::Error, + "You must call stream_view_containing_react_components to render the view containing the react component" + end + + @rorp_rendering_fibers << rendering_fiber + + # return the first chunk of the fiber + # It contains the initial html of the component + # all updates will be appended to the stream sent to browser + rendering_fiber.resume + end + # react_component_hash is used to return multiple HTML strings for server rendering, such as for # adding meta-tags to a page. # It is exactly like react_component except for the following: @@ -317,19 +350,30 @@ def load_pack_for_generated_component(react_component_name, render_options) return unless render_options.auto_load_bundle ReactOnRails::PackerUtils.raise_nested_entries_disabled unless ReactOnRails::PackerUtils.nested_entries? - if Rails.env.development? - is_component_pack_present = File.exist?(generated_components_pack_path(react_component_name)) - raise_missing_autoloaded_bundle(react_component_name) unless is_component_pack_present - end - append_javascript_pack_tag("generated/#{react_component_name}", - defer: ReactOnRails.configuration.defer_generated_component_packs) - append_stylesheet_pack_tag("generated/#{react_component_name}") + append_javascript_pack_tag("client-bundle") + # if Rails.env.development? + # is_component_pack_present = File.exist?(generated_components_pack_path(react_component_name)) + # raise_missing_autoloaded_bundle(react_component_name) unless is_component_pack_present + # end + # append_javascript_pack_tag("generated/#{react_component_name}", + # defer: ReactOnRails.configuration.defer_generated_component_packs) + # append_stylesheet_pack_tag("generated/#{react_component_name}") end # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity private + def stream_react_component_internal(component_name, options = {}) + options = options.merge(stream?: true) + result = internal_react_component(component_name, options) + build_react_component_result_for_server_streamed_content( + rendered_html_stream: result[:result], + component_specification_tag: result[:tag], + render_options: result[:render_options] + ) + end + def generated_components_pack_path(component_name) "#{ReactOnRails::PackerUtils.packer_source_entry_path}/generated/#{component_name}.js" end @@ -361,6 +405,33 @@ def build_react_component_result_for_server_rendered_string( prepend_render_rails_context(result) end + def build_react_component_result_for_server_streamed_content( + rendered_html_stream: required("rendered_html_stream"), + component_specification_tag: required("component_specification_tag"), + render_options: required("render_options") + ) + content_tag_options_html_tag = render_options.html_options[:tag] || "div" + # The component_specification_tag is appended to the first chunk + # We need to pass it early with the first chunk because it's needed in hydration + # We need to make sure that client can hydrate the app early even before all components are streamed + is_first_chunk = true + rendered_html_stream = rendered_html_stream.transform do |chunk| + if is_first_chunk + is_first_chunk = false + html_content = <<-HTML + #{rails_context_if_not_already_rendered} + #{component_specification_tag} + <#{content_tag_options_html_tag} id="#{render_options.dom_id}">#{chunk} + HTML + next html_content.strip + end + chunk + end + + rendered_html_stream.transform(&:html_safe) + # TODO: handle console logs + end + def build_react_component_result_for_server_rendered_hash( server_rendered_html: required("server_rendered_html"), component_specification_tag: required("component_specification_tag"), @@ -404,20 +475,29 @@ def compose_react_component_html_with_spec_and_console(component_specification_t HTML end - # prepend the rails_context if not yet applied - def prepend_render_rails_context(render_value) - return render_value if @rendered_rails_context + def rails_context_if_not_already_rendered + return "" if @rendered_rails_context data = rails_context(server_side: false) @rendered_rails_context = true - rails_context_content = content_tag(:script, - json_safe_and_pretty(data).html_safe, - type: "application/json", - id: "js-react-on-rails-context") + content_tag(:script, + json_safe_and_pretty(data).html_safe, + type: "application/json", + id: "js-react-on-rails-context") + end + + # prepend the rails_context if not yet applied + def prepend_render_rails_context(render_value) + "#{rails_context_if_not_already_rendered}\n#{render_value}".strip.html_safe + end - "#{rails_context_content}\n#{render_value}".html_safe + def internal_rsc_react_component(react_component_name, options = {}) + options = options.merge(rsc?: true) + render_options = ReactOnRails::ReactComponent::RenderOptions.new(react_component_name: react_component_name, + options: options) + server_rendered_react_component(render_options) end def internal_react_component(react_component_name, options = {}) @@ -512,6 +592,9 @@ def server_rendered_react_component(render_options) js_code: js_code) end + # TODO: handle errors for streams + return result if render_options.stream? || render_options.rsc? + if result["hasErrors"] && render_options.raise_on_prerender_error # We caught this exception on our backtrace handler raise ReactOnRails::PrerenderError.new(component_name: react_component_name, diff --git a/lib/react_on_rails/react_component/render_options.rb b/lib/react_on_rails/react_component/render_options.rb index 01f9ffc4e..97576bcf3 100644 --- a/lib/react_on_rails/react_component/render_options.rb +++ b/lib/react_on_rails/react_component/render_options.rb @@ -103,6 +103,14 @@ def set_option(key, value) options[key] = value end + def stream? + options[:stream?] + end + + def rsc? + options[:rsc?] + end + private attr_reader :options diff --git a/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb b/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb index d1e7212d8..2dcd3eb80 100644 --- a/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +++ b/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb @@ -92,6 +92,12 @@ def exec_server_render_js(js_code, render_options, js_evaluator = nil) end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity + # TODO: merge with exec_server_render_js + def exec_server_render_streaming_js(js_code, render_options, js_evaluator = nil) + js_evaluator ||= self + js_evaluator.eval_streaming_js(js_code, render_options) + end + def trace_js_code_used(msg, js_code, file_name = "tmp/server-generated.js", force: false) return unless ReactOnRails.configuration.trace || force diff --git a/lib/react_on_rails/utils.rb b/lib/react_on_rails/utils.rb index bd50dd8da..b4c90bb8a 100644 --- a/lib/react_on_rails/utils.rb +++ b/lib/react_on_rails/utils.rb @@ -66,7 +66,7 @@ def self.server_bundle_path_is_http? server_bundle_js_file_path =~ %r{https?://} end - def self.server_bundle_js_file_path + def self.bundle_js_file_path(bundle_name) # Either: # 1. Using same bundle for both server and client, so server bundle will be hashed in manifest # 2. Using a different bundle (different Webpack config), so file is not hashed, and @@ -76,36 +76,49 @@ def self.server_bundle_js_file_path # a. The webpack manifest plugin would have a race condition where the same manifest.json # is edited by both the webpack-dev-server # b. There is no good reason to hash the server bundle name. - return @server_bundle_path if @server_bundle_path && !Rails.env.development? - - bundle_name = ReactOnRails.configuration.server_bundle_js_file - @server_bundle_path = if ReactOnRails::PackerUtils.using_packer? - begin - bundle_js_file_path(bundle_name) - rescue Object.const_get( - ReactOnRails::PackerUtils.packer_type.capitalize - )::Manifest::MissingEntryError - File.expand_path( - File.join(ReactOnRails::PackerUtils.packer_public_output_path, - bundle_name) - ) - end - else - bundle_js_file_path(bundle_name) - end - end - - def self.bundle_js_file_path(bundle_name) if ReactOnRails::PackerUtils.using_packer? && bundle_name != "manifest.json" - ReactOnRails::PackerUtils.bundle_js_uri_from_packer(bundle_name) + begin + ReactOnRails::PackerUtils.bundle_js_uri_from_packer(bundle_name) + rescue Object.const_get( + ReactOnRails::PackerUtils.packer_type.capitalize + )::Manifest::MissingEntryError + File.expand_path( + File.join(ReactOnRails::PackerUtils.packer_public_output_path, + bundle_name) + ) + end else # Default to the non-hashed name in the specified output directory, which, for legacy # React on Rails, this is the output directory picked up by the asset pipeline. - # For Shakapacker, this is the public output path defined in the (shaka/web)packer.yml file. + # For Webpacker, this is the public output path defined in the webpacker.yml file. File.join(generated_assets_full_path, bundle_name) end end + def self.server_bundle_js_file_path + # Either: + # 1. Using same bundle for both server and client, so server bundle will be hashed in manifest + # 2. Using a different bundle (different Webpack config), so file is not hashed, and + # bundle_js_path will throw so the default path is used without a hash. + # 3. The third option of having the server bundle hashed and a different configuration than + # the client bundle is not supported for 2 reasons: + # a. The webpack manifest plugin would have a race condition where the same manifest.json + # is edited by both the webpack-dev-server + # b. There is no good reason to hash the server bundle name. + return @server_bundle_path if @server_bundle_path && !Rails.env.development? + + bundle_name = ReactOnRails.configuration.server_bundle_js_file + @server_bundle_path = bundle_js_file_path(bundle_name) + end + + def self.rsc_bundle_js_file_path + return @rsc_bundle_path if @rsc_bundle_path && !Rails.env.development? + + # TODO: make it configurable + bundle_name = "rsc-bundle.js" + @rsc_bundle_path = bundle_js_file_path(bundle_name) + end + def self.running_on_windows? (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil end diff --git a/node_package/src/ReactOnRails.ts b/node_package/src/ReactOnRails.ts index 8a2152292..8180ff664 100644 --- a/node_package/src/ReactOnRails.ts +++ b/node_package/src/ReactOnRails.ts @@ -1,10 +1,11 @@ import type { ReactElement } from 'react'; +import type { PassThrough } from 'stream'; import * as ClientStartup from './clientStartup'; import handleError from './handleError'; import ComponentRegistry from './ComponentRegistry'; import StoreRegistry from './StoreRegistry'; -import serverRenderReactComponent from './serverRenderReactComponent'; +import serverRenderReactComponent, { streamServerRenderedReactComponent } from './serverRenderReactComponent'; import buildConsoleReplay from './buildConsoleReplay'; import createReactOutput from './createReactOutput'; import Authenticity from './Authenticity'; @@ -241,6 +242,23 @@ ctx.ReactOnRails = { return serverRenderReactComponent(options); }, + /** + * Used by server rendering by Rails + * @param options + */ + streamServerRenderedReactComponent(options: RenderParams): PassThrough { + return streamServerRenderedReactComponent(options); + }, + + /** + * Used by server rendering by Rails + * @param options + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + serverRenderRSCReactComponent(options: RenderParams): PassThrough { + throw new Error('serverRenderRSCReactComponent is supported in RSC bundle only.'); + }, + /** * Used by Rails to catch errors in rendering * @param options diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts new file mode 100644 index 000000000..cd265b566 --- /dev/null +++ b/node_package/src/ReactOnRailsRSC.ts @@ -0,0 +1,89 @@ +import type { ReactElement } from 'react'; +// @ts-expect-error will define this module types later +import { renderToReadableStream } from 'react-server-dom-webpack/server.edge'; +import { PassThrough } from 'stream'; +import fs from 'fs'; + +import { RenderParams } from './types'; +import ComponentRegistry from './ComponentRegistry'; +import createReactOutput from './createReactOutput'; +import { isPromise, isServerRenderHash } from './isServerRenderResult'; +import ReactOnRails from './ReactOnRails'; + +(async () => { + try { + // @ts-expect-error AsyncLocalStorage is not in the node types + globalThis.AsyncLocalStorage = (await import('node:async_hooks')).AsyncLocalStorage; + } catch (e) { + console.log('AsyncLocalStorage not found'); + } +})(); + +const stringToStream = (str: string) => { + const stream = new PassThrough(); + stream.push(str); + stream.push(null); + return stream; +}; + +const getBundleConfig = () => { + const bundleConfig = JSON.parse(fs.readFileSync('./public/webpack/development/react-client-manifest.json', 'utf8')); + // remove file:// from keys + const newBundleConfig: { [key: string]: any } = {}; + for (const [key, value] of Object.entries(bundleConfig)) { + newBundleConfig[key.replace('file://', '')] = value; + } + return newBundleConfig; +} + +ReactOnRails.serverRenderRSCReactComponent = (options: RenderParams) => { + const { name, domNodeId, trace, props, railsContext, throwJsErrors } = options; + + let renderResult: null | PassThrough = null; + + try { + const componentObj = ComponentRegistry.get(name); + if (componentObj.isRenderer) { + throw new Error(`\ +Detected a renderer while server rendering component '${name}'. \ +See https://github.com/shakacode/react_on_rails#renderer-functions`); + } + + const reactRenderingResult = createReactOutput({ + componentObj, + domNodeId, + trace, + props, + railsContext, + }); + + if (isServerRenderHash(reactRenderingResult) || isPromise(reactRenderingResult)) { + throw new Error('Server rendering of streams is not supported for server render hashes or promises.'); + } + + renderResult = new PassThrough(); + const streamReader = renderToReadableStream(reactRenderingResult, getBundleConfig()).getReader(); + const processStream = async () => { + const { done, value } = await streamReader.read(); + if (done) { + renderResult?.push(null); + return; + } + + renderResult?.push(value); + processStream(); + } + processStream(); + } catch (e: unknown) { + if (throwJsErrors) { + throw e; + } + + renderResult = stringToStream(`Error: ${e}`); + } + + return renderResult; +}; + +export * from './types'; +export default ReactOnRails; diff --git a/node_package/src/serverRenderReactComponent.ts b/node_package/src/serverRenderReactComponent.ts index 422b430c2..2415382b5 100644 --- a/node_package/src/serverRenderReactComponent.ts +++ b/node_package/src/serverRenderReactComponent.ts @@ -1,4 +1,5 @@ import ReactDOMServer from 'react-dom/server'; +import { PassThrough } from 'stream'; import type { ReactElement } from 'react'; import ComponentRegistry from './ComponentRegistry'; @@ -165,4 +166,57 @@ const serverRenderReactComponent: typeof serverRenderReactComponentInternal = (o console.history = []; } }; + +const stringToStream = (str: string) => { + const stream = new PassThrough(); + stream.push(str); + stream.push(null); + return stream; +}; + +export const streamServerRenderedReactComponent = (options: RenderParams) => { + const { name, domNodeId, trace, props, railsContext, throwJsErrors } = options; + + let renderResult: null | PassThrough = null; + + try { + const componentObj = ComponentRegistry.get(name); + if (componentObj.isRenderer) { + throw new Error(`\ +Detected a renderer while server rendering component '${name}'. \ +See https://github.com/shakacode/react_on_rails#renderer-functions`); + } + + const reactRenderingResult = createReactOutput({ + componentObj, + domNodeId, + trace, + props, + railsContext, + }); + + if (isServerRenderHash(reactRenderingResult) || isPromise(reactRenderingResult)) { + throw new Error('Server rendering of streams is not supported for server render hashes or promises.'); + } + + renderResult = new PassThrough(); + ReactDOMServer.renderToPipeableStream(reactRenderingResult).pipe(renderResult); + + // TODO: Add console replay script to the stream + // Ensure to avoid console messages leaking between different components rendering + } catch (e: any) { + if (throwJsErrors) { + throw e; + } + + renderResult = stringToStream(handleError({ + e, + name, + serverSide: true, + })); + } + + return renderResult; +}; + export default serverRenderReactComponent; diff --git a/node_package/src/types/index.ts b/node_package/src/types/index.ts index 06ce51ae7..3701b8a5d 100644 --- a/node_package/src/types/index.ts +++ b/node_package/src/types/index.ts @@ -1,4 +1,5 @@ import type { ReactElement, ReactNode, Component, ComponentType } from 'react'; +import type { PassThrough } from 'stream'; // Don't import redux just for the type definitions // See https://github.com/shakacode/react_on_rails/issues/1321 @@ -137,6 +138,8 @@ export interface ReactOnRails { ): RenderReturnType; getComponent(name: string): RegisteredComponent; serverRenderReactComponent(options: RenderParams): null | string | Promise; + streamServerRenderedReactComponent(options: RenderParams): PassThrough; + serverRenderRSCReactComponent(options: RenderParams): PassThrough; handleError(options: ErrorOptions): string | undefined; buildConsoleReplay(): string; registeredComponents(): Map; diff --git a/node_package/tests/ReactOnRails.test.js b/node_package/tests/ReactOnRails.test.js index da3a56adc..afa6f4c27 100644 --- a/node_package/tests/ReactOnRails.test.js +++ b/node_package/tests/ReactOnRails.test.js @@ -19,10 +19,15 @@ describe('ReactOnRails', () => { }); ReactOnRails.register({ R1 }); - document.body.innerHTML = '
'; - // eslint-disable-next-line no-underscore-dangle - const actual = ReactOnRails.render('R1', {}, 'root')._reactInternals.type; - expect(actual).toEqual(R1); + const root = document.createElement('div'); + root.id = 'root'; + root.textContent = ' WORLD '; + + document.body.innerHTML = ''; + document.body.appendChild(root); + ReactOnRails.render('R1', {}, 'root'); + + expect(document.getElementById('root').textContent).toEqual(' WORLD '); }); it('accepts traceTurbolinks as an option true', () => { diff --git a/node_package/tests/jest.setup.js b/node_package/tests/jest.setup.js new file mode 100644 index 000000000..454efc9cb --- /dev/null +++ b/node_package/tests/jest.setup.js @@ -0,0 +1,13 @@ +// If jsdom environment is set and TextEncoder is not defined, then define TextEncoder and TextDecoder +// The current version of jsdom does not support TextEncoder and TextDecoder +// The following code will tell us when jsdom supports TextEncoder and TextDecoder +if (typeof window !== 'undefined' && typeof window.TextEncoder !== 'undefined') { + throw new Error('TextEncoder is already defined, remove the polyfill'); +} + +if (typeof window !== 'undefined') { + // eslint-disable-next-line global-require + const { TextEncoder, TextDecoder } = require('util'); + global.TextEncoder = TextEncoder; + global.TextDecoder = TextDecoder; +} diff --git a/package.json b/package.json index e409606aa..a25693f6c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,12 @@ "name": "react-on-rails", "version": "14.0.4", "description": "react-on-rails JavaScript for react_on_rails Ruby gem", - "main": "node_package/lib/ReactOnRails.js", + "exports": { + ".": { + "rsc-server": "./node_package/lib/ReactOnRailsRSC.js", + "default": "./node_package/lib/ReactOnRails.js" + } + }, "directories": { "doc": "docs" }, @@ -15,8 +20,8 @@ "@babel/preset-react": "^7.18.6", "@babel/types": "^7.20.7", "@types/jest": "^29.0.0", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "@types/turbolinks": "^5.2.2", "@types/webpack-env": "^1.18.4", "@typescript-eslint/eslint-plugin": "^6.18.1", @@ -39,8 +44,9 @@ "prettier": "^2.8.8", "prettier-eslint-cli": "^5.0.0", "prop-types": "^15.8.1", - "react": "^17.0.0", - "react-dom": "^17.0.0", + "react": "18.3.0-canary-670811593-20240322", + "react-dom": "18.3.0-canary-670811593-20240322", + "react-server-dom-webpack": "18.3.0-canary-670811593-20240322", "react-transform-hmr": "^1.0.4", "redux": "^4.2.1", "ts-jest": "^29.1.0", diff --git a/spec/dummy/config/webpack/alias.js b/spec/dummy/config/webpack/alias.js index 5645c184a..3dd27b046 100644 --- a/spec/dummy/config/webpack/alias.js +++ b/spec/dummy/config/webpack/alias.js @@ -4,6 +4,7 @@ module.exports = { resolve: { alias: { Assets: resolve(__dirname, '..', '..', 'client', 'app', 'assets'), + stream: 'stream-browserify', }, }, }; diff --git a/spec/dummy/config/webpack/commonWebpackConfig.js b/spec/dummy/config/webpack/commonWebpackConfig.js index 998c0d023..c268f81f8 100644 --- a/spec/dummy/config/webpack/commonWebpackConfig.js +++ b/spec/dummy/config/webpack/commonWebpackConfig.js @@ -41,6 +41,7 @@ baseClientWebpackConfig.plugins.push( new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', + process: 'process/browser', }), ); diff --git a/spec/dummy/config/webpack/webpackConfig.js b/spec/dummy/config/webpack/webpackConfig.js index 75747b455..3f99331fe 100644 --- a/spec/dummy/config/webpack/webpackConfig.js +++ b/spec/dummy/config/webpack/webpackConfig.js @@ -4,6 +4,7 @@ const serverWebpackConfig = require('./serverWebpackConfig'); const webpackConfig = (envSpecific) => { const clientConfig = clientWebpackConfig(); const serverConfig = serverWebpackConfig(); + clientConfig.resolve.fallback = { stream: require.resolve('stream-browserify') }; if (envSpecific) { envSpecific(clientConfig, serverConfig); diff --git a/yarn.lock b/yarn.lock index d9f6f6409..2a787a18d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1777,27 +1777,21 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/react-dom@^17.0.0": - version "17.0.25" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.25.tgz#e0e5b3571e1069625b3a3da2b279379aa33a0cb5" - integrity sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA== +"@types/react-dom@^18.2.0": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== dependencies: - "@types/react" "^17" + "@types/react" "*" -"@types/react@^17", "@types/react@^17.0.0": - version "17.0.74" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.74.tgz#ea93059a55e5cfc7a76e7712fe8db5317dd29ee3" - integrity sha512-nBtFGaeTMzpiL/p73xbmCi00SiCQZDTJUk9ZuHOLtil3nI+y7l269LHkHIAYpav99ZwGnPJzuJsJpfLXjiQ52g== +"@types/react@*", "@types/react@^18.2.0": + version "18.3.3" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" + integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== dependencies: "@types/prop-types" "*" - "@types/scheduler" "*" csstype "^3.0.2" -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== - "@types/semver@^7.5.0": version "7.5.6" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" @@ -1978,6 +1972,13 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-loose@^8.3.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/acorn-loose/-/acorn-loose-8.4.0.tgz#26d3e219756d1e180d006f5bcc8d261a28530f55" + integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ== + dependencies: + acorn "^8.11.0" + acorn-walk@^8.0.2: version "8.3.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" @@ -2008,6 +2009,11 @@ acorn@^8.1.0, acorn@^8.8.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.11.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -5161,7 +5167,7 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== -loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.3.1, loose-envify@^1.4.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== @@ -5343,6 +5349,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +neo-async@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -5883,14 +5894,12 @@ react-deep-force-update@^1.0.0: resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.2.tgz#3d2ae45c2c9040cbb1772be52f8ea1ade6ca2ee1" integrity sha512-WUSQJ4P/wWcusaH+zZmbECOk7H5N2pOIl0vzheeornkIMhu+qrNdGFm0bDZLCb0hSF0jf/kH1SgkNGfBdTc4wA== -react-dom@^17.0.0: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" - integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== +react-dom@18.3.0-canary-670811593-20240322: + version "18.3.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.0-canary-670811593-20240322.tgz#ac677b164fd83050272bf985e740ed4ca65337be" + integrity sha512-AHxCnyDzZueXIHY4WA2Uba1yaL7/vbjhO3D3TWPQeruKD5MwgD0/xExZi0T104gBr6Thv6MEsLSxFjBAHhHKKg== dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.20.2" + scheduler "0.24.0-canary-670811593-20240322" react-is@^16.13.1: version "16.13.1" @@ -5910,6 +5919,14 @@ react-proxy@^1.1.7: lodash "^4.6.1" react-deep-force-update "^1.0.0" +react-server-dom-webpack@18.3.0-canary-670811593-20240322: + version "18.3.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-18.3.0-canary-670811593-20240322.tgz#e9b99b1f0179357e5acbf2fbacaee88dd1e8bf3b" + integrity sha512-YaCk3AvvOXcOo0FL7SlAY2GVBeuZKFQ/5FfAtE48IjpI6MvXTwMBu3QVnT/Ukk9Y4M9GzpIbLtuc8hPjfFAOaw== + dependencies: + acorn-loose "^8.3.0" + neo-async "^2.6.1" + react-transform-hmr@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz#e1a40bd0aaefc72e8dfd7a7cda09af85066397bb" @@ -5918,13 +5935,10 @@ react-transform-hmr@^1.0.4: global "^4.3.0" react-proxy "^1.1.7" -react@^17.0.0: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" +react@18.3.0-canary-670811593-20240322: + version "18.3.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.0-canary-670811593-20240322.tgz#3735250b45468d313ed36121324452bb5a732e9b" + integrity sha512-EI6+q3tOT+0z4OkB2sz842Ra/n/yz7b3jOJhSK1HQwi4Ng29VJzLGngWmSuxQ94YfdE3EBhpUKDfgNgzoKM9Vg== readdirp@~3.6.0: version "3.6.0" @@ -6221,13 +6235,10 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" +scheduler@0.24.0-canary-670811593-20240322: + version "0.24.0-canary-670811593-20240322" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.24.0-canary-670811593-20240322.tgz#45c5c45f18a127ab4e3c805dd466bc231b20adf3" + integrity sha512-IGX6Fq969h1L0X7jV0sJ/EdI4fr+mRetbBNJl55nn+/RsCuQSVwgKnZG6Q3NByixDNbkRI8nRmWuhOm8NQowGQ== semver@5.5.0: version "5.5.0"