Skip to content

Commit

Permalink
fix: detect duplicate context errors (#7160)
Browse files Browse the repository at this point in the history
* fix: detect duplicate context errors

* feat: add missing context help URL

---------

Co-authored-by: Rico Kahler <ricokahler@gmail.com>
  • Loading branch information
stipsan and ricokahler committed Jul 19, 2024
1 parent f0d5f82 commit 7f3a881
Show file tree
Hide file tree
Showing 31 changed files with 190 additions and 56 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ const config = {
// Prefer createContext in _singletons
{
files: ['packages/sanity/src/**'],
excludedFiles: ['**/__workshop__/**', 'packages/sanity/src/_singletons/**'],
excludedFiles: [
'**/__workshop__/**',
'packages/sanity/src/_singletons/**',
'packages/sanity/src/_createContext/**',
],
rules: {
'no-restricted-imports': [
'error',
Expand Down
4 changes: 4 additions & 0 deletions dev/test-next-studio/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ const config = {
'@sanity/vision': requireResolve('../../packages/@sanity/vision/src/index.ts'),
'sanity/_internal': requireResolve('../../packages/sanity/src/_exports/_internal.ts'),
'sanity/_singletons': requireResolve('../../packages/sanity/src/_exports/_singletons.ts'),
'sanity/_createContext': requireResolve(
'../../packages/sanity/src/_exports/_createContext.ts',
),
'sanity/cli': requireResolve('../../packages/sanity/src/_exports/cli.ts'),
'sanity/desk': requireResolve('../../packages/sanity/src/_exports/desk.ts'),
'sanity/presentation': requireResolve('../../packages/sanity/src/_exports/presentation.ts'),
Expand Down Expand Up @@ -101,6 +104,7 @@ const config = {
'@sanity/vision': '@sanity/vision/src/index.ts',
'sanity/_internal': 'sanity/src/_exports/_internal.ts',
'sanity/_singletons': 'sanity/src/_exports/_singletons.ts',
'sanity/_createContext': 'sanity/src/_exports/_createContext.ts',
'sanity/cli': 'sanity/src/_exports/cli.ts',
'sanity/desk': 'sanity/src/_exports/desk.ts',
'sanity/presentation': 'sanity/src/_exports/presentation.ts',
Expand Down
1 change: 1 addition & 0 deletions dev/tsconfig.dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"groq": ["./packages/groq/src/groq.ts"],
"sanity/_internal": ["./packages/sanity/src/_exports/_internal.ts"],
"sanity/_singletons": ["./packages/sanity/src/_exports/_singletons.ts"],
"sanity/_createContext": ["./packages/sanity/src/_exports/_createContext.ts"],
"sanity/cli": ["./packages/sanity/src/_exports/cli.ts"],
"sanity/desk": ["./packages/sanity/src/_exports/desk.ts"],
"sanity/migrate": ["./packages/sanity/src/_exports/migrate.ts"],
Expand Down
1 change: 1 addition & 0 deletions examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"groq": ["./packages/groq/src/groq.ts"],
"sanity/_internal": ["./packages/sanity/src/_exports/_internal.ts"],
"sanity/_singletons": ["./packages/sanity/src/_exports/_singletons.ts"],
"sanity/_createContext": ["./packages/sanity/src/_exports/_createContext.ts"],
"sanity/cli": ["./packages/sanity/src/_exports/cli.ts"],
"sanity/desk": ["./packages/sanity/src/_exports/desk.ts"],
"sanity/migrate": ["./packages/sanity/src/_exports/migrate.ts"],
Expand Down
1 change: 1 addition & 0 deletions packages/@sanity/vision/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"groq": ["./node_modules/groq/src/groq.ts"],
"sanity/_internal": ["./node_modules/sanity/src/_exports/_internal.ts"],
"sanity/_singletons": ["./node_modules/sanity/src/_exports/_singletons.ts"],
"sanity/_createContext": ["./node_modules/sanity/src/_exports/_createContext.ts"],
"sanity/cli": ["./node_modules/sanity/src/_exports/cli.ts"],
"sanity/desk": ["./node_modules/sanity/src/_exports/desk.ts"],
"sanity/migrate": ["./node_modules/sanity/src/_exports/migrate.ts"],
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# legacy package exports
_singletons.js
_createContext.js
desk.js
form.js
presentation.js
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
/router.js
/structure.js
/_singletons.js
/_createContext.js

# Playwright-ct artifacts
/playwright-ct/report
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/package.bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default defineConfig(() => {
lib: {
entry: {
_singletons: './src/_exports/_singletons.ts',
_createContext: './src/_exports/_createContext.ts',
// 'sanity' module
index: './src/_exports/index.ts',
desk: './src/_exports/desk.ts',
Expand Down
14 changes: 12 additions & 2 deletions packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
"require": "./lib/_singletons.js",
"default": "./lib/_singletons.js"
},
"./_createContext": {
"source": "./src/_exports/_createContext.ts",
"import": "./lib/_createContext.mjs",
"require": "./lib/_createContext.js",
"default": "./lib/_createContext.js"
},
"./cli": {
"source": "./src/_exports/cli.ts",
"require": "./lib/cli.js",
Expand Down Expand Up @@ -86,6 +92,9 @@
"_singletons": [
"./lib/_singletons.d.ts"
],
"_createContext": [
"./lib/_createContext.d.ts"
],
"cli": [
"./lib/cli.d.ts"
],
Expand Down Expand Up @@ -122,13 +131,14 @@
"presentation.js",
"router.js",
"structure.js",
"_singletons.js"
"_singletons.js",
"_createContext.js"
],
"scripts": {
"build": "pkg-utils build --strict --check --clean",
"build:bundle": "vite build --config package.bundle.ts",
"check:types": "tsc --project tsconfig.lib.json",
"clean": "rimraf _internal.js _singletons.js cli.js desk.js migrate.js presentation.js router.js structure.js lib",
"clean": "rimraf _internal.js _singletons.js _createContext.js cli.js desk.js migrate.js presentation.js router.js structure.js lib",
"coverage": "jest --coverage",
"lint": "eslint .",
"prepublishOnly": "turbo run build",
Expand Down
65 changes: 65 additions & 0 deletions packages/sanity/src/_createContext/createGlobalScopedContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {type Context, createContext} from 'react'

import {SANITY_VERSION} from '../core/version'

const MISSING_CONTEXT_HELP_URL = 'https://www.sanity.io/help/missing-context-error'

/**
* @internal
* @hidden
*/
export function createGlobalScopedContext<ContextType, const T extends ContextType = ContextType>(
/**
* It's important to prefix these keys as they are global
*/
key: `sanity/_singletons/context/${string}`,
defaultValue: T,
): Context<ContextType> {
const symbol = Symbol.for(key)

/**
* Prevent errors about re-renders on React SSR on Next.js App Router
*/
if (typeof document === 'undefined') {
return createContext<ContextType>(defaultValue)
}

if (!globalScope[symbol]) {
globalScope[symbol] = {context: createContext<T>(defaultValue), version: SANITY_VERSION}
} else if (globalScope[symbol].version !== SANITY_VERSION) {
throw new TypeError(
`Duplicate instances of context "${key}" with incompatible versions detected: Expected ${SANITY_VERSION} but got ${globalScope[symbol].version}.\n\n` +
`For more information, please visit ${MISSING_CONTEXT_HELP_URL}`,
)
} else if (!warned.has(SANITY_VERSION)) {
console.warn(
`Duplicate instances of context "${key}" detected. This is likely a mistake and may cause unexpected behavior.\n\n` +
`For more information, please visit ${MISSING_CONTEXT_HELP_URL}`,
)
warned.add(SANITY_VERSION)
}

return globalScope[symbol].context
}

const warned = new Set<typeof SANITY_VERSION>()

/**
* Gets the global scope instance in a given environment.
*
* The strategy is to return the most modern, and if not, the most common:
* - The `globalThis` variable is the modern approach to accessing the global scope
* - The `window` variable is the global scope in a web browser
* - The `self` variable is the global scope in workers and others
* - The `global` variable is the global scope in Node.js
*/
function getGlobalScope() {
if (typeof globalThis !== 'undefined') return globalThis
if (typeof window !== 'undefined') return window
if (typeof self !== 'undefined') return self
if (typeof global !== 'undefined') return global

throw new Error('sanity: could not locate global scope')
}

const globalScope = getGlobalScope() as any
1 change: 1 addition & 0 deletions packages/sanity/src/_createContext/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {createGlobalScopedContext as createContext} from './createGlobalScopedContext'
1 change: 1 addition & 0 deletions packages/sanity/src/_exports/_createContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../_createContext'
7 changes: 5 additions & 2 deletions packages/sanity/src/_singletons/core/form/FormValueContext.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {FormValueContextValue} from '../../../core/form/contexts/FormValue'

/**
* @internal
*/
export const FormValueContext = createContext<FormValueContextValue | null>(null)
export const FormValueContext = createContext<FormValueContextValue | null>(
'sanity/_singletons/context/form-value',
null,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {Path} from '@sanity/types'
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

/**
* @internal
Expand All @@ -10,4 +10,7 @@ export type GetFormValueContextValue = (path: Path) => unknown
/**
* @internal
*/
export const GetFormValueContext = createContext<GetFormValueContextValue | null>(null)
export const GetFormValueContext = createContext<GetFormValueContextValue | null>(
'sanity/_singletons/context/get-form-value',
null,
)
7 changes: 5 additions & 2 deletions packages/sanity/src/_singletons/core/i18n/LocaleContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {i18n} from 'i18next'
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {Locale} from '../../../core/i18n/types'

Expand All @@ -18,4 +18,7 @@ export interface LocaleContextValue {
* @internal
* @hidden
*/
export const LocaleContext = createContext<LocaleContextValue | undefined>(undefined)
export const LocaleContext = createContext<LocaleContextValue | undefined>(
'sanity/_singletons/context/locale',
undefined,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {ResourceCache} from '../../../../core/store/_legacy/ResourceCacheProvider'

/**
* @internal
*/
export const ResourceCacheContext = createContext<ResourceCache | null>(null)
export const ResourceCacheContext = createContext<ResourceCache | null>(
'sanity/_singletons/context/resource-cache',
null,
)
4 changes: 2 additions & 2 deletions packages/sanity/src/_singletons/core/studio/SourceContext.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {Source} from '../../../core/config/types'

/**
* @internal
*/
export const SourceContext = createContext<Source | null>(null)
export const SourceContext = createContext<Source | null>('sanity/_singletons/context/source', null)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {Workspace} from '../../../core/config/types'

/**
* @internal
*/
export const WorkspaceContext = createContext<Workspace | null>(null)
export const WorkspaceContext = createContext<Workspace | null>(
'sanity/_singletons/context/workspace',
null,
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {ActiveWorkspaceMatcherContextValue} from '../../../../core/studio/activeWorkspaceMatcher/ActiveWorkspaceMatcherContext'

/** @internal */
export const ActiveWorkspaceMatcherContext =
createContext<ActiveWorkspaceMatcherContextValue | null>(null)
createContext<ActiveWorkspaceMatcherContextValue | null>(
'sanity/_singletons/context/active-workspace-matcher',
null,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {WorkspacesContextValue} from '../../../../core/studio/workspaces/WorkspacesContext'

/** @internal */
export const WorkspacesContext = createContext<WorkspacesContextValue | null>(null)
export const WorkspacesContext = createContext<WorkspacesContextValue | null>(
'sanity/_singletons/context/workspaces',
null,
)
7 changes: 5 additions & 2 deletions packages/sanity/src/_singletons/router/RouterContext.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {RouterContextValue} from '../../router/types'

/**
* @internal
*/
export const RouterContext = createContext<RouterContextValue | null>(null)
export const RouterContext = createContext<RouterContextValue | null>(
'sanity/_singletons/context/router',
null,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {StructureToolContextValue} from '../../structure/types'

/**
* @internal
*/
export const StructureToolContext = createContext<StructureToolContextValue | null>(null)
export const StructureToolContext = createContext<StructureToolContextValue | null>(
'sanity/_singletons/context/structure-tool',
null,
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {CommentsIntentContextValue} from '../../../../core/comments/context/intent/types'

/**
* @internal
*/
export const CommentsIntentContext = createContext<CommentsIntentContextValue | undefined>(
'sanity/_singletons/context/comments-intent',
undefined,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {PaneContextValue} from '../../../../structure/components/pane/types'

/**
* @internal
*/
export const PaneContext = createContext<PaneContextValue | null>(null)
export const PaneContext = createContext<PaneContextValue | null>(
'sanity/_singletons/context/pane',
null,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {createContext} from 'react'
import {createContext} from 'sanity/_createContext'

import type {PaneLayoutContextValue} from '../../../../structure/components/pane/types'

/**
* @internal
*/
export const PaneLayoutContext = createContext<PaneLayoutContextValue | null>(null)
export const PaneLayoutContext = createContext<PaneLayoutContextValue | null>(
'sanity/_singletons/context/pane-layout',
null,
)
Loading

0 comments on commit 7f3a881

Please sign in to comment.