Skip to content

Commit

Permalink
Merge pull request #2176 from aryaemami59/improve-treeshakeability
Browse files Browse the repository at this point in the history
Improve treeshakeability of build artifacts
  • Loading branch information
markerikson authored Dec 10, 2024
2 parents 560ac02 + 49f8136 commit 4a9ba60
Show file tree
Hide file tree
Showing 16 changed files with 1,532 additions and 1,751 deletions.
10 changes: 8 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
},
"plugins": ["@typescript-eslint", "import", "react"],
"rules": {
"valid-jsdoc": [2],
"valid-jsdoc": [
2,
{ "requireReturnType": false, "requireParamType": false }
],
"react/no-is-mounted": [0]
},
"settings": {
Expand Down Expand Up @@ -55,7 +58,10 @@
{ "fixStyle": "separate-type-imports" }
],
"@typescript-eslint/consistent-type-exports": [2],
"valid-jsdoc": [2],
"valid-jsdoc": [
2,
{ "requireReturnType": false, "requireParamType": false }
],
"react/no-is-mounted": [0]
}
},
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
},
"sideEffects": false,
"files": [
"dist"
"dist",
"src"
],
"scripts": {
"build": "yarn clean && tsup",
Expand All @@ -42,7 +43,7 @@
"format": "prettier --write \"{src,test}/**/*.{js,ts,tsx}\" \"docs/**/*.md\"",
"lint": "eslint src test",
"lint:fix": "eslint src test --fix",
"prepare": "yarn clean && yarn build",
"prepack": "yarn build",
"pretest": "yarn lint",
"test": "vitest --run --typecheck",
"test:watch": "vitest --watch",
Expand Down Expand Up @@ -102,7 +103,7 @@
"react-test-renderer": "18.3.1",
"redux": "^5.0.1",
"rimraf": "^5.0.7",
"tsup": "7.0.0",
"tsup": "^8.3.5",
"typescript": "^5.5.4",
"typescript-eslint": "^7.12.0",
"vitest": "^1.6.0"
Expand Down
4 changes: 1 addition & 3 deletions src/components/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface ReactReduxContextValue<
getServerState?: () => SS
}

