Merged
Conversation
🦋 Changeset detectedLatest commit: afcc588 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📦 Bundle Stats —
|
| Metric | Value | vs main (41bf92f) |
|---|---|---|
| Internal (raw) | 755.0 KB | +1.8 KB, +0.2% |
| Internal (gzip) | 142.0 KB | +526 B, +0.4% |
| Bundled (raw) | 1.36 MB | +1.8 KB, +0.1% |
| Bundled (gzip) | 304.8 KB | +390 B, +0.1% |
| Import time | 100ms | -0ms, -0.1% |
@portabletext/editor/behaviors
| Metric | Value | vs main (41bf92f) |
|---|---|---|
| Internal (raw) | 467 B | - |
| Internal (gzip) | 207 B | - |
| Bundled (raw) | 424 B | - |
| Bundled (gzip) | 171 B | - |
| Import time | 6ms | -0ms, -0.3% |
@portabletext/editor/plugins
| Metric | Value | vs main (41bf92f) |
|---|---|---|
| Internal (raw) | 2.5 KB | - |
| Internal (gzip) | 910 B | - |
| Bundled (raw) | 2.3 KB | - |
| Bundled (gzip) | 839 B | - |
| Import time | 12ms | -0ms, -0.3% |
@portabletext/editor/selectors
| Metric | Value | vs main (41bf92f) |
|---|---|---|
| Internal (raw) | 60.2 KB | - |
| Internal (gzip) | 9.4 KB | +2 B, +0.0% |
| Bundled (raw) | 56.7 KB | - |
| Bundled (gzip) | 8.6 KB | +1 B, +0.0% |
| Import time | 10ms | -0ms, -0.2% |
@portabletext/editor/utils
| Metric | Value | vs main (41bf92f) |
|---|---|---|
| Internal (raw) | 24.2 KB | - |
| Internal (gzip) | 4.7 KB | - |
| Bundled (raw) | 22.2 KB | - |
| Bundled (gzip) | 4.4 KB | +1 B, +0.0% |
| Import time | 10ms | +0ms, +1.3% |
🗺️ . · ./behaviors · ./plugins · ./selectors · ./utils · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
8ece896 to
f3ce145
Compare
f3ce145 to
1dc50d7
Compare
1dc50d7 to
818940f
Compare
818940f to
222849b
Compare
222849b to
5a8b722
Compare
5a8b722 to
bc7f5bd
Compare
23a2dc0 to
f023d35
Compare
faf861c to
a08d14e
Compare
be7788c to
d55f7a0
Compare
robinpyon
approved these changes
Mar 20, 2026
Historically, Slate has used WeakMaps to bridge between DOM elements and Slate nodes. Every component registered nodes into these maps during render. This caused a tight coupling between the model and the DOM which also violates React 19's constraints where render must be pure. This PR replaces all six WeakMaps with a single `data-pt-path` attribute rendered onto the DOM nodes (e.g., `data-pt-path="[_key=="k0"].children[_key=="s1"]"`). Finding a DOM node from a path is now just a `querySelector` and mapping from a DOM node to a node in the model is now just about reading the data attribute, translating the path and traversing the model. No maps, no registration, no cleanup. This is possible since keyed paths are stable. Unlike indexed paths, which is Slate's native path model, inserting or removing a sibling doesn't invalidate keyed paths. The path format also encodes field names between key segments, which means it extends naturally to containers where children live in schema-defined fields like `rows`, `cells`, or `content` rather than a single `children` array. This is a key unlock for when PTE needs to support nested structures with a resilient mapping between DOM and model.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
🎉 This PR removes the last remaining WeakMaps from PTE and from the vendored Slate code. At the point of vendoring Slate we had 26 WeakMaps scattered around the code base, holding state information and aiding DOM mapping. After this PR we have 0.
Historically, Slate has used WeakMaps to bridge between DOM elements and Slate nodes. Every component registered nodes into these maps during render. This caused a tight coupling between the model and the DOM which also violates React 19's constraints where render must be pure.
This PR replaces all six WeakMaps with a single
data-pt-pathattribute rendered onto the DOM nodes (e.g.,data-pt-path='[_key=="k0"].children[_key=="s0"]'). Finding a DOM node from a path is now just aquerySelectorand mapping from a DOM node to a node in the model is now just about reading the data attribute, translating the path and traversing the model. No maps, no registration, no cleanup.This is possible since keyed paths are stable. Unlike indexed paths, which is Slate's native path model, inserting or removing a sibling doesn't invalidate keyed paths. The path format also encodes field names between key segments, which means it extends naturally to containers where children live in schema-defined fields like
rows,cells, orcontentrather than a singlechildrenarray. This is a key unlock for when PTE needs to support nested structures with a resilient mapping between DOM and model.Note
A note on performance:
querySelectoron a full document can be slow, but here it's scoped to a single block's DOM subtree viaeditorElement.children[blockIndex]. For flat content (text blocks, void objects), there's noquerySelectorat all - it's a direct child lookup.querySelectoronly runs for paths deeper than one level, and even then it searches within a single block element, not the full editor.Note
A note on security: Since
data-pt-pathattributes contain key values that flow intoquerySelector, all selectors are escaped withCSS.escapeto prevent selector injection from custom key generators.Note
A note on data integrity: The
data-pt-pathattribute uses Sanity's bracket notation for keyed segments (e.g.,[_key=="k0"].children[_key=="s0"]). This format keeps keys inside brackets, so keys containing dots or other special characters don't break the path serialization. The dot character is only ever a field name separator, never part of a key value.