Skip to content

Conversation

@kean
Copy link
Contributor

@kean kean commented Nov 20, 2025

What?

The subsequent editor loads are now ~1.3s faster. The first launch is ~1s faster.

There are a few changes compared to the previous implementation:

  • Move code that fetches assets (manifest and files) to the new EditorService. The assets manifest is now fetched in parallel with the editor settings.
  • Only EditorService now triggers the actual download of the assets. The assets are put into a GutenbergKit/{siteURL}/assets folder.
  • Remove EditorAssetsProvider and instead inject the plugin styles and scripts in window.GBKit when launching the editor (the same way it's done for editor settings). It happens only if EditorService verifies that all the required files exist on disk. If not, the plugins are disabled until the next time.
  • EditorService reads editor settings and assets manifest in one atomic operation. When new manifest is loaded, it replaces the current one only after fetching all assets for it. Until then, the editor will always use the previous version of the manifest.
  • Remove editorSettings from EditorConfiguration. Add them to internal EditorDependencies types fetched by EditorViewController

Minor changes:

  • Remove editorAssetsEndpoint parameter from EditorConfiguration
  • Make logging a cross-cutting concern, so it could be configured once and would be easier to use across GBK

Planned future changes:

  • Cleanup unused assets on launch to make sure the disk usage does not grow
  • Improve how and when refresh and assets download is triggered
  • Cover with unit tests and generate the same code in Kotlin

Why?

How?

Testing Instructions

Accessibility Testing Instructions

Screenshots or screencast

Before / After

Note: the preparing editor message disappears quicker because "fetch editor settings" is now a step inside EditorViewController.

comparison.mp4

import CryptoKit
import SwiftSoup

struct EditorAssetsManifest: Codable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an existing struct. I made no changes to it – just renamed the file.

@kean kean marked this pull request as draft November 20, 2025 23:02
@kean kean force-pushed the task/editor-assets branch from a9806a5 to 0b9616f Compare November 21, 2025 12:54
@kean kean force-pushed the task/editor-assets branch from 0b9616f to dac7dc6 Compare November 21, 2025 13:26
Copy link
Member

@dcalhoun dcalhoun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for creating this!

This makes sense to me a high level. It feels like a worthwhile improvement overall, especially given the performance improvements.

I left a few thoughts to consider. Many are for bettering my own understanding.

let config = EditorConfigurationBuilder()
.setShouldUsePlugins(false)
.setSiteUrl("")
.setSiteUrl("bundled")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this change? Is this merely an identifier of why the configuration is empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I added it as part of another changes I later reverted. I'll revert this as well.

Comment on lines -35 to -36
/// Endpoint for loading editor assets, used when enabling `shouldUsePlugins`
public var editorAssetsEndpoint: URL?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing editorAssetsEndpoint? I believe it's useful in the future whenever we change the REST API endpoint used, it also allows alternatives for third-parties as needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to add it again in the future if it's needed. For now, I kept it simple as EditorService knows what endpoints to use, and the code in the app was duplicating the same endpoint.

Comment on lines -37 to -38
/// Logs emitted at or above this level will be printed to the debug console
public let logLevel: LogLevel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are host apps still able to configure the log levels emitted from the JavaScript logger utility? I believe that to be beneficial.

Copy link
Contributor Author

@kean kean Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I still inject the same log level in JS: logLevel: '\(EditorLogger.logLevel.rawValue)',

},
logLevel: '\(configuration.logLevel)'
logLevel: '\(EditorLogger.logLevel.rawValue)',
manifest: \(dependencies?.manifest ?? "undefined")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any performance implications from adding this? 🤔 I suppose it is no larger than editorSettings, eh?

I wonder if it's worth spreading the properties over the top-level object. I could see one day allowing GBK configuration to control allowedBlockTypes.

Copy link
Contributor Author

@kean kean Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any performance implications from adding this? 🤔 I suppose it is no larger than editorSettings, eh?

I wouldn't expect it to be slower than the current implementation, but I already had some second thoughts yesterday. Is it safe to pass these larger strings directly in JS this way? Does it need escaping? Is there a more robust way to do it? Should we pass the whole pre-processed manifest as a separate object? Both settings and manifest seem to have <scripts> and <styles>, is there a better way to inject them? What about allowedBlockTypes? Should the app combine them (for core and plugins)?

Comment on lines +340 to +348
const gbkit = getGBKit();

// iOS provides manifest directly in GBKit configuration
if ( window.webkit && gbkit.manifest ) {
return gbkit.manifest;
}

const { siteApiRoot, editorAssetsEndpoint, authHeader } = getGBKit();
// Android fallback - fetch from API
const { siteApiRoot, editorAssetsEndpoint, authHeader } = gbkit;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe there is a downside to de-structuring all values up front.

Suggested change
const gbkit = getGBKit();
// iOS provides manifest directly in GBKit configuration
if ( window.webkit && gbkit.manifest ) {
return gbkit.manifest;
}
const { siteApiRoot, editorAssetsEndpoint, authHeader } = getGBKit();
// Android fallback - fetch from API
const { siteApiRoot, editorAssetsEndpoint, authHeader } = gbkit;
const { manifest, siteApiRoot, editorAssetsEndpoint, authHeader } = getGBKit();
// iOS provides manifest directly in GBKit configuration
if ( window.webkit && manifest ) {
return manifest;
}
// Android fallback - fetch from API

Alternatively, I don't believe there is a downside to invoking getGBKit() multiple times, as it early returns with the cached value on the window.

Suggested change
const gbkit = getGBKit();
// iOS provides manifest directly in GBKit configuration
if ( window.webkit && gbkit.manifest ) {
return gbkit.manifest;
}
const { siteApiRoot, editorAssetsEndpoint, authHeader } = getGBKit();
// Android fallback - fetch from API
const { siteApiRoot, editorAssetsEndpoint, authHeader } = gbkit;
const { manifest } = getGBKit();
// iOS provides manifest directly in GBKit configuration
if ( window.webkit && gbkit.manifest ) {
return gbkit.manifest;
}
// Android fallback - fetch from API
const { siteApiRoot, editorAssetsEndpoint, authHeader } = getGBKit();

@kean kean changed the title WIP: Faster and more robust editor asset management Faster and more robust editor asset management Nov 21, 2025
@kean kean marked this pull request as ready for review November 21, 2025 21:43
@kean
Copy link
Contributor Author

kean commented Nov 21, 2025

I made a few more changes:

  • Added unit tests and fixed a couple of issues (it explains most of the line additions)
  • Updated EditorService to store processed manifest on disk (it takes around 100ms to generate it)
  • Added support for cleaning up orphaned asset files, so we don’t take too much space on disk

With these changes, this whole step now takes 2ms.

I had to switch to a different task today and didn't have time to review the final PR. I'll do it when I have time. It should be good.

@kean kean requested a review from dcalhoun November 21, 2025 21:44
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.

3 participants