16 | type React_StatelessFunctionalComponent = flowtypes.React_StatelessFunctionalComponent<
17 | P
18 | >
19 | type React_ComponentType
= flowtypes.React_ComponentType
20 |
21 | export type Source = {
22 | fileName: string,
23 | lineNumber: number,
24 | }
25 | type Key = string | number
26 | -- ROBLOX deviation: we're using the TypeScript definition here, which is more strict
27 | export type ReactElement
= {
28 | ["$$typeof"]: number,
29 |
30 | -- ROBLOX FIXME Luau: Luau has some trouble and inlining the type param from createElement doesn't help
31 | type: React_StatelessFunctionalComponent
| React_ComponentType
| string,
32 | -- type: T,
33 | key: Key | nil,
34 | ref: any,
35 | props: P,
36 |
37 | -- ROBLOX deviation: upstream has this as interface, which is extensible, Luau types are closed by default
38 | -- ReactFiber
39 | _owner: any,
40 |
41 | -- __DEV__
42 | _store: any?,
43 | _self: React_Element?,
44 | _shadowChildren: any?,
45 | _source: Source?,
46 | }
47 |
48 | -- deviation: Return something so that the module system is happy
49 | return {}
50 |
--------------------------------------------------------------------------------
/modules/shared/src/ReactFiberHostConfig/init.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | * Copyright (c) Roblox Corporation. All rights reserved.
3 | * Licensed under the MIT License (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * https://opensource.org/licenses/MIT
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | ]]
15 | --[[
16 | ROBLOX deviation: ReactFiberHostConfig captures singleton state across the
17 | whole workspace. This file and the modules it requires were moved from React
18 | to untangle a cyclic workspace member dependency.
19 |
20 | Before:
21 | * ReactFiberHostConfig (and the 5 associated modules) lived in React
22 | * React had a dependency on Shared
23 | * Shared reached into React source to re-export ReactFiberHostConfig (cycle)
24 |
25 | After:
26 | * ReactFiberHostConfig (and the 5 associated modules) live in Shared
27 | * React depends on Shared
28 | * Shared has no intra-workspace dependencies (no cycles)
29 | ]]
30 |
31 | -- types that are common across ReactFiberHostConfig files, moved here to avoid circular deps
32 | type Object = { [string]: any }
33 | export type OpaqueIDType = string | Object
34 |
35 | return {
36 | WithNoHydration = require(script.WithNoHydration),
37 | WithNoPersistence = require(script.WithNoPersistence),
38 | WithNoTestSelectors = require(script.WithNoTestSelectors),
39 | }
40 |
--------------------------------------------------------------------------------
/modules/shared/src/UninitializedState.roblox.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | * Copyright (c) Roblox Corporation. All rights reserved.
3 | * Licensed under the MIT License (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * https://opensource.org/licenses/MIT
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | ]]
15 | --!strict
16 | local console = require(script.Parent.console)
17 |
18 | local Packages = script.Parent.Parent
19 | local ReactGlobals = require(Packages.ReactGlobals)
20 |
21 | -- ROBLOX DEVIATION: Initialize state to a singleton that warns on access and errors on assignment
22 | -- initial state singleton
23 | local UninitializedState = {}
24 |
25 | setmetatable(UninitializedState, {
26 | __index = function(table, key)
27 | if ReactGlobals.__DEV__ then
28 | console.warn(
29 | "Attempted to access uninitialized state. Use setState to initialize state"
30 | )
31 | end
32 | return nil
33 | end,
34 | __newindex = function(table, key)
35 | if ReactGlobals.__DEV__ then
36 | console.error(
37 | "Attempted to directly mutate state. Use setState to assign new values to state."
38 | )
39 | end
40 | return nil
41 | end,
42 | __tostring = function(self)
43 | return ""
44 | end,
45 | __metatable = "UninitializedState",
46 | })
47 |
48 | return UninitializedState
49 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Roact Documentation
2 | site_url: https://roblox.github.io/roact-alignment/
3 | repo_name: Roblox/roact-alignment
4 | repo_url: https://github.com/Roblox/roact-alignment
5 |
6 | theme:
7 | name: material
8 | palette:
9 | - media: "(prefers-color-scheme: light)"
10 | primary: indigo
11 | scheme: default
12 | toggle:
13 | icon: material/toggle-switch-off-outline
14 | name: Switch to dark mode
15 | - media: "(prefers-color-scheme: dark)"
16 | primary: indigo
17 | scheme: slate
18 | toggle:
19 | icon: material/toggle-switch
20 | name: Switch to light mode
21 |
22 | plugins:
23 | - search:
24 | separator: '[\s\-\.]'
25 |
26 | nav:
27 | - Home: index.md
28 | - Deviations: deviations.md
29 | - Configuration: configuration.md
30 | - Migrating From 1.x:
31 | - Minimum Requirements: migrating-from-1x/minimum-requirements.md
32 | - Add Roact 17 Dependency: migrating-from-1x/upgrading-to-roact-17.md
33 | - Adopt New Features: migrating-from-1x/adopt-new-features.md
34 | - Convert Legacy Conventions: migrating-from-1x/convert-legacy-conventions.md
35 | - API Reference:
36 | - React: api-reference/react.md
37 | - ReactRoblox: api-reference/react-roblox.md
38 | - RoactCompat: api-reference/roact-compat.md
39 | - Additional Libraries: api-reference/additional-libraries.md
40 | - Benchmarks: bench.md
41 |
42 | extra_css:
43 | - extra.css
44 |
45 | markdown_extensions:
46 | - admonition
47 | - codehilite:
48 | guess_lang: false
49 | - toc:
50 | permalink: true
51 | - pymdownx.superfences
52 | # FIXME: Add this back when the tabbed extension is supported by docs-deploy
53 | # - pymdownx.tabbed:
54 | # alternate_style: false
55 |
--------------------------------------------------------------------------------
/modules/scheduler/src/NoYield.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX note: no upstream
2 | --!strict
3 | --[[
4 | * Copyright (c) Roblox Corporation. All rights reserved.
5 | * Licensed under the MIT License (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * https://opensource.org/licenses/MIT
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | ]]
17 |
18 | local YIELD_ERROR =
19 | "Yielding is not currently supported inside components or hooks. Move this yield into a new thread with `task.spawn` or `task.defer`."
20 |
21 | local function resultHandler(co: thread, ok: boolean, ...)
22 | if not ok then
23 | local err = (...)
24 | if typeof(err) == "string" then
25 | error(debug.traceback(co, err), 3)
26 | else
27 | -- If the error is not of type string, just assume it has some
28 | -- meaningful information and rethrow it so that top-level error
29 | -- handlers can process it.
30 | error(err, 3)
31 | end
32 | end
33 |
34 | if coroutine.status(co) ~= "dead" then
35 | error(debug.traceback(co, YIELD_ERROR), 3)
36 | end
37 |
38 | return ...
39 | end
40 |
41 | --[[
42 | Prevents a callback from yielding. If the callback yields, an error will be
43 | thrown.
44 | ]]
45 | local function NoYield(callback: (A...) -> R..., ...: A...): R...
46 | local co = coroutine.create(callback)
47 | return resultHandler(co, coroutine.resume(co, ...))
48 | end
49 |
50 | return NoYield
51 |
--------------------------------------------------------------------------------
/modules/scheduler/src/unstable_mock.lua:
--------------------------------------------------------------------------------
1 | --!strict
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *]]
8 | local Tracing = require(script.Parent.Tracing)
9 | local TracingSubscriptions = require(script.Parent.TracingSubscriptions)
10 | -- ROBLOX deviation: export Tracing type from the package exports to avoid direct file access
11 | export type Interaction = Tracing.Interaction
12 |
13 | local initializeScheduler = require(script.Parent.Scheduler)
14 | local HostConfig = require(script.Parent.forks["SchedulerHostConfig.mock"])
15 |
16 | local Scheduler = initializeScheduler(HostConfig)
17 |
18 | local exports = {}
19 | exports.tracing = {}
20 | -- ROBLOX FIXME Luau: need to fix CLI-56768 to remove any casts
21 | for key, value in Scheduler :: any do
22 | exports[key] = value
23 | end
24 | for key, value in Tracing :: any do
25 | exports.tracing[key] = value
26 | end
27 | for key, value in TracingSubscriptions :: any do
28 | exports.tracing[key] = value
29 | end
30 |
31 | exports.unstable_flushAllWithoutAsserting = HostConfig.unstable_flushAllWithoutAsserting
32 | exports.unstable_flushNumberOfYields = HostConfig.unstable_flushNumberOfYields
33 | exports.unstable_flushExpired = HostConfig.unstable_flushExpired
34 | exports.unstable_clearYields = HostConfig.unstable_clearYields
35 | exports.unstable_flushUntilNextPaint = HostConfig.unstable_flushUntilNextPaint
36 | exports.unstable_flushAll = HostConfig.unstable_flushAll
37 | exports.unstable_yieldValue = HostConfig.unstable_yieldValue
38 | exports.unstable_advanceTime = HostConfig.unstable_advanceTime
39 | exports.unstable_Profiling = Scheduler.unstable_Profiling
40 |
41 | return exports
42 |
--------------------------------------------------------------------------------
/modules/react-reconciler/README.md:
--------------------------------------------------------------------------------
1 | # react-reconciler
2 | A Roblox Lua port of the `react-reconciler` package from React, which contains the core reconciler logic that drives the various renderers that can be attached.
3 |
4 | Status: 🔨 Port in progress
5 |
6 | Source: https://github.com/facebook/react/tree/master/packages/react-reconciler
7 |
8 | ---
9 |
10 | ### ✏️ Notes
11 |
12 | #### Profiling
13 |
14 | ```
15 | src/SchedulingProfiler.js
16 | src/__tests__/SchedulingProfiler-test.internal.js
17 | ```
18 |
19 | Profiling logic used for debugging the Scheduler. Includes tests that produce flamegraphs of processed tasks. This functionality is gated behind the `enableProfiling` flag defined in `SchedulerFeatureFlags.js`. Additional functionality is gated behind the enableSchedulingProfiler in ReactFeatureFlags. When enabling the Scheduling Profiler, you'll need to plug in a table with a `mark` function to the `_G.performance` global, like this:
20 | ```lua
21 | _G.performance = {
22 | mark = function(str)
23 | debug.profileBegin(str)
24 | debug.profileEnd(str)
25 | }
26 | ```
27 |
28 | #### Debug Tracing
29 |
30 | ```
31 | src/DebugTracing.js
32 | src/__tests__/DebugTracing-test.internal.js
33 | ```
34 |
35 | Debug Tracing is enabled with the enableDebugTracing ReactFeatureFlag. The current Lua implementation outputs using Lua `print`, and strips out the color and styling versus upstream. We may want to more deeply customize this based on real-world use cases of Roblox UI developers.
36 |
37 |
38 | ### ❌ Excluded
39 |
40 | ```
41 | src/__tests__/ReactSuspenseList-test.js
42 | ```
43 |
44 | The initial release of Roact 17 includes support for Suspense, but not the unstable SuspenseList API. This was purely to pull in the delivery schedule and narrow the support surface for the initial release.
45 |
--------------------------------------------------------------------------------
/modules/shared/src/ReactSharedInternals/ReactDebugCurrentFrame.lua:
--------------------------------------------------------------------------------
1 | --!strict
2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/98d410f5005988644d01c9ec79b7181c3dd6c847/packages/react/src/ReactDebugCurrentFrame.js
3 | --[[*
4 | * Copyright (c) Facebook, Inc. and its affiliates.
5 | *
6 | * This source code is licensed under the MIT license found in the
7 | * LICENSE file in the root directory of this source tree.
8 | *
9 | * @flow
10 | ]]
11 |
12 | local Packages = script.Parent.Parent.Parent
13 | local ReactGlobals = require(Packages.ReactGlobals)
14 |
15 | local ReactDebugCurrentFrame = {}
16 |
17 | local currentExtraStackFrame = nil :: nil | string
18 |
19 | function ReactDebugCurrentFrame.setExtraStackFrame(stack: string?): ()
20 | if ReactGlobals.__DEV__ then
21 | currentExtraStackFrame = stack
22 | end
23 | end
24 |
25 | if ReactGlobals.__DEV__ then
26 | -- deviation: in Lua, the implementation is duplicated
27 | -- function ReactDebugCurrentFrame.setExtraStackFrame(stack: string?)
28 | -- if ReactGlobals.__DEV__ then
29 | -- currentExtraStackFrame = stack
30 | -- end
31 | -- end
32 |
33 | -- Stack implementation injected by the current renderer.
34 | ReactDebugCurrentFrame.getCurrentStack = nil :: nil | (() -> string)
35 |
36 | function ReactDebugCurrentFrame.getStackAddendum(): string
37 | local stack = ""
38 |
39 | -- Add an extra top frame while an element is being validated
40 | if currentExtraStackFrame then
41 | stack = stack .. currentExtraStackFrame
42 | end
43 |
44 | -- Delegate to the injected renderer-specific implementation
45 | local impl = ReactDebugCurrentFrame.getCurrentStack
46 | if impl then
47 | stack = stack .. (impl() or "")
48 | end
49 |
50 | return stack
51 | end
52 | end
53 |
54 | return ReactDebugCurrentFrame
55 |
--------------------------------------------------------------------------------
/modules/shared/src/__tests__/getComponentName.roblox.spec.lua:
--------------------------------------------------------------------------------
1 | local Packages = script.Parent.Parent.Parent
2 | local JestGlobals = require(Packages.Dev.JestGlobals)
3 | local beforeEach = JestGlobals.beforeEach
4 | local jestExpect = JestGlobals.expect
5 | local describe = JestGlobals.describe
6 | local it = JestGlobals.it
7 | local React
8 |
9 | local getComponentName
10 | local function MyComponent() end
11 | local anonymous = function() end
12 |
13 | beforeEach(function()
14 | React = require(Packages.Dev.React)
15 |
16 | getComponentName = require(Packages.Shared).getComponentName
17 | end)
18 |
19 | describe("function components", function()
20 | it("gets name from non-anonymous function", function()
21 | jestExpect(getComponentName(MyComponent)).toBe("MyComponent")
22 | end)
23 | it("gets fileName:lineNumber from anonymous function", function()
24 | local anonymous = function() end
25 | jestExpect(getComponentName(anonymous)).toMatch(
26 | "getComponentName.roblox.spec:[0-9]*"
27 | )
28 | end)
29 | end)
30 | describe("Lazy components", function()
31 | it("gets name from lazy-wrapped non-anonymous function", function()
32 | local lazyMyComponent = React.lazy(function()
33 | return {
34 | andThen = function(self, resolve)
35 | resolve({ default = MyComponent })
36 | end,
37 | }
38 | end)
39 | jestExpect(getComponentName(lazyMyComponent)).toBe("MyComponent")
40 | end)
41 | it("gets fileName:lineNumber from lazy-wrapped anonymous function", function()
42 | local lazyAnonymous = React.lazy(function()
43 | return {
44 | andThen = function(self, resolve)
45 | resolve({ default = anonymous })
46 | end,
47 | }
48 | end)
49 | jestExpect(getComponentName(lazyAnonymous)).toMatch(
50 | "getComponentName.roblox.spec:[0-9]*"
51 | )
52 | end)
53 | end)
54 |
--------------------------------------------------------------------------------
/modules/roact-compat/src/oneChild.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/Roblox/roact/blob/master/src/oneChild.lua
2 | --[[
3 | * Copyright (c) Roblox Corporation. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | ]]
16 | local Packages = script.Parent.Parent
17 | local React = require(Packages.React)
18 | local ReactGlobals = require(Packages.ReactGlobals)
19 |
20 | local warnOnce = require(script.Parent.warnOnce)
21 |
22 | local function oneChild(children)
23 | if ReactGlobals.__DEV__ and ReactGlobals.__COMPAT_WARNINGS__ then
24 | warnOnce(
25 | "oneChild",
26 | "You likely don't need this at all! If you were assigning children "
27 | .. "via `React.oneChild(someChildren)`, you can simply use "
28 | .. "`someChildren` directly."
29 | )
30 | end
31 |
32 | -- This behavior is a bit different from upstream, so we're adapting current
33 | -- Roact's logic (which will unwrap a table with a single member)
34 | if not children then
35 | return nil
36 | end
37 |
38 | local key, child = next(children)
39 |
40 | if not child then
41 | return nil
42 | end
43 |
44 | local after = next(children, key)
45 |
46 | if after then
47 | error("Expected at most one child, had more than one child.", 2)
48 | end
49 |
50 | return React.Children.only(child)
51 | end
52 |
53 | return oneChild
54 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactIncrementalErrorReplay.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/d13f5b9538e48f74f7c571ef3cde652ca887cca0/packages/react-reconciler/src/__tests__/ReactIncrementalErrorReplay-test.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @emails react-core
9 | * @jest-environment node
10 | --]]
11 | --!strict
12 |
13 | local Packages = script.Parent.Parent.Parent
14 | local React
15 | local ReactNoop
16 | local Scheduler
17 |
18 | local JestGlobals = require(Packages.Dev.JestGlobals)
19 | local jestExpect = JestGlobals.expect
20 | local beforeEach = JestGlobals.beforeEach
21 | local jest = JestGlobals.jest
22 | local it = JestGlobals.it
23 |
24 | beforeEach(function()
25 | jest.resetModules()
26 |
27 | React = require(Packages.React)
28 | ReactNoop = require(Packages.Dev.ReactNoopRenderer)
29 | Scheduler = require(Packages.Scheduler)
30 | end)
31 |
32 | -- ROBLOX deviation: this test doesn't make sense in not JSX
33 | -- it('should fail gracefully on error in the host environment', () => {
34 | -- ReactNoop.render();
35 | -- jestExpect(Scheduler).toFlushAndThrow('Error in host config.');
36 | -- });
37 |
38 | it("should ignore error if it doesn't throw on retry", function()
39 | local didInit = false
40 |
41 | local function badLazyInit()
42 | local needsInit = not didInit
43 | didInit = true
44 | if needsInit then
45 | error("Hi")
46 | end
47 | end
48 |
49 | local App = React.Component:extend("App")
50 | function App:render()
51 | badLazyInit()
52 | return React.createElement("TextLabel", { Text = "Hello" })
53 | end
54 | ReactNoop.render(React.createElement(App))
55 | jestExpect(Scheduler).toFlushWithoutYielding()
56 | end)
57 |
--------------------------------------------------------------------------------
/modules/react-devtools-timeline/src/init.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX note: no upstream
2 |
3 | local exports = {}
4 |
5 | local constants = require(script.constants)
6 | exports.REACT_TOTAL_NUM_LANES = constants.REACT_TOTAL_NUM_LANES
7 | exports.SCHEDULING_PROFILER_VERSION = constants.SCHEDULING_PROFILER_VERSION
8 |
9 | local types = require(script.types)
10 | export type ScrollState = types.ScrollState
11 | export type ErrorStackFrame = types.ErrorStackFrame
12 | export type Milliseconds = types.Milliseconds
13 | export type ReactLane = types.ReactLane
14 | export type NativeEvent = types.NativeEvent
15 | export type ReactScheduleRenderEvent = types.ReactScheduleRenderEvent
16 | export type ReactScheduleStateUpdateEvent = types.ReactScheduleStateUpdateEvent
17 | export type ReactScheduleForceUpdateEvent = types.ReactScheduleForceUpdateEvent
18 | export type Phase = types.Phase
19 | export type SuspenseEvent = types.SuspenseEvent
20 | export type ThrownError = types.ThrownError
21 | export type SchedulingEvent = types.SchedulingEvent
22 | export type SchedulingEventType = types.SchedulingEventType
23 | export type ReactMeasureType = types.ReactMeasureType
24 | export type BatchUID = types.BatchUID
25 | export type ReactMeasure = types.ReactMeasure
26 | export type NetworkMeasure = types.NetworkMeasure
27 | export type ReactComponentMeasureType = types.ReactComponentMeasureType
28 | export type ReactComponentMeasure = types.ReactComponentMeasure
29 | export type FlamechartStackFrame = types.FlamechartStackFrame
30 | export type UserTimingMark = types.UserTimingMark
31 | export type Snapshot = types.Snapshot
32 | export type FlamechartStackLayer = types.FlamechartStackLayer
33 | export type Flamechart = types.Flamechart
34 | export type HorizontalScrollStateChangeCallback = types.HorizontalScrollStateChangeCallback
35 | export type SearchRegExpStateChangeCallback = types.SearchRegExpStateChangeCallback
36 |
37 | return table.freeze(exports)
38 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/ReactFiberHostConfig.lua:
--------------------------------------------------------------------------------
1 | --!strict
2 | -- ROBLOX upstream: https://github.com/facebook/react/blob/9ac42dd074c42b66ecc0334b75200b1d2989f892/packages/react-reconciler/src/ReactFiberHostConfig.js
3 | --[[*
4 | * Copyright (c) Facebook, Inc. and its affiliates.
5 | *
6 | * This source code is licensed under the MIT license found in the
7 | * LICENSE file in the root directory of this source tree.
8 | *
9 | * @flow
10 | ]]
11 |
12 | --[[ eslint-disable react-internal/invariant-args ]]
13 |
14 | -- ROBLOX FIXME: Cannot carry types over via the module overriding that's in use
15 | -- here; this is a particularly tricky case of cross-dependency type definitions
16 | -- Use a common set of typedefs across ReactTestHostConfig and ReactRobloxHostTypes
17 | type Object = { [string]: any }
18 |
19 | export type Instance = Object
20 | export type HostInstance = Instance
21 | export type TextInstance = Instance
22 | export type Container = Object
23 | export type HostContext = Object
24 | export type HydratableInstance = Instance | SuspenseInstance
25 | export type SuspenseInstance = Object
26 | export type PublicInstance = HostInstance
27 |
28 | export type Type = string
29 | export type Props = Object
30 | export type ChildSet = {} -- void, unused
31 | export type RendererInspectionConfig = Object
32 |
33 | -- if _G.__NO_LOADMODULE__ then
34 | local exports: { [string]: any } = {}
35 | return exports
36 | -- end
37 |
38 | -- -- We expect that our Rollup, Jest, and Flow configurations
39 | -- -- always shim this module with the corresponding host config
40 | -- -- (either provided by a renderer, or a generic shim for npm).
41 | -- --
42 | -- -- We should never resolve to this file, but it exists to make
43 | -- -- sure that if we *do* accidentally break the configuration,
44 | -- -- the failure isn't silent.
45 |
46 | -- -- deviation: FIXME (roblox): is there a way to configure luau to account for this module
47 | -- -- being shimmed?
48 | -- error('This module must be shimmed by a specific renderer.')
49 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactTopLevelText.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/69060e1da6061af845162dcf6854a5d9af28350a/packages/react-reconciler/src/__tests__/ReactTopLevelText-test.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @emails react-core
9 | * @jest-environment node
10 | ]]
11 | --!strict
12 |
13 | local Packages = script.Parent.Parent.Parent
14 | local React
15 | local ReactNoop
16 | local Scheduler
17 |
18 | -- This is a new feature in Fiber so I put it in its own test file. It could
19 | -- probably move to one of the other test files once it is official.
20 | local JestGlobals = require(Packages.Dev.JestGlobals)
21 | local jestExpect = JestGlobals.expect
22 | local beforeEach = JestGlobals.beforeEach
23 | local it = JestGlobals.it
24 | local jest = JestGlobals.jest
25 | local describe = JestGlobals.describe
26 |
27 | describe("ReactTopLevelText", function()
28 | beforeEach(function()
29 | jest.resetModules()
30 |
31 | React = require(Packages.React)
32 | ReactNoop = require(Packages.Dev.ReactNoopRenderer)
33 | Scheduler = require(Packages.Scheduler)
34 | end)
35 |
36 | it("should render a component returning strings directly from render", function()
37 | local Text = function(props)
38 | return props.value
39 | end
40 | ReactNoop.render(React.createElement(Text, { value = "foo" }))
41 | jestExpect(Scheduler).toFlushWithoutYielding()
42 |
43 | jestExpect(ReactNoop).toMatchRenderedOutput("foo")
44 | end)
45 |
46 | it("should render a component returning numbers directly from renderß", function()
47 | local Text = function(props)
48 | return props.value
49 | end
50 | ReactNoop.render(React.createElement(Text, { value = 10 }))
51 | jestExpect(Scheduler).toFlushWithoutYielding()
52 |
53 | jestExpect(ReactNoop).toMatchRenderedOutput("10")
54 | end)
55 | end)
56 |
--------------------------------------------------------------------------------
/modules/shared/src/__tests__/ReactErrorProd-internal.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/9a5576f4d263ac5d7a9462a287d1524fda3355b8/packages/shared/__tests__/ReactErrorProd-test.internal.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @emails react-core
9 | ]]
10 | --!strict
11 |
12 | local Packages = script.Parent.Parent.Parent
13 | local JestGlobals = require(Packages.Dev.JestGlobals)
14 | local beforeEach = JestGlobals.beforeEach
15 | local jestExpect = JestGlobals.expect
16 | local it = JestGlobals.it
17 | local jest = JestGlobals.jest
18 | local formatProdErrorMessage
19 |
20 | beforeEach(function()
21 | jest.resetModules()
22 | formatProdErrorMessage = require(script.Parent.Parent.formatProdErrorMessage)
23 | end)
24 |
25 | it("should throw with the correct number of `%s`s in the URL", function()
26 | jestExpect(formatProdErrorMessage(124, "foo", "bar")).toEqual(
27 | "Minified React error #124; visit "
28 | .. "https://reactjs.org/docs/error-decoder.html?invariant=124&args[]=foo&args[]=bar"
29 | .. " for the full message or use the non-minified dev environment"
30 | .. " for full errors and additional helpful warnings."
31 | )
32 |
33 | jestExpect(formatProdErrorMessage(20)).toEqual(
34 | "Minified React error #20; visit "
35 | .. "https://reactjs.org/docs/error-decoder.html?invariant=20"
36 | .. " for the full message or use the non-minified dev environment"
37 | .. " for full errors and additional helpful warnings."
38 | )
39 |
40 | jestExpect(formatProdErrorMessage(77, "", "&?bar")).toEqual(
41 | "Minified React error #77; visit "
42 | .. "https://reactjs.org/docs/error-decoder.html?invariant=77&args[]=%3Cdiv%3E&args[]=%26%3Fbar"
43 | .. " for the full message or use the non-minified dev environment"
44 | .. " for full errors and additional helpful warnings."
45 | )
46 | end)
47 |
--------------------------------------------------------------------------------
/modules/scheduler/README.md:
--------------------------------------------------------------------------------
1 | # scheduler
2 | A Roblox Lua port of the `scheduler` package from React, which is used under the hood by the Fiber Reconciler logic.
3 |
4 | Status: ✔️ Ported
5 |
6 | Source: https://github.com/facebook/react/tree/master/packages/scheduler
7 |
8 | ---
9 |
10 | ### ✏️ Notes
11 | * The upstream implementation of min-heap does not include tests. To validate the port, `src/__tests__/SchedulerMinHeap.spec.lua` was added
12 | * The scheduler contains two sets of tests
13 | * A small, basic set of tests using the real scheduler and mock timers.
14 | * A more thorough test suite that uses `MockSchedulerHostConfig.lua`, which mocks the entire HostConfig interface and provides functionality to manipulate it within tests.
15 |
16 | #### Profiling
17 |
18 | ```
19 | src/SchedulerProfiling.js
20 | src/__tests__/SchedulerProfiling-test.js
21 | ```
22 |
23 | Profiling logic used for debugging the Scheduler. Includes tests that produce flamegraphs of processed tasks. This functionality is gated behind the `enableProfiling` flag defined in `SchedulerFeatureFlags.js`. Additional functionality is gated behind the enableSchedulingProfiler flag in ReactFeatureFlags. When enabling the Scheduling Profiler, you'll need to plug in a table with a `mark` function to the `_G.performance` global, like this:
24 | ```lua
25 | _G.performance = {
26 | mark = function(str)
27 | debug.profileBegin(str)
28 | debug.profileEnd(str)
29 | }
30 | ```
31 |
32 | We may customize this for deeper integration with the Roblox Studio profiler in the future, based on specific performance optimization workflow needs.
33 |
34 |
35 | ### ❌ Excluded
36 |
37 | #### Post Task
38 |
39 | ```
40 | unstable_post_task.js
41 | src/SchedulerPostTask.js
42 | src/__tests__/SchedulerPostTask-test.js
43 | ```
44 |
45 | An alternate implementation of the Scheduler interface based on the new postTask browser API. There are relatively recent commits to these files, as well, suggesting new or ongoing development. Since the underlying browser API isn't relevant to the Roblox environment, this logic has been excluded.
46 |
--------------------------------------------------------------------------------
/modules/shared/src/ReactFiberHostConfig/WithNoHydration.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/c5d2fc7127654e43de59fff865b74765a103c4a5/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @flow
9 | ]]
10 |
11 | local invariant = require(script.Parent.Parent.invariant)
12 |
13 | -- Renderers that don't support hydration
14 | -- can re-export everything from this module.
15 |
16 | function shim(...)
17 | invariant(
18 | false,
19 | "The current renderer does not support hydration. "
20 | .. "This error is likely caused by a bug in React. "
21 | .. "Please file an issue."
22 | )
23 | end
24 |
25 | -- Hydration (when unsupported)
26 | export type SuspenseInstance = any
27 | return {
28 | supportsHydration = false,
29 | canHydrateInstance = shim,
30 | canHydrateTextInstance = shim,
31 | canHydrateSuspenseInstance = shim,
32 | isSuspenseInstancePending = shim,
33 | isSuspenseInstanceFallback = shim,
34 | registerSuspenseInstanceRetry = shim,
35 | getNextHydratableSibling = shim,
36 | getFirstHydratableChild = shim,
37 | hydrateInstance = shim,
38 | hydrateTextInstance = shim,
39 | hydrateSuspenseInstance = shim,
40 | getNextHydratableInstanceAfterSuspenseInstance = shim,
41 | commitHydratedContainer = shim,
42 | commitHydratedSuspenseInstance = shim,
43 | clearSuspenseBoundary = shim,
44 | clearSuspenseBoundaryFromContainer = shim,
45 | didNotMatchHydratedContainerTextInstance = shim,
46 | didNotMatchHydratedTextInstance = shim,
47 | didNotHydrateContainerInstance = shim,
48 | didNotHydrateInstance = shim,
49 | didNotFindHydratableContainerInstance = shim,
50 | didNotFindHydratableContainerTextInstance = shim,
51 | didNotFindHydratableContainerSuspenseInstance = shim,
52 | didNotFindHydratableInstance = shim,
53 | didNotFindHydratableTextInstance = shim,
54 | didNotFindHydratableSuspenseInstance = shim,
55 | }
56 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | Roact is a lua port of Facebook's [React](https://reactjs.org) UI library.
2 |
3 | By and large, [React's documentation](https://reactjs.org/docs/getting-started.html) should be able to serve most Roact users' needs. This documentation site serves as a comprehensive guide to the _differences_ between Roact and React.
4 |
5 | If you're new to the React library and want **to start learning the concepts of React**, begin with the [React JS documentation](https://reactjs.org/docs/getting-started.html).
6 |
7 | If you want **to find out if a React feature is present in Roact (and if there are any differences to be aware of)**, check out the [API Reference](api-reference/react.md).
8 |
9 | If you're familiar with React and want **to learn where Roact differs**, start with the [Deviations page](deviations.md).
10 |
11 | And if you want **to migrate an existing project from Roact 1.x to Roact 17+**, check out the guide on [Migrating From Roact 1.x](migrating-from-1x/minimum-requirements.md).
12 |
13 | ### Which Part is "Roact"?
14 |
15 | Previously, **Roact** was the name that referred to a single package. It implemented a similar API to that of React 15. This documentation refers to those older versions of Roact as **legacy Roact** or **Roact 1.x**.
16 |
17 | Today, **Roact** is used as an umbrella term to describe the collection of packages that compose the Luau port of the React library. This collection of packages includes a few top-level ones, namely `React`, `ReactRoblox`, and `RoactCompat`. This documentation refers to the initial release of this version of Roact as **Roact 17** (because it aligns its implementation to React JS version 17.0.1) or **Roact 17+** (to include future releases).
18 |
19 | The originating React UI library, written in JavaScript, is referred to in this documentation as **React JS** in order to clearly disambiguate it from Roact and from the top-level package called `React`. In can be considered an umbrella term for the collection of packages, similar to "Roact".
20 |
21 | This documentation endeavors to use these terms consistently throughout to avoid confusion.
22 |
--------------------------------------------------------------------------------
/modules/shared/README.md:
--------------------------------------------------------------------------------
1 | # shared
2 | A Roblox Lua port of the `shared` pseudo-package from React, which contains a number of common utilities and definitions used across the React monorepo.
3 |
4 | Status: ✔️ Ported
5 |
6 | Source: https://github.com/facebook/react/tree/master/packages/shared
7 |
8 | ---
9 |
10 | ### ✏️ Notes
11 | * `ReactTypes.js` contains a number of complex flow-type definitions that are not yet possible with Luau, so it's been simplified to a stub called `ReactTypes.roblox.lua`
12 | * `ReactComponentStackFrame.js` is replaced by a partially-ported stub (`ReactComponentStackFrame.roblox.lua`) since it contains logic for parsing/navigating JS-specific stack structure. This needs to be ported to the equivalent functionality in Luau.
13 | * Some slight changes to `isValidElement.lua` that account for the divergent shape of Component and PureComponent objects in our port (for `react`, they're functions; for us, they're tables)
14 |
15 | ### ❌ Excluded
16 |
17 | #### Forked Config
18 | ```
19 | forks/ReactFeatureFlags.native-fb.js
20 | forks/ReactFeatureFlags.native-oss.js
21 | forks/ReactFeatureFlags.readonly.js
22 | forks/ReactFeatureFlags.test-renderer.js
23 | forks/ReactFeatureFlags.test-renderer.native.js
24 | forks/ReactFeatureFlags.test-renderer.www.js
25 | forks/ReactFeatureFlags.testing.js
26 | forks/ReactFeatureFlags.testing.www.js
27 | forks/ReactFeatureFlags.www-dynamic.js
28 | forks/ReactFeatureFlags.www.js
29 | forks/Scheduler.umd.js
30 | forks/SchedulerTracing.umd.js
31 | forks/consoleWithStackDev.www.js
32 | forks/invokeGuardedCallbackImpl.www.js
33 | forks/object-assign.inline-umd.js
34 | forks/object-assign.umd.js
35 | ```
36 |
37 | Forks that specify different flag states are used in React with the help of a bundler that swaps in the correct file for the given environment. We don't have this kind of functionality yet, nor the same set of environments.
38 |
39 | #### Integration Tests
40 | ```
41 | __tests__/describeComponentFrame-test.js
42 | __tests__/ReactError-test.internal.js
43 | ```
44 | These tests required use of React and ReactDOM, and are not viable to port until we have more of the reconciler ported.
--------------------------------------------------------------------------------
/modules/react-roblox/src/client/roblox/__tests__/getDefaultInstanceProperty.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/Roblox/roact/blob/b2ba9cf4c219c2654e6572219a68d0bf1b541418/src/getDefaultInstanceProperty.spec.lua
2 | --[[
3 | * Copyright (c) Roblox Corporation. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | ]]
16 |
17 | local Packages = script.Parent.Parent.Parent.Parent.Parent
18 | local JestGlobals = require(Packages.Dev.JestGlobals)
19 | local jestExpect = JestGlobals.expect
20 | local it = JestGlobals.it
21 | local getDefaultInstanceProperty =
22 | require(script.Parent.Parent.getDefaultInstanceProperty)
23 |
24 | it("should get default name string values", function()
25 | local _, defaultName = getDefaultInstanceProperty("StringValue", "Name")
26 |
27 | jestExpect(defaultName).toBe("Value")
28 | end)
29 |
30 | it("should get default empty string values", function()
31 | local _, defaultValue = getDefaultInstanceProperty("StringValue", "Value")
32 |
33 | jestExpect(defaultValue).toBe("")
34 | end)
35 |
36 | it("should get default number values", function()
37 | local _, defaultValue = getDefaultInstanceProperty("IntValue", "Value")
38 |
39 | jestExpect(defaultValue).toBe(0)
40 | end)
41 |
42 | it("should get nil default values", function()
43 | local _, defaultValue = getDefaultInstanceProperty("ObjectValue", "Value")
44 |
45 | jestExpect(defaultValue).toBe(nil)
46 | end)
47 |
48 | it("should get bool default values", function()
49 | local _, defaultValue = getDefaultInstanceProperty("BoolValue", "Value")
50 |
51 | jestExpect(defaultValue).toBe(false)
52 | end)
53 |
--------------------------------------------------------------------------------
/bin/upstream-tag.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | REACT_PATH=$1
4 | EQUIVALENT_FOLDER=$2
5 |
6 | echo "Matching upstream files $REACT_PATH/$EQUIVALENT_FOLDER..."
7 |
8 | if [ $# -ne 2 ]; then
9 | echo "Usage:
10 | upstream-tag.sh
11 |
12 | path_to_react:
13 | The path to the local copy of the react repository; this is where we find
14 | the upstream version information (using 'git log' commands)
15 |
16 | target_folder:
17 | For example, if you run this script from the 'modules' folder, then
18 | target_folder should be 'packages'. If you run it from 'modules/react-is/src,
19 | then target_folder should be 'packages/react-is/src'"
20 | exit 1
21 | fi
22 |
23 | count=0
24 | for file in $(find * -name "*.lua")
25 | do
26 | if [[ "$file" == *"roblox-jest"* ]] || [[ "$file" == *"roblox-js-polyfill"* ]]; then
27 | echo "SKIP: $file is Roblox-only"
28 | continue
29 | fi
30 |
31 | if [[ "$file" == *".roblox."*"lua" ]]; then
32 | echo "SKIP: $file is Roblox-only"
33 | continue
34 | fi
35 |
36 | if [[ `head -n 1 $file` == "-- ROBLOX upstream:"* ]]; then
37 | echo "SKIP: $file already has 'upstream' comment"
38 | continue
39 | fi
40 |
41 | targetFileName="${file/-internal.spec/-test.internal}"
42 | targetFileName="${targetFileName/.spec/-test}"
43 | targetFileName="${targetFileName/.lua/.js}"
44 | targetFile="$EQUIVALENT_FOLDER/$targetFileName"
45 |
46 | if [[ ! -f "$REACT_PATH/$targetFile" ]]; then
47 | echo "SKIP: Equivalent file $targetFileName not found"
48 | continue
49 | fi
50 |
51 | pushd $REACT_PATH > /dev/null
52 | COMMIT=`git log -- $targetFile | head -n 1 | sed "s/commit //g"`
53 | REPO_PATH=`realpath --relative-to=$REACT_PATH $targetFile`
54 | PREFIX="-- ROBLOX upstream: https://github.com/facebook/react/blob/$COMMIT/$REPO_PATH"
55 | if [[ "$COMMIT" == "" ]]; then
56 | echo "SKIP: Could not find commit for $targetFile -> $file"
57 | continue
58 | fi
59 |
60 | count=$((count+1))
61 |
62 | echo ""
63 | echo "Prepend to $file..."
64 | echo $PREFIX
65 | popd > /dev/null
66 | echo "$PREFIX
67 | $(cat $file)" > $file
68 | done
69 |
70 | echo -e "\nAdded upstream tag to $count files"
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactFiberContext-internal.spec.lua:
--------------------------------------------------------------------------------
1 | -- awaiting pull request: https://github.com/facebook/react/pull/20155
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @emails react-core
9 | * @jest-environment node
10 | ]]
11 |
12 | local Packages = script.Parent.Parent.Parent
13 | local JestGlobals = require(Packages.Dev.JestGlobals)
14 | local jestExpect = JestGlobals.expect
15 | local jest = JestGlobals.jest
16 | local beforeEach = JestGlobals.beforeEach
17 | local describe = JestGlobals.describe
18 | local it = JestGlobals.it
19 |
20 | local ReactFiberContext
21 | local ReactFiber
22 | local ReactRootTags
23 | local ReactFeatureFlags
24 |
25 | beforeEach(function()
26 | jest.resetModules()
27 |
28 | ReactFiberContext = require(script.Parent.Parent["ReactFiberContext.new"])
29 | ReactFiber = require(script.Parent.Parent["ReactFiber.new"])
30 | ReactRootTags = require(script.Parent.Parent.ReactRootTags)
31 | ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags
32 | ReactFeatureFlags.disableLegacyContext = false
33 | end)
34 |
35 | describe("Context stack", function()
36 | it("should throw when pushing to top level of non-empty stack", function()
37 | local fiber = ReactFiber.createHostRootFiber(ReactRootTags.BlockingRoot)
38 | local context = {
39 | foo = 1,
40 | }
41 | -- The first call here is a valid use of pushTopLevelContextObject
42 | ReactFiberContext.pushTopLevelContextObject(fiber, context, true)
43 | jestExpect(function()
44 | local moreContext = {
45 | bar = 2,
46 | }
47 | ReactFiberContext.pushTopLevelContextObject(fiber, moreContext, true)
48 | end).toThrow("Unexpected context found on stack.")
49 | end)
50 |
51 | it("should throw if when invalidating a provider that isn't initialized", function()
52 | local fiber = ReactFiber.createHostRootFiber(ReactRootTags.BlockingRoot)
53 | jestExpect(function()
54 | ReactFiberContext.invalidateContextProvider(fiber, nil, true)
55 | end).toThrow("Expected to have an instance by this point.")
56 | end)
57 | end)
58 |
--------------------------------------------------------------------------------
/modules/react-devtools-shared/src/__tests__/bridge.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/__tests__/bridge-test.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | ]]
8 | local Packages = script.Parent.Parent.Parent
9 | local JestGlobals = require(Packages.Dev.JestGlobals)
10 | local describe = JestGlobals.describe
11 | local it = JestGlobals.it
12 | local beforeEach = JestGlobals.beforeEach
13 | local jestExpect = JestGlobals.expect
14 | local jest = JestGlobals.jest
15 |
16 | describe("bridge", function()
17 | local Bridge
18 |
19 | beforeEach(function()
20 | jest.resetModules()
21 | jest.useFakeTimers()
22 | Bridge = require(script.Parent.Parent.bridge)
23 | end)
24 |
25 | it("should shutdown properly", function()
26 | local wall = {
27 | listen = jest.fn(function()
28 | return function() end
29 | end),
30 | send = jest.fn(),
31 | }
32 | local bridge = Bridge.new(wall)
33 |
34 | -- Check that we're wired up correctly.
35 | bridge:send("reloadAppForProfiling")
36 | jest.runAllTimers()
37 | jestExpect(wall.send).toHaveBeenCalledWith("reloadAppForProfiling")
38 |
39 | -- Should flush pending messages and then shut down.
40 | wall.send.mockClear()
41 | bridge:send("update", "1")
42 | bridge:send("update", "2")
43 | bridge:shutdown()
44 | jest.runAllTimers()
45 | jestExpect(wall.send).toHaveBeenCalledWith("update", "1")
46 | jestExpect(wall.send).toHaveBeenCalledWith("update", "2")
47 | jestExpect(wall.send).toHaveBeenCalledWith("shutdown")
48 |
49 | -- Verify that the Bridge doesn't send messages after shutdown.
50 |
51 | wall.send.mockClear()
52 | -- ROBLOX deviation: instead of spying on console, use toWarnDev matcher
53 | jestExpect(function()
54 | bridge:send("should not send")
55 | end).toWarnDev(
56 | 'Cannot send message "should not send" through a Bridge that has been shutdown.',
57 | { withoutStack = true }
58 | )
59 | jest.runAllTimers()
60 | jestExpect(wall.send).never.toHaveBeenCalled()
61 | end)
62 | end)
63 |
--------------------------------------------------------------------------------
/modules/react-devtools-shared/src/backend/ReactSymbols.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/backend/ReactSymbols.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | ]]
8 | local exports = {}
9 | exports.CONCURRENT_MODE_NUMBER = 0xeacf
10 | exports.CONCURRENT_MODE_SYMBOL_STRING = "Symbol(react.concurrent_mode)"
11 |
12 | exports.CONTEXT_NUMBER = 0xeace
13 | exports.CONTEXT_SYMBOL_STRING = "Symbol(react.context)"
14 |
15 | exports.DEPRECATED_ASYNC_MODE_SYMBOL_STRING = "Symbol(react.async_mode)"
16 |
17 | exports.ELEMENT_NUMBER = 0xeac7
18 | exports.ELEMENT_SYMBOL_STRING = "Symbol(react.element)"
19 |
20 | exports.DEBUG_TRACING_MODE_NUMBER = 0xeae1
21 | exports.DEBUG_TRACING_MODE_SYMBOL_STRING = "Symbol(react.debug_trace_mode)"
22 |
23 | exports.FORWARD_REF_NUMBER = 0xead0
24 | exports.FORWARD_REF_SYMBOL_STRING = "Symbol(react.forward_ref)"
25 |
26 | exports.FRAGMENT_NUMBER = 0xeacb
27 | exports.FRAGMENT_SYMBOL_STRING = "Symbol(react.fragment)"
28 |
29 | exports.LAZY_NUMBER = 0xead4
30 | exports.LAZY_SYMBOL_STRING = "Symbol(react.lazy)"
31 |
32 | exports.MEMO_NUMBER = 0xead3
33 | exports.MEMO_SYMBOL_STRING = "Symbol(react.memo)"
34 |
35 | exports.OPAQUE_ID_NUMBER = 0xeae0
36 | exports.OPAQUE_ID_SYMBOL_STRING = "Symbol(react.opaque.id)"
37 |
38 | exports.PORTAL_NUMBER = 0xeaca
39 | exports.PORTAL_SYMBOL_STRING = "Symbol(react.portal)"
40 |
41 | exports.PROFILER_NUMBER = 0xead2
42 | exports.PROFILER_SYMBOL_STRING = "Symbol(react.profiler)"
43 |
44 | exports.PROVIDER_NUMBER = 0xeacd
45 | exports.PROVIDER_SYMBOL_STRING = "Symbol(react.provider)"
46 |
47 | exports.SCOPE_NUMBER = 0xead7
48 | exports.SCOPE_SYMBOL_STRING = "Symbol(react.scope)"
49 |
50 | exports.STRICT_MODE_NUMBER = 0xeacc
51 | exports.STRICT_MODE_SYMBOL_STRING = "Symbol(react.strict_mode)"
52 |
53 | exports.SUSPENSE_NUMBER = 0xead1
54 | exports.SUSPENSE_SYMBOL_STRING = "Symbol(react.suspense)"
55 |
56 | exports.SUSPENSE_LIST_NUMBER = 0xead8
57 | exports.SUSPENSE_LIST_SYMBOL_STRING = "Symbol(react.suspense_list)"
58 |
59 | return exports
60 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactFiberStack-test.roblox.spec.lua:
--------------------------------------------------------------------------------
1 | local Packages = script.Parent.Parent.Parent
2 | local JestGlobals = require(Packages.Dev.JestGlobals)
3 | local jestExpect = JestGlobals.expect
4 | local describe = JestGlobals.describe
5 | local beforeEach = JestGlobals.beforeEach
6 | local jest = JestGlobals.jest
7 | local it = JestGlobals.it
8 |
9 | local ReactFiberStack
10 |
11 | describe("ReactFiberStack", function()
12 | beforeEach(function()
13 | jest.resetModules()
14 | ReactFiberStack = require(script.Parent.Parent["ReactFiberStack.new"])
15 | end)
16 |
17 | it("creates a cursor with the given default value", function()
18 | local defaultValue = { foo = 3 }
19 | jestExpect(ReactFiberStack.createCursor(defaultValue)).toEqual({
20 | current = defaultValue,
21 | })
22 | end)
23 |
24 | it("initializes the stack empty", function()
25 | jestExpect(ReactFiberStack.isEmpty()).toBe(true)
26 | end)
27 |
28 | describe("stack manipulations", function()
29 | local cursor
30 | local fiber
31 |
32 | beforeEach(function()
33 | cursor = ReactFiberStack.createCursor(nil)
34 | fiber = {}
35 | end)
36 |
37 | it("pushes an element and the stack is not empty", function()
38 | ReactFiberStack.push(cursor, true, fiber)
39 | jestExpect(ReactFiberStack.isEmpty()).toBe(false)
40 | end)
41 |
42 | it("pushes an element and assigns the value to the cursor", function()
43 | local pushedElement = { foo = 3 }
44 | ReactFiberStack.push(cursor, pushedElement, fiber)
45 | jestExpect(cursor.current).toEqual(pushedElement)
46 | end)
47 |
48 | it("pushes an element, pops it back and the stack is empty", function()
49 | ReactFiberStack.push(cursor, true, fiber)
50 | ReactFiberStack.pop(cursor, fiber)
51 | jestExpect(ReactFiberStack.isEmpty()).toBe(true)
52 | end)
53 |
54 | it(
55 | "pushes an element, pops it back and the cursor has its initial value",
56 | function()
57 | local initialCursorValue = "foo"
58 | cursor.current = initialCursorValue
59 |
60 | ReactFiberStack.push(cursor, true, fiber)
61 | ReactFiberStack.pop(cursor, fiber)
62 | jestExpect(cursor.current).toBe(initialCursorValue)
63 | end
64 | )
65 | end)
66 | end)
67 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactFiberSuspenseContext.roblox.spec.lua:
--------------------------------------------------------------------------------
1 | local Packages = script.Parent.Parent.Parent
2 | local JestGlobals = require(Packages.Dev.JestGlobals)
3 | local jestExpect = JestGlobals.expect
4 | local jest = JestGlobals.jest
5 | local beforeEach = JestGlobals.beforeEach
6 | local describe = JestGlobals.describe
7 | local it = JestGlobals.it
8 | local ReactFiber = require(script.Parent.Parent["ReactFiber.new"])
9 |
10 | local ReactFiberSuspenseContext
11 |
12 | describe("ReactFiberSuspenseContext", function()
13 | beforeEach(function()
14 | jest.resetModules()
15 | ReactFiberSuspenseContext =
16 | require(script.Parent.Parent["ReactFiberSuspenseContext.new"])
17 | end)
18 |
19 | describe("suspense context stack", function()
20 | local someContext
21 | local fiber
22 | local suspenseStackCursor
23 |
24 | beforeEach(function()
25 | someContext = 0b1000
26 | fiber = ReactFiber.createFiberFromText("", 0, 0)
27 | suspenseStackCursor = ReactFiberSuspenseContext.suspenseStackCursor
28 | end)
29 |
30 | it("pushes the context and assigns the value to the cursor", function()
31 | ReactFiberSuspenseContext.pushSuspenseContext(fiber, someContext)
32 | jestExpect(suspenseStackCursor).toEqual({ current = someContext })
33 | end)
34 |
35 | it("pushes and pops and sets the cursor to its initial value", function()
36 | local initialValue = suspenseStackCursor.current
37 |
38 | ReactFiberSuspenseContext.pushSuspenseContext(fiber, someContext)
39 | ReactFiberSuspenseContext.popSuspenseContext(fiber)
40 | jestExpect(suspenseStackCursor).toEqual({ current = initialValue })
41 | end)
42 | end)
43 |
44 | describe("hasSuspenseContext", function()
45 | it("is true for parent context and its subtree context", function()
46 | local subtree = 0b1000
47 | local parent =
48 | ReactFiberSuspenseContext.addSubtreeSuspenseContext(10000, subtree)
49 |
50 | jestExpect(ReactFiberSuspenseContext.hasSuspenseContext(parent, subtree)).toBe(
51 | true
52 | )
53 | end)
54 |
55 | it("is false for two different context", function()
56 | jestExpect(ReactFiberSuspenseContext.hasSuspenseContext(0b1000, 0b10000)).toBe(
57 | false
58 | )
59 | end)
60 | end)
61 | end)
62 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/init.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/43363e2795393a00fd77312a16d6b80e626c29de/packages/react-reconciler/src/index.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @flow
9 | ]]
10 |
11 | --!strict
12 | local ReactInternalTypes = require(script.ReactInternalTypes)
13 | local ReactRootTags = require(script.ReactRootTags)
14 |
15 | export type Dispatcher = ReactInternalTypes.Dispatcher
16 | export type Fiber = ReactInternalTypes.Fiber
17 | export type FiberRoot = ReactInternalTypes.FiberRoot
18 |
19 | -- ROBLOX deviation: explicit export for use in createReactNoop
20 | export type UpdateQueue = ReactInternalTypes.UpdateQueue
21 |
22 | export type RootTag = ReactRootTags.RootTag
23 |
24 | -- ROBLOX deviation: export types needed by ReactFreshRuntime
25 | local ReactFiberHostConfigModule = require(script.ReactFiberHostConfig)
26 | export type Instance = ReactFiberHostConfigModule.Instance
27 | local ReactFiberHotReloading = require(script["ReactFiberHotReloading.new"])
28 | export type Family = ReactFiberHotReloading.Family
29 | export type RefreshUpdate = ReactFiberHotReloading.RefreshUpdate
30 | export type SetRefreshHandler = ReactFiberHotReloading.SetRefreshHandler
31 | export type ScheduleRefresh = ReactFiberHotReloading.ScheduleRefresh
32 | export type ScheduleRoot = ReactFiberHotReloading.ScheduleRoot
33 | export type FindHostInstancesForRefresh = ReactFiberHotReloading.FindHostInstancesForRefresh
34 |
35 | -- ROBLOX deviation: In order to allow host config to be spliced in, we export
36 | -- this top-level package as an initializer function that returns the configured
37 | -- reconciler module
38 | -- ROBLOX TODO: this effectively disconnects type checking from above to reconciler to below
39 | local function initialize(config): { [string]: any }
40 | local ReactFiberHostConfig = require(script.ReactFiberHostConfig)
41 | for name, implementation in config do
42 | ReactFiberHostConfig[name] = implementation
43 | end
44 |
45 | return require(script.ReactFiberReconciler)
46 | end
47 |
48 | return initialize
49 |
--------------------------------------------------------------------------------
/modules/scheduler/src/__tests__/Tracing.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/9abc2785cb070148d64fae81e523246b90b92016/packages/scheduler/src/__tests__/Tracing-test.js
2 | -- /**
3 | -- * Copyright (c) Facebook, Inc. and its affiliates.
4 | -- *
5 | -- * This source code is licensed under the MIT license found in the
6 | -- * LICENSE file in the root directory of this source tree.
7 | -- *
8 | -- * @jest-environment node
9 | -- */
10 |
11 | local Packages = script.Parent.Parent.Parent
12 | local JestGlobals = require(Packages.Dev.JestGlobals)
13 | local beforeEach = JestGlobals.beforeEach
14 | local describe = JestGlobals.describe
15 | local it = JestGlobals.it
16 | local jest = JestGlobals.jest
17 |
18 | describe("Tracing", function()
19 | local JestGlobals = require(Packages.Dev.JestGlobals)
20 | local jestExpect = JestGlobals.expect
21 |
22 | local SchedulerTracing
23 |
24 | beforeEach(function()
25 | jest.resetModules()
26 |
27 | SchedulerTracing = require(Packages.Scheduler).tracing
28 | end)
29 |
30 | it("should return the value of a traced function", function()
31 | jestExpect(SchedulerTracing.unstable_trace("arbitrary", 0, function()
32 | return 123
33 | end)).toBe(123)
34 | end)
35 |
36 | it("should return the value of a wrapped function", function()
37 | local wrapped
38 | SchedulerTracing.unstable_trace("arbitrary", 0, function()
39 | wrapped = SchedulerTracing.unstable_wrap(function()
40 | return 123
41 | end)
42 | end)
43 | jestExpect(wrapped()).toBe(123)
44 | end)
45 |
46 | it("should execute traced callbacks", function()
47 | local done = false
48 |
49 | SchedulerTracing.unstable_trace("some event", 0, function()
50 | done = true
51 | end)
52 |
53 | jestExpect(done).toBe(true)
54 | end)
55 |
56 | it("should return the value of a clear function", function()
57 | jestExpect(SchedulerTracing.unstable_clear(function()
58 | return 123
59 | end)).toBe(123)
60 | end)
61 |
62 | it("should execute wrapped callbacks", function()
63 | local done = false
64 | local wrappedCallback = SchedulerTracing.unstable_wrap(function()
65 | done = true
66 | end)
67 |
68 | wrappedCallback()
69 | jestExpect(done).toBe(true)
70 | end)
71 | end)
72 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactClassSetStateCallback.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/d7dce572c7453737a685e791e7afcbc7e2b2fe16/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @emails react-core
9 | * @jest-environment node
10 | ]]
11 |
12 | --[[ eslint-disable no-func-assign ]]
13 | local Packages = script.Parent.Parent.Parent
14 | local React
15 |
16 | local ReactNoop
17 | local Scheduler
18 |
19 | local JestGlobals = require(Packages.Dev.JestGlobals)
20 | local jestExpect = JestGlobals.expect
21 | local beforeEach = JestGlobals.beforeEach
22 | local it = JestGlobals.it
23 | local jest = JestGlobals.jest
24 |
25 | beforeEach(function()
26 | jest.resetModules()
27 | jest.useFakeTimers()
28 |
29 | React = require(Packages.React)
30 | ReactNoop = require(Packages.Dev.ReactNoopRenderer)
31 | Scheduler = require(Packages.Scheduler)
32 | end)
33 |
34 | local function Text(props)
35 | Scheduler.unstable_yieldValue(props.text)
36 | return React.createElement("span", {
37 | prop = props.text,
38 | })
39 | end
40 |
41 | it(
42 | "regression: setState callback (2nd arg) should only fire once, even after a rebase",
43 | function()
44 | local app
45 | local App = React.Component:extend("App")
46 | function App:init()
47 | self:setState({ step = 0 })
48 | end
49 | function App:render()
50 | app = self
51 | return React.createElement(Text, { text = self.state.step })
52 | end
53 |
54 | local root = ReactNoop.createRoot()
55 | ReactNoop.act(function()
56 | root.render(React.createElement(App))
57 | end)
58 | jestExpect(Scheduler).toHaveYielded({ 0 })
59 |
60 | ReactNoop.act(function()
61 | app:setState({ step = 1 }, function()
62 | return Scheduler.unstable_yieldValue("Callback 1")
63 | end)
64 |
65 | ReactNoop.flushSync(function()
66 | app:setState({ step = 2 }, function()
67 | return Scheduler.unstable_yieldValue("Callback 2")
68 | end)
69 | end)
70 | end)
71 | jestExpect(Scheduler).toHaveYielded({ 2, "Callback 2", 2, "Callback 1" })
72 | end
73 | )
74 |
--------------------------------------------------------------------------------
/modules/react-devtools-shared/src/backend/views/Highlighter/Overlay/OverlayRect.lua:
--------------------------------------------------------------------------------
1 | local OverlayRect = {}
2 | OverlayRect.__index = OverlayRect
3 |
4 | function OverlayRect.new(container: GuiBase2d)
5 | local self = setmetatable({}, OverlayRect)
6 | self.container = container
7 |
8 | local node = Instance.new("Frame")
9 | node.Name = "OverlayRect"
10 | node.BackgroundTransparency = 1
11 | node.Parent = container
12 | self.node = node
13 |
14 | local padding = Instance.new("Frame")
15 | padding.Name = "OverlayRectPadding"
16 | padding.BackgroundColor3 = Color3.fromRGB(77, 200, 0)
17 | padding.Size = UDim2.fromScale(1, 1)
18 | padding.BackgroundTransparency = 0.4
19 | padding.BorderSizePixel = 0
20 | padding.Parent = node
21 | self.padding = padding
22 |
23 | local content = Instance.new("Frame")
24 | content.Name = "OverlayRectContent"
25 | content.BackgroundColor3 = Color3.fromRGB(120, 170, 210)
26 | content.Size = UDim2.fromScale(1, 1)
27 | content.BorderSizePixel = 0
28 | content.ZIndex = 2
29 | content.Parent = node
30 | self.content = content
31 |
32 | return self
33 | end
34 |
35 | export type OverlayRect = typeof(OverlayRect.new(...))
36 |
37 | function OverlayRect.remove(self: OverlayRect)
38 | self.node:Destroy()
39 | end
40 |
41 | function OverlayRect.update(self: OverlayRect, element: GuiBase2d)
42 | local size = element.AbsoluteSize
43 | local position = element.AbsolutePosition
44 |
45 | local padding = element:FindFirstChildOfClass("UIPadding")
46 | if padding then
47 | local top = (padding.PaddingTop.Scale * size.Y) + padding.PaddingTop.Offset
48 | local left = (padding.PaddingLeft.Scale * size.X) + padding.PaddingLeft.Offset
49 | local bottom = (padding.PaddingBottom.Scale * size.Y)
50 | + padding.PaddingBottom.Offset
51 | local right = (padding.PaddingRight.Scale * size.X) + padding.PaddingRight.Offset
52 |
53 | self.content.Position = UDim2.fromOffset(left, top)
54 | self.content.Size = UDim2.fromOffset(size.X - left - right, size.Y - top - bottom)
55 | else
56 | self.content.Position = UDim2.fromOffset(0, 0)
57 | self.content.Size = UDim2.fromOffset(size.X, size.Y)
58 | end
59 |
60 | self.node.Size = UDim2.fromOffset(size.X, size.Y)
61 | self.node.Position = UDim2.fromOffset(position.X, position.Y)
62 | end
63 |
64 | return {
65 | new = OverlayRect.new,
66 | }
67 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactNoopRendererAct.spec.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/d17086c7c813402a550d15a2f56dc43f1dbd1735/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * @jest-environment node
9 | ]]
10 |
11 | -- sanity tests for ReactNoop.act()
12 |
13 | local Packages = script.Parent.Parent.Parent
14 | local React
15 | local ReactNoop
16 | local Scheduler
17 |
18 | local JestGlobals = require(Packages.Dev.JestGlobals)
19 | local Promise = require(Packages.Promise)
20 | local jestExpect = JestGlobals.expect
21 | local beforeEach = JestGlobals.beforeEach
22 | local it = JestGlobals.it
23 | local jest = JestGlobals.jest
24 |
25 | beforeEach(function()
26 | jest.resetModules()
27 |
28 | React = require(Packages.React)
29 | ReactNoop = require(Packages.Dev.ReactNoopRenderer)
30 | Scheduler = require(Packages.Scheduler)
31 | end)
32 |
33 | it("can use act to flush effects", function()
34 | local function App(props)
35 | React.useEffect(props.callback)
36 | return nil
37 | end
38 |
39 | local calledLog = {}
40 | ReactNoop.act(function()
41 | ReactNoop.render(React.createElement(App, {
42 | callback = function()
43 | table.insert(calledLog, #calledLog)
44 | end,
45 | }))
46 | end)
47 | jestExpect(Scheduler).toFlushWithoutYielding()
48 | jestExpect(calledLog).toEqual({ 0 })
49 | end)
50 | it("should work with async/await", function()
51 | local function App()
52 | local ctr, setCtr = React.useState(0)
53 | local function someAsyncFunction()
54 | Scheduler.unstable_yieldValue("stage 1")
55 | Scheduler.unstable_yieldValue("stage 2")
56 | setCtr(1)
57 | end
58 | React.useEffect(function()
59 | someAsyncFunction()
60 | end, {})
61 | return ctr
62 | end
63 | Promise.try(function()
64 | ReactNoop.act(function()
65 | ReactNoop.render(React.createElement(App))
66 | end)
67 | end):await()
68 | jestExpect(Scheduler).toHaveYielded({ "stage 1", "stage 2" })
69 | jestExpect(Scheduler).toFlushWithoutYielding()
70 | jestExpect(ReactNoop.getChildren()).toEqual({ { text = "1", hidden = false } })
71 | end)
72 |
--------------------------------------------------------------------------------
/modules/shared/src/consoleWithStackDev.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/cb141681750c8221ac799074df09df2bb448c7a4/packages/shared/consoleWithStackDev.js
2 | --[[*
3 | * Copyright (c) Facebook, Inc. and its affiliates.
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | ]]
8 | local Packages = script.Parent.Parent
9 | local ReactGlobals = require(Packages.ReactGlobals)
10 | local LuauPolyfill = require(Packages.LuauPolyfill)
11 | local console = LuauPolyfill.console
12 | local Array = LuauPolyfill.Array
13 |
14 | local ReactSharedInternals = require(script.Parent.ReactSharedInternals)
15 | -- In DEV, calls to console.warn and console.error get replaced
16 | -- by calls to these methods by a Babel plugin.
17 | --
18 | -- In PROD (or in packages without access to React internals),
19 | -- they are left as they are instead.
20 |
21 | -- deviation: declare this ahead of time so that `warn` and `error` are able to
22 | -- reference it
23 | local printWarning
24 |
25 | local exports = {}
26 | exports.warn = function(format, ...)
27 | if ReactGlobals.__DEV__ then
28 | printWarning("warn", format, { ... })
29 | end
30 | end
31 | exports.error = function(format, ...)
32 | if ReactGlobals.__DEV__ then
33 | printWarning("error", format, { ... })
34 | end
35 | end
36 |
37 | function printWarning(level, format, args)
38 | -- When changing this logic, you might want to also
39 | -- update consoleWithStackDev.www.js as well.
40 | if ReactGlobals.__DEV__ then
41 | local ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame
42 | local stack = ReactDebugCurrentFrame.getStackAddendum()
43 |
44 | if stack ~= "" then
45 | format ..= "%s"
46 | -- deviation: no array `concat` function in lua
47 | args = Array.slice(args, 1)
48 | table.insert(args, stack)
49 | end
50 |
51 | local argsWithFormat = Array.map(args, tostring)
52 | -- Careful: RN currently depends on this prefix
53 | table.insert(argsWithFormat, 1, "Warning: " .. format)
54 | -- We intentionally don't use spread (or .apply) directly because it
55 | -- breaks IE9: https://github.com/facebook/react/issues/13610
56 | -- eslint-disable-next-line react-internal/no-production-logging
57 | console[level](unpack(argsWithFormat))
58 | end
59 | end
60 |
61 | return exports
62 |
--------------------------------------------------------------------------------
/modules/react-reconciler/src/__tests__/ReactFiberComponentStack.roblox.spec.lua:
--------------------------------------------------------------------------------
1 | local Packages = script.Parent.Parent.Parent
2 | local LuauPolyfill = require(Packages.LuauPolyfill)
3 | local Error = LuauPolyfill.Error
4 | local JestGlobals = require(Packages.Dev.JestGlobals)
5 | local jestExpect = JestGlobals.expect
6 | local describe = JestGlobals.describe
7 | local beforeEach = JestGlobals.beforeEach
8 | local it = JestGlobals.it
9 | local jest = JestGlobals.jest
10 | local ReactInternalTypes = require(script.Parent.Parent.ReactInternalTypes)
11 | type Fiber = ReactInternalTypes.Fiber
12 |
13 | local ReactFiberComponentStack
14 |
15 | describe("ReactFiberComponentStack", function()
16 | beforeEach(function()
17 | jest.resetModules()
18 | ReactFiberComponentStack = require(script.Parent.Parent.ReactFiberComponentStack)
19 | end)
20 |
21 | it("given a nil fiber then it gives correct error message", function()
22 | local message =
23 | ReactFiberComponentStack.getStackByFiberInDevAndProd((nil :: any) :: Fiber)
24 | jestExpect(message).toContain("attempt to index nil")
25 | end)
26 |
27 | it("given a fiber that throws Error then it gives correct error message", function()
28 | local throwingFiber = {}
29 | setmetatable(throwingFiber, {
30 | __index = function(t, k)
31 | if k == "tag" then
32 | error(Error.new("this was an error object in a spec file"))
33 | end
34 | return nil
35 | end,
36 | })
37 |
38 | local message = ReactFiberComponentStack.getStackByFiberInDevAndProd(
39 | (throwingFiber :: any) :: Fiber
40 | )
41 | jestExpect(message).toContain("this was an error object in a spec file")
42 | end)
43 |
44 | it(
45 | "given a fiber that throws a non-Error table then it gives correct error message",
46 | function()
47 | local customErrorTable = {}
48 | setmetatable(customErrorTable, {
49 | __tostring = function(t, k)
50 | return "this was a custom __tostring"
51 | end,
52 | })
53 |
54 | local throwingFiber = {}
55 | setmetatable(throwingFiber, {
56 | __index = function(t, k)
57 | if k == "tag" then
58 | error(customErrorTable)
59 | end
60 | return nil
61 | end,
62 | })
63 |
64 | local message = ReactFiberComponentStack.getStackByFiberInDevAndProd(
65 | (throwingFiber :: any) :: Fiber
66 | )
67 | jestExpect(message).toContain("this was a custom __tostring")
68 | end
69 | )
70 | end)
71 |
--------------------------------------------------------------------------------
/modules/react-devtools-extensions/src/init.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX note: no upstream
2 | -- ROBLOX note: The setup function adds the glue required for DeveloperTools to initialize the Roact devtools correctly
3 | local Packages = script.Parent
4 |
5 | return {
6 | setup = function(debugMode: boolean)
7 | local ReactGlobals = require(Packages.ReactGlobals)
8 |
9 | -- ROBLOX note: Set globals for React devtools to work
10 | ReactGlobals.__DEV__ = true
11 | ReactGlobals.__DEBUG__ = debugMode or false
12 | ReactGlobals.__PROFILE__ = true
13 | ReactGlobals.__EXPERIMENTAL__ = true
14 | -- ROBLOX note: Don't hide host coomponents as the current Developer Inspector uses on these to preserve a
15 | -- direct mapping between the Inspector tree and the Explorer tree as requested by design.
16 | ReactGlobals.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = {}
17 |
18 | local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared)
19 | local setup = require(Packages.ReactDevtoolsExtensions.backend).setup
20 | local installHook = ReactDevtoolsShared.hook.installHook
21 | local Store = ReactDevtoolsShared.devtools.store
22 |
23 | -- ROBLOX note: Ensure that the global hook is installed before the injection into DevTools
24 | installHook(ReactGlobals)
25 |
26 | -- ROBLOX note: Ensure that ReactRoblox is loaded after injection so that the ReactHostConfig is populated correctly
27 | require(Packages.React)
28 | require(Packages.ReactRoblox)
29 |
30 | local hook = ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__
31 |
32 | -- ROBLOX note: Make sure that this method was called before ReactRoblox was first required,
33 | -- otherwise the profiler will not be enabled for the session.
34 | local ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags
35 | if not ReactFeatureFlags.enableSchedulingProfiler then
36 | warn(
37 | "[DeveloperTools] React was initialized before DeveloperTools. Call inspector.setupReactDevtools before requiring React to enable profiling."
38 | )
39 | end
40 |
41 | local result = setup(hook)
42 |
43 | -- ROBLOX note: The DeveloperTools library is only passed the ReactDevtoolsExtensions API to keep the
44 | -- devtools init process compact for users. Initialize the store so DeveloperTools doesn't also need to be
45 | -- passed the ReactDevtoolsShared API.
46 | return {
47 | agent = result.agent,
48 | bridge = result.bridge,
49 | hook = result.hook,
50 | store = Store.new(result.bridge),
51 | }
52 | end,
53 | }
54 |
--------------------------------------------------------------------------------
/modules/react/src/ReactCreateRef.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react/src/ReactCreateRef.js
2 | --!strict
3 | --[[*
4 | * Copyright (c) Facebook, Inc. and its affiliates.
5 | *
6 | * This source code is licensed under the MIT license found in the
7 | * LICENSE file in the root directory of this source tree.
8 | * @flow
9 | *]]
10 |
11 | local Packages = script.Parent.Parent
12 | local ReactGlobals = require(Packages.ReactGlobals)
13 | local ReactTypes = require(Packages.Shared)
14 | type RefObject = ReactTypes.RefObject
15 |
16 | -- ROBLOX DEVIATION: In Roact, refs are implemented in terms of bindings
17 | --[[
18 | A ref is nothing more than a binding with a special field 'current'
19 | that maps to the getValue method of the binding
20 | ]]
21 | local Binding = require(script.Parent["ReactBinding.roblox"])
22 |
23 | local exports = {}
24 |
25 | -- an immutable object with a single mutable value
26 | exports.createRef = function(): RefObject
27 | local binding, _ = Binding.create(nil)
28 |
29 | local ref = {}
30 |
31 | -- ROBLOX DEVIATION: Since refs are used as bindings, they can often be
32 | -- assigned to fields of other Instances; we track creation here parallel to
33 | -- how we do with bindings created via `createBinding` to improve messaging
34 | -- when something goes wrong
35 | if ReactGlobals.__DEV__ then
36 | -- ROBLOX TODO: LUAFDN-619 - improve debug stacktraces for refs
37 | binding._source = debug.traceback("Ref created at:", 1)
38 | end
39 |
40 | --[[
41 | A ref is just redirected to a binding via its metatable
42 | ]]
43 | setmetatable(ref, {
44 | __index = function(self, key)
45 | if key == "current" then
46 | return binding:getValue()
47 | else
48 | return (binding :: any)[key]
49 | end
50 | end,
51 | __newindex = function(self, key, value)
52 | if key == "current" then
53 | -- ROBLOX FIXME: Bindings - This is not allowed in Roact, but is okay in
54 | -- React. Lots of discussion at
55 | -- https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065
56 | -- error("Cannot assign to the 'current' property of refs", 2)
57 | Binding.update(binding, value)
58 | end
59 |
60 | (binding :: any)[key] = value
61 | end,
62 | __tostring = function(self)
63 | return string.format("Ref(%s)", tostring(binding:getValue()))
64 | end,
65 | })
66 |
67 | return (ref :: any) :: RefObject
68 | end
69 |
70 | return exports
71 |
--------------------------------------------------------------------------------
/modules/react-roblox/src/client/roblox/getDefaultInstanceProperty.lua:
--------------------------------------------------------------------------------
1 | -- ROBLOX upstream: https://github.com/Roblox/roact/blob/b2ba9cf4c219c2654e6572219a68d0bf1b541418/src/getDefaultInstanceProperty.lua
2 | --[[
3 | * Copyright (c) Roblox Corporation. All rights reserved.
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | ]]
16 |
17 | --[[
18 | Attempts to get the default value of a given property on a Roblox instance.
19 |
20 | This is used by the reconciler in cases where a prop was previously set on a
21 | primitive component, but is no longer present in a component's new props.
22 |
23 | Eventually, Roblox might provide a nicer API to query the default property
24 | of an object without constructing an instance of it.
25 | ]]
26 |
27 | local Packages = script.Parent.Parent.Parent.Parent
28 | local Symbol = require(Packages.Shared).Symbol
29 |
30 | local Nil = Symbol.named("Nil")
31 | local _cachedPropertyValues = {}
32 |
33 | local function tryPropertyName(instance, propertyName)
34 | return instance[propertyName]
35 | end
36 |
37 | local function getDefaultInstanceProperty(className, propertyName)
38 | local classCache = _cachedPropertyValues[className]
39 |
40 | if classCache then
41 | local propValue = classCache[propertyName]
42 |
43 | -- We have to use a marker here, because Lua doesn't distinguish
44 | -- between 'nil' and 'not in a table'
45 | if propValue == Nil then
46 | return true, nil
47 | end
48 |
49 | if propValue ~= nil then
50 | return true, propValue
51 | end
52 | else
53 | classCache = {}
54 | _cachedPropertyValues[className] = classCache
55 | end
56 |
57 | local created = Instance.new(className)
58 | local ok, defaultValue = pcall(tryPropertyName, created, propertyName)
59 |
60 | created:Destroy()
61 |
62 | if ok then
63 | if defaultValue == nil then
64 | classCache[propertyName] = Nil
65 | else
66 | classCache[propertyName] = defaultValue
67 | end
68 | end
69 |
70 | return ok, defaultValue
71 | end
72 |
73 | return getDefaultInstanceProperty
74 |
--------------------------------------------------------------------------------