├── .styluaignore ├── stylua.toml ├── .gitattributes ├── modules ├── TestRunner │ ├── src │ │ └── init.lua │ ├── default.project.json │ └── rotriever.toml ├── react-devtools │ ├── .luaurc │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ └── init.lua ├── react-telemetry │ ├── .luaurc │ ├── .robloxrc │ ├── default.project.json │ ├── src │ │ ├── init.lua │ │ ├── customFields.lua │ │ ├── reportCounter.lua │ │ └── ReactTelemetry.lua │ ├── README.md │ └── rotriever.toml ├── react-devtools-core │ ├── .luaurc │ ├── src │ │ ├── init.lua │ │ └── setupAttachHook.lua │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── README.md ├── react-devtools-timeline │ ├── .luaurc │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── constants.lua │ │ └── init.lua ├── react-refresh │ ├── src │ │ └── init.lua │ ├── default.project.json │ ├── .robloxrc │ ├── rotriever.toml │ └── README.md ├── react │ ├── default.project.json │ ├── .robloxrc │ ├── README.md │ ├── rotriever.toml │ └── src │ │ ├── None.roblox.lua │ │ ├── ReactMutableSource.lua │ │ ├── __tests__ │ │ └── ReactBaseClasses.roblox.spec.lua │ │ └── ReactCreateRef.lua ├── react-is │ ├── default.project.json │ ├── .robloxrc │ ├── README.md │ └── rotriever.toml ├── shared │ ├── default.project.json │ ├── .robloxrc │ ├── src │ │ ├── ReactVersion.lua │ │ ├── enqueueTask.roblox.lua │ │ ├── ExecutionEnvironment.lua │ │ ├── ReactSharedInternals │ │ │ ├── IsSomeRendererActing.lua │ │ │ ├── ReactCurrentBatchConfig.lua │ │ │ ├── ReactCurrentOwner.lua │ │ │ └── ReactDebugCurrentFrame.lua │ │ ├── PropMarkers │ │ │ ├── Tag.lua │ │ │ ├── __tests__ │ │ │ │ ├── Change.spec.lua │ │ │ │ └── Event.spec.lua │ │ │ ├── Event.lua │ │ │ └── Change.lua │ │ ├── objectIs.lua │ │ ├── ReactFiberHostConfig │ │ │ ├── WithNoTestSelectors.lua │ │ │ ├── WithNoPersistence.lua │ │ │ ├── init.lua │ │ │ └── WithNoHydration.lua │ │ ├── __tests__ │ │ │ ├── isValidElementType.roblox.spec.lua │ │ │ ├── getComponentName.roblox.spec.lua │ │ │ └── ReactErrorProd-internal.spec.lua │ │ ├── formatProdErrorMessage.lua │ │ ├── invariant.lua │ │ ├── shallowEqual.lua │ │ ├── Symbol.roblox.lua │ │ ├── Type.roblox.lua │ │ ├── console.lua │ │ ├── ReactElementType.lua │ │ ├── UninitializedState.roblox.lua │ │ └── consoleWithStackDev.lua │ ├── rotriever.toml │ └── README.md ├── example-app │ ├── default.project.json │ ├── README.md │ ├── rotriever.toml │ └── StarterScript.lua ├── jest-react │ ├── default.project.json │ ├── .robloxrc │ ├── README.md │ ├── rotriever.toml │ └── src │ │ └── init.lua ├── react-cache │ ├── default.project.json │ ├── .robloxrc │ ├── rotriever.toml │ ├── README.md │ └── src │ │ └── init.lua ├── scheduler │ ├── default.project.json │ ├── .robloxrc │ ├── rotriever.toml │ ├── src │ │ ├── SchedulerFeatureFlags.lua │ │ ├── SchedulerPriorities.lua │ │ ├── SchedulerHostConfig.lua │ │ ├── NoYield.lua │ │ ├── unstable_mock.lua │ │ └── __tests__ │ │ │ └── Tracing.spec.lua │ └── README.md ├── react-globals │ ├── default.project.json │ ├── src │ │ └── init.lua │ ├── .robloxrc │ ├── README.md │ └── rotriever.toml ├── react-roblox │ ├── default.project.json │ ├── .robloxrc │ ├── src │ │ ├── ReactReconciler.roblox.lua │ │ ├── init.lua │ │ └── client │ │ │ └── roblox │ │ │ ├── __tests__ │ │ │ ├── waitForEvents.lua │ │ │ └── getDefaultInstanceProperty.spec.lua │ │ │ └── getDefaultInstanceProperty.lua │ └── rotriever.toml ├── roact-compat │ ├── default.project.json │ ├── .robloxrc │ ├── README.md │ ├── rotriever.toml │ └── src │ │ ├── warnOnce.lua │ │ ├── createFragment.lua │ │ ├── Portal.lua │ │ ├── setGlobalConfig.lua │ │ ├── __tests__ │ │ └── warnOnce.spec.lua │ │ └── oneChild.lua ├── react-reconciler │ ├── default.project.json │ ├── .robloxrc │ ├── src │ │ ├── forks │ │ │ └── ReactFiberHostConfig.test.lua │ │ ├── ReactFiberReconciler.lua │ │ ├── ReactRootTags.lua │ │ ├── MaxInts.lua │ │ ├── ReactTypeOfMode.lua │ │ ├── ReactHookEffectTags.lua │ │ ├── ReactFiberTransition.lua │ │ ├── ReactFiberErrorDialog.lua │ │ ├── ReactCapturedValue.lua │ │ ├── __tests__ │ │ │ ├── ReactFiberRoot.roblox.spec.lua │ │ │ ├── ReactIncrementalErrorReplay.spec.lua │ │ │ ├── ReactTopLevelText.spec.lua │ │ │ ├── ReactFiberContext-internal.spec.lua │ │ │ ├── ReactFiberStack-test.roblox.spec.lua │ │ │ ├── ReactFiberSuspenseContext.roblox.spec.lua │ │ │ ├── ReactClassSetStateCallback.spec.lua │ │ │ ├── ReactNoopRendererAct.spec.lua │ │ │ └── ReactFiberComponentStack.roblox.spec.lua │ │ ├── ReactPortal.lua │ │ ├── ReactWorkTags.lua │ │ ├── ReactFiberSchedulerPriorities.roblox.lua │ │ ├── ReactFiberLazyComponent.new.lua │ │ ├── ReactFiberWorkInProgress.lua │ │ ├── ReactFiberOffscreenComponent.lua │ │ ├── ReactFiberHostConfig.lua │ │ └── init.lua │ ├── rotriever.toml │ └── README.md ├── react-debug-tools │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── ReactDebugTools.lua │ │ └── init.lua ├── react-noop-renderer │ ├── default.project.json │ ├── .robloxrc │ ├── src │ │ └── init.lua │ ├── rotriever.toml │ └── README.md ├── react-test-renderer │ ├── default.project.json │ ├── .robloxrc │ ├── src │ │ └── init.lua │ ├── rotriever.toml │ └── README.md ├── react-devtools-extensions │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ └── init.lua ├── react-devtools-shared │ ├── .robloxrc │ ├── default.project.json │ ├── src │ │ ├── clipboardjs.mock.lua │ │ ├── jest.config.lua │ │ ├── devtools │ │ │ └── init.lua │ │ ├── init.lua │ │ ├── backend │ │ │ ├── NativeStyleEditor │ │ │ │ └── types.lua │ │ │ ├── console.lua │ │ │ ├── views │ │ │ │ └── Highlighter │ │ │ │ │ ├── Highlighter.lua │ │ │ │ │ └── Overlay │ │ │ │ │ └── OverlayRect.lua │ │ │ └── ReactSymbols.lua │ │ ├── __tests__ │ │ │ ├── profilingUtils.spec.lua │ │ │ └── bridge.spec.lua │ │ └── storage.lua │ └── rotriever.toml └── react-shallow-renderer │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── README.md ├── docs ├── requirements.txt ├── extra.css ├── diff-packages.png ├── diff-upstream-comment.png ├── diff-bridge-shutdown-method-after.png ├── diff-bridge-shutdown-method-before.png ├── diff-class-arrow-function-method-new.png ├── diff-class-arrow-function-method-old.png ├── diff-spread-flow-type-no-deviation-anymore.png ├── api-reference │ └── additional-libraries.md ├── images │ ├── aligned.svg │ ├── apichange.svg │ ├── deviation.svg │ ├── roblox.svg │ └── reactjs.svg ├── bench.md └── index.md ├── default.project.json ├── Packages └── .robloxrc ├── WorkspaceStatic ├── jest │ ├── .robloxrc │ ├── matchers │ │ ├── toLogDev.lua │ │ ├── toWarnDev.lua │ │ └── toErrorDev.lua │ └── testSetupFile.lua └── jest.config.lua ├── codecov.yml ├── tests.project.json ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .editorconfig ├── .gitignore ├── foreman.toml ├── bin ├── run-deep-tree-benchmark.lua ├── run-wide-tree-benchmark.lua ├── run-sierpinski-triangle-benchmark.lua ├── run-frame-rate-benchmark.lua ├── run-first-render-benchmark.lua ├── convert.sh ├── ci.sh ├── ci-benchmarks.sh ├── spec.lua └── upstream-tag.sh ├── selene.toml ├── rotriever.toml ├── LICENSE └── mkdocs.yml /.styluaignore: -------------------------------------------------------------------------------- 1 | *.snap.lua -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 90 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lua linguist-language=Luau 2 | -------------------------------------------------------------------------------- /modules/TestRunner/src/init.lua: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | pymdown-extensions -------------------------------------------------------------------------------- /modules/react-devtools/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict" 3 | } -------------------------------------------------------------------------------- /modules/react-telemetry/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict" 3 | } -------------------------------------------------------------------------------- /modules/react-devtools-core/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict" 3 | } -------------------------------------------------------------------------------- /modules/react-devtools-timeline/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict" 3 | } -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | .md-typeset hr { 2 | border-bottom: 2px solid rgba(0, 0, 0, 0.15); 3 | } 4 | -------------------------------------------------------------------------------- /docs/diff-packages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-luau/HEAD/docs/diff-packages.png -------------------------------------------------------------------------------- /docs/diff-upstream-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-luau/HEAD/docs/diff-upstream-comment.png -------------------------------------------------------------------------------- /modules/react-refresh/src/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | ReactFreshRuntime = require(script.ReactFreshRuntime), 3 | } 4 | -------------------------------------------------------------------------------- /modules/react/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoactAlignment", 3 | "tree": { 4 | "$path": "modules" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/react-is/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactIs", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/shared/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shared", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Packages/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nocheck" 4 | }, 5 | "lint": { 6 | "*": "disabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/example-app/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExampleApp", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/jest-react/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JestReact", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react-cache/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactCache", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/scheduler/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scheduler", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react-globals/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactGlobals", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react-globals/src/init.lua: -------------------------------------------------------------------------------- 1 | local ReactGlobals = require(script["ReactGlobals.global"]) 2 | 3 | return ReactGlobals 4 | -------------------------------------------------------------------------------- /modules/react-refresh/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactRefresh", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react-roblox/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactRoblox", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/roact-compat/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoactCompat", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/shared/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /WorkspaceStatic/jest/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - '*spec.lua' 3 | - '*__test*' 4 | - '*jest*' 5 | - '*mock*' 6 | - '*Dev*' 7 | - '*/_Index/*' 8 | -------------------------------------------------------------------------------- /modules/jest-react/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-cache/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-devtools-core/src/init.lua: -------------------------------------------------------------------------------- 1 | local backend = require(script.backend) 2 | 3 | return { 4 | backend = backend, 5 | } 6 | -------------------------------------------------------------------------------- /modules/react-devtools/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-devtools/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDevtools", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/react-globals/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-is/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-reconciler/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactReconciler", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react-refresh/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-roblox/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-telemetry/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-telemetry/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactTelemetry", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/roact-compat/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/scheduler/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /docs/diff-bridge-shutdown-method-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-luau/HEAD/docs/diff-bridge-shutdown-method-after.png -------------------------------------------------------------------------------- /modules/react-debug-tools/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-debug-tools/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDebugTools", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/react-devtools-core/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-noop-renderer/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNoopRenderer", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react-reconciler/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-test-renderer/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactTestRenderer", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/diff-bridge-shutdown-method-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-luau/HEAD/docs/diff-bridge-shutdown-method-before.png -------------------------------------------------------------------------------- /docs/diff-class-arrow-function-method-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-luau/HEAD/docs/diff-class-arrow-function-method-new.png -------------------------------------------------------------------------------- /docs/diff-class-arrow-function-method-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-luau/HEAD/docs/diff-class-arrow-function-method-old.png -------------------------------------------------------------------------------- /modules/react-devtools-core/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDevtoolsCore", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/react-devtools-extensions/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-devtools-shared/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-devtools-timeline/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-noop-renderer/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-shallow-renderer/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-shallow-renderer/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactShallowRenderer", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/react-test-renderer/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-devtools-shared/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDevtoolsShared", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/react-devtools-timeline/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDevtoolsTimeline", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/diff-spread-flow-type-no-deviation-anymore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-luau/HEAD/docs/diff-spread-flow-type-no-deviation-anymore.png -------------------------------------------------------------------------------- /modules/react-devtools-extensions/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDevtoolsExtensions", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /modules/example-app/README.md: -------------------------------------------------------------------------------- 1 | # Example App 2 | 3 | A small example app using Foundation. Primarily intended to make testing React features and tooling easier. 4 | -------------------------------------------------------------------------------- /WorkspaceStatic/jest/matchers/toLogDev.lua: -------------------------------------------------------------------------------- 1 | local createConsoleMatcher = require(script.Parent.createConsoleMatcher) 2 | 3 | return createConsoleMatcher("log", "toLogDev") 4 | -------------------------------------------------------------------------------- /WorkspaceStatic/jest/matchers/toWarnDev.lua: -------------------------------------------------------------------------------- 1 | local createConsoleMatcher = require(script.Parent.createConsoleMatcher) 2 | 3 | return createConsoleMatcher("warn", "toWarnDev") 4 | -------------------------------------------------------------------------------- /modules/TestRunner/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TestRunner", 3 | "tree": { 4 | "Src": { 5 | "$path": "src" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/clipboardjs.mock.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX TODO: stub for clipboardjs, remove when we know how we'll handle its intent in a Roblox way 2 | return {} 3 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/jest.config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | setupFilesAfterEnv = { script.Parent.__tests__.setupTests }, 3 | testMatch = { "**/*.(spec|test)" }, 4 | } 5 | -------------------------------------------------------------------------------- /WorkspaceStatic/jest/matchers/toErrorDev.lua: -------------------------------------------------------------------------------- 1 | local createConsoleMatcher = require(script.Parent.createConsoleMatcher) 2 | 3 | return createConsoleMatcher("error", "toErrorDev") 4 | -------------------------------------------------------------------------------- /tests.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoactAlignment", 3 | "tree": { 4 | "$path": "Packages", 5 | "_Workspace": { 6 | "$path": "WorkspaceStatic" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /modules/react-telemetry/src/init.lua: -------------------------------------------------------------------------------- 1 | local customFields = require(script.customFields) 2 | export type CustomFields = customFields.CustomFields 3 | 4 | return require(script.ReactTelemetry) 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Closes (ISSUES HERE). 2 | 3 | *Put your pull request body here!* 4 | 5 | Checklist before submitting: 6 | * [ ] Added/updated relevant tests 7 | * [ ] Added/updated documentation -------------------------------------------------------------------------------- /modules/react-globals/README.md: -------------------------------------------------------------------------------- 1 | # react-globals 2 | 3 | This package is unique to react-lua. It houses React's global environment variables in a way that can be scoped to each copy of React running inside the application. 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.lua] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = false 8 | 9 | [*.json] 10 | indent_style = spaces 11 | indent_width = 2 -------------------------------------------------------------------------------- /docs/api-reference/additional-libraries.md: -------------------------------------------------------------------------------- 1 | # Additional Libraries 2 | 3 | ## ReactIs 4 | *Under construction 🔨* 5 | 6 | ## ReactTestRender 7 | *Under construction 🔨* 8 | 9 | ## ReactDevToolsExtensions 10 | *Under construction 🔨* 11 | -------------------------------------------------------------------------------- /modules/react-devtools-timeline/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactDevtoolsTimeline" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | -------------------------------------------------------------------------------- /modules/react-telemetry/README.md: -------------------------------------------------------------------------------- 1 | # `react-telemetry` 2 | 3 | Library for internal telemetry reporting for React. 4 | 5 | **Note for community:** 6 | 7 | This library only runs on internal applications. It is not collecting telemetry on community uses. 8 | -------------------------------------------------------------------------------- /modules/jest-react/README.md: -------------------------------------------------------------------------------- 1 | # jest-react 2 | A Roblox Lua port of the jest-react package re-exported from React. It provides Jest matchers and utilities for testing React Test Renderer. 3 | 4 | Status: ✔️ Ported 5 | 6 | Source: https://github.com/facebook/react/tree/master/packages/jest-react -------------------------------------------------------------------------------- /modules/react-devtools/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactDevtools" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | ReactDevtoolsCore = { path = "../react-devtools-core" } 11 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/devtools/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | utils = require(script.utils), 3 | store = require(script.store), 4 | cache = require(script.cache), 5 | devtools = { 6 | Components = { 7 | views = { 8 | types = require(script.views.Components.types), 9 | }, 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /modules/react-globals/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactGlobals" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | content_root = "src" 6 | files = ["*", "!**/__tests__/**"] 7 | 8 | [dependencies] 9 | SafeFlags = { workspace = true } 10 | 11 | [dev_dependencies] 12 | JestGlobals = { workspace = true } 13 | -------------------------------------------------------------------------------- /modules/roact-compat/README.md: -------------------------------------------------------------------------------- 1 | # roact-compat 2 | A thin compatibility layer connecting Roact 1.4.0 to the new roact alignment effort (often referred to as "Roact 17"). 3 | 4 | This does _not_ match an upstream package. Its purpose is to encapsulate any deviations whose sole purpose is to provide compatibility with production Roact during the transition. 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sourcemap.json 2 | rotriever.lock 3 | Packages/* 4 | !Packages/.robloxrc 5 | !Packages/_Workspace/ 6 | Packages/_Workspace/* 7 | !Packages/_Workspace/.robloxrc 8 | standalone/Packages 9 | **/*.rbx[lm]* 10 | .DS_Store 11 | .vscode 12 | roblox.yml 13 | roblox.toml 14 | **/luacov.* 15 | .idea/ 16 | site/ 17 | 18 | default_modules 19 | modules_* 20 | 21 | **.log -------------------------------------------------------------------------------- /modules/react-refresh/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactRefresh" 3 | version = { workspace = true } 4 | publish = true 5 | content_root = "src" 6 | files = ["*", "!**/__tests__/**"] 7 | 8 | [dependencies] 9 | LuauPolyfill = { workspace = true } 10 | ReactGlobals = { path = "../react-globals" } 11 | ReactReconciler = { path = "../react-reconciler" } 12 | Shared = { path = "../shared" } 13 | -------------------------------------------------------------------------------- /modules/jest-react/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "JestReact" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | JestGlobals = { workspace = true } 12 | ReactGlobals = { path = "../react-globals" } 13 | Shared = { path = "../shared" } 14 | -------------------------------------------------------------------------------- /modules/react-roblox/src/ReactReconciler.roblox.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX deviation: Initializes the reconciler with this package's host 3 | -- config and returns the resulting module 4 | 5 | local Packages = script.Parent.Parent 6 | local initializeReconciler = require(Packages.ReactReconciler) 7 | 8 | local ReactRobloxHostConfig = require(script.Parent.client.ReactRobloxHostConfig) 9 | 10 | return initializeReconciler(ReactRobloxHostConfig) 11 | -------------------------------------------------------------------------------- /modules/react-telemetry/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactTelemetry" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | ReactGlobals = { path = "../react-globals" } 11 | LuauPolyfill = { workspace = true } 12 | SafeFlags = { workspace = true } 13 | 14 | [dev_dependencies] 15 | JestGlobals = { workspace = true } 16 | -------------------------------------------------------------------------------- /modules/react-is/README.md: -------------------------------------------------------------------------------- 1 | # react-is 2 | A Roblox Lua port of the `react-is` package from React, which provides constants and runtime check functions for the various valid types of elements that can be created. 3 | 4 | Status: ✔️ Ported 5 | 6 | Source: https://github.com/facebook/react/tree/master/packages/react-is 7 | 8 | --- 9 | 10 | ### ✏️ Notes 11 | * Includes some guards where we confirm that values are tables before 12 | 13 | ### ❌ Excluded 14 | -------------------------------------------------------------------------------- /modules/react-noop-renderer/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/e7b255341b059b4e2a109847395d0d0ba2633999/packages/react-noop-renderer/src/index.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | *]] 10 | 11 | --!strict 12 | return require(script.ReactNoop) 13 | -------------------------------------------------------------------------------- /modules/react-telemetry/src/customFields.lua: -------------------------------------------------------------------------------- 1 | export type Context = "universal_app" | "in_experience" | "plugin" | "unknown" 2 | export type CustomFields = { 3 | context: Context, 4 | plugin_name: string?, 5 | } 6 | 7 | local plugin = script:FindFirstAncestorWhichIsA("Plugin") 8 | 9 | local customFields: CustomFields = { 10 | context = if plugin then "plugin" else "unknown", 11 | plugin_name = if plugin then plugin.Name else nil, 12 | } 13 | 14 | return customFields 15 | -------------------------------------------------------------------------------- /modules/scheduler/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Scheduler" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = false 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | SafeFlags = { workspace = true } 12 | ReactGlobals = { path = "../react-globals" } 13 | Shared = { path = "../shared" } 14 | 15 | [dev_dependencies] 16 | JestGlobals = { workspace = true } 17 | -------------------------------------------------------------------------------- /docs/images/aligned.svg: -------------------------------------------------------------------------------- 1 | AlignedAligned -------------------------------------------------------------------------------- /modules/react-test-renderer/src/init.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/702fad4b1b48ac8f626ed3f35e8f86f5ea728084/packages/react-test-renderer/src/index.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @emails react-core 10 | ]] 11 | return require(script.ReactTestRenderer) 12 | -------------------------------------------------------------------------------- /modules/shared/src/ReactVersion.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/a89854bc936668d325cac9a22e2ebfa128c7addf/packages/shared/ReactVersion.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | ]] 9 | 10 | -- TODO: this is special because it gets imported during build. 11 | return "17.3.7" 12 | -------------------------------------------------------------------------------- /docs/images/apichange.svg: -------------------------------------------------------------------------------- 1 | API ChangeAPI Change -------------------------------------------------------------------------------- /modules/TestRunner/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "TestRunner" 3 | version = { workspace = true } 4 | author = "Roblox" 5 | content_root = "src" 6 | files = ["src/**"] 7 | 8 | [dev_dependencies] 9 | LuauPolyfill = { workspace = true } 10 | JestDiff = "github.com/roblox/jest-roblox@3" 11 | Jest = { workspace = true } 12 | JestGlobals = { workspace = true } 13 | JestReact = { path = "../jest-react" } 14 | ReactGlobals = { path = "../react-globals" } 15 | Scheduler = { path = "../scheduler" } 16 | -------------------------------------------------------------------------------- /foreman.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | rotrieve = { source = "Roblox/rotriever", version = "=0.5.13-alpha.4" } 3 | rojo = { source = "Roblox/rojo-rbx-rojo", version = "7.2.1" } 4 | selene = { source = "Roblox/Kampfkarren-selene", version = "=0.28.0" } 5 | stylua = { source = "Roblox/JohnnyMorganz-StyLua", version = "=0.18.1" } 6 | testez = { source = "roblox/testez", version = "0.3.2" } 7 | rbx-aged-cli = { source = "Roblox/rbx-aged-tool", version = "=5.7.9" } 8 | lute = { github = "luau-lang/lute", version = "=0.1.0-nightly.20251108" } 9 | -------------------------------------------------------------------------------- /bin/run-deep-tree-benchmark.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.RoactAlignment 2 | local RotrieverWorkspace = Packages._Workspace 3 | 4 | local Roact = require(RotrieverWorkspace.React.React) 5 | local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox) 6 | 7 | local config = {} 8 | if _G.minSamples ~= nil then 9 | config.sampleCount = tonumber(_G.minSamples) 10 | end 11 | 12 | require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).deepTreeBenchmark( 13 | Roact, 14 | ReactRoblox 15 | )(config) 16 | -------------------------------------------------------------------------------- /modules/react-devtools/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/12adaffef7105e2714f82651ea51936c563fe15c/packages/react-devtools/index.js 2 | 3 | local Packages = script.Parent 4 | local ReactDevtoolsCore = require(Packages.ReactDevtoolsCore) 5 | 6 | local connectToDevtools = ReactDevtoolsCore.backend.connectToDevtools 7 | 8 | -- Connect immediately with default options. 9 | -- If you need more control, use `react-devtools-core` directly instead of `react-devtools`. 10 | connectToDevtools() 11 | -------------------------------------------------------------------------------- /modules/react-noop-renderer/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactNoopRenderer" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = false 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | Scheduler = { path = "../scheduler" } 12 | Shared = { path = "../shared" } 13 | ReactGlobals = { path = "../react-globals" } 14 | ReactReconciler = { path = "../react-reconciler" } 15 | JestGlobals = { workspace = true } 16 | -------------------------------------------------------------------------------- /modules/react-shallow-renderer/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactShallowRenderer" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | React = { path = "../react" } 12 | ReactIs = { path = "../react-is" } 13 | ReactGlobals = { path = "../react-globals" } 14 | Shared = { path = "../shared" } 15 | 16 | [dev_dependencies] 17 | JestGlobals = { workspace = true } 18 | -------------------------------------------------------------------------------- /modules/example-app/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ExampleApp" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = false 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [config] 10 | registry_index = true 11 | 12 | [dependencies] 13 | React = { path = "../react" } 14 | ReactRoblox = { path = "../react-roblox" } 15 | ReactDevtoolsCore = { path = "../react-devtools-core" } 16 | ReactGlobals = { path = "../react-globals" } 17 | 18 | Foundation = "1.42.0" 19 | ReactUtils = "1.6.0" 20 | -------------------------------------------------------------------------------- /bin/run-wide-tree-benchmark.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.RoactAlignment 2 | local RotrieverWorkspace = Packages._Workspace 3 | 4 | local Roact = require(RotrieverWorkspace.React.React) 5 | local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox) 6 | local wideTreeBenchmark = 7 | require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).wideTreeBenchmark 8 | 9 | local config = {} 10 | if _G.minSamples ~= nil then 11 | config.sampleCount = tonumber(_G.minSamples) 12 | end 13 | 14 | wideTreeBenchmark(Roact, ReactRoblox)(config) 15 | -------------------------------------------------------------------------------- /modules/scheduler/src/SchedulerFeatureFlags.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/9abc2785cb070148d64fae81e523246b90b92016/packages/scheduler/src/SchedulerFeatureFlags.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | ]] 10 | 11 | return { 12 | enableSchedulerDebugging = false, 13 | enableIsInputPending = false, 14 | enableProfiling = false, 15 | } 16 | -------------------------------------------------------------------------------- /docs/images/deviation.svg: -------------------------------------------------------------------------------- 1 | 2 | Deviation 3 | 4 | 5 | 6 | 7 | Deviation 8 | 9 | -------------------------------------------------------------------------------- /modules/react-devtools-timeline/src/constants.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v19.1.1/packages/react-devtools-timeline/src/constants.js 2 | 3 | local Constants = {} 4 | 5 | local REACT_TOTAL_NUM_LANES = 31 6 | Constants.REACT_TOTAL_NUM_LANES = REACT_TOTAL_NUM_LANES 7 | 8 | -- Increment this number any time a backwards breaking change is made to the profiler metadata. 9 | local SCHEDULING_PROFILER_VERSION = 1 10 | Constants.SCHEDULING_PROFILER_VERSION = SCHEDULING_PROFILER_VERSION 11 | 12 | return table.freeze(Constants) 13 | -------------------------------------------------------------------------------- /modules/react-devtools-core/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactDevtoolsCore" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | SafeFlags = { workspace = true } 12 | ReactDevtoolsShared = { path = "../react-devtools-shared" } 13 | ReactGlobals = { path = "../react-globals" } 14 | ReactTelemetry = { path = "../react-telemetry" } 15 | 16 | [dev_dependencies] 17 | JestGlobals = { workspace = true } 18 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/forks/ReactFiberHostConfig.test.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-reconciler/src/forks/ReactFiberHostConfig.test.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | 11 | local Packages = script.Parent.Parent.Parent 12 | 13 | return require(Packages.Dev.ReactTestRenderer) 14 | -------------------------------------------------------------------------------- /modules/example-app/StarterScript.lua: -------------------------------------------------------------------------------- 1 | local CorePackages = game:GetService("CorePackages") 2 | local RunService = game:GetService("RunService") 3 | 4 | if RunService:IsServer() then 5 | return 6 | end 7 | 8 | -- Set global flags and initialize devtools before React is ever used 9 | local ReactGlobals = require(CorePackages.ReactGlobals) 10 | ReactGlobals.__DEV__ = true 11 | ReactGlobals.__PROFILE__ = true 12 | 13 | local ReactDevtoolsCore = require(CorePackages.ReactDevtoolsCore) 14 | ReactDevtoolsCore.backend.connectToDevtools() 15 | 16 | require(CorePackages.ExampleApp) 17 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberReconciler.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/faa697f4f9afe9f1c98e315b2a9e70f5a74a7a74/packages/react-reconciler/src/ReactFiberReconciler.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | -- deviation: old version of reconciler not ported 13 | return require(script.Parent["ReactFiberReconciler.new"]) 14 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactRootTags.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/c5d2fc7127654e43de59fff865b74765a103c4a5/packages/react-reconciler/src/ReactRootTags.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | export type RootTag = number 13 | 14 | return { 15 | LegacyRoot = 0, 16 | BlockingRoot = 1, 17 | ConcurrentRoot = 2, 18 | } 19 | -------------------------------------------------------------------------------- /modules/shared/src/enqueueTask.roblox.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * 9 | ]] 10 | local Packages = script.Parent.Parent 11 | local LuauPolyfill = require(Packages.LuauPolyfill) 12 | local setTimeout = LuauPolyfill.setTimeout 13 | 14 | return function(task) 15 | -- deviation: Replace with setImmediate once we create an equivalent polyfill 16 | return setTimeout(task, 0) 17 | end 18 | -------------------------------------------------------------------------------- /modules/react-is/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactIs" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | ReactGlobals = { path = "../react-globals" } 11 | SafeFlags = { workspace = true } 12 | Shared = { path = "../shared" } 13 | 14 | [dev_dependencies] 15 | JestGlobals = { workspace = true } 16 | LuauPolyfill = { workspace = true } 17 | Promise = { workspace = true } 18 | React = { path = "../react" } 19 | ReactRoblox = { path = "../react-roblox" } 20 | -------------------------------------------------------------------------------- /modules/react-roblox/src/init.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/efd8f6442d1aa7c4566fe812cba03e7e83aaccc3/packages/react-native-renderer/index.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | local HostTypes = require(script.client["ReactRobloxHostTypes.roblox"]) 13 | export type RootType = HostTypes.RootType 14 | return require(script.client.ReactRoblox) 15 | -------------------------------------------------------------------------------- /modules/react-refresh/README.md: -------------------------------------------------------------------------------- 1 | # react-refresh 2 | 3 | This package implements the wiring necessary to integrate Fast Refresh into bundlers. Fast Refresh is a feature that lets you edit React components in a running application without losing their state. It is similar to an old feature known as "hot reloading", but Fast Refresh is more reliable and officially supported by React. 4 | 5 | This package is primarily aimed at developers of bundler plugins. If you’re working on one, here is a [rough guide](https://github.com/facebook/react/issues/16604#issuecomment-528663101) for Fast Refresh integration using this package. -------------------------------------------------------------------------------- /modules/react-roblox/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactRoblox" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | Shared = { path = "../shared" } 12 | React = { path = "../react" } 13 | ReactGlobals = { path = "../react-globals" } 14 | ReactReconciler = { path = "../react-reconciler" } 15 | Scheduler = { path = "../scheduler" } 16 | 17 | [dev_dependencies] 18 | JestGlobals = { workspace = true } 19 | Promise = { workspace = true } 20 | -------------------------------------------------------------------------------- /modules/shared/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Shared" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = false 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | SafeFlags = { workspace = true } 12 | ReactGlobals = { path = "../react-globals" } 13 | 14 | [dev_dependencies] 15 | JestGlobals = { workspace = true } 16 | React = { path = "../react" } 17 | Scheduler = { path = "../scheduler" } 18 | ReactNoop = { path = "../react-noop-renderer" } 19 | JestReact = { path = "../jest-react" } 20 | -------------------------------------------------------------------------------- /bin/run-sierpinski-triangle-benchmark.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.RoactAlignment 2 | local RotrieverWorkspace = Packages._Workspace 3 | 4 | local Roact = require(RotrieverWorkspace.React.React) 5 | local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox) 6 | local sierpinskiTriangleBenchmark = require( 7 | RotrieverWorkspace.React.Dev.PerformanceBenchmarks 8 | ).sierpinskiTriangleBenchmark 9 | 10 | local config = {} 11 | if _G.minSamples ~= nil then 12 | config.sampleCount = tonumber(_G.minSamples) 13 | end 14 | 15 | sierpinskiTriangleBenchmark(Roact, ReactRoblox)(config) 16 | wait(1) 17 | -------------------------------------------------------------------------------- /modules/roact-compat/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "RoactCompat" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | React = { path = "../react" } 12 | ReactGlobals = { path = "../react-globals" } 13 | ReactRoblox = { path = "../react-roblox" } 14 | Shared = { path = "../shared" } 15 | 16 | [dev_dependencies] 17 | Roact = "github.com/roblox/roact@1.4.0" 18 | JestGlobals = { workspace = true } 19 | Scheduler = { path = "../scheduler" } 20 | -------------------------------------------------------------------------------- /modules/shared/src/ExecutionEnvironment.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/55cb0b7eeb0e539d89858b8ed69beabf7fe2fb46/packages/shared/ExecutionEnvironment.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | local exports = {} 12 | 13 | exports.canUseDOM = function() 14 | -- ROBLOX deviation START 15 | return false 16 | -- ROBLOX deviation END 17 | end 18 | 19 | return exports 20 | -------------------------------------------------------------------------------- /modules/react-cache/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactCache" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | content_root = "src" 6 | files = ["*", "!**/__tests__/**"] 7 | 8 | [dependencies] 9 | ReactGlobals = { path = "../react-globals" } 10 | Scheduler = { path = "../scheduler" } 11 | Shared = { path = "../shared" } 12 | React = { path = "../react" } 13 | LuauPolyfill = { workspace = true } 14 | 15 | [dev_dependencies] 16 | JestGlobals = { workspace = true } 17 | Promise = { workspace = true } 18 | JestReact = { path = "../jest-react" } 19 | ReactTestRenderer = { path = "../react-test-renderer" } 20 | -------------------------------------------------------------------------------- /modules/react-debug-tools/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactDebugTools" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | 8 | [dependencies] 9 | LuauPolyfill = { workspace = true } 10 | Shared = { path = "../shared" } 11 | ReactGlobals = { path = "../react-globals" } 12 | ReactReconciler = { path = "../react-reconciler" } 13 | 14 | [dev_dependencies] 15 | JestGlobals = { workspace = true } 16 | Promise = { workspace = true } 17 | React = { path = "../react" } 18 | ReactTestRenderer = { path = "../react-test-renderer" } 19 | Scheduler = { path = "../scheduler" } 20 | -------------------------------------------------------------------------------- /modules/shared/src/ReactSharedInternals/IsSomeRendererActing.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/a457e02ae3a2d3903fcf8748380b1cc293a2445e/packages/react/src/IsSomeRendererActing.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | --[[* 13 | * Used by act() to track whether you're inside an act() scope. 14 | ]] 15 | 16 | local IsSomeRendererActing = { 17 | current = false, 18 | } 19 | return IsSomeRendererActing 20 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/MaxInts.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/c5d2fc7127654e43de59fff865b74765a103c4a5/packages/react-reconciler/src/MaxInts.js 3 | -- /** 4 | -- * Copyright (c) Facebook, Inc. and its affiliates. 5 | -- * 6 | -- * This source code is licensed under the MIT license found in the 7 | -- * LICENSE file in the root directory of this source tree. 8 | -- * 9 | -- * @flow 10 | -- */ 11 | 12 | -- // Max 31 bit integer. The max integer size in V8 for 32-bit systems. 13 | -- // Math.pow(2, 30) - 1 14 | -- // 0b111111111111111111111111111111 15 | return { MAX_SIGNED_31_BIT_INT = 1073741823 } 16 | -------------------------------------------------------------------------------- /bin/run-frame-rate-benchmark.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.RoactAlignment 2 | local RotrieverWorkspace = Packages._Workspace 3 | 4 | local Roact = require(RotrieverWorkspace.React.React) 5 | local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox) 6 | local Scheduler = require(RotrieverWorkspace.Scheduler.Scheduler) 7 | local frameRateBenchmark = 8 | require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).frameRateBenchmark 9 | 10 | local config = { 11 | minSamples = 600, 12 | } 13 | if _G.minSamples ~= nil then 14 | config.minSamples = tonumber(_G.minSamples) 15 | end 16 | 17 | frameRateBenchmark(Roact, ReactRoblox, Scheduler)(config) 18 | -------------------------------------------------------------------------------- /bin/run-first-render-benchmark.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.RoactAlignment 2 | local RotrieverWorkspace = Packages._Workspace 3 | 4 | local Roact = require(RotrieverWorkspace.React.React) 5 | local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox) 6 | local Scheduler = require(RotrieverWorkspace.Scheduler.Scheduler) 7 | local firstRenderBenchmark = 8 | require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).firstRenderBenchmark 9 | 10 | local config = { 11 | minSamples = 200, 12 | } 13 | if _G.minSamples ~= nil then 14 | config.minSamples = tonumber(_G.minSamples) 15 | end 16 | 17 | firstRenderBenchmark(Roact, ReactRoblox, Scheduler)(config) 18 | -------------------------------------------------------------------------------- /modules/scheduler/src/SchedulerPriorities.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/00748c53e183952696157088a858352cc77b0010/packages/scheduler/src/SchedulerHostConfig.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | export type PriorityLevel = number 13 | 14 | -- TODO: Use symbols? 15 | return { 16 | NoPriority = 0, 17 | ImmediatePriority = 1, 18 | UserBlockingPriority = 2, 19 | NormalPriority = 3, 20 | LowPriority = 4, 21 | IdlePriority = 5, 22 | } 23 | -------------------------------------------------------------------------------- /modules/react-cache/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # react-cache 4 | 5 | A basic cache for React applications. It also serves as a reference for more 6 | advanced caching implementations. 7 | 8 | This package is meant to be used alongside yet-to-be-released, experimental 9 | React features. It's unlikely to be useful in any other context. 10 | 11 | **Do not use in a real application.** We're publishing this early for 12 | demonstration purposes. 13 | 14 | **Use it at your own risk.** 15 | 16 | # No, Really, It Is Unstable 17 | 18 | The API ~~may~~ will change wildly between versions. 19 | -------------------------------------------------------------------------------- /modules/react-test-renderer/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactTestRenderer" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | React = { path = "../react" } 12 | ReactGlobals = { path = "../react-globals" } 13 | ReactReconciler = { path = "../react-reconciler" } 14 | Scheduler = { path = "../scheduler" } 15 | Shared = { path = "../shared" } 16 | 17 | [dev_dependencies] 18 | Promise = { workspace = true } 19 | JestGlobals = { workspace = true } 20 | ReactRoblox = { path = "../react-roblox" } 21 | JestReact = { path = "../jest-react" } 22 | -------------------------------------------------------------------------------- /modules/scheduler/src/SchedulerHostConfig.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/00748c53e183952696157088a858352cc77b0010/packages/scheduler/src/SchedulerHostConfig.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | -- deviation: In React, this module throws an error and is expected to be 13 | -- replaced via a bundler. In our case, we mock it explicitly when we need to 14 | -- mock it, and return the "default" here 15 | return require(script.Parent.forks["SchedulerHostConfig.default"]) 16 | -------------------------------------------------------------------------------- /modules/shared/src/ReactSharedInternals/ReactCurrentBatchConfig.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/92fcd46cc79bbf45df4ce86b0678dcef3b91078d/packages/react/src/ReactCurrentBatchConfig.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | --[[* 13 | * Keeps track of the current batch's configuration such as how long an update 14 | * should suspend for if it needs to. 15 | ]] 16 | local ReactCurrentBatchConfig = { 17 | transition = 0, 18 | } 19 | 20 | return ReactCurrentBatchConfig 21 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX note: upstream doesn't have a root index.js, we may want to contribute a proper contract upstream 2 | 3 | local Bridge = require(script.bridge) 4 | local Types = require(script.types) 5 | local Backend = require(script.backend) 6 | 7 | export type BackendBridge = Bridge.BackendBridge 8 | export type ComponentFilter = Types.ComponentFilter 9 | export type DevtoolsHook = Backend.DevToolsHook 10 | 11 | return { 12 | constants = require(script.constants), 13 | backend = require(script.backend), 14 | bridge = require(script.bridge), 15 | devtools = require(script.devtools), 16 | hydration = require(script.hydration), 17 | hook = require(script.hook), 18 | utils = require(script.utils), 19 | } 20 | -------------------------------------------------------------------------------- /modules/react-cache/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-cache/index.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | -- ROBLOX deviation START: simplify 11 | -- local Packages --[[ ROBLOX comment: must define Packages module ]] 12 | -- local LuauPolyfill = require(Packages.LuauPolyfill) 13 | -- local Object = LuauPolyfill.Object 14 | -- local exports = {} 15 | -- 16 | -- Object.assign(exports, require(script.src.ReactCacheOld)) 17 | -- return exports 18 | return require(script.ReactCacheOld) 19 | -- ROBLOX deviation END 20 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactTypeOfMode.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/22dc2e42bdc00d87fc19c5e75fc7c0b3fdcdc572/packages/react-reconciler/src/ReactTypeOfMode.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | export type TypeOfMode = number 13 | 14 | return { 15 | NoMode = 0b00000, 16 | StrictMode = 0b00001, 17 | -- TODO: Remove BlockingMode and ConcurrentMode by reading from the root 18 | -- tag instead 19 | BlockingMode = 0b00010, 20 | ConcurrentMode = 0b00100, 21 | ProfileMode = 0b01000, 22 | DebugTracingMode = 0b10000, 23 | } 24 | -------------------------------------------------------------------------------- /modules/jest-react/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/jest-react/index.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | ]] 8 | local Packages = script.Parent 9 | local LuauPolyfill = require(Packages.LuauPolyfill) 10 | local Object = LuauPolyfill.Object 11 | local exports = {} 12 | -- ROBLOX deviation START: extract to variable, fix import and export type 13 | -- Object.assign(exports, require(script.src.JestReact)) 14 | local jestReactModule = Object.assign(exports, require(script.JestReact)) 15 | return exports :: typeof(exports) & typeof(jestReactModule) 16 | -- ROBLOX deviation END 17 | -------------------------------------------------------------------------------- /docs/bench.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | 13 |
14 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactHookEffectTags.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/16654436039dd8f16a63928e71081c7745872e8f/packages/react-reconciler/src/ReactHookEffectTags.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | export type HookFlags = number 13 | 14 | return { 15 | --[[ ]] 16 | NoFlags = 0b000, 17 | 18 | -- Represents whether effect should fire. 19 | --[[ ]] 20 | HasEffect = 0b001, 21 | 22 | -- Represents the phase in which the effect (not the clean-up) fires. 23 | --[[ ]] 24 | Layout = 0b010, 25 | --[[ ]] 26 | Passive = 0b100, 27 | } 28 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactDevtoolsShared" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | Shared = { path = "../shared" } 12 | React = { path = "../react" } 13 | ReactReconciler = { path = "../react-reconciler" } 14 | ReactRoblox = { path = "../react-roblox" } 15 | ReactIs = { path = "../react-is" } 16 | ReactDebugTools = { path = "../react-debug-tools" } 17 | ReactGlobals = { path = "../react-globals" } 18 | 19 | [dev_dependencies] 20 | JestGlobals = { workspace = true } 21 | Scheduler = { path = "../scheduler" } 22 | Promise = { workspace = true } 23 | ReactTestRenderer = { path = "../react-test-renderer" } 24 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberTransition.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/ddd1faa1972b614dfbfae205f2aa4a6c0b39a759/packages/react-reconciler/src/ReactFiberTransition.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | local Packages = script.Parent.Parent 13 | 14 | local ReactSharedInternals = require(Packages.Shared).ReactSharedInternals 15 | 16 | local ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig 17 | 18 | return { 19 | NoTransition = 0, 20 | requestCurrentTransition = function(): number 21 | return ReactCurrentBatchConfig.transition 22 | end, 23 | } 24 | -------------------------------------------------------------------------------- /modules/react/README.md: -------------------------------------------------------------------------------- 1 | # react 2 | A Roblox Lua port of the `react` package from React. This is the main React package 3 | 4 | Status: 🔨 Port in progress 5 | 6 | Source: https://github.com/facebook/react/tree/master/packages/react 7 | 8 | --- 9 | 10 | ### ✏️ Notes 11 | * Includes some relatively significant deviations in `ReactBaseClasses.lua` to accommodate Lua idioms for objects, constructors, and inheritance. 12 | * Exports React-related type definitions from DefinitelyTyped, like `ReactElement`, used by Apollo GraphQL and jest. These are the types users of the library should prefer to use. 13 | * Exports React-related type definitions that are built-in to flowtype. Only used by the framework internals itself, and some sibling projects like VirtualizedList. It is not recommended to use these types in application code. 14 | 15 | ### ❌ Excluded 16 | -------------------------------------------------------------------------------- /modules/react/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "React" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | ReactGlobals = { path = "../react-globals" } 11 | LuauPolyfill = { workspace = true } 12 | Shared = { path = "../shared" } 13 | 14 | [dev_dependencies] 15 | PerformanceBenchmarks = { git = "https://github.com/Roblox/roact-performance-benchmarks", rev = "main" } 16 | JestReact = { path = "../jest-react" } 17 | JestGlobals = { workspace = true } 18 | Promise = { workspace = true } 19 | ReactCache = { path = "../react-cache" } 20 | ReactNoopRenderer = { path = "../react-noop-renderer" } 21 | ReactRoblox = { path = "../react-roblox" } 22 | ReactTestRenderer = { path = "../react-test-renderer" } 23 | Scheduler = { path = "../scheduler" } 24 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberErrorDialog.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/56e9feead0f91075ba0a4f725c9e4e343bca1c67/packages/react-reconciler/src/ReactFiberErrorDialog.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | -- This module is forked in different environments. 13 | -- By default, return `true` to log errors to the console. 14 | -- Forks can return `false` if this isn't desirable. 15 | local exports = {} 16 | 17 | exports.showErrorDialog = function(boundary, errorInfo): boolean 18 | -- ROBLOX TODO: we may replace this with something that sends telemetry LUAFDN-222 19 | return true 20 | end 21 | 22 | return exports 23 | -------------------------------------------------------------------------------- /modules/react-devtools-extensions/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactDevtoolsExtensions" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | Shared = { path = "../shared" } 12 | React = { path = "../react" } 13 | ReactDevtoolsShared = { path = "../react-devtools-shared" } 14 | ReactGlobals = { path = "../react-globals" } 15 | ReactRoblox = { path = "../react-roblox" } 16 | 17 | [dev_dependencies] 18 | JestGlobals = { workspace = true } 19 | # FIXME: Version 0.2.4 breaks the test "can connect to a Roact tree and inspect 20 | # its children and child branch nodes" in devtools-integration.roblox.spec.lua 21 | DeveloperTools = "github.com/Roblox/developer-tools@=0.2.3" 22 | ReactTestRenderer = { path = "../react-test-renderer" } 23 | -------------------------------------------------------------------------------- /modules/react-roblox/src/client/roblox/__tests__/waitForEvents.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the MIT License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/MIT 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | 17 | -- Defers remaining execution until after deferred lua events have run 18 | return function() 19 | task.defer(coroutine.running()) 20 | coroutine.yield() 21 | end 22 | -------------------------------------------------------------------------------- /modules/shared/src/PropMarkers/Tag.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | --[[ 16 | Special value for assigning tags to roblox instances via Roact 17 | ]] 18 | local Symbol = require(script.Parent.Parent["Symbol.roblox"]) 19 | 20 | local Tag = Symbol.named("RobloxTag") 21 | 22 | return Tag 23 | -------------------------------------------------------------------------------- /docs/images/roblox.svg: -------------------------------------------------------------------------------- 1 | RobloxRoblox -------------------------------------------------------------------------------- /modules/shared/src/objectIs.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/6faf6f5eb1705eef39a1d762d6ee381930f36775/packages/shared/objectIs.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | --[[* 13 | * inlined Object.is polyfill to avoid requiring consumers ship their own 14 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is 15 | ]] 16 | local function is(x: any, y: any): boolean 17 | return x == y and (x ~= 0 or 1 / x == 1 / y) or x ~= x and y ~= y -- eslint-disable-line no-self-compare 18 | end 19 | 20 | -- deviation: Object isn't a global in lua, so `Object.is` will never exist 21 | local objectIs = is 22 | 23 | return objectIs 24 | -------------------------------------------------------------------------------- /modules/react-reconciler/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ReactReconciler" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | publish = true 6 | content_root = "src" 7 | files = ["*", "!**/__tests__/**"] 8 | 9 | [dependencies] 10 | LuauPolyfill = { workspace = true } 11 | Promise = { workspace = true } 12 | SafeFlags = { workspace = true } 13 | ReactGlobals = { path = "../react-globals" } 14 | Scheduler = { path = "../scheduler" } 15 | Shared = { path = "../shared" } 16 | React = { path = "../react" } 17 | 18 | [dev_dependencies] 19 | JestGlobals = { workspace = true } 20 | JestReact = { path = "../jest-react" } 21 | ReactCache = { path = "../react-cache" } 22 | ReactNoopRenderer = { path = "../react-noop-renderer" } 23 | ReactTestRenderer = { path = "../react-test-renderer" } 24 | ReactRoblox = { path = "../react-roblox" } 25 | ReactDevtoolsShared = { path = "../react-devtools-shared" } 26 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/backend/NativeStyleEditor/types.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/backend/NativeStyleEditor/types.js 2 | -- /** 3 | -- * Copyright (c) Facebook, Inc. and its affiliates. 4 | -- * 5 | -- * This source code is licensed under the MIT license found in the 6 | -- * LICENSE file in the root directory of this source tree. 7 | -- * 8 | -- * @flow 9 | -- */ 10 | type Object = { [string]: any } 11 | 12 | export type BoxStyle = { bottom: number, left: number, right: number, top: number } 13 | 14 | export type Layout = { 15 | x: number, 16 | y: number, 17 | width: number, 18 | height: number, 19 | left: number, 20 | top: number, 21 | margin: BoxStyle, 22 | padding: BoxStyle, 23 | } 24 | 25 | export type Style = Object 26 | 27 | export type StyleAndLayout = { id: number, style: Style | nil, layout: Layout | nil } 28 | 29 | return {} 30 | -------------------------------------------------------------------------------- /modules/react-debug-tools/src/ReactDebugTools.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/src/ReactDebugTools.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | local exports = {} 11 | local reactDebugHooksModule = require(script.Parent.ReactDebugHooks) 12 | -- ROBLOX deviation START: add re-exporting of types 13 | export type HooksNode = reactDebugHooksModule.HooksNode 14 | export type HooksTree = reactDebugHooksModule.HooksTree 15 | -- ROBLOX deviation END 16 | local inspectHooks = reactDebugHooksModule.inspectHooks 17 | local inspectHooksOfFiber = reactDebugHooksModule.inspectHooksOfFiber 18 | exports.inspectHooks = inspectHooks 19 | exports.inspectHooksOfFiber = inspectHooksOfFiber 20 | return exports 21 | -------------------------------------------------------------------------------- /modules/react-debug-tools/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.2/packages/react-debug-tools/index.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | ]] 8 | -- ROBLOX deviation START: simplify and re-export types 9 | -- local Packages --[[ ROBLOX comment: must define Packages module ]] 10 | -- local LuauPolyfill = require(Packages.LuauPolyfill) 11 | -- local Object = LuauPolyfill.Object 12 | -- local exports = {} 13 | -- Object.assign(exports, require(script.src.ReactDebugTools)) 14 | -- return exports 15 | local reactDebugToolsModule = require(script.ReactDebugTools) 16 | export type HooksNode = reactDebugToolsModule.HooksNode 17 | export type HooksTree = reactDebugToolsModule.HooksTree 18 | return reactDebugToolsModule 19 | -- ROBLOX deviation END 20 | -------------------------------------------------------------------------------- /modules/shared/src/ReactSharedInternals/ReactCurrentOwner.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/376d5c1b5aa17724c5fea9412f8fcde14a7b23f1/packages/react/src/ReactCurrentOwner.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | --[[* 13 | * Keeps track of the current owner. 14 | * 15 | * The current owner is the component who should own any components that are 16 | * currently being constructed. 17 | ]] 18 | local ReactCurrentOwner = { 19 | --[[* 20 | * @internal 21 | * @type {ReactComponent} 22 | ]] 23 | -- ROBLOX deviation START: upstream types this as Fiber, but that would incur a circular dependency between reconciler and shared 24 | current = nil :: any, 25 | -- ROBLOX deviation END 26 | } 27 | 28 | return ReactCurrentOwner 29 | -------------------------------------------------------------------------------- /WorkspaceStatic/jest.config.lua: -------------------------------------------------------------------------------- 1 | local Workspace = script.Parent 2 | 3 | -- In case we need to specify a custom testSetupFile for a project, we need to do that in in a separate jest.config.lua file that's in the project's root folder. 4 | -- Therefore we specify the project here and provide it to the "projects" field in this config file. 5 | -- We also need to add the project to the "testPathIgnorePatterns" field so that Jest doesn't try to run the project's tests again. 6 | local projectsWithCustomJestConfig = { 7 | Workspace.ReactDevtoolsShared.ReactDevtoolsShared, 8 | } 9 | local testPathIgnorePatterns = {} 10 | local allProjects = { Workspace } 11 | 12 | for _, project in projectsWithCustomJestConfig do 13 | table.insert(testPathIgnorePatterns, tostring(project)) 14 | table.insert(allProjects, project) 15 | end 16 | 17 | return { 18 | setupFilesAfterEnv = { Workspace.jest.testSetupFile }, 19 | projects = allProjects, 20 | testMatch = { 21 | "**/__tests__/*.(spec|test)", 22 | }, 23 | testPathIgnorePatterns = testPathIgnorePatterns, 24 | } 25 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/backend/console.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/backend/console.js 2 | -- /** 3 | -- * Copyright (c) Facebook, Inc. and its affiliates. 4 | -- * 5 | -- * This source code is licensed under the MIT license found in the 6 | -- * LICENSE file in the root directory of this source tree. 7 | -- * 8 | -- * @flow 9 | -- */ 10 | 11 | local Types = require(script.Parent.types) 12 | type ReactRenderer = Types.ReactRenderer 13 | 14 | local exports = {} 15 | 16 | -- ROBLOX FIXME: Stub for now 17 | function exports.patch(_object: { 18 | appendComponentStack: boolean, 19 | breakOnConsoleErrors: boolean, 20 | }): () end 21 | 22 | function exports.unpatch(): () end 23 | 24 | function exports.error(...) 25 | error(...) 26 | end 27 | 28 | function exports.warn(...) 29 | warn(...) 30 | end 31 | 32 | function exports.log(...) 33 | print(...) 34 | end 35 | 36 | function exports.registerRenderer(_renderer: ReactRenderer): () end 37 | 38 | return exports 39 | -------------------------------------------------------------------------------- /selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | [config] 4 | empty_if = { comments_count = true } 5 | unused_variable = { ignore_pattern = "result|ok|^_" } 6 | # this comes up when translating nested try/finally scenarios 7 | shadowing = { ignore_pattern = "result|ok|^_" } 8 | # feature request for this config: https://github.com/Kampfkarren/selene/issues/181 9 | # global_usage = { ignore_pattern = "^__" } 10 | 11 | [rules] 12 | # remove this once the feature request here is implemented: https://github.com/Kampfkarren/selene/issues/181 13 | global_usage = "allow" 14 | unused_variable = "allow" 15 | # remove when the Luau type narrowing issues (and the workarounds) are resolved 16 | shadowing = "allow" 17 | 18 | # remove when this issue is fixed: https://github.com/Kampfkarren/selene/issues/179 19 | if_same_then_else = "allow" 20 | 21 | # Many of the tests and examples create instances that don't exist. That's okay 22 | # ignore them. 23 | roblox_incorrect_roact_usage = "allow" 24 | 25 | # Doesn't apply to roact-alignment 26 | roblox_internal_custom_color = "allow" 27 | -------------------------------------------------------------------------------- /modules/react-devtools-core/src/setupAttachHook.lua: -------------------------------------------------------------------------------- 1 | -- Based on: https://github.com/facebook/react/blob/8e5adfbd7e605bda9c5e96c10e015b3dc0df688e/packages/react-devtools-extensions/src/renderer.js 2 | 3 | --[[ 4 | In order to support launching DevTools after the client has started, the 5 | renderer needs to be injected before any other scripts. The hook will look 6 | for the presence of a global __REACT_DEVTOOLS_ATTACH__ and attach an 7 | injected renderer early. 8 | ]] 9 | 10 | local Packages = script.Parent.Parent 11 | local ReactGlobals = require(Packages.ReactGlobals) 12 | local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared) 13 | 14 | local attach 15 | ReactGlobals.__REACT_DEVTOOLS_ATTACH__ = function(...) 16 | -- Importing the renderer module immediately causes React to initialize 17 | -- prematurely and error. We import lazily here to avoid this, because 18 | -- React will be initialized by the time this function is called. 19 | if attach == nil then 20 | attach = ReactDevtoolsShared.backend.getRendererLazy().attach 21 | end 22 | 23 | return attach(...) 24 | end 25 | 26 | return nil 27 | -------------------------------------------------------------------------------- /rotriever.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | version = "17.3.7" 3 | members = ["modules/*"] 4 | authors = [ 5 | "Paul Doyle ", 6 | "Matt Hargett ", 7 | "Max Mines ", 8 | "Carlo Conte ", 9 | "Henry Allen ", 10 | "Olivier Trepanier ", 11 | "Hugh Collins ", 12 | ] 13 | 14 | [config] 15 | registry_index = true 16 | 17 | [workspace.dependencies] 18 | Promise = "github.com/roblox/roblox-lua-promise@3.3.0" 19 | Jest = "github.com/roblox/jest-roblox@3" 20 | JestGlobals = "github.com/roblox/jest-roblox@3" 21 | LuauPolyfill = "github.com/roblox/luau-polyfill@1" 22 | SafeFlags = "github.com/Roblox/safe-flags-internal@0.1.1" 23 | 24 | # Patch dependencies for the example app 25 | [config.patch."https://github.com/roblox/roact"] 26 | Roact = { path = "modules/roact-compat" } 27 | 28 | [config.patch."https://github.com/roblox/roact-alignment"] 29 | React = { path = "modules/react" } 30 | ReactRoblox = { path = "modules/react-roblox" } 31 | ReactIs = { path = "modules/react-is" } 32 | RoactCompat = { path = "modules/roact-compat" } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 Roblox Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for file in *.js 4 | do 5 | mv "$file" "${file/.js/.lua}" 6 | done 7 | 8 | for file in *.lua 9 | do 10 | sed -i '' -e "s/\/\*/--[[/g" "$file" 11 | sed -i '' -e "s/\*\//]]/g" "$file" 12 | sed -i '' -e "s/\/\//--/g" "$file" 13 | sed -i '' -e "s/;$//g" "$file" 14 | sed -i '' -e "s/let\ /local\ /g" "$file" 15 | sed -i '' -e "s/const\ /local\ /g" "$file" 16 | sed -i '' -e "s/===/==/g" "$file" 17 | sed -i '' -e "s/!==/!=/g" "$file" 18 | sed -i '' -e "s/!=/~=/g" "$file" 19 | sed -i '' -e "s/&&/and/g" "$file" 20 | sed -i '' -e "s/||/or/g" "$file" 21 | sed -i '' -e "s/\ null/\ nil/g" "$file" 22 | sed -i '' -e "s/^’\s}$/end/g" "$file" 23 | sed -i '' -e "s/\s\}$/\ end/g" "$file" 24 | sed -i '' -e "s/if\ (/if\ /g" "$file" 25 | sed -i '' -e "s/export\ function\ /exports./g" "$file" 26 | sed -i '' -e "s/)\ {$/)/g" "$file" 27 | sed -i '' -e "s/^}$/end/g" "$file" 28 | sed -i '' -e "s/'object'/'table’'/g" "$file" 29 | sed -i '' -e "s/import\ /local\ /g" "$file" 30 | sed -i '' -e "s/\ from\ '/\ =\ require(Packages./g" "$file" 31 | # sed -i '' -E -e "s/(typeof\s+)(\w+)/typeof($2)/g" "$file" 32 | done 33 | -------------------------------------------------------------------------------- /modules/react-noop-renderer/README.md: -------------------------------------------------------------------------------- 1 | # ReactNoopRenderer 2 | A Roblox Lua port of the react-noop-renderer package from React. 3 | 4 | Original source: https://github.com/facebook/react/tree/master/packages/react-noop-renderer 5 | 6 | ## Status 7 | 8 | ### Ported 9 | Files: 10 | * `src/createReactNoop.js` -> `src/createReactNoop.lua` 11 | * `src/ReactNoop.js` -> `src/ReactNoop.lua` 12 | 13 | The entire implementation of the NoopRenderer, plus an entry point that exports all the relevant members. 14 | 15 | ### Not Ported 16 | Files: 17 | * `src/ReactNoopFlightClient.js` 18 | * `src/ReactNoopFlightServer.js` 19 | * `src/ReactNoopServer.js` 20 | 21 | Serves additional entry points that enable use with the `react-client` and `react-server` packages. We can revisit this if we port those 22 | 23 | Files: 24 | * `src/ReactNoopPersistent.js` 25 | 26 | A version of the NoopRenderer that's created with `useMutation = false`. If this is needed at some point, it'll be trivial to port. 27 | 28 | ### Intentional Deviations 29 | Currently, we're not supporting JSX or an equivalent markup syntax. Select parts of `createReactNoop.lua` are only partially translated and are commented out. -------------------------------------------------------------------------------- /modules/roact-compat/src/warnOnce.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the MIT License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/MIT 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | 17 | local Packages = script.Parent.Parent 18 | local console = require(Packages.Shared).console 19 | 20 | local warnedAbout = {} 21 | 22 | local function warnOnce(name: string, message: string) 23 | if not warnedAbout[name] then 24 | console.warn( 25 | "The legacy Roact API '%s' is deprecated, and will be removed " 26 | .. "in a future release.\n\n%s", 27 | name, 28 | message 29 | ) 30 | end 31 | warnedAbout[name] = true 32 | end 33 | 34 | return warnOnce 35 | -------------------------------------------------------------------------------- /modules/react-shallow-renderer/README.md: -------------------------------------------------------------------------------- 1 | # react-shallow-renderer 2 | A Roblox Lua port of the react-shallow-reconciler package re-exported from React. Used to run integration-level tests in `react-dom`. Will likely be useful with the Roblox renderer when it's more thoroughly integrated. 3 | 4 | Status: ✔️ Ported 5 | 6 | Source: https://github.com/NMinhNguyen/react-shallow-renderer/ 7 | 8 | --- 9 | 10 | ### ✏️ Notes 11 | * Includes some minor adjustments to the shallow renderer interface to better facilitate its translation 12 | * Most member functions use `:` function calls instead of `.` to avoid having to explicitly bind them to self. We could revisit this to align it more directly. 13 | * Implementation for `useState` returns multiple values instead of an array. This will likely be carried over to the full implementation in the reconciler as well 14 | * Context narrowing via `contextTypes` is only available on class components (since functions cannot have fields in Luau) 15 | * PropTypes are unsupported for now 16 | 17 | ### ❌ Excluded 18 | 19 | ``` 20 | src/__tests__/ReactShallowRendererMemo-test.js 21 | ``` 22 | 23 | Small test that didn't seem critical to port right now. -------------------------------------------------------------------------------- /modules/react/src/None.roblox.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- code derived from https://github.com/Roblox/roact/blob/master/src/None.lua 3 | --[[ 4 | * Copyright (c) Roblox Corporation. All rights reserved. 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | ]] 17 | 18 | local Packages = script.Parent.Parent 19 | local LuauPolyfill = require(Packages.LuauPolyfill) 20 | 21 | -- Roact uses `Object.assign` internally to assign new state values; the same 22 | -- None value should give us the proper semantics. We can re-export this value 23 | -- as React.None for easy use, and to mirror Roact.None in legacy Roact. 24 | return LuauPolyfill.Object.None 25 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/__tests__/profilingUtils.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/profilingUtils-test.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | 11 | local Packages = script.Parent.Parent.Parent 12 | local JestGlobals = require(Packages.Dev.JestGlobals) 13 | local jestExpect = JestGlobals.expect 14 | local describe = JestGlobals.describe 15 | local it = JestGlobals.it 16 | local beforeEach = JestGlobals.beforeEach 17 | 18 | describe("profiling utils", function() 19 | local utils 20 | beforeEach(function() 21 | utils = require(script.Parent.Parent.devtools.views.Profiler.utils) 22 | end) 23 | it("should throw if importing older/unsupported data", function() 24 | jestExpect(function() 25 | return utils.prepareProfilingDataFrontendFromExport({ 26 | version = 0, 27 | dataForRoots = {}, 28 | }) 29 | end).toThrow('Unsupported profiler export version "0"') 30 | end) 31 | end) 32 | -------------------------------------------------------------------------------- /modules/react-test-renderer/README.md: -------------------------------------------------------------------------------- 1 | # react-test-renderer 2 | A Roblox Lua port of the react-test-reconciler package re-exported from React. Used to run integration-level tests in `react-dom`. Will likely be useful with the Roblox renderer when it's more thoroughly integrated. 3 | 4 | Status: 🔨 Partially Ported 5 | 6 | Source: https://github.com/facebook/react/tree/master/packages/react-test-renderer 7 | 8 | --- 9 | 10 | ### ✏️ Notes 11 | * For now, only includes configs for testing with the noop renderer, 12 | 13 | ### ❌ Excluded 14 | 15 | ``` 16 | src/__tests__/ReactTestRenderer-test.internal.js 17 | src/__tests__/ReactTestRenderer-test.js 18 | src/__tests__/ReactTestRendererAct-test.js 19 | src/__tests__/ReactTestRendererAsync-test.js 20 | src/__tests__/ReactTestRendererTraversal-test.js 21 | src/ReactTestRenderer.js 22 | ``` 23 | 24 | Actual test renderer impl is excluded for now. 25 | 26 | ``` 27 | src/__tests__/ReactShallowRenderer-test.js 28 | src/__tests__/ReactShallowRendererHooks-test.js 29 | src/__tests__/ReactShallowRendererMemo-test.js 30 | ``` 31 | 32 | These tests appear to be copied from the shallow renderer, which react depends upon and re-exports via `react-test-renderer` -------------------------------------------------------------------------------- /modules/shared/src/ReactFiberHostConfig/WithNoTestSelectors.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/3cde22a84e246fc5361f038bf0c23405b2572c22/packages/react-reconciler/src/ReactFiberHostConfigWithNoTestSelectors.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | 11 | local invariant = require(script.Parent.Parent.invariant) 12 | 13 | -- Renderers that don't support test selectors 14 | -- can re-export everything from this module. 15 | 16 | local function shim(...) 17 | invariant( 18 | false, 19 | "The current renderer does not support test selectors. " 20 | .. "This error is likely caused by a bug in React. " 21 | .. "Please file an issue." 22 | ) 23 | end 24 | 25 | -- Test selectors (when unsupported) 26 | return { 27 | supportsTestSelectors = false, 28 | findFiberRoot = shim, 29 | getBoundingRect = shim, 30 | getTextContent = shim, 31 | isHiddenSubtree = shim, 32 | matchAccessibilityRole = shim, 33 | setFocusIfFocusable = shim, 34 | setupIntersectionObserver = shim, 35 | } 36 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactCapturedValue.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/56e9feead0f91075ba0a4f725c9e4e343bca1c67/packages/react-reconciler/src/ReactCapturedValue.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | local ReactInternalTypes = require(script.Parent.ReactInternalTypes) 13 | type Fiber = ReactInternalTypes.Fiber 14 | 15 | local getStackByFiberInDevAndProd = 16 | require(script.Parent.ReactFiberComponentStack).getStackByFiberInDevAndProd 17 | 18 | export type CapturedValue = { 19 | value: T, 20 | source: Fiber | nil, 21 | stack: string | nil, 22 | } 23 | 24 | local exports = {} 25 | 26 | exports.createCapturedValue = function(value: T, source: Fiber | nil): CapturedValue 27 | -- If the value is an error, call this function immediately after it is thrown 28 | -- so the stack is accurate. 29 | return { 30 | value = value, 31 | source = source, 32 | stack = getStackByFiberInDevAndProd(source), 33 | } 34 | end 35 | 36 | return exports 37 | -------------------------------------------------------------------------------- /modules/roact-compat/src/createFragment.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the MIT License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/MIT 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | local Packages = script.Parent.Parent 17 | local ReactGlobals = require(Packages.ReactGlobals) 18 | local React = require(Packages.React) 19 | 20 | local warnOnce = require(script.Parent.warnOnce) 21 | 22 | return function(elements) 23 | if ReactGlobals.__DEV__ and ReactGlobals.__COMPAT_WARNINGS__ then 24 | warnOnce( 25 | "createFragment", 26 | "Please instead use:\n\tReact.createElement(React.Fragment, ...)" 27 | ) 28 | end 29 | return React.createElement(React.Fragment, nil, elements) 30 | end 31 | -------------------------------------------------------------------------------- /modules/shared/src/__tests__/isValidElementType.roblox.spec.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.Parent.Parent 2 | local JestGlobals = require(Packages.Dev.JestGlobals) 3 | local jestExpect = JestGlobals.expect 4 | local describe = JestGlobals.describe 5 | local it = JestGlobals.it 6 | 7 | local isValidElementType = require(Packages.Shared).isValidElementType 8 | local ReactSymbols = require(Packages.Shared).ReactSymbols 9 | local element 10 | 11 | describe("accept element primitives", function() 12 | it("from strings", function() 13 | element = "TextLabel" 14 | jestExpect(isValidElementType(element)).toBe(true) 15 | end) 16 | 17 | it("from functions", function() 18 | element = function() end 19 | jestExpect(isValidElementType(element)).toBe(true) 20 | end) 21 | 22 | it("from tables", function() 23 | element = {} 24 | element["$$typeof"] = ReactSymbols.REACT_CONTEXT_TYPE 25 | jestExpect(isValidElementType(element)).toBe(true) 26 | end) 27 | end) 28 | 29 | describe("does not accept", function() 30 | it("REACT_ELEMENT_TYPE", function() 31 | element = {} 32 | element["$$typeof"] = ReactSymbols.REACT_ELEMENT_TYPE 33 | jestExpect(isValidElementType(element)).toBe(false) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /modules/roact-compat/src/Portal.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the MIT License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/MIT 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | 17 | local Packages = script.Parent.Parent 18 | local ReactRoblox = require(Packages.ReactRoblox) 19 | local ReactGlobals = require(Packages.ReactGlobals) 20 | 21 | local warnOnce = require(script.Parent.warnOnce) 22 | 23 | local function PortalComponent(props) 24 | if ReactGlobals.__DEV__ and ReactGlobals.__COMPAT_WARNINGS__ then 25 | warnOnce("Roact.Portal", "Please use the createPortal API on ReactRoblox instead") 26 | end 27 | return ReactRoblox.createPortal(props.children, props.target) 28 | end 29 | 30 | return PortalComponent 31 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactFiberRoot.roblox.spec.lua: -------------------------------------------------------------------------------- 1 | -- awaiting pull request: https://github.com/facebook/react/pull/20155 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @emails react-core 9 | * @jest-environment node 10 | ]] 11 | 12 | local Packages = script.Parent.Parent.Parent 13 | local JestGlobals = require(Packages.Dev.JestGlobals) 14 | local jestExpect = JestGlobals.expect 15 | local jest = JestGlobals.jest 16 | local beforeEach = JestGlobals.beforeEach 17 | local it = JestGlobals.it 18 | 19 | local ReactFiberRoot 20 | local ReactRootTags 21 | 22 | beforeEach(function() 23 | jest.resetModules() 24 | 25 | ReactFiberRoot = require(script.Parent.Parent["ReactFiberRoot.new"]) 26 | ReactRootTags = require(script.Parent.Parent.ReactRootTags) 27 | end) 28 | 29 | it("should properly initialize a fiber created with createFiberRoot", function() 30 | local fiberRoot = 31 | ReactFiberRoot.createFiberRoot({}, ReactRootTags.BlockingRoot, false) 32 | 33 | jestExpect(fiberRoot.current).toBeDefined() 34 | jestExpect(fiberRoot.current.updateQueue).toBeDefined() 35 | end) 36 | -------------------------------------------------------------------------------- /modules/shared/src/ReactFiberHostConfig/WithNoPersistence.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/c5d2fc7127654e43de59fff865b74765a103c4a5/packages/react-reconciler/src/ReactFiberHostConfigWithNoPersistence.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | 11 | local invariant = require(script.Parent.Parent.invariant) 12 | 13 | -- Renderers that don't support persistence 14 | -- can re-export everything from this module. 15 | 16 | local function shim(...) 17 | invariant( 18 | false, 19 | "The current renderer does not support persistence. " 20 | .. "This error is likely caused by a bug in React. " 21 | .. "Please file an issue." 22 | ) 23 | end 24 | 25 | -- Persistence (when unsupported) 26 | return { 27 | supportsPersistence = false, 28 | cloneInstance = shim, 29 | cloneFundamentalInstance = shim, 30 | createContainerChildSet = shim, 31 | appendChildToContainerChildSet = shim, 32 | finalizeContainerChildren = shim, 33 | replaceContainerChildren = shim, 34 | cloneHiddenInstance = shim, 35 | cloneHiddenTextInstance = shim, 36 | } 37 | -------------------------------------------------------------------------------- /WorkspaceStatic/jest/testSetupFile.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX note: no upstream 2 | -- loosely based on https://github.com/facebook/react/blob/9abc2785cb070148d64fae81e523246b90b92016/scripts/jest/setupTests.js 3 | -- in a way that we use this file to extend jestExpect with custom matchers 4 | 5 | local Packages = script.Parent.Parent.TestRunner 6 | local JestGlobals = require(Packages.Dev.JestGlobals) 7 | local jestExpect = JestGlobals.expect 8 | 9 | local InteractionTracingMatchers = 10 | require(script.Parent.matchers.interactionTracingMatchers) 11 | 12 | jestExpect.extend(require(script.Parent.matchers.reactTestMatchers)) 13 | jestExpect.extend({ 14 | toErrorDev = require(script.Parent.matchers.toErrorDev), 15 | toWarnDev = require(script.Parent.matchers.toWarnDev), 16 | toLogDev = require(script.Parent.matchers.toLogDev), 17 | toContainNoInteractions = InteractionTracingMatchers.toContainNoInteractions, 18 | toHaveBeenLastNotifiedOfInteraction = InteractionTracingMatchers.toHaveBeenLastNotifiedOfInteraction, 19 | toHaveBeenLastNotifiedOfWork = InteractionTracingMatchers.toHaveBeenLastNotifiedOfWork, 20 | toMatchInteraction = InteractionTracingMatchers.toMatchInteraction, 21 | toMatchInteractions = InteractionTracingMatchers.toMatchInteractions, 22 | }) 23 | 24 | _G.jest = true 25 | -------------------------------------------------------------------------------- /modules/react/src/ReactMutableSource.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/142d4f1c00c66f3d728177082dbc027fd6335115/packages/react/src/ReactMutableSource.js 3 | -- [[ 4 | -- * Copyright (c) Facebook, Inc. and its affiliates. 5 | -- * 6 | -- * This source code is licensed under the MIT license found in the 7 | -- * LICENSE file in the root directory of this source tree. 8 | -- * 9 | -- * @flow 10 | -- ]] 11 | 12 | local Packages = script.Parent.Parent 13 | local ReactGlobals = require(Packages.ReactGlobals) 14 | local ReactTypes = require(Packages.Shared) 15 | type MutableSourceGetVersionFn = ReactTypes.MutableSourceGetVersionFn 16 | type MutableSource = ReactTypes.MutableSource 17 | 18 | local function createMutableSource( 19 | source: Source, 20 | getVersion: MutableSourceGetVersionFn 21 | ): MutableSource 22 | local mutableSource: MutableSource = { 23 | _getVersion = getVersion, 24 | _source = source, 25 | _workInProgressVersionPrimary = nil, 26 | _workInProgressVersionSecondary = nil, 27 | } 28 | 29 | if ReactGlobals.__DEV__ then 30 | mutableSource._currentPrimaryRenderer = nil 31 | mutableSource._currentSecondaryRenderer = nil 32 | end 33 | 34 | return mutableSource 35 | end 36 | 37 | return createMutableSource 38 | -------------------------------------------------------------------------------- /modules/roact-compat/src/setGlobalConfig.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the MIT License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/MIT 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | 17 | local Packages = script.Parent.Parent 18 | local ReactGlobals = require(Packages.ReactGlobals) 19 | local warnOnce = require(script.Parent.warnOnce) 20 | 21 | return function(_config) 22 | if ReactGlobals.__DEV__ and ReactGlobals.__COMPAT_WARNINGS__ then 23 | warnOnce( 24 | "setGlobalConfig", 25 | "Roact 17 uses a `ReactGlobals.__DEV__` flag to enable development behavior. " 26 | .. "If you're seeing this warning, you already have it enabled. " 27 | .. "Please remove any redundant uses of `setGlobalConfig`." 28 | ) 29 | end 30 | -- No equivalent behavior can be applied here 31 | end 32 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactPortal.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/142d4f1c00c66f3d728177082dbc027fd6335115/packages/react-reconciler/src/ReactPortal.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | 11 | local Packages = script.Parent.Parent 12 | 13 | local REACT_PORTAL_TYPE = require(Packages.Shared).ReactSymbols.REACT_PORTAL_TYPE 14 | 15 | local ReactTypes = require(Packages.Shared) 16 | type ReactNodeList = ReactTypes.ReactNodeList 17 | type ReactPortal = ReactTypes.ReactPortal 18 | 19 | local function createPortal( 20 | children: ReactNodeList, 21 | containerInfo: any, 22 | -- TODO: figure out the API for cross-renderer implementation. 23 | implementation: any, 24 | key: string? 25 | ): ReactPortal 26 | if key ~= nil then 27 | key = tostring(key) 28 | end 29 | return { 30 | -- This tag allow us to uniquely identify this as a React Portal 31 | ["$$typeof"] = REACT_PORTAL_TYPE, 32 | key = key, 33 | children = children, 34 | containerInfo = containerInfo, 35 | implementation = implementation, 36 | } 37 | end 38 | 39 | return { 40 | createPortal = createPortal, 41 | } 42 | -------------------------------------------------------------------------------- /modules/react-telemetry/src/reportCounter.lua: -------------------------------------------------------------------------------- 1 | local TelemetryService = game:GetService("TelemetryService") 2 | 3 | local Packages = script.Parent.Parent 4 | local SafeFlags = require(Packages.SafeFlags) 5 | local ReactGlobals = require(Packages.ReactGlobals) 6 | 7 | local FFlagReactTelemetryEnabled = 8 | SafeFlags.createGetFFlag("ReactTelemetryEnabled", false)() 9 | 10 | export type CounterOptions = { 11 | eventName: string, 12 | lastUpdated: { number }, 13 | description: string, 14 | links: string, 15 | customFields: { [string]: any }?, 16 | value: number?, 17 | } 18 | 19 | local function reportCounter(options: CounterOptions) 20 | if not FFlagReactTelemetryEnabled then 21 | return 22 | end 23 | 24 | local config = { 25 | eventName = options.eventName, 26 | backends = { "RobloxTelemetryCounter" }, 27 | lastUpdated = options.lastUpdated, 28 | description = options.description, 29 | links = options.links, 30 | } 31 | 32 | local data = { 33 | customFields = options.customFields or {}, 34 | } 35 | 36 | local success, result = 37 | pcall(TelemetryService.LogCounter, TelemetryService, config, data, options.value) 38 | 39 | if ReactGlobals.__DEV__ and not success then 40 | warn(`React telemetry collection is enabled but log failed: {result}`) 41 | end 42 | end 43 | 44 | return reportCounter 45 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactWorkTags.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/56e9feead0f91075ba0a4f725c9e4e343bca1c67/packages/react-reconciler/src/ReactWorkTags.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | export type WorkTag = number 13 | 14 | return { 15 | FunctionComponent = 0, 16 | ClassComponent = 1, 17 | IndeterminateComponent = 2, -- Before we know whether it is function or class 18 | HostRoot = 3, -- Root of a host tree. Could be nested inside another node. 19 | HostPortal = 4, -- A subtree. Could be an entry point to a different renderer. 20 | HostComponent = 5, 21 | HostText = 6, 22 | Fragment = 7, 23 | Mode = 8, 24 | ContextConsumer = 9, 25 | ContextProvider = 10, 26 | ForwardRef = 11, 27 | Profiler = 12, 28 | SuspenseComponent = 13, 29 | MemoComponent = 14, 30 | SimpleMemoComponent = 15, 31 | LazyComponent = 16, 32 | IncompleteClassComponent = 17, 33 | DehydratedFragment = 18, 34 | SuspenseListComponent = 19, 35 | FundamentalComponent = 20, 36 | ScopeComponent = 21, 37 | Block = 22, 38 | OffscreenComponent = 23, 39 | LegacyHiddenComponent = 24, 40 | } 41 | -------------------------------------------------------------------------------- /modules/shared/src/formatProdErrorMessage.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/75955bf1d7ff6c2c1f4052f4a84dd2ce6944c62e/packages/shared/formatProdErrorMessage.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | ]] 10 | 11 | -- Do not require this module directly! Use normal `invariant` calls with 12 | -- template literal strings. The messages will be replaced with error codes 13 | -- during build. 14 | 15 | local HttpService = game:GetService("HttpService") 16 | 17 | local function formatProdErrorMessage(code, ...) 18 | local url = "https://reactjs.org/docs/error-decoder.html?invariant=" .. tostring(code) 19 | local argsLength = select("#", ...) 20 | for i = 1, argsLength, 1 do 21 | -- deviation: UrlEncode should be equivalent to encodeURIComponent 22 | url = url .. "&args[]=" .. HttpService:UrlEncode(select(i, ...)) 23 | end 24 | return string.format( 25 | "Minified React error #%d; visit %s for the full message or " 26 | .. "use the non-minified dev environment for full errors and additional " 27 | .. "helpful warnings.", 28 | code, 29 | url 30 | ) 31 | end 32 | 33 | return formatProdErrorMessage 34 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberSchedulerPriorities.roblox.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/d17086c7c813402a550d15a2f56dc43f1dbd1735/packages/react-reconciler/src/SchedulerWithReactIntegration.new.js 3 | --[[ 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | -- deviation: Type definition and values extracted from 13 | -- SchedulerWithReactIntegration.new. This helps avoid a cyclic dependency that 14 | -- can occur between SchedulerWithReactIntegration.new, ReactFiberLanes, and 15 | -- various files that depend upon them 16 | 17 | export type ReactPriorityLevel = number 18 | 19 | local exports: { [string]: ReactPriorityLevel } = { 20 | -- // Except for NoPriority, these correspond to Scheduler priorities. We use 21 | -- // ascending numbers so we can compare them like numbers. They start at 90 to 22 | -- // avoid clashing with Scheduler's priorities. 23 | ImmediatePriority = 99, 24 | UserBlockingPriority = 98, 25 | NormalPriority = 97, 26 | LowPriority = 96, 27 | IdlePriority = 95, 28 | -- // NoPriority is the absence of priority. Also React-only. 29 | NoPriority = 90, 30 | } 31 | 32 | return exports 33 | -------------------------------------------------------------------------------- /modules/shared/src/invariant.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/42c3c967d1e4ca4731b47866f2090bc34caa086c/packages/shared/invariant.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | ]] 10 | 11 | --[[* 12 | * Use invariant() to assert state which your program assumes to be true. 13 | * 14 | * Provide sprintf-style format (only %s is supported) and arguments 15 | * to provide information about what broke and what you were 16 | * expecting. 17 | * 18 | * The invariant message will be stripped in production, but the invariant 19 | * will remain to ensure logic does not differ in production. 20 | ]] 21 | local Packages = script.Parent.Parent 22 | local LuauPolyfill = require(Packages.LuauPolyfill) 23 | local Error = LuauPolyfill.Error 24 | 25 | local function invariant(condition, format, ...) 26 | -- ROBLOX TODO: we should encapsulate all formatting compatibility here, 27 | -- rather than spreading workarounds throughout the codebase, eg this 28 | -- should print an array without the need for a table.concat on the consumer side 29 | if not condition then 30 | error(Error(string.format(format, ...))) 31 | end 32 | end 33 | 34 | return invariant 35 | -------------------------------------------------------------------------------- /modules/react-devtools-core/README.md: -------------------------------------------------------------------------------- 1 | # `react-devtools-core` 2 | 3 | A React DevTools bridge implementation for React Lua. The implementation of this package deviates heavily from the upstream version. 4 | 5 | ## API 6 | 7 | Requiring this package is similar to requiring `react-devtools`, but provides several configurable options. Unlike `react-devtools`, requiring `react-devtools-core` doesn't connect immediately but instead exports a function: 8 | 9 | ```luau 10 | local ReactDevtoolsCore = require(Packages.ReactDevtoolsCore) 11 | local connectToDevtools = ReactDevtoolsCore.connectToDevtools 12 | connectToDevTools(config) 13 | ``` 14 | 15 | Run `connectToDevTools()` in the same context as React to set up a connection to DevTools. 16 | Be sure to run this function before importing *any* React package -- e.g. `react`, `react-roblox`. 17 | 18 | The `config` object may contain: 19 | 20 | - `host: string` (defaults to "localhost") - Websocket will connect to this host. 21 | - `port: number` (defaults to `8097`) - Websocket will connect to this port. 22 | - `useHttps: boolean` (defaults to `false`) - Websocket should use a secure protocol (wss). 23 | - `resolveRNStyle: (style: number) => ?Object` - Used by the React Native style plug-in. 24 | - `isAppActive: () => boolean` - If provided, DevTools will poll this method and wait until it returns true before connecting to React. 25 | -------------------------------------------------------------------------------- /docs/images/reactjs.svg: -------------------------------------------------------------------------------- 1 | 2 | View React Docs 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | View React Docs 11 | 12 | 13 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberLazyComponent.new.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/17f582e0453b808860be59ed3437c6a426ae52de/packages/react-reconciler/src/ReactFiberLazyComponent.new.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | type Object = { [any]: any } 13 | 14 | local function resolveDefaultProps(Component: any, baseProps: Object): Object 15 | -- ROBLOX deviation: check if type is table before checking defaultProps to prevent non-table index 16 | if Component and typeof(Component) == "table" and Component.defaultProps then 17 | -- Resolve default props. Taken from ReactElement 18 | -- ROBLOX FIXME Luau: hard cast to object until we can model this better in Luau. avoids Expected type table, got 'Object & any & any & { [any]: any }' instead 19 | local props = table.clone(baseProps) :: Object 20 | local defaultProps = Component.defaultProps 21 | for propName, _ in defaultProps do 22 | if props[propName] == nil then 23 | props[propName] = defaultProps[propName] 24 | end 25 | end 26 | return props 27 | end 28 | return baseProps 29 | end 30 | 31 | return { 32 | resolveDefaultProps = resolveDefaultProps, 33 | } 34 | -------------------------------------------------------------------------------- /modules/shared/src/shallowEqual.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/a9b035b0c2b8235405835beca0c4db2cc37f18d0/packages/shared/shallowEqual.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * 10 | ]] 11 | local is = require(script.Parent.objectIs) 12 | 13 | --[[* 14 | * Performs equality by iterating through keys on an object and returning false 15 | * when any key has values which are not strictly equal between the arguments. 16 | * Returns true when the values of all keys are strictly equal. 17 | ]] 18 | local function shallowEqual(objA, objB) 19 | if is(objA, objB) then 20 | return true 21 | end 22 | 23 | if 24 | typeof(objA) ~= "table" 25 | or objA == nil 26 | or typeof(objB) ~= "table" 27 | or objB == nil 28 | then 29 | return false 30 | end 31 | 32 | -- deviation: `Object.keys` does not have an equivalent in Lua, so we 33 | -- iterate through each table instead 34 | for key, value in objA do 35 | if not is(objB[key], value) then 36 | return false 37 | end 38 | end 39 | 40 | for key, value in objB do 41 | if not is(objA[key], value) then 42 | return false 43 | end 44 | end 45 | 46 | return true 47 | end 48 | 49 | return shallowEqual 50 | -------------------------------------------------------------------------------- /modules/shared/src/Symbol.roblox.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | --[[ 16 | A 'Symbol' is an opaque marker type. 17 | 18 | Symbols have the type 'userdata', but when printed to the console, the name 19 | of the symbol is shown. 20 | ]] 21 | 22 | local Symbol = {} 23 | 24 | --[[ 25 | Creates a Symbol with the given name. 26 | 27 | When printed or coerced to a string, the symbol will turn into the string 28 | given as its name. 29 | ]] 30 | function Symbol.named(name) 31 | assert(type(name) == "string", "Symbols must be created using a string name!") 32 | 33 | local self = newproxy(true) 34 | 35 | local wrappedName = string.format("Symbol(%s)", name) 36 | 37 | getmetatable(self).__tostring = function() 38 | return wrappedName 39 | end 40 | 41 | return self 42 | end 43 | 44 | return Symbol 45 | -------------------------------------------------------------------------------- /modules/react-telemetry/src/ReactTelemetry.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.Parent 2 | 3 | local LuauPolyfill = require(Packages.LuauPolyfill) 4 | local Object = LuauPolyfill.Object 5 | 6 | local reportCounter = require(script.Parent.reportCounter) 7 | local customFields = require(script.Parent.customFields) 8 | 9 | local ReactTelemetry = {} 10 | ReactTelemetry.customFields = customFields 11 | 12 | local function reportNewDevtoolsConnection() 13 | reportCounter({ 14 | eventName = "react_new_devtools_connection", 15 | lastUpdated = { 2025, 8, 28 }, 16 | description = "A new connection to React Devtools", 17 | links = "https://roblox.atlassian.net/wiki/spaces/luauee/pages/3836510338/DevTools+Telemetry", 18 | customFields = customFields, 19 | }) 20 | end 21 | ReactTelemetry.reportNewDevtoolsConnection = reportNewDevtoolsConnection 22 | 23 | local function reportFailedDevtoolsConnection( 24 | type: "create_client_failed" | "socket_closed" 25 | ) 26 | reportCounter({ 27 | eventName = "react_failed_devtools_connection", 28 | lastUpdated = { 2025, 8, 28 }, 29 | description = "A failed connection to React Devtools", 30 | links = "https://roblox.atlassian.net/wiki/spaces/luauee/pages/3836510338/DevTools+Telemetry", 31 | customFields = Object.assign({}, customFields, { 32 | error_type = type, 33 | }), 34 | }) 35 | end 36 | ReactTelemetry.reportFailedDevtoolsConnection = reportFailedDevtoolsConnection 37 | 38 | return ReactTelemetry 39 | -------------------------------------------------------------------------------- /bin/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | rotrieve install 6 | 7 | echo "Remove .robloxrc from dependencies" 8 | find Packages/_Index -name "*.robloxrc" | xargs rm -f 9 | 10 | echo "Run static analysis" 11 | roblox-cli analyze --project tests.project.json 12 | selene --version 13 | selene --config selene.toml modules/ WorkspaceStatic/ 14 | stylua --version 15 | stylua -c modules bin WorkspaceStatic 16 | 17 | echo "Run tests in DEV" 18 | robloxdev-cli run --load.model tests.project.json \ 19 | --run bin/spec.lua \ 20 | --fastFlags.allOnLuau --fastFlags.overrides UseDateTimeType3=true EnableLoadModule=true DebugDisableOptimizedBytecode=true EnableDelayedTaskMethods=true MaxDeferReentrancyDepth=65 \ 21 | --load.asRobloxScript --headlessRenderer 1 --virtualInput 1 --fs.readwrite=$PWD --lua.globals=__COMPAT_WARNINGS__=true \ 22 | --lua.globals=UPDATESNAPSHOT=false --lua.globals=CI=true --lua.globals=__ROACT_17_MOCK_SCHEDULER__=true \ 23 | --lua.globals=__DEV__=true 24 | 25 | echo "Run tests in release" 26 | robloxdev-cli run --load.model tests.project.json \ 27 | --run bin/spec.lua \ 28 | --fastFlags.allOnLuau --fastFlags.overrides UseDateTimeType3=true EnableLoadModule=true DebugDisableOptimizedBytecode=true EnableDelayedTaskMethods=true MaxDeferReentrancyDepth=65 \ 29 | --load.asRobloxScript --headlessRenderer 1 --virtualInput 1 --fs.readwrite=$PWD \ 30 | --lua.globals=UPDATESNAPSHOT=false --lua.globals=CI=true --lua.globals=__ROACT_17_MOCK_SCHEDULER__=true 31 | -------------------------------------------------------------------------------- /modules/shared/src/PropMarkers/__tests__/Change.spec.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | 16 | local Packages = script.Parent.Parent.Parent.Parent 17 | local JestGlobals = require(Packages.Dev.JestGlobals) 18 | local jestExpect = JestGlobals.expect 19 | local it = JestGlobals.it 20 | 21 | local Type = require(script.Parent.Parent.Parent["Type.roblox"]) 22 | local Change = require(script.Parent.Parent.Change) 23 | 24 | it("should yield change listener objects when indexed", function() 25 | jestExpect(Type.of(Change.Text)).toBe(Type.HostChangeEvent) 26 | jestExpect(Type.of(Change.Selected)).toBe(Type.HostChangeEvent) 27 | end) 28 | 29 | it("should yield the same object when indexed again", function() 30 | local a = Change.Text 31 | local b = Change.Text 32 | local c = Change.Selected 33 | 34 | jestExpect(a).toBe(b) 35 | jestExpect(a).never.toBe(c) 36 | end) 37 | -------------------------------------------------------------------------------- /bin/ci-benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | rotrieve install 6 | rojo build tests.project.json --output model.rbxm 7 | 8 | echo "Remove .robloxrc from dependencies" 9 | find Packages/_Index -name "*.robloxrc" | xargs rm -f 10 | 11 | echo "Run static analysis" 12 | roblox-cli analyze tests.project.json 13 | selene --version 14 | selene --config selene.toml modules/ 15 | stylua --version 16 | stylua -c modules -g "*[a-bdh-km-oquvyz].lua" 17 | 18 | echo "Run benchmarks" 19 | robloxdev-cli run --load.model model.rbxm --run bin/run-first-render-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1 20 | robloxdev-cli run --load.model model.rbxm --run bin/run-frame-rate-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1 21 | robloxdev-cli run --load.model model.rbxm --run bin/run-deep-tree-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1 22 | robloxdev-cli run --load.model model.rbxm --run bin/run-wide-tree-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1 23 | robloxdev-cli run --load.model model.rbxm --run bin/run-sierpinski-triangle-benchmark.lua --fastFlags.allOnLuau --fastFlags.overrides EnableLoadModule=true EnableDelayedTaskMethods=true --headlessRenderer 1 24 | -------------------------------------------------------------------------------- /modules/shared/src/PropMarkers/__tests__/Event.spec.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | 16 | local Packages = script.Parent.Parent.Parent.Parent 17 | local JestGlobals = require(Packages.Dev.JestGlobals) 18 | local jestExpect = JestGlobals.expect 19 | local it = JestGlobals.it 20 | 21 | local Type = require(script.Parent.Parent.Parent["Type.roblox"]) 22 | local Event = require(script.Parent.Parent.Event) 23 | 24 | it("should yield event objects when indexed", function() 25 | jestExpect(Type.of(Event.MouseButton1Click)).toBe(Type.HostEvent) 26 | jestExpect(Type.of(Event.Touched)).toBe(Type.HostEvent) 27 | end) 28 | 29 | it("should yield the same object when indexed again", function() 30 | local a = Event.MouseButton1Click 31 | local b = Event.MouseButton1Click 32 | local c = Event.Touched 33 | 34 | jestExpect(a).toBe(b) 35 | jestExpect(a).never.toBe(c) 36 | end) 37 | -------------------------------------------------------------------------------- /modules/roact-compat/src/__tests__/warnOnce.spec.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the MIT License (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://opensource.org/licenses/MIT 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | 17 | local Packages = script.Parent.Parent.Parent 18 | 19 | local JestGlobals = require(Packages.Dev.JestGlobals) 20 | local beforeEach = JestGlobals.beforeEach 21 | local jestExpect = JestGlobals.expect 22 | local it = JestGlobals.it 23 | local jest = JestGlobals.jest 24 | local warnOnce 25 | 26 | beforeEach(function() 27 | jest.resetModules() 28 | warnOnce = require(script.Parent.Parent.warnOnce) 29 | end) 30 | 31 | it("warns exactly once", function() 32 | jestExpect(function() 33 | warnOnce("oldAPI", "Foo") 34 | end).toWarnDev( 35 | "Warning: The legacy Roact API 'oldAPI' is deprecated, and will be " 36 | .. "removed in a future release.\n\nFoo", 37 | { withoutStack = true } 38 | ) 39 | 40 | jestExpect(function() 41 | warnOnce("oldAPI", "Foo") 42 | end).toWarnDev({}) 43 | end) 44 | -------------------------------------------------------------------------------- /modules/shared/src/Type.roblox.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | --[[ 16 | Contains markers for annotating objects with types. 17 | 18 | To set the type of an object, use `Type` as a key and the actual marker as 19 | the value: 20 | 21 | local foo = { 22 | [Type] = Type.Foo, 23 | } 24 | ]] 25 | 26 | local Symbol = require(script.Parent["Symbol.roblox"]) 27 | 28 | local Type = newproxy(true) 29 | 30 | local TypeInternal = {} 31 | 32 | local function addType(name) 33 | TypeInternal[name] = Symbol.named("Roact" .. name) 34 | end 35 | 36 | addType("HostChangeEvent") 37 | addType("HostEvent") 38 | 39 | function TypeInternal.of(value) 40 | if typeof(value) ~= "table" then 41 | return nil 42 | end 43 | 44 | return value[Type] 45 | end 46 | 47 | getmetatable(Type).__index = TypeInternal 48 | 49 | getmetatable(Type).__tostring = function() 50 | return "RoactType" 51 | end 52 | 53 | return Type 54 | -------------------------------------------------------------------------------- /bin/spec.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | local Workspace = script.Parent.RoactAlignment._Workspace 16 | local runCLI = require(Workspace.TestRunner.Dev.Jest).runCLI 17 | 18 | local processServiceExists, ProcessService = pcall(function() 19 | return game:GetService("ProcessService") 20 | end) 21 | 22 | local status, result = runCLI(Workspace, { 23 | verbose = if _G.verbose == "true" then true else nil, 24 | ci = _G.CI == "true", 25 | updateSnapshot = _G.UPDATESNAPSHOT == "true", 26 | }, { Workspace }):awaitStatus() 27 | 28 | if status == "Rejected" then 29 | print(result) 30 | end 31 | 32 | if 33 | status == "Resolved" 34 | and result.results.numFailedTestSuites == 0 35 | and result.results.numFailedTests == 0 36 | then 37 | if processServiceExists then 38 | ProcessService:ExitAsync(0) 39 | end 40 | end 41 | 42 | if processServiceExists then 43 | ProcessService:ExitAsync(1) 44 | end 45 | 46 | return nil 47 | -------------------------------------------------------------------------------- /modules/react/src/__tests__/ReactBaseClasses.roblox.spec.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | local Packages = script.Parent.Parent.Parent 3 | local ReactBaseClasses = require(script.Parent.Parent.ReactBaseClasses) 4 | local JestGlobals = require(Packages.Dev.JestGlobals) 5 | local jestExpect = JestGlobals.expect 6 | local describe = JestGlobals.describe 7 | local it = JestGlobals.it 8 | local Component = ReactBaseClasses.Component 9 | local PureComponent = ReactBaseClasses.Component 10 | local component 11 | 12 | describe("Component", function() 13 | it("should prevent extending a second time", function() 14 | component = Component:extend("Sheev") 15 | 16 | jestExpect(function() 17 | (component :: any):extend("Frank") 18 | end).toThrow() 19 | end) 20 | 21 | it("should use a given name", function() 22 | component = Component:extend("FooBar") 23 | 24 | local name = tostring(component) 25 | 26 | jestExpect(name).toEqual(jestExpect.any("string")) 27 | jestExpect(name).toContain("FooBar") 28 | end) 29 | end) 30 | 31 | describe("PureComponent", function() 32 | it("should prevent extending a second time", function() 33 | component = PureComponent:extend("Sheev") 34 | 35 | jestExpect(function() 36 | (component :: any):extend("Frank") 37 | end).toThrow() 38 | end) 39 | 40 | it("should use a given name", function() 41 | component = PureComponent:extend("FooBar") 42 | 43 | local name = tostring(component) 44 | 45 | jestExpect(name).toEqual(jestExpect.any("string")) 46 | jestExpect(name).toContain("FooBar") 47 | end) 48 | end) 49 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberWorkInProgress.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX deviation: this is an extraction of a single state field 3 | -- (and associated mutation/getters) from ReactFiberWorkLooop.new 4 | -- which allows us to break dependency cycles involving that module 5 | -- ROBLOX upstream: https://github.com/facebook/react/blob/56e9feead0f91075ba0a4f725c9e4e343bca1c67/packages/react-reconciler/src/ReactFiberWorkLoop.new.js 6 | --[[* 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | * 12 | * @flow 13 | ]] 14 | 15 | local ReactFiberLane = require(script.Parent.ReactFiberLane) 16 | local _workInProgressRootSkippedLanes: Lanes = ReactFiberLane.NoLanes 17 | local mergeLanes = ReactFiberLane.mergeLanes 18 | type Lanes = ReactFiberLane.Lanes 19 | type Lane = ReactFiberLane.Lane 20 | 21 | local exports = {} 22 | 23 | -- ROBLOX TODO: turn this into newindex property accessor 24 | exports.workInProgressRootSkippedLanes = function(value: Lanes?): Lanes 25 | if value == nil then 26 | return _workInProgressRootSkippedLanes 27 | end 28 | 29 | -- ROBLOX FIXME Luau: Luau should narrow based on guard above 30 | _workInProgressRootSkippedLanes = value :: Lanes 31 | return _workInProgressRootSkippedLanes 32 | end 33 | 34 | exports.markSkippedUpdateLanes = function(lane: Lane | Lanes): () 35 | _workInProgressRootSkippedLanes = mergeLanes(lane, _workInProgressRootSkippedLanes) 36 | end 37 | 38 | return exports 39 | -------------------------------------------------------------------------------- /modules/shared/src/PropMarkers/Event.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | --[[ 16 | Index into `Event` to get a prop key for attaching to an event on a Roblox 17 | Instance. 18 | 19 | Example: 20 | 21 | Roact.createElement("TextButton", { 22 | Text = "Hello, world!", 23 | 24 | [Roact.Event.MouseButton1Click] = function(rbx) 25 | print("Clicked", rbx) 26 | end 27 | }) 28 | ]] 29 | 30 | local Type = require(script.Parent.Parent["Type.roblox"]) 31 | 32 | local Event = {} 33 | 34 | local eventMetatable = { 35 | __tostring = function(self) 36 | return string.format("RoactHostEvent(%s)", self.name) 37 | end, 38 | } 39 | 40 | setmetatable(Event, { 41 | __index = function(self, eventName) 42 | local event = { 43 | [Type] = Type.HostEvent, 44 | name = eventName, 45 | } 46 | 47 | setmetatable(event, eventMetatable) 48 | 49 | Event[eventName] = event 50 | 51 | return event 52 | end, 53 | }) 54 | 55 | return Event 56 | -------------------------------------------------------------------------------- /modules/shared/src/console.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | 16 | -- deviation: this lets us have the same functionality as in React, without 17 | -- having something like Babel to inject a different implementation of 18 | -- console.warn and console.error into the code 19 | -- Instead of using `LuauPolyfill.console`, React internals should use this 20 | -- wrapper to be able to use consoleWithStackDev in dev mode 21 | local Shared = script.Parent 22 | local Packages = Shared.Parent 23 | local ReactGlobals = require(Packages.ReactGlobals) 24 | local LuauPolyfill = require(Packages.LuauPolyfill) 25 | local console = LuauPolyfill.console 26 | local consoleWithStackDev = require(Shared.consoleWithStackDev) 27 | 28 | if ReactGlobals.__DEV__ then 29 | local newConsole = setmetatable({ 30 | warn = consoleWithStackDev.warn, 31 | error = consoleWithStackDev.error, 32 | }, { 33 | __index = console, 34 | }) 35 | return newConsole 36 | end 37 | 38 | return console 39 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberOffscreenComponent.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/1faf9e3dd5d6492f3607d5c721055819e4106bc6/packages/react-reconciler/src/ReactFiberOffscreenComponent.js 2 | --!strict 3 | -- /** 4 | -- * Copyright (c) Facebook, Inc. and its affiliates. 5 | -- * 6 | -- * This source code is licensed under the MIT license found in the 7 | -- * LICENSE file in the root directory of this source tree. 8 | -- * 9 | -- * @flow 10 | -- */ 11 | 12 | local Packages = script.Parent.Parent 13 | local ReactTypes = require(Packages.Shared) 14 | type ReactNodeList = ReactTypes.ReactNodeList 15 | 16 | local ReactFiberLanes = require(script.Parent.ReactFiberLane) 17 | type Lanes = ReactFiberLanes.Lanes 18 | 19 | export type OffscreenProps = { 20 | -- TODO: Pick an API before exposing the Offscreen type. I've chosen an enum 21 | -- for now, since we might have multiple variants. For example, hiding the 22 | -- content without changing the layout. 23 | -- 24 | -- Default mode is visible. Kind of a weird default for a component 25 | -- called "Offscreen." Possible alt: ? 26 | mode: string | nil, 27 | children: ReactNodeList, 28 | } 29 | 30 | -- We use the existence of the state object as an indicator that the component 31 | -- is hidden. 32 | export type OffscreenState = { 33 | -- TODO: This doesn't do anything, yet. It's always NoLanes. But eventually it 34 | -- will represent the pending work that must be included in the render in 35 | -- order to unhide the component. 36 | baseLanes: Lanes, 37 | } 38 | 39 | return {} 40 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/storage.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/storage.js 2 | -- /* 3 | -- * Copyright (c) Facebook, Inc. and its affiliates. 4 | -- * 5 | -- * This source code is licensed under the MIT license found in the 6 | -- * LICENSE file in the root directory of this source tree. 7 | -- * 8 | -- */ 9 | 10 | local Packages = script.Parent.Parent 11 | local ReactGlobals = require(Packages.ReactGlobals) 12 | 13 | local exports = {} 14 | if ReactGlobals.__LOCALSTORAGE__ == nil then 15 | ReactGlobals.__LOCALSTORAGE__ = {} 16 | end 17 | 18 | if ReactGlobals.__SESSIONSTORAGE__ == nil then 19 | ReactGlobals.__SESSIONSTORAGE__ = {} 20 | end 21 | 22 | -- ROBLOX FIXME: what's a high-performance storage that for temporal (current DM lifetime) and permanent (beyond current DM lifetime) 23 | local localStorage = ReactGlobals.__LOCALSTORAGE__ 24 | local sessionStorage = ReactGlobals.__SESSIONSTORAGE__ 25 | 26 | exports.localStorageGetItem = function(key: string): any 27 | return localStorage[key] 28 | end 29 | exports.localStorageRemoveItem = function(key: string): () 30 | localStorage[key] = nil 31 | end 32 | exports.localStorageSetItem = function(key: string, value: any): () 33 | localStorage[key] = value 34 | end 35 | exports.sessionStorageGetItem = function(key: string): any 36 | return sessionStorage[key] 37 | end 38 | exports.sessionStorageRemoveItem = function(key: string): () 39 | sessionStorage[key] = nil 40 | end 41 | exports.sessionStorageSetItem = function(key: string, value: any): () 42 | sessionStorage[key] = value 43 | end 44 | 45 | return exports 46 | -------------------------------------------------------------------------------- /modules/shared/src/PropMarkers/Change.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | --[[ 16 | Change is used to generate special prop keys that can be used to connect to 17 | GetPropertyChangedSignal. 18 | 19 | Generally, Change is indexed by a Roblox property name: 20 | 21 | Roact.createElement("TextBox", { 22 | [Roact.Change.Text] = function(rbx) 23 | print("The TextBox", rbx, "changed text to", rbx.Text) 24 | end, 25 | }) 26 | ]] 27 | 28 | local Type = require(script.Parent.Parent["Type.roblox"]) 29 | 30 | local Change = {} 31 | 32 | local changeMetatable = { 33 | __tostring = function(self) 34 | return string.format("RoactHostChangeEvent(%s)", self.name) 35 | end, 36 | } 37 | 38 | setmetatable(Change, { 39 | __index = function(self, propertyName) 40 | local changeListener = { 41 | [Type] = Type.HostChangeEvent, 42 | name = propertyName, 43 | } 44 | 45 | setmetatable(changeListener, changeMetatable) 46 | Change[propertyName] = changeListener 47 | 48 | return changeListener 49 | end, 50 | }) 51 | 52 | return Change 53 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/backend/views/Highlighter/Highlighter.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/b8390310b14cce89fd26df83f969505b5f129f10/packages/react-devtools-shared/src/backend/views/Highlighter/Highlighter.js 2 | 3 | local Overlay = require(script.Parent.Overlay.Overlay) 4 | type Overlay = Overlay.Overlay 5 | 6 | local exports = {} 7 | 8 | local timeoutThread: thread? = nil 9 | local overlay: Overlay? = nil 10 | 11 | local SHOW_DURATION = 2 12 | 13 | function exports.hideOverlay() 14 | if timeoutThread then 15 | task.cancel(timeoutThread) 16 | end 17 | timeoutThread = nil 18 | 19 | if overlay ~= nil then 20 | overlay:remove() 21 | overlay = nil 22 | end 23 | end 24 | 25 | function exports.showOverlay( 26 | elements: { GuiBase2d }?, 27 | componentName: string?, 28 | hideAfterTimeout: boolean 29 | ) 30 | if timeoutThread ~= nil then 31 | task.cancel(timeoutThread) 32 | timeoutThread = nil 33 | end 34 | 35 | if elements == nil then 36 | return 37 | end 38 | 39 | if overlay == nil then 40 | overlay = Overlay.new() 41 | end 42 | 43 | -- Sometimes the object the overlay is attached to is destroyed 44 | -- In that case, create a new overlay 45 | if overlay and overlay.container.Parent == nil then 46 | overlay:remove() 47 | overlay = Overlay.new() 48 | end 49 | 50 | assert(overlay, "Luau") 51 | overlay:inspect(elements, componentName) 52 | 53 | if hideAfterTimeout then 54 | timeoutThread = task.delay(SHOW_DURATION, function() 55 | exports.hideOverlay() 56 | end) 57 | end 58 | end 59 | 60 | function exports.getOverlay(): Overlay? 61 | return overlay 62 | end 63 | 64 | return exports 65 | -------------------------------------------------------------------------------- /modules/shared/src/ReactElementType.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | local Packages = script.Parent.Parent 11 | local LuauPolyfill = require(Packages.LuauPolyfill) 12 | type Object = LuauPolyfill.Object 13 | 14 | local flowtypes = require(script.Parent["flowtypes.roblox"]) 15 | type React_Element = flowtypes.React_Element 16 | type React_StatelessFunctionalComponent

= flowtypes.React_StatelessFunctionalComponent< 17 | P 18 | > 19 | type React_ComponentType

= flowtypes.React_ComponentType

20 | 21 | export type Source = { 22 | fileName: string, 23 | lineNumber: number, 24 | } 25 | type Key = string | number 26 | -- ROBLOX deviation: we're using the TypeScript definition here, which is more strict 27 | export type ReactElement

= { 28 | ["$$typeof"]: number, 29 | 30 | -- ROBLOX FIXME Luau: Luau has some trouble and inlining the type param from createElement doesn't help 31 | type: React_StatelessFunctionalComponent

| React_ComponentType

| string, 32 | -- type: T, 33 | key: Key | nil, 34 | ref: any, 35 | props: P, 36 | 37 | -- ROBLOX deviation: upstream has this as interface, which is extensible, Luau types are closed by default 38 | -- ReactFiber 39 | _owner: any, 40 | 41 | -- __DEV__ 42 | _store: any?, 43 | _self: React_Element?, 44 | _shadowChildren: any?, 45 | _source: Source?, 46 | } 47 | 48 | -- deviation: Return something so that the module system is happy 49 | return {} 50 | -------------------------------------------------------------------------------- /modules/shared/src/ReactFiberHostConfig/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | --[[ 16 | ROBLOX deviation: ReactFiberHostConfig captures singleton state across the 17 | whole workspace. This file and the modules it requires were moved from React 18 | to untangle a cyclic workspace member dependency. 19 | 20 | Before: 21 | * ReactFiberHostConfig (and the 5 associated modules) lived in React 22 | * React had a dependency on Shared 23 | * Shared reached into React source to re-export ReactFiberHostConfig (cycle) 24 | 25 | After: 26 | * ReactFiberHostConfig (and the 5 associated modules) live in Shared 27 | * React depends on Shared 28 | * Shared has no intra-workspace dependencies (no cycles) 29 | ]] 30 | 31 | -- types that are common across ReactFiberHostConfig files, moved here to avoid circular deps 32 | type Object = { [string]: any } 33 | export type OpaqueIDType = string | Object 34 | 35 | return { 36 | WithNoHydration = require(script.WithNoHydration), 37 | WithNoPersistence = require(script.WithNoPersistence), 38 | WithNoTestSelectors = require(script.WithNoTestSelectors), 39 | } 40 | -------------------------------------------------------------------------------- /modules/shared/src/UninitializedState.roblox.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) Roblox Corporation. All rights reserved. 3 | * Licensed under the MIT License (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://opensource.org/licenses/MIT 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | ]] 15 | --!strict 16 | local console = require(script.Parent.console) 17 | 18 | local Packages = script.Parent.Parent 19 | local ReactGlobals = require(Packages.ReactGlobals) 20 | 21 | -- ROBLOX DEVIATION: Initialize state to a singleton that warns on access and errors on assignment 22 | -- initial state singleton 23 | local UninitializedState = {} 24 | 25 | setmetatable(UninitializedState, { 26 | __index = function(table, key) 27 | if ReactGlobals.__DEV__ then 28 | console.warn( 29 | "Attempted to access uninitialized state. Use setState to initialize state" 30 | ) 31 | end 32 | return nil 33 | end, 34 | __newindex = function(table, key) 35 | if ReactGlobals.__DEV__ then 36 | console.error( 37 | "Attempted to directly mutate state. Use setState to assign new values to state." 38 | ) 39 | end 40 | return nil 41 | end, 42 | __tostring = function(self) 43 | return "" 44 | end, 45 | __metatable = "UninitializedState", 46 | }) 47 | 48 | return UninitializedState 49 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Roact Documentation 2 | site_url: https://roblox.github.io/roact-alignment/ 3 | repo_name: Roblox/roact-alignment 4 | repo_url: https://github.com/Roblox/roact-alignment 5 | 6 | theme: 7 | name: material 8 | palette: 9 | - media: "(prefers-color-scheme: light)" 10 | primary: indigo 11 | scheme: default 12 | toggle: 13 | icon: material/toggle-switch-off-outline 14 | name: Switch to dark mode 15 | - media: "(prefers-color-scheme: dark)" 16 | primary: indigo 17 | scheme: slate 18 | toggle: 19 | icon: material/toggle-switch 20 | name: Switch to light mode 21 | 22 | plugins: 23 | - search: 24 | separator: '[\s\-\.]' 25 | 26 | nav: 27 | - Home: index.md 28 | - Deviations: deviations.md 29 | - Configuration: configuration.md 30 | - Migrating From 1.x: 31 | - Minimum Requirements: migrating-from-1x/minimum-requirements.md 32 | - Add Roact 17 Dependency: migrating-from-1x/upgrading-to-roact-17.md 33 | - Adopt New Features: migrating-from-1x/adopt-new-features.md 34 | - Convert Legacy Conventions: migrating-from-1x/convert-legacy-conventions.md 35 | - API Reference: 36 | - React: api-reference/react.md 37 | - ReactRoblox: api-reference/react-roblox.md 38 | - RoactCompat: api-reference/roact-compat.md 39 | - Additional Libraries: api-reference/additional-libraries.md 40 | - Benchmarks: bench.md 41 | 42 | extra_css: 43 | - extra.css 44 | 45 | markdown_extensions: 46 | - admonition 47 | - codehilite: 48 | guess_lang: false 49 | - toc: 50 | permalink: true 51 | - pymdownx.superfences 52 | # FIXME: Add this back when the tabbed extension is supported by docs-deploy 53 | # - pymdownx.tabbed: 54 | # alternate_style: false 55 | -------------------------------------------------------------------------------- /modules/scheduler/src/NoYield.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX note: no upstream 2 | --!strict 3 | --[[ 4 | * Copyright (c) Roblox Corporation. All rights reserved. 5 | * Licensed under the MIT License (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://opensource.org/licenses/MIT 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | ]] 17 | 18 | local YIELD_ERROR = 19 | "Yielding is not currently supported inside components or hooks. Move this yield into a new thread with `task.spawn` or `task.defer`." 20 | 21 | local function resultHandler(co: thread, ok: boolean, ...) 22 | if not ok then 23 | local err = (...) 24 | if typeof(err) == "string" then 25 | error(debug.traceback(co, err), 3) 26 | else 27 | -- If the error is not of type string, just assume it has some 28 | -- meaningful information and rethrow it so that top-level error 29 | -- handlers can process it. 30 | error(err, 3) 31 | end 32 | end 33 | 34 | if coroutine.status(co) ~= "dead" then 35 | error(debug.traceback(co, YIELD_ERROR), 3) 36 | end 37 | 38 | return ... 39 | end 40 | 41 | --[[ 42 | Prevents a callback from yielding. If the callback yields, an error will be 43 | thrown. 44 | ]] 45 | local function NoYield(callback: (A...) -> R..., ...: A...): R... 46 | local co = coroutine.create(callback) 47 | return resultHandler(co, coroutine.resume(co, ...)) 48 | end 49 | 50 | return NoYield 51 | -------------------------------------------------------------------------------- /modules/scheduler/src/unstable_mock.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | *]] 8 | local Tracing = require(script.Parent.Tracing) 9 | local TracingSubscriptions = require(script.Parent.TracingSubscriptions) 10 | -- ROBLOX deviation: export Tracing type from the package exports to avoid direct file access 11 | export type Interaction = Tracing.Interaction 12 | 13 | local initializeScheduler = require(script.Parent.Scheduler) 14 | local HostConfig = require(script.Parent.forks["SchedulerHostConfig.mock"]) 15 | 16 | local Scheduler = initializeScheduler(HostConfig) 17 | 18 | local exports = {} 19 | exports.tracing = {} 20 | -- ROBLOX FIXME Luau: need to fix CLI-56768 to remove any casts 21 | for key, value in Scheduler :: any do 22 | exports[key] = value 23 | end 24 | for key, value in Tracing :: any do 25 | exports.tracing[key] = value 26 | end 27 | for key, value in TracingSubscriptions :: any do 28 | exports.tracing[key] = value 29 | end 30 | 31 | exports.unstable_flushAllWithoutAsserting = HostConfig.unstable_flushAllWithoutAsserting 32 | exports.unstable_flushNumberOfYields = HostConfig.unstable_flushNumberOfYields 33 | exports.unstable_flushExpired = HostConfig.unstable_flushExpired 34 | exports.unstable_clearYields = HostConfig.unstable_clearYields 35 | exports.unstable_flushUntilNextPaint = HostConfig.unstable_flushUntilNextPaint 36 | exports.unstable_flushAll = HostConfig.unstable_flushAll 37 | exports.unstable_yieldValue = HostConfig.unstable_yieldValue 38 | exports.unstable_advanceTime = HostConfig.unstable_advanceTime 39 | exports.unstable_Profiling = Scheduler.unstable_Profiling 40 | 41 | return exports 42 | -------------------------------------------------------------------------------- /modules/react-reconciler/README.md: -------------------------------------------------------------------------------- 1 | # react-reconciler 2 | A Roblox Lua port of the `react-reconciler` package from React, which contains the core reconciler logic that drives the various renderers that can be attached. 3 | 4 | Status: 🔨 Port in progress 5 | 6 | Source: https://github.com/facebook/react/tree/master/packages/react-reconciler 7 | 8 | --- 9 | 10 | ### ✏️ Notes 11 | 12 | #### Profiling 13 | 14 | ``` 15 | src/SchedulingProfiler.js 16 | src/__tests__/SchedulingProfiler-test.internal.js 17 | ``` 18 | 19 | Profiling logic used for debugging the Scheduler. Includes tests that produce flamegraphs of processed tasks. This functionality is gated behind the `enableProfiling` flag defined in `SchedulerFeatureFlags.js`. Additional functionality is gated behind the enableSchedulingProfiler in ReactFeatureFlags. When enabling the Scheduling Profiler, you'll need to plug in a table with a `mark` function to the `_G.performance` global, like this: 20 | ```lua 21 | _G.performance = { 22 | mark = function(str) 23 | debug.profileBegin(str) 24 | debug.profileEnd(str) 25 | } 26 | ``` 27 | 28 | #### Debug Tracing 29 | 30 | ``` 31 | src/DebugTracing.js 32 | src/__tests__/DebugTracing-test.internal.js 33 | ``` 34 | 35 | Debug Tracing is enabled with the enableDebugTracing ReactFeatureFlag. The current Lua implementation outputs using Lua `print`, and strips out the color and styling versus upstream. We may want to more deeply customize this based on real-world use cases of Roblox UI developers. 36 | 37 | 38 | ### ❌ Excluded 39 | 40 | ``` 41 | src/__tests__/ReactSuspenseList-test.js 42 | ``` 43 | 44 | The initial release of Roact 17 includes support for Suspense, but not the unstable SuspenseList API. This was purely to pull in the delivery schedule and narrow the support surface for the initial release. 45 | -------------------------------------------------------------------------------- /modules/shared/src/ReactSharedInternals/ReactDebugCurrentFrame.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/98d410f5005988644d01c9ec79b7181c3dd6c847/packages/react/src/ReactDebugCurrentFrame.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | local Packages = script.Parent.Parent.Parent 13 | local ReactGlobals = require(Packages.ReactGlobals) 14 | 15 | local ReactDebugCurrentFrame = {} 16 | 17 | local currentExtraStackFrame = nil :: nil | string 18 | 19 | function ReactDebugCurrentFrame.setExtraStackFrame(stack: string?): () 20 | if ReactGlobals.__DEV__ then 21 | currentExtraStackFrame = stack 22 | end 23 | end 24 | 25 | if ReactGlobals.__DEV__ then 26 | -- deviation: in Lua, the implementation is duplicated 27 | -- function ReactDebugCurrentFrame.setExtraStackFrame(stack: string?) 28 | -- if ReactGlobals.__DEV__ then 29 | -- currentExtraStackFrame = stack 30 | -- end 31 | -- end 32 | 33 | -- Stack implementation injected by the current renderer. 34 | ReactDebugCurrentFrame.getCurrentStack = nil :: nil | (() -> string) 35 | 36 | function ReactDebugCurrentFrame.getStackAddendum(): string 37 | local stack = "" 38 | 39 | -- Add an extra top frame while an element is being validated 40 | if currentExtraStackFrame then 41 | stack = stack .. currentExtraStackFrame 42 | end 43 | 44 | -- Delegate to the injected renderer-specific implementation 45 | local impl = ReactDebugCurrentFrame.getCurrentStack 46 | if impl then 47 | stack = stack .. (impl() or "") 48 | end 49 | 50 | return stack 51 | end 52 | end 53 | 54 | return ReactDebugCurrentFrame 55 | -------------------------------------------------------------------------------- /modules/shared/src/__tests__/getComponentName.roblox.spec.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.Parent.Parent 2 | local JestGlobals = require(Packages.Dev.JestGlobals) 3 | local beforeEach = JestGlobals.beforeEach 4 | local jestExpect = JestGlobals.expect 5 | local describe = JestGlobals.describe 6 | local it = JestGlobals.it 7 | local React 8 | 9 | local getComponentName 10 | local function MyComponent() end 11 | local anonymous = function() end 12 | 13 | beforeEach(function() 14 | React = require(Packages.Dev.React) 15 | 16 | getComponentName = require(Packages.Shared).getComponentName 17 | end) 18 | 19 | describe("function components", function() 20 | it("gets name from non-anonymous function", function() 21 | jestExpect(getComponentName(MyComponent)).toBe("MyComponent") 22 | end) 23 | it("gets fileName:lineNumber from anonymous function", function() 24 | local anonymous = function() end 25 | jestExpect(getComponentName(anonymous)).toMatch( 26 | "getComponentName.roblox.spec:[0-9]*" 27 | ) 28 | end) 29 | end) 30 | describe("Lazy components", function() 31 | it("gets name from lazy-wrapped non-anonymous function", function() 32 | local lazyMyComponent = React.lazy(function() 33 | return { 34 | andThen = function(self, resolve) 35 | resolve({ default = MyComponent }) 36 | end, 37 | } 38 | end) 39 | jestExpect(getComponentName(lazyMyComponent)).toBe("MyComponent") 40 | end) 41 | it("gets fileName:lineNumber from lazy-wrapped anonymous function", function() 42 | local lazyAnonymous = React.lazy(function() 43 | return { 44 | andThen = function(self, resolve) 45 | resolve({ default = anonymous }) 46 | end, 47 | } 48 | end) 49 | jestExpect(getComponentName(lazyAnonymous)).toMatch( 50 | "getComponentName.roblox.spec:[0-9]*" 51 | ) 52 | end) 53 | end) 54 | -------------------------------------------------------------------------------- /modules/roact-compat/src/oneChild.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/Roblox/roact/blob/master/src/oneChild.lua 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | local Packages = script.Parent.Parent 17 | local React = require(Packages.React) 18 | local ReactGlobals = require(Packages.ReactGlobals) 19 | 20 | local warnOnce = require(script.Parent.warnOnce) 21 | 22 | local function oneChild(children) 23 | if ReactGlobals.__DEV__ and ReactGlobals.__COMPAT_WARNINGS__ then 24 | warnOnce( 25 | "oneChild", 26 | "You likely don't need this at all! If you were assigning children " 27 | .. "via `React.oneChild(someChildren)`, you can simply use " 28 | .. "`someChildren` directly." 29 | ) 30 | end 31 | 32 | -- This behavior is a bit different from upstream, so we're adapting current 33 | -- Roact's logic (which will unwrap a table with a single member) 34 | if not children then 35 | return nil 36 | end 37 | 38 | local key, child = next(children) 39 | 40 | if not child then 41 | return nil 42 | end 43 | 44 | local after = next(children, key) 45 | 46 | if after then 47 | error("Expected at most one child, had more than one child.", 2) 48 | end 49 | 50 | return React.Children.only(child) 51 | end 52 | 53 | return oneChild 54 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactIncrementalErrorReplay.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/d13f5b9538e48f74f7c571ef3cde652ca887cca0/packages/react-reconciler/src/__tests__/ReactIncrementalErrorReplay-test.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @emails react-core 9 | * @jest-environment node 10 | --]] 11 | --!strict 12 | 13 | local Packages = script.Parent.Parent.Parent 14 | local React 15 | local ReactNoop 16 | local Scheduler 17 | 18 | local JestGlobals = require(Packages.Dev.JestGlobals) 19 | local jestExpect = JestGlobals.expect 20 | local beforeEach = JestGlobals.beforeEach 21 | local jest = JestGlobals.jest 22 | local it = JestGlobals.it 23 | 24 | beforeEach(function() 25 | jest.resetModules() 26 | 27 | React = require(Packages.React) 28 | ReactNoop = require(Packages.Dev.ReactNoopRenderer) 29 | Scheduler = require(Packages.Scheduler) 30 | end) 31 | 32 | -- ROBLOX deviation: this test doesn't make sense in not JSX 33 | -- it('should fail gracefully on error in the host environment', () => { 34 | -- ReactNoop.render(); 35 | -- jestExpect(Scheduler).toFlushAndThrow('Error in host config.'); 36 | -- }); 37 | 38 | it("should ignore error if it doesn't throw on retry", function() 39 | local didInit = false 40 | 41 | local function badLazyInit() 42 | local needsInit = not didInit 43 | didInit = true 44 | if needsInit then 45 | error("Hi") 46 | end 47 | end 48 | 49 | local App = React.Component:extend("App") 50 | function App:render() 51 | badLazyInit() 52 | return React.createElement("TextLabel", { Text = "Hello" }) 53 | end 54 | ReactNoop.render(React.createElement(App)) 55 | jestExpect(Scheduler).toFlushWithoutYielding() 56 | end) 57 | -------------------------------------------------------------------------------- /modules/react-devtools-timeline/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX note: no upstream 2 | 3 | local exports = {} 4 | 5 | local constants = require(script.constants) 6 | exports.REACT_TOTAL_NUM_LANES = constants.REACT_TOTAL_NUM_LANES 7 | exports.SCHEDULING_PROFILER_VERSION = constants.SCHEDULING_PROFILER_VERSION 8 | 9 | local types = require(script.types) 10 | export type ScrollState = types.ScrollState 11 | export type ErrorStackFrame = types.ErrorStackFrame 12 | export type Milliseconds = types.Milliseconds 13 | export type ReactLane = types.ReactLane 14 | export type NativeEvent = types.NativeEvent 15 | export type ReactScheduleRenderEvent = types.ReactScheduleRenderEvent 16 | export type ReactScheduleStateUpdateEvent = types.ReactScheduleStateUpdateEvent 17 | export type ReactScheduleForceUpdateEvent = types.ReactScheduleForceUpdateEvent 18 | export type Phase = types.Phase 19 | export type SuspenseEvent = types.SuspenseEvent 20 | export type ThrownError = types.ThrownError 21 | export type SchedulingEvent = types.SchedulingEvent 22 | export type SchedulingEventType = types.SchedulingEventType 23 | export type ReactMeasureType = types.ReactMeasureType 24 | export type BatchUID = types.BatchUID 25 | export type ReactMeasure = types.ReactMeasure 26 | export type NetworkMeasure = types.NetworkMeasure 27 | export type ReactComponentMeasureType = types.ReactComponentMeasureType 28 | export type ReactComponentMeasure = types.ReactComponentMeasure 29 | export type FlamechartStackFrame = types.FlamechartStackFrame 30 | export type UserTimingMark = types.UserTimingMark 31 | export type Snapshot = types.Snapshot 32 | export type FlamechartStackLayer = types.FlamechartStackLayer 33 | export type Flamechart = types.Flamechart 34 | export type HorizontalScrollStateChangeCallback = types.HorizontalScrollStateChangeCallback 35 | export type SearchRegExpStateChangeCallback = types.SearchRegExpStateChangeCallback 36 | 37 | return table.freeze(exports) 38 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/ReactFiberHostConfig.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/9ac42dd074c42b66ecc0334b75200b1d2989f892/packages/react-reconciler/src/ReactFiberHostConfig.js 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @flow 10 | ]] 11 | 12 | --[[ eslint-disable react-internal/invariant-args ]] 13 | 14 | -- ROBLOX FIXME: Cannot carry types over via the module overriding that's in use 15 | -- here; this is a particularly tricky case of cross-dependency type definitions 16 | -- Use a common set of typedefs across ReactTestHostConfig and ReactRobloxHostTypes 17 | type Object = { [string]: any } 18 | 19 | export type Instance = Object 20 | export type HostInstance = Instance 21 | export type TextInstance = Instance 22 | export type Container = Object 23 | export type HostContext = Object 24 | export type HydratableInstance = Instance | SuspenseInstance 25 | export type SuspenseInstance = Object 26 | export type PublicInstance = HostInstance 27 | 28 | export type Type = string 29 | export type Props = Object 30 | export type ChildSet = {} -- void, unused 31 | export type RendererInspectionConfig = Object 32 | 33 | -- if _G.__NO_LOADMODULE__ then 34 | local exports: { [string]: any } = {} 35 | return exports 36 | -- end 37 | 38 | -- -- We expect that our Rollup, Jest, and Flow configurations 39 | -- -- always shim this module with the corresponding host config 40 | -- -- (either provided by a renderer, or a generic shim for npm). 41 | -- -- 42 | -- -- We should never resolve to this file, but it exists to make 43 | -- -- sure that if we *do* accidentally break the configuration, 44 | -- -- the failure isn't silent. 45 | 46 | -- -- deviation: FIXME (roblox): is there a way to configure luau to account for this module 47 | -- -- being shimmed? 48 | -- error('This module must be shimmed by a specific renderer.') 49 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactTopLevelText.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/69060e1da6061af845162dcf6854a5d9af28350a/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @emails react-core 9 | * @jest-environment node 10 | ]] 11 | --!strict 12 | 13 | local Packages = script.Parent.Parent.Parent 14 | local React 15 | local ReactNoop 16 | local Scheduler 17 | 18 | -- This is a new feature in Fiber so I put it in its own test file. It could 19 | -- probably move to one of the other test files once it is official. 20 | local JestGlobals = require(Packages.Dev.JestGlobals) 21 | local jestExpect = JestGlobals.expect 22 | local beforeEach = JestGlobals.beforeEach 23 | local it = JestGlobals.it 24 | local jest = JestGlobals.jest 25 | local describe = JestGlobals.describe 26 | 27 | describe("ReactTopLevelText", function() 28 | beforeEach(function() 29 | jest.resetModules() 30 | 31 | React = require(Packages.React) 32 | ReactNoop = require(Packages.Dev.ReactNoopRenderer) 33 | Scheduler = require(Packages.Scheduler) 34 | end) 35 | 36 | it("should render a component returning strings directly from render", function() 37 | local Text = function(props) 38 | return props.value 39 | end 40 | ReactNoop.render(React.createElement(Text, { value = "foo" })) 41 | jestExpect(Scheduler).toFlushWithoutYielding() 42 | 43 | jestExpect(ReactNoop).toMatchRenderedOutput("foo") 44 | end) 45 | 46 | it("should render a component returning numbers directly from renderß", function() 47 | local Text = function(props) 48 | return props.value 49 | end 50 | ReactNoop.render(React.createElement(Text, { value = 10 })) 51 | jestExpect(Scheduler).toFlushWithoutYielding() 52 | 53 | jestExpect(ReactNoop).toMatchRenderedOutput("10") 54 | end) 55 | end) 56 | -------------------------------------------------------------------------------- /modules/shared/src/__tests__/ReactErrorProd-internal.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/9a5576f4d263ac5d7a9462a287d1524fda3355b8/packages/shared/__tests__/ReactErrorProd-test.internal.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @emails react-core 9 | ]] 10 | --!strict 11 | 12 | local Packages = script.Parent.Parent.Parent 13 | local JestGlobals = require(Packages.Dev.JestGlobals) 14 | local beforeEach = JestGlobals.beforeEach 15 | local jestExpect = JestGlobals.expect 16 | local it = JestGlobals.it 17 | local jest = JestGlobals.jest 18 | local formatProdErrorMessage 19 | 20 | beforeEach(function() 21 | jest.resetModules() 22 | formatProdErrorMessage = require(script.Parent.Parent.formatProdErrorMessage) 23 | end) 24 | 25 | it("should throw with the correct number of `%s`s in the URL", function() 26 | jestExpect(formatProdErrorMessage(124, "foo", "bar")).toEqual( 27 | "Minified React error #124; visit " 28 | .. "https://reactjs.org/docs/error-decoder.html?invariant=124&args[]=foo&args[]=bar" 29 | .. " for the full message or use the non-minified dev environment" 30 | .. " for full errors and additional helpful warnings." 31 | ) 32 | 33 | jestExpect(formatProdErrorMessage(20)).toEqual( 34 | "Minified React error #20; visit " 35 | .. "https://reactjs.org/docs/error-decoder.html?invariant=20" 36 | .. " for the full message or use the non-minified dev environment" 37 | .. " for full errors and additional helpful warnings." 38 | ) 39 | 40 | jestExpect(formatProdErrorMessage(77, "

", "&?bar")).toEqual( 41 | "Minified React error #77; visit " 42 | .. "https://reactjs.org/docs/error-decoder.html?invariant=77&args[]=%3Cdiv%3E&args[]=%26%3Fbar" 43 | .. " for the full message or use the non-minified dev environment" 44 | .. " for full errors and additional helpful warnings." 45 | ) 46 | end) 47 | -------------------------------------------------------------------------------- /modules/scheduler/README.md: -------------------------------------------------------------------------------- 1 | # scheduler 2 | A Roblox Lua port of the `scheduler` package from React, which is used under the hood by the Fiber Reconciler logic. 3 | 4 | Status: ✔️ Ported 5 | 6 | Source: https://github.com/facebook/react/tree/master/packages/scheduler 7 | 8 | --- 9 | 10 | ### ✏️ Notes 11 | * The upstream implementation of min-heap does not include tests. To validate the port, `src/__tests__/SchedulerMinHeap.spec.lua` was added 12 | * The scheduler contains two sets of tests 13 | * A small, basic set of tests using the real scheduler and mock timers. 14 | * A more thorough test suite that uses `MockSchedulerHostConfig.lua`, which mocks the entire HostConfig interface and provides functionality to manipulate it within tests. 15 | 16 | #### Profiling 17 | 18 | ``` 19 | src/SchedulerProfiling.js 20 | src/__tests__/SchedulerProfiling-test.js 21 | ``` 22 | 23 | Profiling logic used for debugging the Scheduler. Includes tests that produce flamegraphs of processed tasks. This functionality is gated behind the `enableProfiling` flag defined in `SchedulerFeatureFlags.js`. Additional functionality is gated behind the enableSchedulingProfiler flag in ReactFeatureFlags. When enabling the Scheduling Profiler, you'll need to plug in a table with a `mark` function to the `_G.performance` global, like this: 24 | ```lua 25 | _G.performance = { 26 | mark = function(str) 27 | debug.profileBegin(str) 28 | debug.profileEnd(str) 29 | } 30 | ``` 31 | 32 | We may customize this for deeper integration with the Roblox Studio profiler in the future, based on specific performance optimization workflow needs. 33 | 34 | 35 | ### ❌ Excluded 36 | 37 | #### Post Task 38 | 39 | ``` 40 | unstable_post_task.js 41 | src/SchedulerPostTask.js 42 | src/__tests__/SchedulerPostTask-test.js 43 | ``` 44 | 45 | An alternate implementation of the Scheduler interface based on the new postTask browser API. There are relatively recent commits to these files, as well, suggesting new or ongoing development. Since the underlying browser API isn't relevant to the Roblox environment, this logic has been excluded. 46 | -------------------------------------------------------------------------------- /modules/shared/src/ReactFiberHostConfig/WithNoHydration.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/c5d2fc7127654e43de59fff865b74765a103c4a5/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | 11 | local invariant = require(script.Parent.Parent.invariant) 12 | 13 | -- Renderers that don't support hydration 14 | -- can re-export everything from this module. 15 | 16 | function shim(...) 17 | invariant( 18 | false, 19 | "The current renderer does not support hydration. " 20 | .. "This error is likely caused by a bug in React. " 21 | .. "Please file an issue." 22 | ) 23 | end 24 | 25 | -- Hydration (when unsupported) 26 | export type SuspenseInstance = any 27 | return { 28 | supportsHydration = false, 29 | canHydrateInstance = shim, 30 | canHydrateTextInstance = shim, 31 | canHydrateSuspenseInstance = shim, 32 | isSuspenseInstancePending = shim, 33 | isSuspenseInstanceFallback = shim, 34 | registerSuspenseInstanceRetry = shim, 35 | getNextHydratableSibling = shim, 36 | getFirstHydratableChild = shim, 37 | hydrateInstance = shim, 38 | hydrateTextInstance = shim, 39 | hydrateSuspenseInstance = shim, 40 | getNextHydratableInstanceAfterSuspenseInstance = shim, 41 | commitHydratedContainer = shim, 42 | commitHydratedSuspenseInstance = shim, 43 | clearSuspenseBoundary = shim, 44 | clearSuspenseBoundaryFromContainer = shim, 45 | didNotMatchHydratedContainerTextInstance = shim, 46 | didNotMatchHydratedTextInstance = shim, 47 | didNotHydrateContainerInstance = shim, 48 | didNotHydrateInstance = shim, 49 | didNotFindHydratableContainerInstance = shim, 50 | didNotFindHydratableContainerTextInstance = shim, 51 | didNotFindHydratableContainerSuspenseInstance = shim, 52 | didNotFindHydratableInstance = shim, 53 | didNotFindHydratableTextInstance = shim, 54 | didNotFindHydratableSuspenseInstance = shim, 55 | } 56 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | Roact is a lua port of Facebook's [React](https://reactjs.org) UI library. 2 | 3 | By and large, [React's documentation](https://reactjs.org/docs/getting-started.html) should be able to serve most Roact users' needs. This documentation site serves as a comprehensive guide to the _differences_ between Roact and React. 4 | 5 | If you're new to the React library and want **to start learning the concepts of React**, begin with the [React JS documentation](https://reactjs.org/docs/getting-started.html). 6 | 7 | If you want **to find out if a React feature is present in Roact (and if there are any differences to be aware of)**, check out the [API Reference](api-reference/react.md). 8 | 9 | If you're familiar with React and want **to learn where Roact differs**, start with the [Deviations page](deviations.md). 10 | 11 | And if you want **to migrate an existing project from Roact 1.x to Roact 17+**, check out the guide on [Migrating From Roact 1.x](migrating-from-1x/minimum-requirements.md). 12 | 13 | ### Which Part is "Roact"? 14 | 15 | Previously, **Roact** was the name that referred to a single package. It implemented a similar API to that of React 15. This documentation refers to those older versions of Roact as **legacy Roact** or **Roact 1.x**. 16 | 17 | Today, **Roact** is used as an umbrella term to describe the collection of packages that compose the Luau port of the React library. This collection of packages includes a few top-level ones, namely `React`, `ReactRoblox`, and `RoactCompat`. This documentation refers to the initial release of this version of Roact as **Roact 17** (because it aligns its implementation to React JS version 17.0.1) or **Roact 17+** (to include future releases). 18 | 19 | The originating React UI library, written in JavaScript, is referred to in this documentation as **React JS** in order to clearly disambiguate it from Roact and from the top-level package called `React`. In can be considered an umbrella term for the collection of packages, similar to "Roact". 20 | 21 | This documentation endeavors to use these terms consistently throughout to avoid confusion. 22 | -------------------------------------------------------------------------------- /modules/shared/README.md: -------------------------------------------------------------------------------- 1 | # shared 2 | A Roblox Lua port of the `shared` pseudo-package from React, which contains a number of common utilities and definitions used across the React monorepo. 3 | 4 | Status: ✔️ Ported 5 | 6 | Source: https://github.com/facebook/react/tree/master/packages/shared 7 | 8 | --- 9 | 10 | ### ✏️ Notes 11 | * `ReactTypes.js` contains a number of complex flow-type definitions that are not yet possible with Luau, so it's been simplified to a stub called `ReactTypes.roblox.lua` 12 | * `ReactComponentStackFrame.js` is replaced by a partially-ported stub (`ReactComponentStackFrame.roblox.lua`) since it contains logic for parsing/navigating JS-specific stack structure. This needs to be ported to the equivalent functionality in Luau. 13 | * Some slight changes to `isValidElement.lua` that account for the divergent shape of Component and PureComponent objects in our port (for `react`, they're functions; for us, they're tables) 14 | 15 | ### ❌ Excluded 16 | 17 | #### Forked Config 18 | ``` 19 | forks/ReactFeatureFlags.native-fb.js 20 | forks/ReactFeatureFlags.native-oss.js 21 | forks/ReactFeatureFlags.readonly.js 22 | forks/ReactFeatureFlags.test-renderer.js 23 | forks/ReactFeatureFlags.test-renderer.native.js 24 | forks/ReactFeatureFlags.test-renderer.www.js 25 | forks/ReactFeatureFlags.testing.js 26 | forks/ReactFeatureFlags.testing.www.js 27 | forks/ReactFeatureFlags.www-dynamic.js 28 | forks/ReactFeatureFlags.www.js 29 | forks/Scheduler.umd.js 30 | forks/SchedulerTracing.umd.js 31 | forks/consoleWithStackDev.www.js 32 | forks/invokeGuardedCallbackImpl.www.js 33 | forks/object-assign.inline-umd.js 34 | forks/object-assign.umd.js 35 | ``` 36 | 37 | Forks that specify different flag states are used in React with the help of a bundler that swaps in the correct file for the given environment. We don't have this kind of functionality yet, nor the same set of environments. 38 | 39 | #### Integration Tests 40 | ``` 41 | __tests__/describeComponentFrame-test.js 42 | __tests__/ReactError-test.internal.js 43 | ``` 44 | These tests required use of React and ReactDOM, and are not viable to port until we have more of the reconciler ported. -------------------------------------------------------------------------------- /modules/react-roblox/src/client/roblox/__tests__/getDefaultInstanceProperty.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/Roblox/roact/blob/b2ba9cf4c219c2654e6572219a68d0bf1b541418/src/getDefaultInstanceProperty.spec.lua 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | 17 | local Packages = script.Parent.Parent.Parent.Parent.Parent 18 | local JestGlobals = require(Packages.Dev.JestGlobals) 19 | local jestExpect = JestGlobals.expect 20 | local it = JestGlobals.it 21 | local getDefaultInstanceProperty = 22 | require(script.Parent.Parent.getDefaultInstanceProperty) 23 | 24 | it("should get default name string values", function() 25 | local _, defaultName = getDefaultInstanceProperty("StringValue", "Name") 26 | 27 | jestExpect(defaultName).toBe("Value") 28 | end) 29 | 30 | it("should get default empty string values", function() 31 | local _, defaultValue = getDefaultInstanceProperty("StringValue", "Value") 32 | 33 | jestExpect(defaultValue).toBe("") 34 | end) 35 | 36 | it("should get default number values", function() 37 | local _, defaultValue = getDefaultInstanceProperty("IntValue", "Value") 38 | 39 | jestExpect(defaultValue).toBe(0) 40 | end) 41 | 42 | it("should get nil default values", function() 43 | local _, defaultValue = getDefaultInstanceProperty("ObjectValue", "Value") 44 | 45 | jestExpect(defaultValue).toBe(nil) 46 | end) 47 | 48 | it("should get bool default values", function() 49 | local _, defaultValue = getDefaultInstanceProperty("BoolValue", "Value") 50 | 51 | jestExpect(defaultValue).toBe(false) 52 | end) 53 | -------------------------------------------------------------------------------- /bin/upstream-tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REACT_PATH=$1 4 | EQUIVALENT_FOLDER=$2 5 | 6 | echo "Matching upstream files $REACT_PATH/$EQUIVALENT_FOLDER..." 7 | 8 | if [ $# -ne 2 ]; then 9 | echo "Usage: 10 | upstream-tag.sh 11 | 12 | path_to_react: 13 | The path to the local copy of the react repository; this is where we find 14 | the upstream version information (using 'git log' commands) 15 | 16 | target_folder: 17 | For example, if you run this script from the 'modules' folder, then 18 | target_folder should be 'packages'. If you run it from 'modules/react-is/src, 19 | then target_folder should be 'packages/react-is/src'" 20 | exit 1 21 | fi 22 | 23 | count=0 24 | for file in $(find * -name "*.lua") 25 | do 26 | if [[ "$file" == *"roblox-jest"* ]] || [[ "$file" == *"roblox-js-polyfill"* ]]; then 27 | echo "SKIP: $file is Roblox-only" 28 | continue 29 | fi 30 | 31 | if [[ "$file" == *".roblox."*"lua" ]]; then 32 | echo "SKIP: $file is Roblox-only" 33 | continue 34 | fi 35 | 36 | if [[ `head -n 1 $file` == "-- ROBLOX upstream:"* ]]; then 37 | echo "SKIP: $file already has 'upstream' comment" 38 | continue 39 | fi 40 | 41 | targetFileName="${file/-internal.spec/-test.internal}" 42 | targetFileName="${targetFileName/.spec/-test}" 43 | targetFileName="${targetFileName/.lua/.js}" 44 | targetFile="$EQUIVALENT_FOLDER/$targetFileName" 45 | 46 | if [[ ! -f "$REACT_PATH/$targetFile" ]]; then 47 | echo "SKIP: Equivalent file $targetFileName not found" 48 | continue 49 | fi 50 | 51 | pushd $REACT_PATH > /dev/null 52 | COMMIT=`git log -- $targetFile | head -n 1 | sed "s/commit //g"` 53 | REPO_PATH=`realpath --relative-to=$REACT_PATH $targetFile` 54 | PREFIX="-- ROBLOX upstream: https://github.com/facebook/react/blob/$COMMIT/$REPO_PATH" 55 | if [[ "$COMMIT" == "" ]]; then 56 | echo "SKIP: Could not find commit for $targetFile -> $file" 57 | continue 58 | fi 59 | 60 | count=$((count+1)) 61 | 62 | echo "" 63 | echo "Prepend to $file..." 64 | echo $PREFIX 65 | popd > /dev/null 66 | echo "$PREFIX 67 | $(cat $file)" > $file 68 | done 69 | 70 | echo -e "\nAdded upstream tag to $count files" -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactFiberContext-internal.spec.lua: -------------------------------------------------------------------------------- 1 | -- awaiting pull request: https://github.com/facebook/react/pull/20155 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @emails react-core 9 | * @jest-environment node 10 | ]] 11 | 12 | local Packages = script.Parent.Parent.Parent 13 | local JestGlobals = require(Packages.Dev.JestGlobals) 14 | local jestExpect = JestGlobals.expect 15 | local jest = JestGlobals.jest 16 | local beforeEach = JestGlobals.beforeEach 17 | local describe = JestGlobals.describe 18 | local it = JestGlobals.it 19 | 20 | local ReactFiberContext 21 | local ReactFiber 22 | local ReactRootTags 23 | local ReactFeatureFlags 24 | 25 | beforeEach(function() 26 | jest.resetModules() 27 | 28 | ReactFiberContext = require(script.Parent.Parent["ReactFiberContext.new"]) 29 | ReactFiber = require(script.Parent.Parent["ReactFiber.new"]) 30 | ReactRootTags = require(script.Parent.Parent.ReactRootTags) 31 | ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags 32 | ReactFeatureFlags.disableLegacyContext = false 33 | end) 34 | 35 | describe("Context stack", function() 36 | it("should throw when pushing to top level of non-empty stack", function() 37 | local fiber = ReactFiber.createHostRootFiber(ReactRootTags.BlockingRoot) 38 | local context = { 39 | foo = 1, 40 | } 41 | -- The first call here is a valid use of pushTopLevelContextObject 42 | ReactFiberContext.pushTopLevelContextObject(fiber, context, true) 43 | jestExpect(function() 44 | local moreContext = { 45 | bar = 2, 46 | } 47 | ReactFiberContext.pushTopLevelContextObject(fiber, moreContext, true) 48 | end).toThrow("Unexpected context found on stack.") 49 | end) 50 | 51 | it("should throw if when invalidating a provider that isn't initialized", function() 52 | local fiber = ReactFiber.createHostRootFiber(ReactRootTags.BlockingRoot) 53 | jestExpect(function() 54 | ReactFiberContext.invalidateContextProvider(fiber, nil, true) 55 | end).toThrow("Expected to have an instance by this point.") 56 | end) 57 | end) 58 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/__tests__/bridge.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/bridge-test.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | ]] 8 | local Packages = script.Parent.Parent.Parent 9 | local JestGlobals = require(Packages.Dev.JestGlobals) 10 | local describe = JestGlobals.describe 11 | local it = JestGlobals.it 12 | local beforeEach = JestGlobals.beforeEach 13 | local jestExpect = JestGlobals.expect 14 | local jest = JestGlobals.jest 15 | 16 | describe("bridge", function() 17 | local Bridge 18 | 19 | beforeEach(function() 20 | jest.resetModules() 21 | jest.useFakeTimers() 22 | Bridge = require(script.Parent.Parent.bridge) 23 | end) 24 | 25 | it("should shutdown properly", function() 26 | local wall = { 27 | listen = jest.fn(function() 28 | return function() end 29 | end), 30 | send = jest.fn(), 31 | } 32 | local bridge = Bridge.new(wall) 33 | 34 | -- Check that we're wired up correctly. 35 | bridge:send("reloadAppForProfiling") 36 | jest.runAllTimers() 37 | jestExpect(wall.send).toHaveBeenCalledWith("reloadAppForProfiling") 38 | 39 | -- Should flush pending messages and then shut down. 40 | wall.send.mockClear() 41 | bridge:send("update", "1") 42 | bridge:send("update", "2") 43 | bridge:shutdown() 44 | jest.runAllTimers() 45 | jestExpect(wall.send).toHaveBeenCalledWith("update", "1") 46 | jestExpect(wall.send).toHaveBeenCalledWith("update", "2") 47 | jestExpect(wall.send).toHaveBeenCalledWith("shutdown") 48 | 49 | -- Verify that the Bridge doesn't send messages after shutdown. 50 | 51 | wall.send.mockClear() 52 | -- ROBLOX deviation: instead of spying on console, use toWarnDev matcher 53 | jestExpect(function() 54 | bridge:send("should not send") 55 | end).toWarnDev( 56 | 'Cannot send message "should not send" through a Bridge that has been shutdown.', 57 | { withoutStack = true } 58 | ) 59 | jest.runAllTimers() 60 | jestExpect(wall.send).never.toHaveBeenCalled() 61 | end) 62 | end) 63 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/backend/ReactSymbols.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/backend/ReactSymbols.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | ]] 8 | local exports = {} 9 | exports.CONCURRENT_MODE_NUMBER = 0xeacf 10 | exports.CONCURRENT_MODE_SYMBOL_STRING = "Symbol(react.concurrent_mode)" 11 | 12 | exports.CONTEXT_NUMBER = 0xeace 13 | exports.CONTEXT_SYMBOL_STRING = "Symbol(react.context)" 14 | 15 | exports.DEPRECATED_ASYNC_MODE_SYMBOL_STRING = "Symbol(react.async_mode)" 16 | 17 | exports.ELEMENT_NUMBER = 0xeac7 18 | exports.ELEMENT_SYMBOL_STRING = "Symbol(react.element)" 19 | 20 | exports.DEBUG_TRACING_MODE_NUMBER = 0xeae1 21 | exports.DEBUG_TRACING_MODE_SYMBOL_STRING = "Symbol(react.debug_trace_mode)" 22 | 23 | exports.FORWARD_REF_NUMBER = 0xead0 24 | exports.FORWARD_REF_SYMBOL_STRING = "Symbol(react.forward_ref)" 25 | 26 | exports.FRAGMENT_NUMBER = 0xeacb 27 | exports.FRAGMENT_SYMBOL_STRING = "Symbol(react.fragment)" 28 | 29 | exports.LAZY_NUMBER = 0xead4 30 | exports.LAZY_SYMBOL_STRING = "Symbol(react.lazy)" 31 | 32 | exports.MEMO_NUMBER = 0xead3 33 | exports.MEMO_SYMBOL_STRING = "Symbol(react.memo)" 34 | 35 | exports.OPAQUE_ID_NUMBER = 0xeae0 36 | exports.OPAQUE_ID_SYMBOL_STRING = "Symbol(react.opaque.id)" 37 | 38 | exports.PORTAL_NUMBER = 0xeaca 39 | exports.PORTAL_SYMBOL_STRING = "Symbol(react.portal)" 40 | 41 | exports.PROFILER_NUMBER = 0xead2 42 | exports.PROFILER_SYMBOL_STRING = "Symbol(react.profiler)" 43 | 44 | exports.PROVIDER_NUMBER = 0xeacd 45 | exports.PROVIDER_SYMBOL_STRING = "Symbol(react.provider)" 46 | 47 | exports.SCOPE_NUMBER = 0xead7 48 | exports.SCOPE_SYMBOL_STRING = "Symbol(react.scope)" 49 | 50 | exports.STRICT_MODE_NUMBER = 0xeacc 51 | exports.STRICT_MODE_SYMBOL_STRING = "Symbol(react.strict_mode)" 52 | 53 | exports.SUSPENSE_NUMBER = 0xead1 54 | exports.SUSPENSE_SYMBOL_STRING = "Symbol(react.suspense)" 55 | 56 | exports.SUSPENSE_LIST_NUMBER = 0xead8 57 | exports.SUSPENSE_LIST_SYMBOL_STRING = "Symbol(react.suspense_list)" 58 | 59 | return exports 60 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactFiberStack-test.roblox.spec.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.Parent.Parent 2 | local JestGlobals = require(Packages.Dev.JestGlobals) 3 | local jestExpect = JestGlobals.expect 4 | local describe = JestGlobals.describe 5 | local beforeEach = JestGlobals.beforeEach 6 | local jest = JestGlobals.jest 7 | local it = JestGlobals.it 8 | 9 | local ReactFiberStack 10 | 11 | describe("ReactFiberStack", function() 12 | beforeEach(function() 13 | jest.resetModules() 14 | ReactFiberStack = require(script.Parent.Parent["ReactFiberStack.new"]) 15 | end) 16 | 17 | it("creates a cursor with the given default value", function() 18 | local defaultValue = { foo = 3 } 19 | jestExpect(ReactFiberStack.createCursor(defaultValue)).toEqual({ 20 | current = defaultValue, 21 | }) 22 | end) 23 | 24 | it("initializes the stack empty", function() 25 | jestExpect(ReactFiberStack.isEmpty()).toBe(true) 26 | end) 27 | 28 | describe("stack manipulations", function() 29 | local cursor 30 | local fiber 31 | 32 | beforeEach(function() 33 | cursor = ReactFiberStack.createCursor(nil) 34 | fiber = {} 35 | end) 36 | 37 | it("pushes an element and the stack is not empty", function() 38 | ReactFiberStack.push(cursor, true, fiber) 39 | jestExpect(ReactFiberStack.isEmpty()).toBe(false) 40 | end) 41 | 42 | it("pushes an element and assigns the value to the cursor", function() 43 | local pushedElement = { foo = 3 } 44 | ReactFiberStack.push(cursor, pushedElement, fiber) 45 | jestExpect(cursor.current).toEqual(pushedElement) 46 | end) 47 | 48 | it("pushes an element, pops it back and the stack is empty", function() 49 | ReactFiberStack.push(cursor, true, fiber) 50 | ReactFiberStack.pop(cursor, fiber) 51 | jestExpect(ReactFiberStack.isEmpty()).toBe(true) 52 | end) 53 | 54 | it( 55 | "pushes an element, pops it back and the cursor has its initial value", 56 | function() 57 | local initialCursorValue = "foo" 58 | cursor.current = initialCursorValue 59 | 60 | ReactFiberStack.push(cursor, true, fiber) 61 | ReactFiberStack.pop(cursor, fiber) 62 | jestExpect(cursor.current).toBe(initialCursorValue) 63 | end 64 | ) 65 | end) 66 | end) 67 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactFiberSuspenseContext.roblox.spec.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.Parent.Parent 2 | local JestGlobals = require(Packages.Dev.JestGlobals) 3 | local jestExpect = JestGlobals.expect 4 | local jest = JestGlobals.jest 5 | local beforeEach = JestGlobals.beforeEach 6 | local describe = JestGlobals.describe 7 | local it = JestGlobals.it 8 | local ReactFiber = require(script.Parent.Parent["ReactFiber.new"]) 9 | 10 | local ReactFiberSuspenseContext 11 | 12 | describe("ReactFiberSuspenseContext", function() 13 | beforeEach(function() 14 | jest.resetModules() 15 | ReactFiberSuspenseContext = 16 | require(script.Parent.Parent["ReactFiberSuspenseContext.new"]) 17 | end) 18 | 19 | describe("suspense context stack", function() 20 | local someContext 21 | local fiber 22 | local suspenseStackCursor 23 | 24 | beforeEach(function() 25 | someContext = 0b1000 26 | fiber = ReactFiber.createFiberFromText("", 0, 0) 27 | suspenseStackCursor = ReactFiberSuspenseContext.suspenseStackCursor 28 | end) 29 | 30 | it("pushes the context and assigns the value to the cursor", function() 31 | ReactFiberSuspenseContext.pushSuspenseContext(fiber, someContext) 32 | jestExpect(suspenseStackCursor).toEqual({ current = someContext }) 33 | end) 34 | 35 | it("pushes and pops and sets the cursor to its initial value", function() 36 | local initialValue = suspenseStackCursor.current 37 | 38 | ReactFiberSuspenseContext.pushSuspenseContext(fiber, someContext) 39 | ReactFiberSuspenseContext.popSuspenseContext(fiber) 40 | jestExpect(suspenseStackCursor).toEqual({ current = initialValue }) 41 | end) 42 | end) 43 | 44 | describe("hasSuspenseContext", function() 45 | it("is true for parent context and its subtree context", function() 46 | local subtree = 0b1000 47 | local parent = 48 | ReactFiberSuspenseContext.addSubtreeSuspenseContext(10000, subtree) 49 | 50 | jestExpect(ReactFiberSuspenseContext.hasSuspenseContext(parent, subtree)).toBe( 51 | true 52 | ) 53 | end) 54 | 55 | it("is false for two different context", function() 56 | jestExpect(ReactFiberSuspenseContext.hasSuspenseContext(0b1000, 0b10000)).toBe( 57 | false 58 | ) 59 | end) 60 | end) 61 | end) 62 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/43363e2795393a00fd77312a16d6b80e626c29de/packages/react-reconciler/src/index.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @flow 9 | ]] 10 | 11 | --!strict 12 | local ReactInternalTypes = require(script.ReactInternalTypes) 13 | local ReactRootTags = require(script.ReactRootTags) 14 | 15 | export type Dispatcher = ReactInternalTypes.Dispatcher 16 | export type Fiber = ReactInternalTypes.Fiber 17 | export type FiberRoot = ReactInternalTypes.FiberRoot 18 | 19 | -- ROBLOX deviation: explicit export for use in createReactNoop 20 | export type UpdateQueue = ReactInternalTypes.UpdateQueue 21 | 22 | export type RootTag = ReactRootTags.RootTag 23 | 24 | -- ROBLOX deviation: export types needed by ReactFreshRuntime 25 | local ReactFiberHostConfigModule = require(script.ReactFiberHostConfig) 26 | export type Instance = ReactFiberHostConfigModule.Instance 27 | local ReactFiberHotReloading = require(script["ReactFiberHotReloading.new"]) 28 | export type Family = ReactFiberHotReloading.Family 29 | export type RefreshUpdate = ReactFiberHotReloading.RefreshUpdate 30 | export type SetRefreshHandler = ReactFiberHotReloading.SetRefreshHandler 31 | export type ScheduleRefresh = ReactFiberHotReloading.ScheduleRefresh 32 | export type ScheduleRoot = ReactFiberHotReloading.ScheduleRoot 33 | export type FindHostInstancesForRefresh = ReactFiberHotReloading.FindHostInstancesForRefresh 34 | 35 | -- ROBLOX deviation: In order to allow host config to be spliced in, we export 36 | -- this top-level package as an initializer function that returns the configured 37 | -- reconciler module 38 | -- ROBLOX TODO: this effectively disconnects type checking from above to reconciler to below 39 | local function initialize(config): { [string]: any } 40 | local ReactFiberHostConfig = require(script.ReactFiberHostConfig) 41 | for name, implementation in config do 42 | ReactFiberHostConfig[name] = implementation 43 | end 44 | 45 | return require(script.ReactFiberReconciler) 46 | end 47 | 48 | return initialize 49 | -------------------------------------------------------------------------------- /modules/scheduler/src/__tests__/Tracing.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/9abc2785cb070148d64fae81e523246b90b92016/packages/scheduler/src/__tests__/Tracing-test.js 2 | -- /** 3 | -- * Copyright (c) Facebook, Inc. and its affiliates. 4 | -- * 5 | -- * This source code is licensed under the MIT license found in the 6 | -- * LICENSE file in the root directory of this source tree. 7 | -- * 8 | -- * @jest-environment node 9 | -- */ 10 | 11 | local Packages = script.Parent.Parent.Parent 12 | local JestGlobals = require(Packages.Dev.JestGlobals) 13 | local beforeEach = JestGlobals.beforeEach 14 | local describe = JestGlobals.describe 15 | local it = JestGlobals.it 16 | local jest = JestGlobals.jest 17 | 18 | describe("Tracing", function() 19 | local JestGlobals = require(Packages.Dev.JestGlobals) 20 | local jestExpect = JestGlobals.expect 21 | 22 | local SchedulerTracing 23 | 24 | beforeEach(function() 25 | jest.resetModules() 26 | 27 | SchedulerTracing = require(Packages.Scheduler).tracing 28 | end) 29 | 30 | it("should return the value of a traced function", function() 31 | jestExpect(SchedulerTracing.unstable_trace("arbitrary", 0, function() 32 | return 123 33 | end)).toBe(123) 34 | end) 35 | 36 | it("should return the value of a wrapped function", function() 37 | local wrapped 38 | SchedulerTracing.unstable_trace("arbitrary", 0, function() 39 | wrapped = SchedulerTracing.unstable_wrap(function() 40 | return 123 41 | end) 42 | end) 43 | jestExpect(wrapped()).toBe(123) 44 | end) 45 | 46 | it("should execute traced callbacks", function() 47 | local done = false 48 | 49 | SchedulerTracing.unstable_trace("some event", 0, function() 50 | done = true 51 | end) 52 | 53 | jestExpect(done).toBe(true) 54 | end) 55 | 56 | it("should return the value of a clear function", function() 57 | jestExpect(SchedulerTracing.unstable_clear(function() 58 | return 123 59 | end)).toBe(123) 60 | end) 61 | 62 | it("should execute wrapped callbacks", function() 63 | local done = false 64 | local wrappedCallback = SchedulerTracing.unstable_wrap(function() 65 | done = true 66 | end) 67 | 68 | wrappedCallback() 69 | jestExpect(done).toBe(true) 70 | end) 71 | end) 72 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactClassSetStateCallback.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/d7dce572c7453737a685e791e7afcbc7e2b2fe16/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @emails react-core 9 | * @jest-environment node 10 | ]] 11 | 12 | --[[ eslint-disable no-func-assign ]] 13 | local Packages = script.Parent.Parent.Parent 14 | local React 15 | 16 | local ReactNoop 17 | local Scheduler 18 | 19 | local JestGlobals = require(Packages.Dev.JestGlobals) 20 | local jestExpect = JestGlobals.expect 21 | local beforeEach = JestGlobals.beforeEach 22 | local it = JestGlobals.it 23 | local jest = JestGlobals.jest 24 | 25 | beforeEach(function() 26 | jest.resetModules() 27 | jest.useFakeTimers() 28 | 29 | React = require(Packages.React) 30 | ReactNoop = require(Packages.Dev.ReactNoopRenderer) 31 | Scheduler = require(Packages.Scheduler) 32 | end) 33 | 34 | local function Text(props) 35 | Scheduler.unstable_yieldValue(props.text) 36 | return React.createElement("span", { 37 | prop = props.text, 38 | }) 39 | end 40 | 41 | it( 42 | "regression: setState callback (2nd arg) should only fire once, even after a rebase", 43 | function() 44 | local app 45 | local App = React.Component:extend("App") 46 | function App:init() 47 | self:setState({ step = 0 }) 48 | end 49 | function App:render() 50 | app = self 51 | return React.createElement(Text, { text = self.state.step }) 52 | end 53 | 54 | local root = ReactNoop.createRoot() 55 | ReactNoop.act(function() 56 | root.render(React.createElement(App)) 57 | end) 58 | jestExpect(Scheduler).toHaveYielded({ 0 }) 59 | 60 | ReactNoop.act(function() 61 | app:setState({ step = 1 }, function() 62 | return Scheduler.unstable_yieldValue("Callback 1") 63 | end) 64 | 65 | ReactNoop.flushSync(function() 66 | app:setState({ step = 2 }, function() 67 | return Scheduler.unstable_yieldValue("Callback 2") 68 | end) 69 | end) 70 | end) 71 | jestExpect(Scheduler).toHaveYielded({ 2, "Callback 2", 2, "Callback 1" }) 72 | end 73 | ) 74 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/src/backend/views/Highlighter/Overlay/OverlayRect.lua: -------------------------------------------------------------------------------- 1 | local OverlayRect = {} 2 | OverlayRect.__index = OverlayRect 3 | 4 | function OverlayRect.new(container: GuiBase2d) 5 | local self = setmetatable({}, OverlayRect) 6 | self.container = container 7 | 8 | local node = Instance.new("Frame") 9 | node.Name = "OverlayRect" 10 | node.BackgroundTransparency = 1 11 | node.Parent = container 12 | self.node = node 13 | 14 | local padding = Instance.new("Frame") 15 | padding.Name = "OverlayRectPadding" 16 | padding.BackgroundColor3 = Color3.fromRGB(77, 200, 0) 17 | padding.Size = UDim2.fromScale(1, 1) 18 | padding.BackgroundTransparency = 0.4 19 | padding.BorderSizePixel = 0 20 | padding.Parent = node 21 | self.padding = padding 22 | 23 | local content = Instance.new("Frame") 24 | content.Name = "OverlayRectContent" 25 | content.BackgroundColor3 = Color3.fromRGB(120, 170, 210) 26 | content.Size = UDim2.fromScale(1, 1) 27 | content.BorderSizePixel = 0 28 | content.ZIndex = 2 29 | content.Parent = node 30 | self.content = content 31 | 32 | return self 33 | end 34 | 35 | export type OverlayRect = typeof(OverlayRect.new(...)) 36 | 37 | function OverlayRect.remove(self: OverlayRect) 38 | self.node:Destroy() 39 | end 40 | 41 | function OverlayRect.update(self: OverlayRect, element: GuiBase2d) 42 | local size = element.AbsoluteSize 43 | local position = element.AbsolutePosition 44 | 45 | local padding = element:FindFirstChildOfClass("UIPadding") 46 | if padding then 47 | local top = (padding.PaddingTop.Scale * size.Y) + padding.PaddingTop.Offset 48 | local left = (padding.PaddingLeft.Scale * size.X) + padding.PaddingLeft.Offset 49 | local bottom = (padding.PaddingBottom.Scale * size.Y) 50 | + padding.PaddingBottom.Offset 51 | local right = (padding.PaddingRight.Scale * size.X) + padding.PaddingRight.Offset 52 | 53 | self.content.Position = UDim2.fromOffset(left, top) 54 | self.content.Size = UDim2.fromOffset(size.X - left - right, size.Y - top - bottom) 55 | else 56 | self.content.Position = UDim2.fromOffset(0, 0) 57 | self.content.Size = UDim2.fromOffset(size.X, size.Y) 58 | end 59 | 60 | self.node.Size = UDim2.fromOffset(size.X, size.Y) 61 | self.node.Position = UDim2.fromOffset(position.X, position.Y) 62 | end 63 | 64 | return { 65 | new = OverlayRect.new, 66 | } 67 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactNoopRendererAct.spec.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/d17086c7c813402a550d15a2f56dc43f1dbd1735/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * @jest-environment node 9 | ]] 10 | 11 | -- sanity tests for ReactNoop.act() 12 | 13 | local Packages = script.Parent.Parent.Parent 14 | local React 15 | local ReactNoop 16 | local Scheduler 17 | 18 | local JestGlobals = require(Packages.Dev.JestGlobals) 19 | local Promise = require(Packages.Promise) 20 | local jestExpect = JestGlobals.expect 21 | local beforeEach = JestGlobals.beforeEach 22 | local it = JestGlobals.it 23 | local jest = JestGlobals.jest 24 | 25 | beforeEach(function() 26 | jest.resetModules() 27 | 28 | React = require(Packages.React) 29 | ReactNoop = require(Packages.Dev.ReactNoopRenderer) 30 | Scheduler = require(Packages.Scheduler) 31 | end) 32 | 33 | it("can use act to flush effects", function() 34 | local function App(props) 35 | React.useEffect(props.callback) 36 | return nil 37 | end 38 | 39 | local calledLog = {} 40 | ReactNoop.act(function() 41 | ReactNoop.render(React.createElement(App, { 42 | callback = function() 43 | table.insert(calledLog, #calledLog) 44 | end, 45 | })) 46 | end) 47 | jestExpect(Scheduler).toFlushWithoutYielding() 48 | jestExpect(calledLog).toEqual({ 0 }) 49 | end) 50 | it("should work with async/await", function() 51 | local function App() 52 | local ctr, setCtr = React.useState(0) 53 | local function someAsyncFunction() 54 | Scheduler.unstable_yieldValue("stage 1") 55 | Scheduler.unstable_yieldValue("stage 2") 56 | setCtr(1) 57 | end 58 | React.useEffect(function() 59 | someAsyncFunction() 60 | end, {}) 61 | return ctr 62 | end 63 | Promise.try(function() 64 | ReactNoop.act(function() 65 | ReactNoop.render(React.createElement(App)) 66 | end) 67 | end):await() 68 | jestExpect(Scheduler).toHaveYielded({ "stage 1", "stage 2" }) 69 | jestExpect(Scheduler).toFlushWithoutYielding() 70 | jestExpect(ReactNoop.getChildren()).toEqual({ { text = "1", hidden = false } }) 71 | end) 72 | -------------------------------------------------------------------------------- /modules/shared/src/consoleWithStackDev.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/cb141681750c8221ac799074df09df2bb448c7a4/packages/shared/consoleWithStackDev.js 2 | --[[* 3 | * Copyright (c) Facebook, Inc. and its affiliates. 4 | * 5 | * This source code is licensed under the MIT license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | ]] 8 | local Packages = script.Parent.Parent 9 | local ReactGlobals = require(Packages.ReactGlobals) 10 | local LuauPolyfill = require(Packages.LuauPolyfill) 11 | local console = LuauPolyfill.console 12 | local Array = LuauPolyfill.Array 13 | 14 | local ReactSharedInternals = require(script.Parent.ReactSharedInternals) 15 | -- In DEV, calls to console.warn and console.error get replaced 16 | -- by calls to these methods by a Babel plugin. 17 | -- 18 | -- In PROD (or in packages without access to React internals), 19 | -- they are left as they are instead. 20 | 21 | -- deviation: declare this ahead of time so that `warn` and `error` are able to 22 | -- reference it 23 | local printWarning 24 | 25 | local exports = {} 26 | exports.warn = function(format, ...) 27 | if ReactGlobals.__DEV__ then 28 | printWarning("warn", format, { ... }) 29 | end 30 | end 31 | exports.error = function(format, ...) 32 | if ReactGlobals.__DEV__ then 33 | printWarning("error", format, { ... }) 34 | end 35 | end 36 | 37 | function printWarning(level, format, args) 38 | -- When changing this logic, you might want to also 39 | -- update consoleWithStackDev.www.js as well. 40 | if ReactGlobals.__DEV__ then 41 | local ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame 42 | local stack = ReactDebugCurrentFrame.getStackAddendum() 43 | 44 | if stack ~= "" then 45 | format ..= "%s" 46 | -- deviation: no array `concat` function in lua 47 | args = Array.slice(args, 1) 48 | table.insert(args, stack) 49 | end 50 | 51 | local argsWithFormat = Array.map(args, tostring) 52 | -- Careful: RN currently depends on this prefix 53 | table.insert(argsWithFormat, 1, "Warning: " .. format) 54 | -- We intentionally don't use spread (or .apply) directly because it 55 | -- breaks IE9: https://github.com/facebook/react/issues/13610 56 | -- eslint-disable-next-line react-internal/no-production-logging 57 | console[level](unpack(argsWithFormat)) 58 | end 59 | end 60 | 61 | return exports 62 | -------------------------------------------------------------------------------- /modules/react-reconciler/src/__tests__/ReactFiberComponentStack.roblox.spec.lua: -------------------------------------------------------------------------------- 1 | local Packages = script.Parent.Parent.Parent 2 | local LuauPolyfill = require(Packages.LuauPolyfill) 3 | local Error = LuauPolyfill.Error 4 | local JestGlobals = require(Packages.Dev.JestGlobals) 5 | local jestExpect = JestGlobals.expect 6 | local describe = JestGlobals.describe 7 | local beforeEach = JestGlobals.beforeEach 8 | local it = JestGlobals.it 9 | local jest = JestGlobals.jest 10 | local ReactInternalTypes = require(script.Parent.Parent.ReactInternalTypes) 11 | type Fiber = ReactInternalTypes.Fiber 12 | 13 | local ReactFiberComponentStack 14 | 15 | describe("ReactFiberComponentStack", function() 16 | beforeEach(function() 17 | jest.resetModules() 18 | ReactFiberComponentStack = require(script.Parent.Parent.ReactFiberComponentStack) 19 | end) 20 | 21 | it("given a nil fiber then it gives correct error message", function() 22 | local message = 23 | ReactFiberComponentStack.getStackByFiberInDevAndProd((nil :: any) :: Fiber) 24 | jestExpect(message).toContain("attempt to index nil") 25 | end) 26 | 27 | it("given a fiber that throws Error then it gives correct error message", function() 28 | local throwingFiber = {} 29 | setmetatable(throwingFiber, { 30 | __index = function(t, k) 31 | if k == "tag" then 32 | error(Error.new("this was an error object in a spec file")) 33 | end 34 | return nil 35 | end, 36 | }) 37 | 38 | local message = ReactFiberComponentStack.getStackByFiberInDevAndProd( 39 | (throwingFiber :: any) :: Fiber 40 | ) 41 | jestExpect(message).toContain("this was an error object in a spec file") 42 | end) 43 | 44 | it( 45 | "given a fiber that throws a non-Error table then it gives correct error message", 46 | function() 47 | local customErrorTable = {} 48 | setmetatable(customErrorTable, { 49 | __tostring = function(t, k) 50 | return "this was a custom __tostring" 51 | end, 52 | }) 53 | 54 | local throwingFiber = {} 55 | setmetatable(throwingFiber, { 56 | __index = function(t, k) 57 | if k == "tag" then 58 | error(customErrorTable) 59 | end 60 | return nil 61 | end, 62 | }) 63 | 64 | local message = ReactFiberComponentStack.getStackByFiberInDevAndProd( 65 | (throwingFiber :: any) :: Fiber 66 | ) 67 | jestExpect(message).toContain("this was a custom __tostring") 68 | end 69 | ) 70 | end) 71 | -------------------------------------------------------------------------------- /modules/react-devtools-extensions/src/init.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX note: no upstream 2 | -- ROBLOX note: The setup function adds the glue required for DeveloperTools to initialize the Roact devtools correctly 3 | local Packages = script.Parent 4 | 5 | return { 6 | setup = function(debugMode: boolean) 7 | local ReactGlobals = require(Packages.ReactGlobals) 8 | 9 | -- ROBLOX note: Set globals for React devtools to work 10 | ReactGlobals.__DEV__ = true 11 | ReactGlobals.__DEBUG__ = debugMode or false 12 | ReactGlobals.__PROFILE__ = true 13 | ReactGlobals.__EXPERIMENTAL__ = true 14 | -- ROBLOX note: Don't hide host coomponents as the current Developer Inspector uses on these to preserve a 15 | -- direct mapping between the Inspector tree and the Explorer tree as requested by design. 16 | ReactGlobals.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = {} 17 | 18 | local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared) 19 | local setup = require(Packages.ReactDevtoolsExtensions.backend).setup 20 | local installHook = ReactDevtoolsShared.hook.installHook 21 | local Store = ReactDevtoolsShared.devtools.store 22 | 23 | -- ROBLOX note: Ensure that the global hook is installed before the injection into DevTools 24 | installHook(ReactGlobals) 25 | 26 | -- ROBLOX note: Ensure that ReactRoblox is loaded after injection so that the ReactHostConfig is populated correctly 27 | require(Packages.React) 28 | require(Packages.ReactRoblox) 29 | 30 | local hook = ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__ 31 | 32 | -- ROBLOX note: Make sure that this method was called before ReactRoblox was first required, 33 | -- otherwise the profiler will not be enabled for the session. 34 | local ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags 35 | if not ReactFeatureFlags.enableSchedulingProfiler then 36 | warn( 37 | "[DeveloperTools] React was initialized before DeveloperTools. Call inspector.setupReactDevtools before requiring React to enable profiling." 38 | ) 39 | end 40 | 41 | local result = setup(hook) 42 | 43 | -- ROBLOX note: The DeveloperTools library is only passed the ReactDevtoolsExtensions API to keep the 44 | -- devtools init process compact for users. Initialize the store so DeveloperTools doesn't also need to be 45 | -- passed the ReactDevtoolsShared API. 46 | return { 47 | agent = result.agent, 48 | bridge = result.bridge, 49 | hook = result.hook, 50 | store = Store.new(result.bridge), 51 | } 52 | end, 53 | } 54 | -------------------------------------------------------------------------------- /modules/react/src/ReactCreateRef.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react/src/ReactCreateRef.js 2 | --!strict 3 | --[[* 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * @flow 9 | *]] 10 | 11 | local Packages = script.Parent.Parent 12 | local ReactGlobals = require(Packages.ReactGlobals) 13 | local ReactTypes = require(Packages.Shared) 14 | type RefObject = ReactTypes.RefObject 15 | 16 | -- ROBLOX DEVIATION: In Roact, refs are implemented in terms of bindings 17 | --[[ 18 | A ref is nothing more than a binding with a special field 'current' 19 | that maps to the getValue method of the binding 20 | ]] 21 | local Binding = require(script.Parent["ReactBinding.roblox"]) 22 | 23 | local exports = {} 24 | 25 | -- an immutable object with a single mutable value 26 | exports.createRef = function(): RefObject 27 | local binding, _ = Binding.create(nil) 28 | 29 | local ref = {} 30 | 31 | -- ROBLOX DEVIATION: Since refs are used as bindings, they can often be 32 | -- assigned to fields of other Instances; we track creation here parallel to 33 | -- how we do with bindings created via `createBinding` to improve messaging 34 | -- when something goes wrong 35 | if ReactGlobals.__DEV__ then 36 | -- ROBLOX TODO: LUAFDN-619 - improve debug stacktraces for refs 37 | binding._source = debug.traceback("Ref created at:", 1) 38 | end 39 | 40 | --[[ 41 | A ref is just redirected to a binding via its metatable 42 | ]] 43 | setmetatable(ref, { 44 | __index = function(self, key) 45 | if key == "current" then 46 | return binding:getValue() 47 | else 48 | return (binding :: any)[key] 49 | end 50 | end, 51 | __newindex = function(self, key, value) 52 | if key == "current" then 53 | -- ROBLOX FIXME: Bindings - This is not allowed in Roact, but is okay in 54 | -- React. Lots of discussion at 55 | -- https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065 56 | -- error("Cannot assign to the 'current' property of refs", 2) 57 | Binding.update(binding, value) 58 | end 59 | 60 | (binding :: any)[key] = value 61 | end, 62 | __tostring = function(self) 63 | return string.format("Ref(%s)", tostring(binding:getValue())) 64 | end, 65 | }) 66 | 67 | return (ref :: any) :: RefObject 68 | end 69 | 70 | return exports 71 | -------------------------------------------------------------------------------- /modules/react-roblox/src/client/roblox/getDefaultInstanceProperty.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/Roblox/roact/blob/b2ba9cf4c219c2654e6572219a68d0bf1b541418/src/getDefaultInstanceProperty.lua 2 | --[[ 3 | * Copyright (c) Roblox Corporation. All rights reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ]] 16 | 17 | --[[ 18 | Attempts to get the default value of a given property on a Roblox instance. 19 | 20 | This is used by the reconciler in cases where a prop was previously set on a 21 | primitive component, but is no longer present in a component's new props. 22 | 23 | Eventually, Roblox might provide a nicer API to query the default property 24 | of an object without constructing an instance of it. 25 | ]] 26 | 27 | local Packages = script.Parent.Parent.Parent.Parent 28 | local Symbol = require(Packages.Shared).Symbol 29 | 30 | local Nil = Symbol.named("Nil") 31 | local _cachedPropertyValues = {} 32 | 33 | local function tryPropertyName(instance, propertyName) 34 | return instance[propertyName] 35 | end 36 | 37 | local function getDefaultInstanceProperty(className, propertyName) 38 | local classCache = _cachedPropertyValues[className] 39 | 40 | if classCache then 41 | local propValue = classCache[propertyName] 42 | 43 | -- We have to use a marker here, because Lua doesn't distinguish 44 | -- between 'nil' and 'not in a table' 45 | if propValue == Nil then 46 | return true, nil 47 | end 48 | 49 | if propValue ~= nil then 50 | return true, propValue 51 | end 52 | else 53 | classCache = {} 54 | _cachedPropertyValues[className] = classCache 55 | end 56 | 57 | local created = Instance.new(className) 58 | local ok, defaultValue = pcall(tryPropertyName, created, propertyName) 59 | 60 | created:Destroy() 61 | 62 | if ok then 63 | if defaultValue == nil then 64 | classCache[propertyName] = Nil 65 | else 66 | classCache[propertyName] = defaultValue 67 | end 68 | end 69 | 70 | return ok, defaultValue 71 | end 72 | 73 | return getDefaultInstanceProperty 74 | --------------------------------------------------------------------------------