Skip to content

Commit

Permalink
fix(stega): 2x faster encoding of portable text (#920)
Browse files Browse the repository at this point in the history
  • Loading branch information
stipsan authored Nov 18, 2024
1 parent a57acd7 commit 8ae6d30
Show file tree
Hide file tree
Showing 8 changed files with 10,307 additions and 35 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
run: echo "::add-matcher::.github/eslint-compact.json"
- run: npm run lint -- --report-unused-disable-directives -f compact
- run: npm run coverage
- run: npm run benchmark
- uses: actions/upload-artifact@v4
name: Cache build output
with:
Expand Down
20 changes: 18 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sanity/client",
"version": "6.22.4",
"version": "6.22.5-canary.0",
"description": "Client for retrieving, creating and patching data from Sanity.io",
"keywords": [
"sanity",
Expand Down Expand Up @@ -89,6 +89,7 @@
"umd"
],
"scripts": {
"benchmark": "npm test -- bench",
"prebuild": "npm run clean",
"build": "pkg build --strict && pkg --strict && npm run rollup && npm run minify",
"clean": "npx rimraf dist coverage umd/*.js",
Expand Down Expand Up @@ -126,6 +127,7 @@
"@edge-runtime/vm": "^4.0.4",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@sanity/client-latest": "npm:@sanity/client@latest",
"@sanity/pkg-utils": "^6.11.11",
"@types/json-diff": "^1.0.3",
"@types/node": "^22.9.0",
Expand Down
11 changes: 11 additions & 0 deletions src/csm/walkMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ export function walkMap(
}

if (isRecord(value)) {
// Handle Portable Text in a faster way
if (value._type === 'block' || value._type === 'span') {
const result = {...value}
if (value._type === 'block') {
result.children = walkMap(value.children, mappingFn, path.concat('children'))
} else if (value._type === 'span') {
result.text = walkMap(value.text, mappingFn, path.concat('text'))
}
return result
}

return Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, walkMap(v, mappingFn, path.concat(k))]),
)
Expand Down
24 changes: 0 additions & 24 deletions src/stega/filterDefault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,6 @@ export const filterDefault: FilterDefault = ({sourcePath, resultPath, value}) =>
return false
}

/**
* Best effort infer Portable Text paths that should not be encoded.
* Nothing is for certain, and the below implementation may cause paths that aren't Portable Text and otherwise be safe to encode to be skipped.
* However, that's ok as userland can always opt-in with the `encodeSourceMapAtPath` option and mark known safe paths as such, which will override this heuristic.
*/
// If the path ends in marks[number] it's likely a PortableTextSpan: https://github.com/portabletext/types/blob/e54eb24f136d8efd51a46c6a190e7c46e79b5380/src/portableText.ts#LL154C16-L154C16
if (typeof endPath === 'number' && sourcePath.at(-2) === 'marks') {
return false
}
// Or if it's [number].markDefs[number].href it's likely a PortableTextLink: https://github.com/portabletext/types/blob/e54eb24f136d8efd51a46c6a190e7c46e79b5380/src/portableText.ts#L163
if (
endPath === 'href' &&
typeof sourcePath.at(-2) === 'number' &&
sourcePath.at(-3) === 'markDefs'
) {
return false
}
// Otherwise we have to deal with special properties of PortableTextBlock, and we can't confidently know if it's actually a `_type: 'block'` array item or not.
// All we know is that if it is indeed a block, and we encode the strings on these keys it'll for sure break the PortableText rendering and thus we skip encoding.
// https://github.com/portabletext/types/blob/e54eb24f136d8efd51a46c6a190e7c46e79b5380/src/portableText.ts#L48-L58
if (endPath === 'style' || endPath === 'listItem') {
return false
}

// Don't encode into anything that is suggested it'll render for SEO in meta tags
if (
sourcePath.some(
Expand Down
10 changes: 2 additions & 8 deletions test/stega/__snapshots__/stegaEncodeSourceMap.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,14 @@ exports[`resolveEditUrl '/' > logger.log 1`] = `
"[@sanity/client]: Encoding source map into result",
],
[
"[@sanity/client]: Paths encoded: 18, skipped: 81",
"[@sanity/client]: Paths encoded: 18, skipped: 36",
],
[
"[@sanity/client]: Table of encoded paths",
],
[
"[@sanity/client]: List of skipped paths",
[
"description[]._key",
"description[]._type",
"description[].style",
"slug.current",
"slug._type",
"media[]._type",
Expand Down Expand Up @@ -227,17 +224,14 @@ exports[`resolveEditUrl 'https://test.sanity.studio' > logger.log 1`] = `
"[@sanity/client]: Encoding source map into result",
],
[
"[@sanity/client]: Paths encoded: 18, skipped: 81",
"[@sanity/client]: Paths encoded: 18, skipped: 36",
],
[
"[@sanity/client]: Table of encoded paths",
],
[
"[@sanity/client]: List of skipped paths",
[
"description[]._key",
"description[]._type",
"description[].style",
"slug.current",
"slug._type",
"media[]._type",
Expand Down
20 changes: 20 additions & 0 deletions test/stega/stegaEncodeSourceMap.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {stegaEncodeSourceMap as stegaEncodeSourceMapLatest} from '@sanity/client-latest/stega'
import {bench, describe} from 'vitest'

import {stegaEncodeSourceMap} from '../../src/stega/stegaEncodeSourceMap'
import type {InitializedStegaConfig} from '../../src/stega/types'
import data from './stegaSnapshotHuge.json' with {type: 'json'}

const config = {
enabled: true,
studioUrl: 'https://test.sanity.studio',
} satisfies InitializedStegaConfig

describe('stegaEncodeSourceMap can handle Portable Text in a performant way', () => {
bench('@sanity/client@latest', () => {
stegaEncodeSourceMapLatest(data.result, data.resultSourceMap as any, config)
})
bench('src', () => {
stegaEncodeSourceMap(data.result, data.resultSourceMap as any, config)
})
})
Loading

0 comments on commit 8ae6d30

Please sign in to comment.