Skip to content

Improve rendering and schema loading performance#1690

Open
DCkQ6 wants to merge 1 commit into
json-editor:masterfrom
DCkQ6:feature/performance-by-opus
Open

Improve rendering and schema loading performance#1690
DCkQ6 wants to merge 1 commit into
json-editor:masterfrom
DCkQ6:feature/performance-by-opus

Conversation

@DCkQ6

@DCkQ6 DCkQ6 commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Hello, we are using this library to render a fairly large schema, and in our case it became quite painful to wait until it finished loading. I used Claude Opus to optimize it and ran a few review loops. It significantly decreased the loading time (1.5s vs 13s on my machine in the Chrome browser). All tests pass, and our large schema renders correctly. I hope these improvements will be useful to you.

Below Summary of changes from my fellow friend Opus-4.6


This PR reduces initial render time and schema loading overhead through several targeted optimizations.

Off-screen DOM build (core.js, editor.js)

The editor tree is now built while root_container is detached from the live DOM and re-attached in a single appendChild call. This eliminates per-element reflows during construction. A building flag suppresses onChange() calls for the duration. The build block is wrapped in try/finally to guarantee root_container is always re-attached and building is always cleared, even if a build step throws.
A new onContainerAttached() lifecycle hook (empty stub on AbstractEditor) is called on all editors after re-attachment, allowing editors to perform layout-dependent initialization that requires a live DOM.

Schema loading (schemaloader.js)

External ref loading is parallelized using Promise.all instead of sequential await per URI. The monolithic _asyncloadExternalRefs loop is split into _loadSingleRef, _loadUrnRef, and _loadAjaxRef helpers to improve readability and testability. onAllSchemasLoaded() is moved to the top-level load() method so it fires exactly once after the full ref graph resolves, restoring the pre-existing single-fire guarantee.
expandSchema() and expandRefs() results are memoized by schema object identity (Map) to avoid re-expanding the same schema repeatedly. The oneOf handler is fixed to use map() instead of reduce() and to avoid clobbering the parent schema's default when merging.

Validation error dispatch (object.js, array.js)

showValidationErrors() in both ObjectEditor and ArrayEditor now pre-buckets errors by direct child key/index before passing them down. Previously the full error array was passed to every child, causing O(n×m) path comparisons. Each child now only receives errors that belong to it.

MultipleEditor lazy validators (multiple.js)

Validator instances are now created lazily via _getValidator(i) instead of being eagerly instantiated for all types at build time. _getTypeSchema(i) is extracted as a shared helper used by both _getValidator and buildChildEditor. A getDefault() override checks per-type default values in oneOf/anyOf schemas before falling back to the parent default.

SignatureEditor off-screen fix (signature.js)

The canvas reference is stored on this.signatureCanvas and initial CSS sizing (width: 100%) is applied at build time. A onContainerAttached() override reads the real container width after the DOM is live and updates canvas.width accordingly, fixing the canvas sizing regression caused by off-screen construction.

Grid layout fix (object.js)

ObjectEditor adds an onContainerAttached() override that re-runs layoutEditors() for grid-format objects after the container is in the live DOM. The existing diff-check inside layoutEditors() makes the second call a no-op when heights do not affect the computed layout.

Misc

  • extend({}, ...) replaced with { ...obj } or Object.assign throughout affected files
  • Null-guard added to overwriteExistingProperties in utilities.js
  • E2E test for issue Signature Pad not working #1367 gains a short wait to accommodate the deferred canvas resize
Q A
Is bugfix?
New feature?
Is backward-compatible? ✔️
Tests pass? ✔️
Fixed issues none
Updated README/docs?
Added CHANGELOG entry? ✔️

- Build editor tree off-screen to avoid per-element reflows; re-attach
  in a single DOM operation. Suppress onChange during build via a
  building flag. Wrap in try/finally to guarantee cleanup on error.
- Parallelize external ref loading with Promise.all; split loader into
  helper methods. Move onAllSchemasLoaded() to load() so it fires once.
  Memoize expandSchema/expandRefs by schema object identity.
- Pre-bucket validation errors by child key/index in ObjectEditor and
  ArrayEditor before passing down, avoiding full-list scans per child.
- Lazy-init Validator instances in MultipleEditor; extract _getTypeSchema
  helper; add getDefault() override for oneOf/anyOf per-type defaults.
- Add onContainerAttached() hook to AbstractEditor; use it in
  ObjectEditor (re-run grid layout) and SignatureEditor (resize canvas)
  after the container is in the live DOM.
- Replace extend({}, ...) with spread/Object.assign throughout; add
  null-guard to overwriteExistingProperties; update docker compose CLI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant