├── .editorconfig ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .styluaignore ├── CODE_OF_CONDUCT.md ├── DEVIATIONS.md ├── LICENSE ├── Packages └── .robloxrc ├── README.md ├── WorkspaceStatic ├── jest.config.lua └── jest │ ├── .robloxrc │ ├── matchers │ ├── __tests__ │ │ └── toWarnDev.spec.lua │ ├── createConsoleMatcher.lua │ ├── interactionTracingMatchers.lua │ ├── reactTestMatchers.lua │ ├── schedulerTestMatchers.lua │ ├── toErrorDev.lua │ ├── toLogDev.lua │ └── toWarnDev.lua │ └── testSetupFile.lua ├── bin ├── ci-benchmarks.sh ├── ci.sh ├── compare-benchmarks.py ├── convert.sh ├── run-benchmarks.py ├── run-deep-tree-benchmark.lua ├── run-first-render-benchmark.lua ├── run-frame-rate-benchmark.lua ├── run-sierpinski-triangle-benchmark.lua ├── run-wide-tree-benchmark.lua ├── run-with-cachegrind.sh ├── spec.lua ├── svg.py ├── testing.sh └── upstream-tag.sh ├── codecov.yml ├── default.project.json ├── docs ├── align-files-guide.md ├── api-reference │ ├── additional-libraries.md │ ├── react-roblox.md │ ├── react.md │ └── roact-compat.md ├── bench.md ├── bench │ ├── data.json │ └── index.js ├── configuration.md ├── deviations.md ├── 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-packages.png ├── diff-spread-flow-type-no-deviation-anymore.png ├── diff-upstream-comment.png ├── extra.css ├── images │ ├── aligned.svg │ ├── apichange.svg │ ├── deviation.svg │ ├── reactjs.svg │ └── roblox.svg ├── index.md ├── migrating-from-1x │ ├── adopt-new-features.md │ ├── convert-legacy-conventions.md │ ├── minimum-requirements.md │ └── upgrading-to-roact-17.md └── requirements.txt ├── examples.project.json ├── examples ├── ProjectWorkspace │ ├── DeveloperTools.lua │ ├── React.lua │ ├── ReactDevtoolsExtensions.lua │ └── ReactRoblox.lua ├── binding │ └── init.lua ├── changed-signal │ └── init.lua ├── clock │ └── init.lua ├── event │ └── init.lua ├── hello-roact │ └── init.lua ├── init.client.lua ├── ref │ └── init.lua └── stress-test │ └── init.lua ├── foreman.toml ├── js-to-lua.config.js ├── mkdocs.yml ├── modules ├── TestRunner │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ └── init.lua ├── jest-react │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── JestReact.lua │ │ └── init.lua ├── react-cache │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── LRU.lua │ │ ├── ReactCacheOld.lua │ │ ├── __tests__ │ │ └── ReactCacheOld-internal.spec.lua │ │ └── init.lua ├── react-debug-tools │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── ReactDebugHooks.lua │ │ ├── ReactDebugTools.lua │ │ ├── __tests__ │ │ ├── ReactDevToolsHooksIntegration.spec.lua │ │ ├── ReactHooksInspection.spec.lua │ │ └── ReactHooksInspectionIntegration.spec.lua │ │ └── init.lua ├── react-devtools-core │ ├── .luaurc │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── backend.lua │ │ ├── init.lua │ │ └── utils │ │ └── serializeTable.lua ├── react-devtools-extensions │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── __tests__ │ │ └── devtools-integration.roblox.spec.lua │ │ ├── backend.lua │ │ └── init.lua ├── react-devtools-shared │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── storeComponentFilters.spec.snap.lua │ │ ├── bridge.spec.lua │ │ ├── console.spec.lua │ │ ├── events.spec.lua │ │ ├── profilerStore.spec.lua │ │ ├── profilingCache.spec.lua │ │ ├── profilingCharts.spec.lua │ │ ├── profilingCommitTreeBuilder.spec.lua │ │ ├── profilingUtils.spec.lua │ │ ├── setupTests.lua │ │ ├── store.spec.lua │ │ ├── storeComponentFilters.spec.lua │ │ ├── storeOwners.spec.lua │ │ ├── utils.lua │ │ └── utils.spec.lua │ │ ├── backend │ │ ├── NativeStyleEditor │ │ │ └── types.lua │ │ ├── ReactSymbols.lua │ │ ├── agent.lua │ │ ├── console.lua │ │ ├── init.lua │ │ ├── renderer.lua │ │ ├── types.lua │ │ ├── utils.lua │ │ └── views │ │ │ └── Highlighter │ │ │ ├── Highlighter.lua │ │ │ ├── Overlay │ │ │ ├── Overlay.lua │ │ │ ├── OverlayRect.lua │ │ │ └── OverlayTip.lua │ │ │ └── init.lua │ │ ├── bridge.lua │ │ ├── clipboardjs.mock.lua │ │ ├── constants.lua │ │ ├── devtools │ │ ├── ProfilerStore.lua │ │ ├── ProfilingCache.lua │ │ ├── cache.lua │ │ ├── init.lua │ │ ├── store.lua │ │ ├── types.lua │ │ ├── utils.lua │ │ └── views │ │ │ ├── Components │ │ │ └── types.lua │ │ │ └── Profiler │ │ │ ├── CommitTreeBuilder.lua │ │ │ ├── FlamegraphChartBuilder.lua │ │ │ ├── InteractionsChartBuilder.lua │ │ │ ├── RankedChartBuilder.lua │ │ │ ├── types.lua │ │ │ └── utils.lua │ │ ├── events.lua │ │ ├── hook.lua │ │ ├── hydration.lua │ │ ├── init.lua │ │ ├── jest.config.lua │ │ ├── storage.lua │ │ ├── types.lua │ │ └── utils.lua ├── react-devtools │ ├── .luaurc │ ├── .robloxrc │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ └── init.lua ├── react-is │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── __tests__ │ │ └── ReactIs.spec.lua │ │ └── init.lua ├── react-noop-renderer │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── ReactNoop.lua │ │ ├── createReactNoop.lua │ │ └── init.lua ├── react-reconciler │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── DebugTracing.lua │ │ ├── MaxInts.lua │ │ ├── ReactCapturedValue.lua │ │ ├── ReactChildFiber.new.lua │ │ ├── ReactCurrentFiber.lua │ │ ├── ReactFiber.new.lua │ │ ├── ReactFiberBeginWork.new.lua │ │ ├── ReactFiberClassComponent.new.lua │ │ ├── ReactFiberCommitWork.new.lua │ │ ├── ReactFiberCompleteWork.new.lua │ │ ├── ReactFiberComponentStack.lua │ │ ├── ReactFiberContext.new.lua │ │ ├── ReactFiberDevToolsHook.new.lua │ │ ├── ReactFiberErrorDialog.lua │ │ ├── ReactFiberErrorLogger.lua │ │ ├── ReactFiberFlags.lua │ │ ├── ReactFiberHooks.new.lua │ │ ├── ReactFiberHostConfig.lua │ │ ├── ReactFiberHostContext.new.lua │ │ ├── ReactFiberHotReloading.new.lua │ │ ├── ReactFiberHydrationContext.new.lua │ │ ├── ReactFiberLane.lua │ │ ├── ReactFiberLazyComponent.new.lua │ │ ├── ReactFiberNewContext.new.lua │ │ ├── ReactFiberOffscreenComponent.lua │ │ ├── ReactFiberReconciler.lua │ │ ├── ReactFiberReconciler.new.lua │ │ ├── ReactFiberRoot.new.lua │ │ ├── ReactFiberSchedulerPriorities.roblox.lua │ │ ├── ReactFiberStack.new.lua │ │ ├── ReactFiberSuspenseComponent.new.lua │ │ ├── ReactFiberSuspenseContext.new.lua │ │ ├── ReactFiberThrow.new.lua │ │ ├── ReactFiberTransition.lua │ │ ├── ReactFiberTreeReflection.lua │ │ ├── ReactFiberUnwindWork.new.lua │ │ ├── ReactFiberWorkInProgress.lua │ │ ├── ReactFiberWorkLoop.new.lua │ │ ├── ReactHookEffectTags.lua │ │ ├── ReactInternalTypes.lua │ │ ├── ReactMutableSource.new.lua │ │ ├── ReactPortal.lua │ │ ├── ReactProfilerTimer.new.lua │ │ ├── ReactRootTags.lua │ │ ├── ReactStrictModeWarnings.new.lua │ │ ├── ReactTestSelectors.lua │ │ ├── ReactTypeOfMode.lua │ │ ├── ReactUpdateQueue.new.lua │ │ ├── ReactWorkTags.lua │ │ ├── RobloxReactProfiling.lua │ │ ├── SchedulerWithReactIntegration.new.lua │ │ ├── SchedulingProfiler.lua │ │ ├── __tests__ │ │ ├── DebugTracing-test.internal.spec.lua │ │ ├── ReactClassSetStateCallback.spec.lua │ │ ├── ReactComponentLifeCycle.roblox.spec.lua │ │ ├── ReactComponentLifeCycle.spec.lua │ │ ├── ReactErrorBoundaries-internal.spec.lua │ │ ├── ReactFiberComponentStack.roblox.spec.lua │ │ ├── ReactFiberContext-internal.spec.lua │ │ ├── ReactFiberDevToolsHook-internal.spec.lua │ │ ├── ReactFiberHostContext-internal.spec.lua │ │ ├── ReactFiberLane.roblox.spec.lua │ │ ├── ReactFiberRoot.roblox.spec.lua │ │ ├── ReactFiberStack-test.roblox.spec.lua │ │ ├── ReactFiberSuspenseComponent.roblox.spec.lua │ │ ├── ReactFiberSuspenseContext.roblox.spec.lua │ │ ├── ReactFiberTreeReflection.roblox.spec.lua │ │ ├── ReactHooks-internal.spec.lua │ │ ├── ReactHooksWithNoopRenderer.spec.lua │ │ ├── ReactIdentity.spec.lua │ │ ├── ReactIncremental.spec.lua │ │ ├── ReactIncrementalErrorReplay.spec.lua │ │ ├── ReactIncrementalReflection.spec.lua │ │ ├── ReactIncrementalScheduling.spec.lua │ │ ├── ReactIncrementalSideEffects.spec.lua │ │ ├── ReactIncrementalUpdates.spec.lua │ │ ├── ReactIncrementalUpdatesMinimalism.spec.lua │ │ ├── ReactLazy-internal.spec.lua │ │ ├── ReactNewContext.spec.lua │ │ ├── ReactNoopRendererAct.spec.lua │ │ ├── ReactSuspense-internal.spec.lua │ │ ├── ReactTopLevelFragment.spec.lua │ │ ├── ReactTopLevelText.spec.lua │ │ ├── ReactUpdateQueue.roblox.spec.lua │ │ ├── ReactUseRef.roblox.spec.lua │ │ ├── SchedulingProfiler-internal.spec.lua │ │ └── useMutableSource-internal.spec.lua │ │ ├── forks │ │ └── ReactFiberHostConfig.test.lua │ │ └── init.lua ├── react-roblox │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── ReactReconciler.roblox.lua │ │ ├── client │ │ ├── ReactRoblox.lua │ │ ├── ReactRobloxComponent.lua │ │ ├── ReactRobloxComponentTree.lua │ │ ├── ReactRobloxHostConfig.lua │ │ ├── ReactRobloxHostTypes.roblox.lua │ │ ├── ReactRobloxRoot.lua │ │ ├── __tests__ │ │ │ ├── PropAssignmentErrors.roblox.spec.lua │ │ │ ├── ReactRobloxBindings.roblox.spec.lua │ │ │ ├── ReactRobloxComponentTree.roblox.spec.lua │ │ │ ├── ReactRobloxFiber.spec.lua │ │ │ └── RobloxRenderer.roblox.spec.lua │ │ └── roblox │ │ │ ├── RobloxComponentProps.lua │ │ │ ├── SingleEventManager.lua │ │ │ ├── __tests__ │ │ │ ├── RobloxComponentProps.roblox.spec.lua │ │ │ ├── SingleEventManager.spec.lua │ │ │ ├── Tagging.spec.lua │ │ │ ├── getDefaultInstanceProperty.spec.lua │ │ │ └── waitForEvents.lua │ │ │ └── getDefaultInstanceProperty.lua │ │ └── init.lua ├── react-shallow-renderer │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── __tests__ │ │ ├── ReactShallowRenderer.spec.lua │ │ └── ReactShallowRendererHooks.spec.lua │ │ └── init.lua ├── react-test-renderer │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── ReactTestHostConfig.lua │ │ ├── ReactTestRenderer.lua │ │ ├── __tests__ │ │ ├── ReactTestRenderer-internal.spec.lua │ │ ├── ReactTestRenderer.spec.lua │ │ ├── ReactTestRendererAct.spec.lua │ │ ├── ReactTestRendererMockPropMarkers.roblox.spec.lua │ │ ├── ReactTestRendererTraversal.spec.lua │ │ └── RobloxComponentProps.roblox.spec.lua │ │ ├── init.lua │ │ └── roblox │ │ └── RobloxComponentProps.lua ├── react │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── None.roblox.lua │ │ ├── React.lua │ │ ├── ReactBaseClasses.lua │ │ ├── ReactBinding.roblox.lua │ │ ├── ReactChildren.lua │ │ ├── ReactContext.lua │ │ ├── ReactCreateRef.lua │ │ ├── ReactElement.lua │ │ ├── ReactElementValidator.lua │ │ ├── ReactForwardRef.lua │ │ ├── ReactHooks.lua │ │ ├── ReactLazy.lua │ │ ├── ReactMemo.lua │ │ ├── ReactMutableSource.lua │ │ ├── ReactNoopUpdateQueue.lua │ │ ├── __tests__ │ │ ├── ReactBaseClasses.roblox.spec.lua │ │ ├── ReactBinding.spec.lua │ │ ├── ReactChildren.spec.lua │ │ ├── ReactDeprecationWarnings-internal.spec.lua │ │ ├── ReactElement.roblox.spec.lua │ │ ├── ReactElementValidator-internal.spec.lua │ │ ├── ReactProfiler-internal.spec.lua │ │ ├── ReactProfilerDevToolsIntegration-internal.spec.lua │ │ ├── ReactStrictMode.spec.lua │ │ ├── ReactUpdates.spec.lua │ │ ├── SetStateInConstructor.roblox.spec.lua │ │ ├── createSignal.spec.lua │ │ ├── forwardRef-internal.spec.lua │ │ └── forwardRef.spec.lua │ │ ├── createSignal.roblox.lua │ │ └── init.lua ├── roact-compat │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── Portal.lua │ │ ├── RoactTree.lua │ │ ├── __tests__ │ │ ├── RoactCompatibility.spec.lua │ │ ├── RoactRecursiveLayoutPcallDepth.spec.lua │ │ ├── RoactTree.spec.lua │ │ ├── act.spec.lua │ │ └── warnOnce.spec.lua │ │ ├── createFragment.lua │ │ ├── init.lua │ │ ├── oneChild.lua │ │ ├── setGlobalConfig.lua │ │ └── warnOnce.lua ├── scheduler │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ │ ├── Scheduler.lua │ │ ├── SchedulerFeatureFlags.lua │ │ ├── SchedulerHostConfig.lua │ │ ├── SchedulerMinHeap.lua │ │ ├── SchedulerPriorities.lua │ │ ├── SchedulerProfiling.lua │ │ ├── Tracing.lua │ │ ├── TracingSubscriptions.lua │ │ ├── __tests__ │ │ ├── Scheduler.spec.lua │ │ ├── SchedulerMinHeap.roblox.spec.lua │ │ ├── SchedulerNoDOM.spec.lua │ │ ├── SchedulerProfiling.spec.lua │ │ ├── Tracing-internal.spec.lua │ │ ├── Tracing.spec.lua │ │ └── TracingSubscriptions-internal.spec.lua │ │ ├── forks │ │ ├── SchedulerHostConfig.default.lua │ │ └── SchedulerHostConfig.mock.lua │ │ ├── init.lua │ │ └── unstable_mock.lua └── shared │ ├── .robloxrc │ ├── README.md │ ├── default.project.json │ ├── rotriever.toml │ └── src │ ├── ConsolePatchingDev.roblox.lua │ ├── ErrorHandling.roblox.lua │ ├── ExecutionEnvironment.lua │ ├── PropMarkers │ ├── Change.lua │ ├── Event.lua │ ├── Tag.lua │ └── __tests__ │ │ ├── Change.spec.lua │ │ └── Event.spec.lua │ ├── ReactComponentStackFrame.lua │ ├── ReactElementType.lua │ ├── ReactErrorUtils.lua │ ├── ReactFeatureFlags.lua │ ├── ReactFiberHostConfig │ ├── WithNoHydration.lua │ ├── WithNoPersistence.lua │ ├── WithNoTestSelectors.lua │ └── init.lua │ ├── ReactInstanceMap.lua │ ├── ReactSharedInternals │ ├── IsSomeRendererActing.lua │ ├── ReactCurrentBatchConfig.lua │ ├── ReactCurrentDispatcher.lua │ ├── ReactCurrentOwner.lua │ ├── ReactDebugCurrentFrame.lua │ └── init.lua │ ├── ReactSymbols.lua │ ├── ReactTypes.lua │ ├── ReactVersion.lua │ ├── Symbol.roblox.lua │ ├── Type.roblox.lua │ ├── UninitializedState.roblox.lua │ ├── __tests__ │ ├── ErrorHandling.roblox.spec.lua │ ├── ReactComponentStackFrame.roblox.spec.lua │ ├── ReactErrorProd-internal.spec.lua │ ├── ReactErrorUtils-internal.spec.lua │ ├── ReactInstanceMap.roblox.spec.lua │ ├── ReactSymbols-internal.spec.lua │ ├── checkPropTypes.roblox.spec.lua │ ├── getComponentName.roblox.spec.lua │ └── isValidElementType.roblox.spec.lua │ ├── checkPropTypes.lua │ ├── console.lua │ ├── consoleWithStackDev.lua │ ├── enqueueTask.roblox.lua │ ├── flowtypes.roblox.lua │ ├── formatProdErrorMessage.lua │ ├── getComponentName.lua │ ├── init.lua │ ├── invariant.lua │ ├── invokeGuardedCallbackImpl.lua │ ├── isValidElementType.lua │ ├── objectIs.lua │ └── shallowEqual.lua ├── rotriever.toml ├── selene.toml ├── standalone ├── default.project.json └── rotriever.toml ├── stylua.toml └── tests.project.json /.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 -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.styluaignore: -------------------------------------------------------------------------------- 1 | *.snap.lua -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2023 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 | -------------------------------------------------------------------------------- /Packages/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nocheck" 4 | }, 5 | "lint": { 6 | "*": "disabled" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /WorkspaceStatic/jest/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /WorkspaceStatic/jest/matchers/toErrorDev.lua: -------------------------------------------------------------------------------- 1 | local createConsoleMatcher = require(script.Parent.createConsoleMatcher) 2 | 3 | return createConsoleMatcher("error", "toErrorDev") 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/compare-benchmarks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This script creates a CSV comparing the difference between two benchmark CSVs 4 | # Pass --output or -o to set an output dir, helpful for comparisons and organization (ex: -o bin/featureName-benchmarks) 5 | # Pass the two CSVs as direct args, no --flag-prefix needed 6 | 7 | import os 8 | import sys 9 | import re 10 | 11 | def percentChange(a, b): 12 | a = a if type(a) == float else float(a) 13 | b = b if type(b) == float else float(b) 14 | return (b-a)/abs(b) * 100 15 | 16 | CSVs = [] 17 | 18 | # Default parameters 19 | parameters = { 20 | "directory": "bin/benchmarks", 21 | } 22 | 23 | # Parse command line arguments 24 | argNum = 1 25 | while argNum < len(sys.argv): 26 | arg = sys.argv[argNum] 27 | if arg == "-o" or arg == "--output": 28 | value = sys.argv[argNum+1] 29 | if value[0:1] != "-": 30 | parameters['directory'] = value 31 | else: 32 | print(f"Error: Argument for {arg} is missing, please specify an output directory") 33 | exit(1) 34 | 35 | argNum += 2 36 | elif arg[0:1] == "-": 37 | print(f"Error: Unsupported flag {arg}") 38 | exit(1) 39 | else: 40 | CSVs.append(arg) 41 | argNum += 1 42 | 43 | csvA = open(CSVs[0], mode="r", encoding="utf-8") 44 | csvB = open(CSVs[1], mode="r", encoding="utf-8") 45 | 46 | aName = re.search(r"([\w\-]+)\.csv$", CSVs[0]).group(1).strip().replace("-benchmark", "") 47 | bName = re.search(r"([\w\-]+)\.csv$", CSVs[1]).group(1).strip().replace("-benchmark", "") 48 | 49 | csvALines = csvA.readlines() 50 | csvBLines = csvB.readlines() 51 | 52 | # Create the results directory 53 | if not os.path.exists(parameters['directory']): 54 | os.makedirs(parameters['directory']) 55 | 56 | outputFile = open(f"{parameters['directory']}/compare-{aName}-to-{bName}-benchmark.csv", mode="w", encoding="utf-8") 57 | outputFile.write(f"Test,Metric,{aName},{bName},Unit,Change") 58 | outputFile.write('\n') 59 | 60 | headers = {} 61 | for i, line in enumerate(csvALines): 62 | if i == 0: 63 | rawHeaders = line.split(",") 64 | for idx, header in enumerate(rawHeaders): 65 | headers[header] = idx 66 | continue 67 | 68 | aValues = line.split(",") 69 | bValues = csvBLines[i].split(",") 70 | 71 | outputFile.write("{test},{metric},{a},{b},{unit},{change:.3f}%".format( 72 | test=aValues[headers['Test']], 73 | metric=aValues[headers['Metric']], 74 | a = aValues[headers['Value']], 75 | b = bValues[headers['Value']], 76 | unit = aValues[headers['Unit']], 77 | change = percentChange(aValues[headers['Value']], bValues[headers['Value']]), 78 | )) 79 | outputFile.write('\n') 80 | 81 | outputFile.close() 82 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - '*spec.lua' 3 | - '*__test*' 4 | - '*jest*' 5 | - '*mock*' 6 | - '*Dev*' 7 | - '*/_Index/*' 8 | -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoactAlignment", 3 | "tree": { 4 | "$path": "modules" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/bench.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | 13 |
14 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/diff-bridge-shutdown-method-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-lua/0a322eafe87c17468c3637a120eb66ab8fc20b00/docs/diff-bridge-shutdown-method-after.png -------------------------------------------------------------------------------- /docs/diff-bridge-shutdown-method-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-lua/0a322eafe87c17468c3637a120eb66ab8fc20b00/docs/diff-bridge-shutdown-method-before.png -------------------------------------------------------------------------------- /docs/diff-class-arrow-function-method-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-lua/0a322eafe87c17468c3637a120eb66ab8fc20b00/docs/diff-class-arrow-function-method-new.png -------------------------------------------------------------------------------- /docs/diff-class-arrow-function-method-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-lua/0a322eafe87c17468c3637a120eb66ab8fc20b00/docs/diff-class-arrow-function-method-old.png -------------------------------------------------------------------------------- /docs/diff-packages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-lua/0a322eafe87c17468c3637a120eb66ab8fc20b00/docs/diff-packages.png -------------------------------------------------------------------------------- /docs/diff-spread-flow-type-no-deviation-anymore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-lua/0a322eafe87c17468c3637a120eb66ab8fc20b00/docs/diff-spread-flow-type-no-deviation-anymore.png -------------------------------------------------------------------------------- /docs/diff-upstream-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roblox/react-lua/0a322eafe87c17468c3637a120eb66ab8fc20b00/docs/diff-upstream-comment.png -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | .md-typeset hr { 2 | border-bottom: 2px solid rgba(0, 0, 0, 0.15); 3 | } 4 | -------------------------------------------------------------------------------- /docs/images/aligned.svg: -------------------------------------------------------------------------------- 1 | AlignedAligned -------------------------------------------------------------------------------- /docs/images/apichange.svg: -------------------------------------------------------------------------------- 1 | API ChangeAPI Change -------------------------------------------------------------------------------- /docs/images/deviation.svg: -------------------------------------------------------------------------------- 1 | 2 | Deviation 3 | 4 | 5 | 6 | 7 | Deviation 8 | 9 | -------------------------------------------------------------------------------- /docs/images/reactjs.svg: -------------------------------------------------------------------------------- 1 | 2 | View React Docs 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | View React Docs 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/images/roblox.svg: -------------------------------------------------------------------------------- 1 | RobloxRoblox -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | pymdown-extensions -------------------------------------------------------------------------------- /examples.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Roact Test Place", 3 | "tree": { 4 | "$className": "DataModel", 5 | 6 | "ReplicatedStorage": { 7 | "$className": "ReplicatedStorage", 8 | "Packages": { 9 | "$path": "Packages" 10 | }, 11 | "DeveloperTools": { 12 | "$className": "BindableEvent" 13 | } 14 | }, 15 | 16 | "StarterPlayer": { 17 | "$className": "StarterPlayer", 18 | 19 | "StarterPlayerScripts": { 20 | "$className": "StarterPlayerScripts", 21 | 22 | "RoactExamples": { 23 | "$path": "examples" 24 | } 25 | } 26 | }, 27 | 28 | "Players": { 29 | "$className": "Players", 30 | "$properties": { 31 | "CharacterAutoLoads": false 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/ProjectWorkspace/DeveloperTools.lua: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | -- FIXME: This is a bit hacky; should rotriever provide a smoother way to access 4 | -- dev dependencies? 5 | -- return require(ReplicatedStorage.Packages._Index["DeveloperTools"]["developer-tools"]) 6 | return require(ReplicatedStorage.Packages._Index["DeveloperTools"]["DeveloperTools"]) 7 | -------------------------------------------------------------------------------- /examples/ProjectWorkspace/React.lua: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local Workspace = ReplicatedStorage.Packages._Workspace 3 | 4 | -- FIXME: This is a bit hacky; should rotriever provide a smoother way to access 5 | -- local workspace members, even if they don't see use? 6 | return require(Workspace.React.React) 7 | -------------------------------------------------------------------------------- /examples/ProjectWorkspace/ReactDevtoolsExtensions.lua: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | 3 | return require(ReplicatedStorage.Packages._Workspace["ReactDevtoolsExtensions"]["ReactDevtoolsExtensions"]) 4 | -------------------------------------------------------------------------------- /examples/ProjectWorkspace/ReactRoblox.lua: -------------------------------------------------------------------------------- 1 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 2 | local Workspace = ReplicatedStorage.Packages._Workspace 3 | 4 | -- FIXME: This is a bit hacky; should rotriever provide a smoother way to access 5 | -- local workspace members, even if they don't see use? 6 | return require(Workspace.ReactRoblox.ReactRoblox) -------------------------------------------------------------------------------- /examples/binding/init.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local LocalPlayer = game:GetService("Players").LocalPlayer 3 | local PlayerGui = LocalPlayer.PlayerGui 4 | local Mouse = LocalPlayer:GetMouse() 5 | 6 | local COUNT = 500 7 | local React = require(script.Parent.ProjectWorkspace.React) 8 | local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox) 9 | 10 | local BindingExample = React.Component:extend("BindingExample") 11 | 12 | local function Follower(props) 13 | return React.createElement("Frame", { 14 | Size = UDim2.fromOffset(12, 12), 15 | AnchorPoint = Vector2.new(0.5, 0.5), 16 | Position = props.position, 17 | BackgroundColor3 = props.color, 18 | }) 19 | end 20 | 21 | function BindingExample:init() 22 | self.binding, self.updateBinding = React.createBinding({}) 23 | end 24 | 25 | function BindingExample:render() 26 | local followers = {} 27 | for i = 0, COUNT do 28 | followers[i] = React.createElement(Follower, { 29 | position = self.binding:map(function(lastPositions) 30 | return lastPositions[i] 31 | end), 32 | color = Color3.new(i / COUNT, 0, i / COUNT), 33 | }) 34 | end 35 | 36 | return followers 37 | end 38 | 39 | function BindingExample:componentDidMount() 40 | local incrementor = 1 41 | 42 | self.mouseConnection = Mouse.Move:Connect(function() 43 | local positions = self.binding:getValue() 44 | positions[incrementor % COUNT + 1] = UDim2.fromOffset(Mouse.X, Mouse.Y) 45 | self.updateBinding(positions) 46 | incrementor += 1 47 | end) 48 | end 49 | 50 | function BindingExample:componentWillUnmount() 51 | self.mouseConnection:Disconnect() 52 | end 53 | 54 | local app = React.createElement("ScreenGui", nil, { 55 | BindingExample = React.createElement(BindingExample), 56 | }) 57 | 58 | local rootInstance = Instance.new("Folder") 59 | rootInstance.Parent = PlayerGui 60 | 61 | local root = ReactRoblox.createBlockingRoot(rootInstance) 62 | root:render(app) 63 | 64 | local function stop() 65 | root:unmount() 66 | rootInstance.Parent = nil 67 | end 68 | 69 | return stop 70 | end -------------------------------------------------------------------------------- /examples/changed-signal/init.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui 3 | 4 | local React = require(script.Parent.ProjectWorkspace.React) 5 | local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox) 6 | 7 | --[[ 8 | A TextBox that the user can type into. Takes a callback to be 9 | triggered when text changes. 10 | ]] 11 | local function InputTextBox(props) 12 | local onTextChanged = props.onTextChanged 13 | local layoutOrder = props.layoutOrder 14 | 15 | return React.createElement("TextBox", { 16 | LayoutOrder = layoutOrder, 17 | Text = "Type Here!", 18 | Size = UDim2.new(1, 0, 0.5, 0), 19 | [ReactRoblox.Change.Text] = onTextChanged, 20 | }) 21 | end 22 | 23 | --[[ 24 | A TextLabel that display the given text in reverse. 25 | ]] 26 | local function ReversedText(props) 27 | local inputText = props.inputText 28 | local layoutOrder = props.layoutOrder 29 | 30 | return React.createElement("TextLabel", { 31 | LayoutOrder = layoutOrder, 32 | Size = UDim2.new(1, 0, 0.5, 0), 33 | Text = "Reversed: " .. string.reverse(inputText), 34 | }) 35 | end 36 | 37 | --[[ 38 | Displays a TextBox and a TextLabel that shows the reverse of 39 | the TextBox's input in real time 40 | ]] 41 | local TextReverser = React.Component:extend("TextReverser") 42 | 43 | function TextReverser:init() 44 | self.state = { 45 | text = "", 46 | } 47 | end 48 | 49 | function TextReverser:render() 50 | local text = self.state.text 51 | 52 | return React.createElement("Frame", { 53 | Size = UDim2.new(0, 400, 0, 400), 54 | Position = UDim2.new(0.5, 0, 0.5, 0), 55 | AnchorPoint = Vector2.new(0.5, 0.5), 56 | }, { 57 | React.createElement("UIListLayout", { 58 | SortOrder = Enum.SortOrder.LayoutOrder, 59 | }), 60 | React.createElement(InputTextBox, { 61 | layoutOrder = 1, 62 | onTextChanged = function(rbx) 63 | self:setState({ 64 | text = rbx.Text or "", 65 | }) 66 | end, 67 | }), 68 | React.createElement(ReversedText, { 69 | layoutOrder = 2, 70 | inputText = text, 71 | }), 72 | }) 73 | end 74 | 75 | local app = React.createElement("ScreenGui", nil, { 76 | TextReverser = React.createElement(TextReverser), 77 | }) 78 | 79 | local rootInstance = Instance.new("Folder") 80 | rootInstance.Parent = PlayerGui 81 | 82 | local root = ReactRoblox.createBlockingRoot(rootInstance) 83 | root:render(app) 84 | 85 | local function stop() 86 | root:unmount() 87 | rootInstance.Parent = nil 88 | end 89 | 90 | return stop 91 | end 92 | -------------------------------------------------------------------------------- /examples/clock/init.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui 3 | 4 | local React = require(script.Parent.ProjectWorkspace.React) 5 | local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox) 6 | 7 | local function ClockApp(props) 8 | local timeValue = props.time 9 | 10 | return React.createElement("ScreenGui", nil, { 11 | Main = React.createElement("TextLabel", { 12 | Size = UDim2.new(0, 400, 0, 300), 13 | Position = UDim2.new(0.5, 0, 0.5, 0), 14 | AnchorPoint = Vector2.new(0.5, 0.5), 15 | Text = "The current time is: " .. timeValue, 16 | }), 17 | }) 18 | end 19 | 20 | local rootInstance = Instance.new("Folder") 21 | rootInstance.Parent = PlayerGui 22 | 23 | local running = true 24 | local currentTime = 0 25 | local root = ReactRoblox.createBlockingRoot(rootInstance) 26 | root:render(React.createElement(ClockApp, { 27 | time = currentTime, 28 | })) 29 | 30 | spawn(function() 31 | while running do 32 | currentTime = currentTime + 1 33 | 34 | root:render(React.createElement(ClockApp, { 35 | time = currentTime, 36 | })) 37 | 38 | wait(1) 39 | end 40 | end) 41 | 42 | local function stop() 43 | running = false 44 | root:unmount() 45 | rootInstance.Parent = nil 46 | end 47 | 48 | return stop 49 | end -------------------------------------------------------------------------------- /examples/event/init.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui 3 | 4 | local React = require(script.Parent.ProjectWorkspace.React) 5 | local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox) 6 | 7 | local app = React.createElement("ScreenGui", nil, { 8 | Button = React.createElement("TextButton", { 9 | Size = UDim2.new(0.5, 0, 0.5, 0), 10 | Position = UDim2.new(0.5, 0, 0.5, 0), 11 | AnchorPoint = Vector2.new(0.5, 0.5), 12 | 13 | -- Attach event listeners using `ReactRoblox.Event[eventName]` 14 | -- Event listeners get `rbx` as their first parameter 15 | -- followed by their normal event arguments. 16 | [ReactRoblox.Event.Activated] = function(rbx) 17 | print("The button was clicked!") 18 | end, 19 | }), 20 | }) 21 | 22 | local rootInstance = Instance.new("Folder") 23 | rootInstance.Parent = PlayerGui 24 | 25 | local root = ReactRoblox.createBlockingRoot(rootInstance) 26 | root:render(app) 27 | 28 | local function stop() 29 | root:unmount() 30 | rootInstance.Parent = nil 31 | end 32 | 33 | return stop 34 | end 35 | -------------------------------------------------------------------------------- /examples/hello-roact/init.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | 3 | local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui 4 | 5 | local React = require(script.Parent.ProjectWorkspace.React) 6 | local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox) 7 | 8 | local app = React.createElement("ScreenGui", nil, { 9 | Main = React.createElement("TextLabel", { 10 | Size = UDim2.new(0, 400, 0, 300), 11 | Position = UDim2.new(0.5, 0, 0.5, 0), 12 | AnchorPoint = Vector2.new(0.5, 0.5), 13 | Text = "Hello, React!", 14 | }), 15 | }) 16 | 17 | local rootInstance = Instance.new("Folder") 18 | rootInstance.Parent = PlayerGui 19 | 20 | local root = ReactRoblox.createBlockingRoot(rootInstance) 21 | root:render(app) 22 | 23 | local function stop() 24 | root:unmount() 25 | rootInstance.Parent = nil 26 | end 27 | 28 | return stop 29 | end -------------------------------------------------------------------------------- /examples/ref/init.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui 3 | 4 | local React = require(script.Parent.ProjectWorkspace.React) 5 | local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox) 6 | 7 | --[[ 8 | A search bar with an icon and a text box 9 | When the icon is clicked, the TextBox captures focus 10 | ]] 11 | local SearchBar = React.Component:extend("SearchBar") 12 | 13 | function SearchBar:init() 14 | self.textBoxRef = React.createRef() 15 | end 16 | 17 | function SearchBar:render() 18 | return React.createElement("Frame", { 19 | Size = UDim2.new(0, 300, 0, 50), 20 | Position = UDim2.new(0.5, 0, 0.5, 0), 21 | AnchorPoint = Vector2.new(0.5, 0.5), 22 | }, { 23 | SearchIcon = React.createElement("TextButton", { 24 | Size = UDim2.new(0, 50, 0, 50), 25 | AutoButtonColor = false, 26 | Text = "->", 27 | 28 | -- Handle click events on the search button 29 | [ReactRoblox.Event.Activated] = function() 30 | print("Button clicked; use our ref to have the TextBox capture focus") 31 | self.textBoxRef.current:CaptureFocus() 32 | end, 33 | }), 34 | 35 | SearchTextBox = React.createElement("TextBox", { 36 | Size = UDim2.new(1, -50, 1, 0), 37 | Position = UDim2.new(0, 50, 0, 0), 38 | 39 | -- Use ref to initalize a reference to the underlying object 40 | ref = self.textBoxRef, 41 | }), 42 | }) 43 | end 44 | 45 | local app = React.createElement("ScreenGui", nil, { 46 | SearchBar = React.createElement(SearchBar), 47 | }) 48 | 49 | local rootInstance = Instance.new("Folder") 50 | rootInstance.Parent = PlayerGui 51 | 52 | local root = ReactRoblox.createBlockingRoot(rootInstance) 53 | root:render(app) 54 | 55 | local function stop() 56 | root:unmount() 57 | rootInstance.Parent = nil 58 | end 59 | 60 | return stop 61 | end 62 | -------------------------------------------------------------------------------- /examples/stress-test/init.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local RunService = game:GetService("RunService") 3 | local PlayerGui = game:GetService("Players").LocalPlayer.PlayerGui 4 | 5 | local React = require(script.Parent.ProjectWorkspace.React) 6 | local ReactRoblox = require(script.Parent.ProjectWorkspace.ReactRoblox) 7 | 8 | local NODE_SIZE = 10 9 | local GRID_SIZE = 70 10 | 11 | --[[ 12 | A frame that changes its background color according to time and position props 13 | ]] 14 | local function Node(props) 15 | local x = props.x 16 | local y = props.y 17 | local currentTime = props.time 18 | 19 | local n = currentTime + x / NODE_SIZE + y / NODE_SIZE 20 | 21 | return React.createElement("Frame", { 22 | Size = UDim2.new(0, NODE_SIZE, 0, NODE_SIZE), 23 | Position = UDim2.new(0, NODE_SIZE * x, 0, NODE_SIZE * y), 24 | BackgroundColor3 = Color3.new(0.5 + 0.5 * math.sin(n), 0.5, 0.5), 25 | }) 26 | end 27 | 28 | --[[ 29 | Displays a large number of nodes and updates each of them every RunService step 30 | ]] 31 | local App = React.Component:extend("App") 32 | 33 | function App:init() 34 | self.state = { 35 | time = tick(), 36 | } 37 | end 38 | 39 | function App:render() 40 | local currentTime = self.state.time 41 | local nodes = {} 42 | 43 | local n = 0 44 | for x = 0, GRID_SIZE - 1 do 45 | for y = 0, GRID_SIZE - 1 do 46 | n = n + 1 47 | nodes[n] = React.createElement(Node, { 48 | x = x, 49 | y = y, 50 | time = currentTime, 51 | }) 52 | end 53 | end 54 | 55 | return React.createElement("Frame", { 56 | Size = UDim2.new(0, GRID_SIZE * NODE_SIZE, 0, GRID_SIZE * NODE_SIZE), 57 | Position = UDim2.new(0.5, 0, 0.5, 0), 58 | AnchorPoint = Vector2.new(0.5, 0.5), 59 | }, nodes) 60 | end 61 | 62 | function App:componentDidMount() 63 | self.connection = RunService.Stepped:Connect(function() 64 | self:setState({ 65 | time = tick(), 66 | }) 67 | end) 68 | end 69 | 70 | function App:componentWillUnmount() 71 | self.connection:Disconnect() 72 | end 73 | 74 | local app = React.createElement("ScreenGui", nil, { 75 | Main = React.createElement(App), 76 | }) 77 | 78 | local rootInstance = Instance.new("Folder") 79 | rootInstance.Parent = PlayerGui 80 | 81 | local root = ReactRoblox.createRoot(rootInstance) 82 | root:render(app) 83 | 84 | local function stop() 85 | root:unmount() 86 | rootInstance.Parent = nil 87 | end 88 | 89 | return stop 90 | end -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/TestRunner/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TestRunner", 3 | "tree": { 4 | "Src": { 5 | "$path": "src" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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 | Scheduler = { path = "../scheduler" } 15 | -------------------------------------------------------------------------------- /modules/TestRunner/src/init.lua: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /modules/jest-react/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 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/jest-react/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JestReact", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | Shared = { path = "../shared" } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /modules/react-cache/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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-cache/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactCache", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | Scheduler = { path = "../scheduler" } 10 | Shared = { path = "../shared" } 11 | React = { path = "../react" } 12 | LuauPolyfill = { workspace = true } 13 | 14 | [dev_dependencies] 15 | JestGlobals = { workspace = true } 16 | Promise = { workspace = true } 17 | JestReact = { path = "../jest-react" } 18 | ReactTestRenderer = { path = "../react-test-renderer" } 19 | -------------------------------------------------------------------------------- /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-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-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 | ReactReconciler = { path = "../react-reconciler" } 12 | 13 | [dev_dependencies] 14 | JestGlobals = { workspace = true } 15 | Promise = { workspace = true } 16 | React = { path = "../react" } 17 | ReactTestRenderer = { path = "../react-test-renderer" } 18 | Scheduler = { path = "../scheduler" } 19 | 20 | -------------------------------------------------------------------------------- /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/react-devtools-core/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict" 3 | } -------------------------------------------------------------------------------- /modules/react-devtools-core/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /modules/react-devtools-core/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDevtoolsCore", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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 | ReactDevtoolsShared = { path = "../react-devtools-shared" } 12 | 13 | [dev_dependencies] 14 | JestGlobals = { workspace = true } -------------------------------------------------------------------------------- /modules/react-devtools-core/src/init.lua: -------------------------------------------------------------------------------- 1 | local backend = require(script.backend) 2 | 3 | return { 4 | backend = backend, 5 | } 6 | -------------------------------------------------------------------------------- /modules/react-devtools-extensions/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-devtools-extensions/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactDevtoolsExtensions", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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 | ReactRoblox = { path = "../react-roblox" } 15 | 16 | [dev_dependencies] 17 | JestGlobals ={ workspace = true } 18 | # FIXME: Version 0.2.4 breaks the test "can connect to a Roact tree and inspect 19 | # its children and child branch nodes" in devtools-integration.roblox.spec.lua 20 | DeveloperTools = "github.com/Roblox/developer-tools@=0.2.3" 21 | ReactTestRenderer = { path = "../react-test-renderer" } -------------------------------------------------------------------------------- /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 | -- ROBLOX note: Set globals for React devtools to work 8 | _G.__DEV__ = true 9 | _G.__DEBUG__ = debugMode or false 10 | _G.__PROFILE__ = true 11 | _G.__EXPERIMENTAL__ = true 12 | -- ROBLOX note: Don't hide host coomponents as the current Developer Inspector uses on these to preserve a 13 | -- direct mapping between the Inspector tree and the Explorer tree as requested by design. 14 | _G.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = {} 15 | 16 | local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared) 17 | local setup = require(Packages.ReactDevtoolsExtensions.backend).setup 18 | local installHook = ReactDevtoolsShared.hook.installHook 19 | local Store = ReactDevtoolsShared.devtools.store 20 | 21 | -- ROBLOX note: Ensure that the global hook is installed before the injection into DevTools 22 | installHook(_G) 23 | 24 | -- ROBLOX note: Ensure that ReactRoblox is loaded after injection so that the ReactHostConfig is populated correctly 25 | require(Packages.React) 26 | require(Packages.ReactRoblox) 27 | 28 | local hook = _G.__REACT_DEVTOOLS_GLOBAL_HOOK__ 29 | 30 | -- ROBLOX note: Make sure that this method was called before ReactRoblox was first required, 31 | -- otherwise the profiler will not be enabled for the session. 32 | local ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags 33 | if not ReactFeatureFlags.enableSchedulingProfiler then 34 | warn( 35 | "[DeveloperTools] React was initialized before DeveloperTools. Call inspector.setupReactDevtools before requiring React to enable profiling." 36 | ) 37 | end 38 | 39 | local result = setup(hook) 40 | 41 | -- ROBLOX note: The DeveloperTools library is only passed the ReactDevtoolsExtensions API to keep the 42 | -- devtools init process compact for users. Initialize the store so DeveloperTools doesn't also need to be 43 | -- passed the ReactDevtoolsShared API. 44 | return { 45 | agent = result.agent, 46 | bridge = result.bridge, 47 | hook = result.hook, 48 | store = Store.new(result.bridge), 49 | } 50 | end, 51 | } 52 | -------------------------------------------------------------------------------- /modules/react-devtools-shared/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "strict" 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-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 | 18 | [dev_dependencies] 19 | JestGlobals ={ workspace = true } 20 | Scheduler = { path = "../scheduler" } 21 | Promise = { workspace = true } 22 | ReactTestRenderer = { path = "../react-test-renderer" } 23 | -------------------------------------------------------------------------------- /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/__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-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-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-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 | -------------------------------------------------------------------------------- /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/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("CanvasGroup") 9 | node.Name = "OverlayRect" 10 | node.BackgroundTransparency = 1 11 | node.GroupTransparency = 0.3 12 | node.Parent = container 13 | self.node = node 14 | 15 | local padding = Instance.new("Frame") 16 | padding.Name = "OverlayRectPadding" 17 | padding.BackgroundColor3 = Color3.fromRGB(77, 200, 0) 18 | padding.Size = UDim2.fromScale(1, 1) 19 | padding.BackgroundTransparency = 0.4 20 | padding.BorderSizePixel = 0 21 | padding.Parent = node 22 | self.padding = padding 23 | 24 | local content = Instance.new("Frame") 25 | content.Name = "OverlayRectContent" 26 | content.BackgroundColor3 = Color3.fromRGB(120, 170, 210) 27 | content.Size = UDim2.fromScale(1, 1) 28 | content.BorderSizePixel = 0 29 | content.ZIndex = 2 30 | content.Parent = node 31 | self.content = content 32 | 33 | return self 34 | end 35 | 36 | export type OverlayRect = typeof(OverlayRect.new(...)) 37 | 38 | function OverlayRect.remove(self: OverlayRect) 39 | self.node:Destroy() 40 | end 41 | 42 | function OverlayRect.update(self: OverlayRect, element: GuiBase2d) 43 | local size = element.AbsoluteSize 44 | local position = element.AbsolutePosition 45 | 46 | local padding = element:FindFirstChildOfClass("UIPadding") 47 | if padding then 48 | local top = (padding.PaddingTop.Scale * size.Y) + padding.PaddingTop.Offset 49 | local left = (padding.PaddingLeft.Scale * size.X) + padding.PaddingLeft.Offset 50 | local bottom = (padding.PaddingBottom.Scale * size.Y) 51 | + padding.PaddingBottom.Offset 52 | local right = (padding.PaddingRight.Scale * size.X) + padding.PaddingRight.Offset 53 | 54 | self.content.Position = UDim2.fromOffset(left, top) 55 | self.content.Size = UDim2.fromOffset(size.X - left - right, size.Y - top - bottom) 56 | else 57 | self.content.Position = UDim2.fromOffset(0, 0) 58 | self.content.Size = UDim2.fromOffset(size.X, size.Y) 59 | end 60 | 61 | self.node.Size = UDim2.fromOffset(size.X, size.Y) 62 | self.node.Position = UDim2.fromOffset(position.X, position.Y) 63 | end 64 | 65 | return { 66 | new = OverlayRect.new, 67 | } 68 | -------------------------------------------------------------------------------- /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/constants.lua: -------------------------------------------------------------------------------- 1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/constants.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 exports = {} 12 | 13 | -- Flip this flag to true to enable verbose console debug logging. 14 | exports.__DEBUG__ = _G.__DEBUG__ 15 | 16 | exports.TREE_OPERATION_ADD = 1 17 | exports.TREE_OPERATION_REMOVE = 2 18 | exports.TREE_OPERATION_REORDER_CHILDREN = 3 19 | exports.TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4 20 | 21 | exports.LOCAL_STORAGE_FILTER_PREFERENCES_KEY = "React::DevTools::componentFilters" 22 | 23 | exports.SESSION_STORAGE_LAST_SELECTION_KEY = "React::DevTools::lastSelection" 24 | 25 | exports.SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY = 26 | "React::DevTools::recordChangeDescriptions" 27 | 28 | exports.SESSION_STORAGE_RELOAD_AND_PROFILE_KEY = "React::DevTools::reloadAndProfile" 29 | 30 | exports.LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS = 31 | "React::DevTools::breakOnConsoleErrors" 32 | 33 | exports.LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY = "React::DevTools::appendComponentStack" 34 | 35 | exports.LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY = "React::DevTools::traceUpdatesEnabled" 36 | 37 | exports.PROFILER_EXPORT_VERSION = 4 38 | 39 | exports.CHANGE_LOG_URL = 40 | "https://github.com/facebook/react/blob/master/packages/react-devtools/CHANGELOG.md" 41 | 42 | exports.UNSUPPORTED_VERSION_URL = 43 | "https://reactjs.org/blog/2019/08/15/new-react-devtools.html#how-do-i-get-the-old-version-back" 44 | 45 | -- HACK 46 | -- 47 | -- Extracting during build time avoids a temporarily invalid state for the inline target. 48 | -- Sometimes the inline target is rendered before root styles are applied, 49 | -- which would result in e.g. NaN itemSize being passed to react-window list. 50 | -- 51 | local COMFORTABLE_LINE_HEIGHT 52 | local COMPACT_LINE_HEIGHT 53 | 54 | -- ROBLOX deviation: we won't use the CSS, and don't have a bundler, so always use the 'fallback' 55 | -- We can't use the Webpack loader syntax in the context of Jest, 56 | -- so tests need some reasonably meaningful fallback value. 57 | COMFORTABLE_LINE_HEIGHT = 15 58 | COMPACT_LINE_HEIGHT = 10 59 | 60 | exports.COMFORTABLE_LINE_HEIGHT = COMFORTABLE_LINE_HEIGHT 61 | exports.COMPACT_LINE_HEIGHT = COMPACT_LINE_HEIGHT 62 | 63 | return exports 64 | -------------------------------------------------------------------------------- /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-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-devtools-shared/src/jest.config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | setupFilesAfterEnv = { script.Parent.__tests__.setupTests }, 3 | testMatch = { "**/*.(spec|test)" }, 4 | } 5 | -------------------------------------------------------------------------------- /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 exports = {} 11 | if _G.__LOCALSTORAGE__ == nil then 12 | _G.__LOCALSTORAGE__ = {} 13 | end 14 | 15 | if _G.__SESSIONSTORAGE__ == nil then 16 | _G.__SESSIONSTORAGE__ = {} 17 | end 18 | 19 | -- ROBLOX FIXME: what's a high-performance storage that for temporal (current DM lifetime) and permanent (beyond current DM lifetime) 20 | local localStorage = _G.__LOCALSTORAGE__ 21 | local sessionStorage = _G.__SESSIONSTORAGE__ 22 | 23 | exports.localStorageGetItem = function(key: string): any 24 | return localStorage[key] 25 | end 26 | exports.localStorageRemoveItem = function(key: string): () 27 | localStorage[key] = nil 28 | end 29 | exports.localStorageSetItem = function(key: string, value: any): () 30 | localStorage[key] = value 31 | end 32 | exports.sessionStorageGetItem = function(key: string): any 33 | return sessionStorage[key] 34 | end 35 | exports.sessionStorageRemoveItem = function(key: string): () 36 | sessionStorage[key] = nil 37 | end 38 | exports.sessionStorageSetItem = function(key: string, value: any): () 39 | sessionStorage[key] = value 40 | end 41 | 42 | return exports 43 | -------------------------------------------------------------------------------- /modules/react-devtools/.luaurc: -------------------------------------------------------------------------------- 1 | { 2 | "languageMode": "strict" 3 | } -------------------------------------------------------------------------------- /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-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/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-is/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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-is/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactIs", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | SafeFlags = { workspace = true } 11 | Shared = { path = "../shared" } 12 | 13 | [dev_dependencies] 14 | JestGlobals = { workspace = true } 15 | LuauPolyfill = { workspace = true } 16 | Promise = { workspace = true } 17 | React = { path = "../react" } 18 | ReactRoblox = { path = "../react-roblox" } 19 | -------------------------------------------------------------------------------- /modules/react-noop-renderer/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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/react-noop-renderer/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNoopRenderer", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | ReactReconciler = { path = "../react-reconciler" } 14 | JestGlobals = { workspace = true } 15 | -------------------------------------------------------------------------------- /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-reconciler/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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/react-reconciler/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactReconciler", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | Scheduler = { path = "../scheduler" } 14 | Shared = { path = "../shared" } 15 | React = { path = "../react" } 16 | 17 | [dev_dependencies] 18 | JestGlobals = { workspace = true } 19 | JestReact = { path = "../jest-react" } 20 | ReactCache = { path = "../react-cache" } 21 | ReactNoopRenderer = { path = "../react-noop-renderer" } 22 | ReactTestRenderer = { path = "../react-test-renderer" } 23 | ReactRoblox = { path = "../react-roblox" } 24 | ReactDevtoolsShared = { path = "../react-devtools-shared" } 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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-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/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/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-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/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/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-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/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-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-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/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/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/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-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-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-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/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/__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-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/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/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/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: In order to allow host config to be spliced in, we export 25 | -- this top-level package as an initializer function that returns the configured 26 | -- reconciler module 27 | -- ROBLOX TODO: this effectively disconnects type checking from above to reconciler to below 28 | local function initialize(config): { [string]: any } 29 | local ReactFiberHostConfig = require(script.ReactFiberHostConfig) 30 | for name, implementation in config do 31 | ReactFiberHostConfig[name] = implementation 32 | end 33 | 34 | return require(script.ReactFiberReconciler) 35 | end 36 | 37 | return initialize 38 | -------------------------------------------------------------------------------- /modules/react-roblox/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /modules/react-roblox/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactRoblox", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | ReactReconciler = { path = "../react-reconciler" } 14 | Scheduler = { path = "../scheduler" } 15 | 16 | [dev_dependencies] 17 | JestGlobals ={ workspace = true } 18 | Promise = { workspace = true } 19 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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-shallow-renderer/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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-shallow-renderer/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactShallowRenderer", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | Shared = { path = "../shared" } 14 | 15 | [dev_dependencies] 16 | JestGlobals = { workspace = true } 17 | -------------------------------------------------------------------------------- /modules/react-test-renderer/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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/react-test-renderer/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactTestRenderer", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | ReactReconciler = { path = "../react-reconciler" } 13 | Scheduler = { path = "../scheduler" } 14 | Shared = { path = "../shared" } 15 | 16 | [dev_dependencies] 17 | Promise = { workspace = true } 18 | JestGlobals ={ workspace = true } 19 | ReactRoblox = { path = "../react-roblox" } 20 | JestReact = { path = "../jest-react" } 21 | -------------------------------------------------------------------------------- /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/react/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | LuauPolyfill = { workspace = true } 11 | Shared = { path = "../shared" } 12 | 13 | [dev_dependencies] 14 | PerformanceBenchmarks = { git = "https://github.com/Roblox/roact-performance-benchmarks", rev = "main" } 15 | JestReact = { path = "../jest-react" } 16 | JestGlobals = { workspace = true } 17 | Promise = { workspace = true} 18 | ReactCache = { path = "../react-cache" } 19 | ReactNoopRenderer = { path = "../react-noop-renderer" } 20 | ReactRoblox = { path = "../react-roblox" } 21 | ReactTestRenderer = { path = "../react-test-renderer" } 22 | Scheduler = { path = "../scheduler" } 23 | -------------------------------------------------------------------------------- /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/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 ReactTypes = require(Packages.Shared) 13 | type RefObject = ReactTypes.RefObject 14 | 15 | -- ROBLOX DEVIATION: In Roact, refs are implemented in terms of bindings 16 | --[[ 17 | A ref is nothing more than a binding with a special field 'current' 18 | that maps to the getValue method of the binding 19 | ]] 20 | local Binding = require(script.Parent["ReactBinding.roblox"]) 21 | 22 | local exports = {} 23 | 24 | -- an immutable object with a single mutable value 25 | exports.createRef = function(): RefObject 26 | local binding, _ = Binding.create(nil) 27 | 28 | local ref = {} 29 | 30 | -- ROBLOX DEVIATION: Since refs are used as bindings, they can often be 31 | -- assigned to fields of other Instances; we track creation here parallel to 32 | -- how we do with bindings created via `createBinding` to improve messaging 33 | -- when something goes wrong 34 | if _G.__DEV__ then 35 | -- ROBLOX TODO: LUAFDN-619 - improve debug stacktraces for refs 36 | binding._source = debug.traceback("Ref created at:", 1) 37 | end 38 | 39 | --[[ 40 | A ref is just redirected to a binding via its metatable 41 | ]] 42 | setmetatable(ref, { 43 | __index = function(self, key) 44 | if key == "current" then 45 | return binding:getValue() 46 | else 47 | return (binding :: any)[key] 48 | end 49 | end, 50 | __newindex = function(self, key, value) 51 | if key == "current" then 52 | -- ROBLOX FIXME: Bindings - This is not allowed in Roact, but is okay in 53 | -- React. Lots of discussion at 54 | -- https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065 55 | -- error("Cannot assign to the 'current' property of refs", 2) 56 | Binding.update(binding, value) 57 | end 58 | 59 | (binding :: any)[key] = value 60 | end, 61 | __tostring = function(self) 62 | return string.format("Ref(%s)", tostring(binding:getValue())) 63 | end, 64 | }) 65 | 66 | return (ref :: any) :: RefObject 67 | end 68 | 69 | return exports 70 | -------------------------------------------------------------------------------- /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 ReactTypes = require(Packages.Shared) 14 | type MutableSourceGetVersionFn = ReactTypes.MutableSourceGetVersionFn 15 | type MutableSource = ReactTypes.MutableSource 16 | 17 | local function createMutableSource( 18 | source: Source, 19 | getVersion: MutableSourceGetVersionFn 20 | ): MutableSource 21 | local mutableSource: MutableSource = { 22 | _getVersion = getVersion, 23 | _source = source, 24 | _workInProgressVersionPrimary = nil, 25 | _workInProgressVersionSecondary = nil, 26 | } 27 | 28 | if _G.__DEV__ then 29 | mutableSource._currentPrimaryRenderer = nil 30 | mutableSource._currentSecondaryRenderer = nil 31 | end 32 | 33 | return mutableSource 34 | end 35 | 36 | return createMutableSource 37 | -------------------------------------------------------------------------------- /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/roact-compat/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /modules/roact-compat/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoactCompat", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | ReactRoblox = { path = "../react-roblox" } 13 | Shared = { path = "../shared" } 14 | 15 | [dev_dependencies] 16 | Roact = "github.com/roblox/roact@1.4.0" 17 | JestGlobals ={ workspace = true } 18 | Scheduler = { path = "../scheduler" } 19 | -------------------------------------------------------------------------------- /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 | 20 | local warnOnce = require(script.Parent.warnOnce) 21 | 22 | local function PortalComponent(props) 23 | if _G.__DEV__ and _G.__COMPAT_WARNINGS__ then 24 | warnOnce("Roact.Portal", "Please use the createPortal API on ReactRoblox instead") 25 | end 26 | return ReactRoblox.createPortal(props.children, props.target) 27 | end 28 | 29 | return PortalComponent 30 | -------------------------------------------------------------------------------- /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/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 React = require(Packages.React) 18 | 19 | local warnOnce = require(script.Parent.warnOnce) 20 | 21 | return function(elements) 22 | if _G.__DEV__ and _G.__COMPAT_WARNINGS__ then 23 | warnOnce( 24 | "createFragment", 25 | "Please instead use:\n\tReact.createElement(React.Fragment, ...)" 26 | ) 27 | end 28 | return React.createElement(React.Fragment, nil, elements) 29 | end 30 | -------------------------------------------------------------------------------- /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 | 19 | local warnOnce = require(script.Parent.warnOnce) 20 | 21 | local function oneChild(children) 22 | if _G.__DEV__ and _G.__COMPAT_WARNINGS__ then 23 | warnOnce( 24 | "oneChild", 25 | "You likely don't need this at all! If you were assigning children " 26 | .. "via `React.oneChild(someChildren)`, you can simply use " 27 | .. "`someChildren` directly." 28 | ) 29 | end 30 | 31 | -- This behavior is a bit different from upstream, so we're adapting current 32 | -- Roact's logic (which will unwrap a table with a single member) 33 | if not children then 34 | return nil 35 | end 36 | 37 | local key, child = next(children) 38 | 39 | if not child then 40 | return nil 41 | end 42 | 43 | local after = next(children, key) 44 | 45 | if after then 46 | error("Expected at most one child, had more than one child.", 2) 47 | end 48 | 49 | return React.Children.only(child) 50 | end 51 | 52 | return oneChild 53 | -------------------------------------------------------------------------------- /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 warnOnce = require(script.Parent.warnOnce) 18 | 19 | return function(_config) 20 | if _G.__DEV__ and _G.__COMPAT_WARNINGS__ then 21 | warnOnce( 22 | "setGlobalConfig", 23 | "Roact 17 uses a `_G.__DEV__` flag to enable development behavior. " 24 | .. "If you're seeing this warning, you already have it enabled. " 25 | .. "Please remove any redundant uses of `setGlobalConfig`." 26 | ) 27 | end 28 | -- No equivalent behavior can be applied here 29 | end 30 | -------------------------------------------------------------------------------- /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/scheduler/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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/scheduler/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scheduler", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /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 | Shared = { path = "../shared" } 13 | 14 | [dev_dependencies] 15 | JestGlobals ={ workspace = true } 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/shared/.robloxrc: -------------------------------------------------------------------------------- 1 | { 2 | "language": { 3 | "mode": "nonstrict" 4 | }, 5 | "lint": { 6 | "*": "enabled" 7 | } 8 | } -------------------------------------------------------------------------------- /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/shared/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shared", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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 | 13 | [dev_dependencies] 14 | JestGlobals = { workspace = true } 15 | React = { path = "../react" } 16 | Scheduler = { path = "../scheduler" } 17 | ReactNoop = { path = "../react-noop-renderer" } 18 | JestReact = { path = "../jest-react" } 19 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 ReactDebugCurrentFrame = {} 13 | 14 | local currentExtraStackFrame = nil :: nil | string 15 | 16 | function ReactDebugCurrentFrame.setExtraStackFrame(stack: string?): () 17 | if _G.__DEV__ then 18 | currentExtraStackFrame = stack 19 | end 20 | end 21 | 22 | if _G.__DEV__ then 23 | -- deviation: in Lua, the implementation is duplicated 24 | -- function ReactDebugCurrentFrame.setExtraStackFrame(stack: string?) 25 | -- if _G.__DEV__ then 26 | -- currentExtraStackFrame = stack 27 | -- end 28 | -- end 29 | 30 | -- Stack implementation injected by the current renderer. 31 | ReactDebugCurrentFrame.getCurrentStack = nil :: nil | (() -> string) 32 | 33 | function ReactDebugCurrentFrame.getStackAddendum(): string 34 | local stack = "" 35 | 36 | -- Add an extra top frame while an element is being validated 37 | if currentExtraStackFrame then 38 | stack = stack .. currentExtraStackFrame 39 | end 40 | 41 | -- Delegate to the injected renderer-specific implementation 42 | local impl = ReactDebugCurrentFrame.getCurrentStack 43 | if impl then 44 | stack = stack .. (impl() or "") 45 | end 46 | 47 | return stack 48 | end 49 | end 50 | 51 | return ReactDebugCurrentFrame 52 | -------------------------------------------------------------------------------- /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.0.1" 12 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -- ROBLOX DEVIATION: Initialize state to a singleton that warns on access and errors on assignment 19 | -- initial state singleton 20 | local UninitializedState = {} 21 | 22 | setmetatable(UninitializedState, { 23 | __index = function(table, key) 24 | if _G.__DEV__ then 25 | console.warn( 26 | "Attempted to access uninitialized state. Use setState to initialize state" 27 | ) 28 | end 29 | return nil 30 | end, 31 | __newindex = function(table, key) 32 | if _G.__DEV__ then 33 | console.error( 34 | "Attempted to directly mutate state. Use setState to assign new values to state." 35 | ) 36 | end 37 | return nil 38 | end, 39 | __tostring = function(self) 40 | return "" 41 | end, 42 | __metatable = "UninitializedState", 43 | }) 44 | 45 | return UninitializedState 46 | -------------------------------------------------------------------------------- /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/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/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/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 LuauPolyfill = require(Packages.LuauPolyfill) 24 | local console = LuauPolyfill.console 25 | local consoleWithStackDev = require(Shared.consoleWithStackDev) 26 | 27 | if _G.__DEV__ then 28 | local newConsole = setmetatable({ 29 | warn = consoleWithStackDev.warn, 30 | error = consoleWithStackDev.error, 31 | }, { 32 | __index = console, 33 | }) 34 | return newConsole 35 | end 36 | 37 | return console 38 | -------------------------------------------------------------------------------- /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 LuauPolyfill = require(Packages.LuauPolyfill) 10 | local console = LuauPolyfill.console 11 | local Array = LuauPolyfill.Array 12 | 13 | local ReactSharedInternals = require(script.Parent.ReactSharedInternals) 14 | -- In DEV, calls to console.warn and console.error get replaced 15 | -- by calls to these methods by a Babel plugin. 16 | -- 17 | -- In PROD (or in packages without access to React internals), 18 | -- they are left as they are instead. 19 | 20 | -- deviation: declare this ahead of time so that `warn` and `error` are able to 21 | -- reference it 22 | local printWarning 23 | 24 | local exports = {} 25 | exports.warn = function(format, ...) 26 | if _G.__DEV__ then 27 | printWarning("warn", format, { ... }) 28 | end 29 | end 30 | exports.error = function(format, ...) 31 | if _G.__DEV__ then 32 | printWarning("error", format, { ... }) 33 | end 34 | end 35 | 36 | function printWarning(level, format, args) 37 | -- When changing this logic, you might want to also 38 | -- update consoleWithStackDev.www.js as well. 39 | if _G.__DEV__ then 40 | local ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame 41 | local stack = ReactDebugCurrentFrame.getStackAddendum() 42 | 43 | if stack ~= "" then 44 | format ..= "%s" 45 | -- deviation: no array `concat` function in lua 46 | args = Array.slice(args, 1) 47 | table.insert(args, stack) 48 | end 49 | 50 | local argsWithFormat = Array.map(args, tostring) 51 | -- Careful: RN currently depends on this prefix 52 | table.insert(argsWithFormat, 1, "Warning: " .. format) 53 | -- We intentionally don't use spread (or .apply) directly because it 54 | -- breaks IE9: https://github.com/facebook/react/issues/13610 55 | -- eslint-disable-next-line react-internal/no-production-logging 56 | console[level](unpack(argsWithFormat)) 57 | end 58 | end 59 | 60 | return exports 61 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /rotriever.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | version = "17.2.0" 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /standalone/default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Roact", 3 | "tree": { 4 | "$path": "Packages" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /standalone/rotriever.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Roact17" 3 | version = "17.0.1" 4 | content_root = "src" 5 | 6 | [dependencies] 7 | RoactCompat = "github.com/roblox/roact-alignment@17.0.1" 8 | React = "github.com/roblox/roact-alignment@17.0.1" 9 | ReactRoblox = "github.com/roblox/roact-alignment@17.0.1" 10 | ReactIs = "github.com/roblox/roact-alignment@17.0.1" 11 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 90 -------------------------------------------------------------------------------- /tests.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoactAlignment", 3 | "tree": { 4 | "$path": "Packages", 5 | "_Workspace": { 6 | "$path": "WorkspaceStatic" 7 | } 8 | } 9 | } --------------------------------------------------------------------------------