const ContextKey = Symbol.for(`react-redux-context`)
const ContextKey = /* @__PURE__ */ Symbol.for(`react-redux-context`)
const gT: {
[ContextKey]?: Map<
typeof React.createContext,
Expand Down Expand Up @@ -48,5 +48,3 @@ function getContext(): Context<ReactReduxContextValue | null> {
export const ReactReduxContext = /*#__PURE__*/ getContext()

export type ReactReduxContextInstance = typeof ReactReduxContext

export default ReactReduxContext
32 changes: 20 additions & 12 deletions src/components/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,32 @@ export interface ProviderProps<
children: ReactNode
}

function Provider<A extends Action<string> = UnknownAction, S = unknown>({
store,
context,
children,
serverState,
stabilityCheck = 'once',
identityFunctionCheck = 'once',
}: ProviderProps<A, S>) {
function Provider<A extends Action<string> = UnknownAction, S = unknown>(
providerProps: ProviderProps<A, S>,
) {
const { children, context, serverState, store } = providerProps

const contextValue = React.useMemo(() => {
const subscription = createSubscription(store)
return {

const baseContextValue = {
store,
subscription,
getServerState: serverState ? () => serverState : undefined,
stabilityCheck,
identityFunctionCheck,
}
}, [store, serverState, stabilityCheck, identityFunctionCheck])

if (process.env.NODE_ENV === 'production') {
return baseContextValue
} else {
const { identityFunctionCheck = 'once', stabilityCheck = 'once' } =
providerProps

return /* @__PURE__ */ Object.assign(baseContextValue, {
stabilityCheck,
identityFunctionCheck,
})
}
}, [store, serverState])

const previousState = React.useMemo(() => store.getState(), [store])

Expand Down
10 changes: 1 addition & 9 deletions src/components/connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,6 @@ import type {
} from './Context'
import { ReactReduxContext } from './Context'

import type { uSES } from '../utils/useSyncExternalStore'
import { notInitialized } from '../utils/useSyncExternalStore'

let useSyncExternalStore = notInitialized as uSES
export const initializeConnect = (fn: uSES) => {
useSyncExternalStore = fn
}

// Define some constant arrays just to avoid re-creating these
const EMPTY_ARRAY: [unknown, number] = [null, 0]
const NO_SUBSCRIPTION_ARRAY = [null, null]
Expand Down Expand Up @@ -726,7 +718,7 @@ function connect<
let actualChildProps: Record<string, unknown>

try {
actualChildProps = useSyncExternalStore(
actualChildProps = React.useSyncExternalStore(
// TODO We're passing through a big wrapper that does a bunch of extra side effects besides subscribing
subscribeForReact,
// TODO This is incredibly hacky. We've already processed the store update and calculated new child props,
Expand Down
4 changes: 2 additions & 2 deletions src/connect/mergeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createInvalidArgFactory } from './invalidArgFactory'
import type { MergeProps } from './selectorFactory'
import type { EqualityFn } from '../types'

export function defaultMergeProps<
function defaultMergeProps<
TStateProps,
TDispatchProps,
TOwnProps,
Expand All @@ -18,7 +18,7 @@ export function defaultMergeProps<
return { ...ownProps, ...stateProps, ...dispatchProps }
}

export function wrapMergePropsFunc<
function wrapMergePropsFunc<
TStateProps,
TDispatchProps,
TOwnProps,
Expand Down
2 changes: 1 addition & 1 deletion src/connect/selectorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ interface PureSelectorFactoryComparisonOptions<TStateProps, TOwnProps, State> {
readonly areOwnPropsEqual: EqualityFn<TOwnProps>
}

export function pureFinalPropsSelectorFactory<
function pureFinalPropsSelectorFactory<
TStateProps,
TOwnProps,
TDispatchProps,
Expand Down
2 changes: 1 addition & 1 deletion src/connect/wrapMapToProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function wrapMapToPropsConstant(
// A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and
// therefore not reporting its length accurately..
// TODO Can this get pulled out so that we can subscribe directly to the store if we don't need ownProps?
export function getDependsOnOwnProps(mapToProps: MapToProps) {
function getDependsOnOwnProps(mapToProps: MapToProps) {
return mapToProps.dependsOnOwnProps
? Boolean(mapToProps.dependsOnOwnProps)
: mapToProps.length !== 1
Expand Down
28 changes: 11 additions & 17 deletions src/hooks/useSelector.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
//import * as React from 'react'
import { React } from '../utils/react'

import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'
import type { ReactReduxContextValue } from '../components/Context'
import { ReactReduxContext } from '../components/Context'
import type { EqualityFn, NoInfer } from '../types'
import type { uSESWS } from '../utils/useSyncExternalStore'
import { notInitialized } from '../utils/useSyncExternalStore'
import {
createReduxContextHook,
useReduxContext as useDefaultReduxContext,
Expand Down Expand Up @@ -118,11 +116,6 @@ export interface UseSelector<StateType = unknown> {
>() => UseSelector<OverrideStateType>
}

let useSyncExternalStoreWithSelector = notInitialized as uSESWS
export const initializeUseSelector = (fn: uSESWS) => {
useSyncExternalStoreWithSelector = fn
}

const refEquality: EqualityFn<any> = (a, b) => a === b

/**
Expand All @@ -148,7 +141,7 @@ export function createSelectorHook(
| EqualityFn<NoInfer<Selected>>
| UseSelectorOptions<NoInfer<Selected>> = {},
): Selected => {
const { equalityFn = refEquality, devModeChecks = {} } =
const { equalityFn = refEquality } =
typeof equalityFnOrOptions === 'function'
? { equalityFn: equalityFnOrOptions }
: equalityFnOrOptions
Expand All @@ -166,13 +159,9 @@ export function createSelectorHook(
}
}

const {
store,
subscription,
getServerState,
stabilityCheck,
identityFunctionCheck,
} = useReduxContext()
const reduxContext = useReduxContext()

const { store, subscription, getServerState } = reduxContext

const firstRun = React.useRef(true)

Expand All @@ -181,6 +170,11 @@ export function createSelectorHook(
[selector.name](state: TState) {
const selected = selector(state)
if (process.env.NODE_ENV !== 'production') {
const { devModeChecks = {} } =
typeof equalityFnOrOptions === 'function'
? {}
: equalityFnOrOptions
const { identityFunctionCheck, stabilityCheck } = reduxContext
const {
identityFunctionCheck: finalIdentityFunctionCheck,
stabilityCheck: finalStabilityCheck,
Expand Down Expand Up @@ -243,7 +237,7 @@ export function createSelectorHook(
return selected
},
}[selector.name],
[selector, stabilityCheck, devModeChecks.stabilityCheck],
[selector],
)

const selectedState = useSyncExternalStoreWithSelector(
Expand Down
14 changes: 0 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1 @@
// The primary entry point assumes we are working with React 18, and thus have
// useSyncExternalStore available. We can import that directly from React itself.
// The useSyncExternalStoreWithSelector has to be imported, but we can use the
// non-shim version. This shaves off the byte size of the shim.

import * as React from 'react'
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'

import { initializeUseSelector } from './hooks/useSelector'
import { initializeConnect } from './components/connect'

initializeUseSelector(useSyncExternalStoreWithSelector)
initializeConnect(React.useSyncExternalStore)

export * from './exports'
36 changes: 21 additions & 15 deletions src/utils/react-is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import type { ElementType, MemoExoticComponent, ReactElement } from 'react'
// It's very possible this could change in the future, but given that
// we only use these in `connect`, this is a low priority.

const REACT_ELEMENT_TYPE = Symbol.for('react.element')
const REACT_PORTAL_TYPE = Symbol.for('react.portal')
const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment')
const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode')
const REACT_PROFILER_TYPE = Symbol.for('react.profiler')
const REACT_PROVIDER_TYPE = Symbol.for('react.provider')
const REACT_CONTEXT_TYPE = Symbol.for('react.context')
const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context')
const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref')
const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense')
const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list')
const REACT_MEMO_TYPE = Symbol.for('react.memo')
const REACT_LAZY_TYPE = Symbol.for('react.lazy')
const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen')
const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference')
const REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for('react.element')
const REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for('react.portal')
const REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for('react.fragment')
const REACT_STRICT_MODE_TYPE = /* @__PURE__ */ Symbol.for('react.strict_mode')
const REACT_PROFILER_TYPE = /* @__PURE__ */ Symbol.for('react.profiler')
const REACT_PROVIDER_TYPE = /* @__PURE__ */ Symbol.for('react.provider')
const REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for('react.context')
const REACT_SERVER_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for(
'react.server_context',
)
const REACT_FORWARD_REF_TYPE = /* @__PURE__ */ Symbol.for('react.forward_ref')
const REACT_SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for('react.suspense')
const REACT_SUSPENSE_LIST_TYPE = /* @__PURE__ */ Symbol.for(
'react.suspense_list',
)
const REACT_MEMO_TYPE = /* @__PURE__ */ Symbol.for('react.memo')
const REACT_LAZY_TYPE = /* @__PURE__ */ Symbol.for('react.lazy')
const REACT_OFFSCREEN_TYPE = /* @__PURE__ */ Symbol.for('react.offscreen')
const REACT_CLIENT_REFERENCE = /* @__PURE__ */ Symbol.for(
'react.client.reference',
)

export const ForwardRef = REACT_FORWARD_REF_TYPE
export const Memo = REACT_MEMO_TYPE
Expand Down
7 changes: 2 additions & 5 deletions src/utils/react.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import type * as ReactNamespace from 'react'
import * as ReactOriginal from 'react'
import * as React from 'react'

export const React: typeof ReactNamespace =
// prettier-ignore
'default' in ReactOriginal ? ReactOriginal['default'] : ReactOriginal as any
export { React }
24 changes: 17 additions & 7 deletions src/utils/useIsomorphicLayoutEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,31 @@ import { React } from '../utils/react'
// subscription is created and an inconsistent state may be observed

// Matches logic in React's `shared/ExecutionEnvironment` file
export const canUseDOM = !!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
)
const canUseDOM = () =>
!!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
)

const isDOM = /* @__PURE__ */ canUseDOM()

// Under React Native, we know that we always want to use useLayoutEffect

/**
* Checks if the code is running in a React Native environment.
*
* @returns Whether the code is running in a React Native environment.
*
* @see {@link https://github.com/facebook/react-native/issues/1331 Reference}
*/
export const isReactNative =
const isRunningInReactNative = () =>
typeof navigator !== 'undefined' && navigator.product === 'ReactNative'

const isReactNative = /* @__PURE__ */ isRunningInReactNative()

const getUseIsomorphicLayoutEffect = () =>
isDOM || isReactNative ? React.useLayoutEffect : React.useEffect

export const useIsomorphicLayoutEffect =
canUseDOM || isReactNative ? React.useLayoutEffect : React.useEffect
/* @__PURE__ */ getUseIsomorphicLayoutEffect()
Loading

0 comments on commit 4a9ba60

Please sign in to comment.