Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf deep dive with Million Lint #8030

Draft
wants to merge 15 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions dev/test-studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
"workshop:dev": "node -r esbuild-register scripts/workshop/dev.ts"
},
"dependencies": {
"@portabletext/editor": "^1.16.3",
"@portabletext/editor": "^1.15.3",
"@portabletext/react": "^3.0.0",
"@sanity/assist": "^3.0.2",
"@sanity/block-tools": "3.67.1",
"@sanity/client": "^6.24.1",
"@sanity/color": "^3.0.0",
"@sanity/color-input": "^4.0.1",
"@sanity/google-maps-input": "^4.0.0",
"@sanity/icons": "^3.5.3",
"@sanity/icons": "^3.5.2",
"@sanity/image-url": "^1.0.2",
"@sanity/locale-ko-kr": "^1.0.1",
"@sanity/locale-nb-no": "^1.0.1",
Expand All @@ -37,12 +37,12 @@
"@sanity/react-loader": "^1.8.3",
"@sanity/tsdoc": "1.0.150",
"@sanity/types": "workspace:*",
"@sanity/ui": "^2.10.11",
"@sanity/ui": "^2.10.9",
"@sanity/ui-workshop": "^1.0.0",
"@sanity/util": "workspace:*",
"@sanity/uuid": "^3.0.1",
"@sanity/vision": "workspace:*",
"@sanity/visual-editing": "2.10.10",
"@sanity/visual-editing": "2.10.6",
"@turf/helpers": "^6.0.1",
"@turf/points-within-polygon": "^5.1.5",
"@vercel/stega": "0.1.2",
Expand All @@ -52,7 +52,7 @@
"lodash": "^4.17.21",
"qs": "^6.10.2",
"react": "^18.3.1",
"react-compiler-runtime": "19.0.0-beta-201e55d-20241215",
"react-compiler-runtime": "19.0.0-beta-37ed2a7-20241206",
"react-dom": "^18.3.1",
"react-refractor": "^2.1.6",
"refractor": "^3.6.0",
Expand All @@ -65,8 +65,8 @@
"styled-components": "^6.1.11"
},
"devDependencies": {
"@million/lint": "1.0.14",
"babel-plugin-react-compiler": "19.0.0-beta-201e55d-20241215",
"@million/lint": "^1.0.14",
"babel-plugin-react-compiler": "19.0.0-beta-37ed2a7-20241206",
"chokidar": "^3.6.0",
"vite": "^5.4.11"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import {pathFor} from '@sanity/util/paths'
import {useMemo} from 'react'
import {
type FieldError,
type FieldMember,
type FieldSetMember,
ObjectInput,
type ObjectInputProps,
type ObjectMember,
useFormBuilder,
} from 'sanity'

Expand All @@ -26,39 +24,33 @@ export function LanguageFilterObjectInput(
collapsedFieldSets,
)

const defaultMembers = useMemo(
() =>
membersProp.filter(
(member) => member.kind === 'field' && options.defaultLanguages?.includes(member.name),
),
[membersProp, options],
const defaultMembers = membersProp.filter(
(member) => member.kind === 'field' && options.defaultLanguages?.includes(member.name),
)

const members: ObjectMember[] = useMemo(() => {
const translationsFieldSetMembers = membersProp
.filter(
(member) =>
member.kind === 'field' &&
selectedLanguages.includes(member.name) &&
!options.defaultLanguages?.includes(member.name),
)
.map((member): FieldMember | FieldError => {
if (member.kind === 'fieldSet') {
return {
kind: 'error',
key: member.key,
fieldName: member.fieldSet.name,
error: new Error('test') as any, // @todo
}
}
let members = defaultMembers

return member
})
const translationsFieldSetMembers = membersProp
.filter(
(member) =>
member.kind === 'field' &&
selectedLanguages.includes(member.name) &&
!options.defaultLanguages?.includes(member.name),
)
.map((member): FieldMember | FieldError => {
if (member.kind === 'fieldSet') {
return {
kind: 'error',
key: member.key,
fieldName: member.fieldSet.name,
error: new Error('test') as any, // @todo
}
}

if (translationsFieldSetMembers.length === 0) {
return defaultMembers
}
return member
})

if (translationsFieldSetMembers.length > 0) {
const translationsFieldSet: FieldSetMember = {
kind: 'fieldSet',
key: 'translationsFieldSet',
Expand All @@ -73,16 +65,8 @@ export function LanguageFilterObjectInput(
},
}

return defaultMembers.concat([translationsFieldSet])
}, [
defaultMembers,
translationsFieldSetCollapsed,
level,
membersProp,
options,
selectedLanguages,
translationsFieldSetPath,
])
members = defaultMembers.concat([translationsFieldSet])
}

return <ObjectInput {...restProps} level={level} members={members} path={path} />
}
64 changes: 28 additions & 36 deletions dev/test-studio/plugins/language-filter/usePaneLanguages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useMemo} from 'react'
/* eslint-disable no-nested-ternary */
import {usePaneRouter} from 'sanity/structure'

import {type LanguageFilterPluginOptions} from './types'
Expand All @@ -19,50 +19,42 @@ export function usePaneLanguages(props: {options: LanguageFilterPluginOptions}):
(lang) => !options.defaultLanguages?.includes(lang.id),
)

const selectedLanguages: string[] = useMemo(() => {
if (params?.langs === '$none') {
return []
}

if (params?.langs === '$all') {
return selectableLanguages.map((lang) => lang.id)
}

return params?.langs?.split(LANG_ID_SEPARATOR) || selectableLanguages.map((lang) => lang.id)
}, [params, selectableLanguages])
const selectedLanguages: string[] =
params?.langs === '$none'
? []
: params?.langs === '$all'
? selectableLanguages.map((lang) => lang.id)
: params?.langs?.split(LANG_ID_SEPARATOR) || selectableLanguages.map((lang) => lang.id)

const selectAll = useCallback(() => {
const selectAll = () => {
setParams({...params, langs: '$all'})
}, [params, setParams])
}

const selectNone = useCallback(() => {
const selectNone = () => {
setParams({...params, langs: '$none'})
}, [params, setParams])
}

const toggleLanguage = useCallback(
(languageId: string) => {
let lang = selectedLanguages
const toggleLanguage = (languageId: string) => {
let lang = selectedLanguages

if (lang.includes(languageId)) {
lang = lang.filter((l) => l !== languageId)
} else {
lang = [...lang, languageId]
}
if (lang.includes(languageId)) {
lang = lang.filter((l) => l !== languageId)
} else {
lang = [...lang, languageId]
}

if (lang.length === 0) {
setParams({...params, langs: '$none'}) // none
return
}
if (lang.length === 0) {
setParams({...params, langs: '$none'}) // none
return
}

if (lang.length === selectableLanguages.length) {
setParams({...params, langs: '$all'})
return
}
if (lang.length === selectableLanguages.length) {
setParams({...params, langs: '$all'})
return
}

setParams({...params, langs: lang.join(LANG_ID_SEPARATOR)})
},
[params, selectableLanguages, selectedLanguages, setParams],
)
setParams({...params, langs: lang.join(LANG_ID_SEPARATOR)})
}

return {selectableLanguages, selectedLanguages, selectAll, selectNone, toggleLanguage}
}
2 changes: 1 addition & 1 deletion dev/test-studio/plugins/router-debug/RouterDebug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function RouterDebug() {
},
})

const client = useClient()
const client = useClient({apiVersion: '2024-12-12'})

return (
<Card sizing="border" padding={5}>
Expand Down
8 changes: 0 additions & 8 deletions dev/test-studio/sanity.cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ export default defineCliConfig({
? {
target: '18',
sources: (filename) => {
/**
* This is the default filter when `sources` is not defined.
* Since we're overriding it we have to ensure we don't accidentally try running the compiler on non-src files from npm.
*/
if (filename.includes('node_modules')) {
return false
}
Expand All @@ -51,10 +47,6 @@ export default defineCliConfig({
...viteConfig,
plugins: millionLintEnabled
? [
/**
* We're doing a dynamic import here, instead of a static import, to avoid an issue where a WebSocket Server is created by Million for `vite dev` that isn't closed.
* Which leaves `sanity build` hanging, even if the plugin itself isn't actually used.
*/
require('@million/lint').vite({
filter: {
include: millionInclude,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react'
Expand All @@ -31,7 +30,7 @@ export const AuthorReferenceInput = forwardRef(function AuthorReferenceInput(
const {readOnly} = inputProps
const client = useClient({apiVersion: '2022-09-09'})
const current = value && value._ref
const imageBuilder = useMemo(() => imageUrlBuilder(client), [client])
const imageBuilder = imageUrlBuilder(client)

const inputRef = useRef<HTMLButtonElement | null>(null)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Select} from '@sanity/ui'
import {type ForwardedRef, forwardRef, useCallback, useState} from 'react'
import {type ForwardedRef, forwardRef, useCallback, useId} from 'react'
import {isValidationError, type ObjectInputProps, type ObjectSchemaType, set, unset} from 'sanity'

type Value = {title: string; value: string}
Expand All @@ -12,8 +12,6 @@ type CustomObjectSelectInputProps = ObjectInputProps<Value, CustomSchemaType>

const EMPTY_ARRAY: Value[] = []

let objectSelectInputIdx = 0

export const CustomObjectSelectInput = forwardRef(function CustomObjectSelectInput(
props: CustomObjectSelectInputProps,
forwardedRef: ForwardedRef<HTMLSelectElement>,
Expand All @@ -22,7 +20,7 @@ export const CustomObjectSelectInput = forwardRef(function CustomObjectSelectInp

const items = (schemaType.options && schemaType.options.list) || EMPTY_ARRAY
const errors = validation.filter(isValidationError)
const [inputId] = useState(() => String(++objectSelectInputIdx))
const inputId = useId()

const handleChange = useCallback(
(evt: any) => {
Expand Down
78 changes: 32 additions & 46 deletions dev/test-studio/schema/debug/components/DebugStega.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-nested-ternary */
import {type ContentSourceMap, type ContentSourceMapDocuments, studioPath} from '@sanity/client/csm'
import {type ContentSourceMap, studioPath} from '@sanity/client/csm'
import {stegaEncodeSourceMap} from '@sanity/client/stega'
import {Box, Button, Card, Code, Label, Stack} from '@sanity/ui'
import {vercelStegaDecodeAll} from '@vercel/stega'
import {useMemo} from 'react'
import {type InputProps, isDocumentSchemaType} from 'sanity'
import {useDocumentPane, usePaneRouter} from 'sanity/structure'
import {styled} from 'styled-components'
Expand Down Expand Up @@ -73,50 +72,37 @@ function InputDebugger(props: InputProps) {
// onFocus,
} = useDocumentPane()
const sourcePath = 'field'
const resultSourceMap = useMemo(() => {
const documents = [
{_id: documentId, _type: documentType} satisfies ContentSourceMapDocuments[number],
]
const paths = [
`${props.path
.map((segment) =>
typeof segment === 'string'
? `['${segment}']`
: typeof segment === 'object' &&
!Array.isArray(segment) &&
typeof segment?._key === 'string'
? `[?(@._key=='${segment._key}')]`
: undefined,
)
.filter(Boolean)
.join('')}`,
]
const mappings = {
[`$['${sourcePath}']`]: {
source: {
document: 0,
path: 0,
type: 'documentValue' as const,
},
type: 'value' as const,
},
}

return {documents, paths, mappings} satisfies ContentSourceMap
}, [documentId, documentType, props.path])
const stegaResult = useMemo(
() =>
stegaEncodeSourceMap({[sourcePath]: value}, resultSourceMap, {
enabled: true,
studioUrl: '/stega',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any),
[resultSourceMap, value],
)
const stegaEditLinks = useMemo(
() => vercelStegaDecodeAll(JSON.stringify(stegaResult)),
[stegaResult],
)
const documents: ContentSourceMap['documents'] = [{_id: documentId, _type: documentType}]
const paths: ContentSourceMap['paths'] = [
`${props.path
.map((segment) =>
typeof segment === 'string'
? `['${segment}']`
: typeof segment === 'object' &&
!Array.isArray(segment) &&
typeof segment?._key === 'string'
? `[?(@._key=='${segment._key}')]`
: undefined,
)
.filter(Boolean)
.join('')}`,
]
const mappings: ContentSourceMap['mappings'] = {}
mappings[`$['${sourcePath}']`] = {
source: {
document: 0,
path: 0,
type: 'documentValue' as const,
},
type: 'value' as const,
}
const resultSourceMap = {documents, paths, mappings}
const stegaResult = stegaEncodeSourceMap({[sourcePath]: value}, resultSourceMap, {
enabled: true,
studioUrl: '/stega',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)
const stegaEditLinks = vercelStegaDecodeAll(JSON.stringify(stegaResult))
if (!stegaEditLinks || stegaEditLinks.length < 1) return null

return (
Expand Down
Loading
Loading