From 806c4f0816c119b522a042f04defb263bec70e0e Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 19 Dec 2025 15:11:49 -0500 Subject: [PATCH 1/3] task: Make targets reuse JavaScript build Ideally, the `makefile` serves as the de facto interface for project scripts. This avoids multiple sources of truth and simplifies project onboarding and documentation. Previously, rerunning various `make` targets resulted in rerunning the expensive and slow `make build` target. It's important to ensure the build output exists for many targets, but is often unnecessary to rerun the build when running targets multiple times--e.g., Swift test targets. This introduces a caching mechanism. The `make build` target skips running and relies upon the cache unless one of the following is true. - dist doesn't exist - REFRESH_JS_BUILD is set to true or 1 - build was invoked directly This enables quicker reruns while also ensure the build is always recreated when important to do so--e.g., direct invocations, CI runs, releases. See https://github.com/wordpress-mobile/GutenbergKit/pull/253#discussion_r2628633586 --- .buildkite/pipeline.yml | 4 ++-- CLAUDE.md | 6 +++--- Makefile | 23 ++++++++++++++--------- bin/release.sh | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index d57fb340..f2997174 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -6,7 +6,7 @@ env: steps: - label: ':react: Build React App' - command: make build REFRESH_L10N=1 + command: make build REFRESH_L10N=1 REFRESH_JS_BUILD=1 plugins: &plugins - $CI_TOOLKIT_PLUGIN - $NVM_PLUGIN @@ -21,7 +21,7 @@ steps: - label: ':android: Publish Android Library' command: | - make build REFRESH_L10N=1 + make build REFRESH_L10N=1 REFRESH_JS_BUILD=1 echo "--- :android: Publishing Android Library" ./android/gradlew -p ./android :gutenberg:prepareToPublishToS3 $(prepare_to_publish_to_s3_params) :gutenberg:publish agents: diff --git a/CLAUDE.md b/CLAUDE.md index fd960b22..a3198d65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,9 +82,9 @@ make build # - ios/Sources/GutenbergKit/Gutenberg/ # - android/Gutenberg/src/main/assets/ -# By default, dependencies and translations are only installed if their directories don't exist -# Force refresh of dependencies and translations -make build REFRESH_DEPS=1 REFRESH_L10N=1 +# By default, the build is skipped if output directories already exist +# Force refresh of dependencies, translations, and JS build +make build REFRESH_DEPS=1 REFRESH_L10N=1 REFRESH_JS_BUILD=1 # Clean build artifacts make clean # Clean both dist and translations diff --git a/Makefile b/Makefile index 7882bbef..79bb8ef1 100644 --- a/Makefile +++ b/Makefile @@ -68,15 +68,20 @@ clean: ## Remove build artifacts and translation string files .PHONY: build build: npm-dependencies prep-translations ## Build the project for all platforms (iOS, Android, web) - @echo "--- :node: Building Gutenberg" - - npm run build - -# Copy build products into place - @echo "--- :open_file_folder: Copying Build Products into place" - rm -rf ./ios/Sources/GutenbergKit/Gutenberg/ ./android/Gutenberg/src/main/assets/ - cp -r ./dist/. ./ios/Sources/GutenbergKit/Gutenberg/ - cp -r ./dist/. ./android/Gutenberg/src/main/assets +# Skip unless... +# - dist doesn't exist +# - REFRESH_JS_BUILD is set to true or 1 +# - build was invoked directly + @if [ ! -d "dist" ] || [ "$(REFRESH_JS_BUILD)" = "true" ] || [ "$(REFRESH_JS_BUILD)" = "1" ] || echo "$(MAKECMDGOALS)" | grep -q "^build$$"; then \ + echo "--- :node: Building Gutenberg"; \ + npm run build; \ + echo "--- :open_file_folder: Copying Build Products into place"; \ + rm -rf ./ios/Sources/GutenbergKit/Gutenberg/ ./android/Gutenberg/src/main/assets/; \ + cp -r ./dist/. ./ios/Sources/GutenbergKit/Gutenberg/; \ + cp -r ./dist/. ./android/Gutenberg/src/main/assets; \ + else \ + echo "--- :white_check_mark: Skipping JS build (dist already exists). Use REFRESH_JS_BUILD=1 to force refresh."; \ + fi .PHONY: build-swift-package build-swift-package: build ## Build the Swift package for iOS diff --git a/bin/release.sh b/bin/release.sh index 848af673..81a76108 100755 --- a/bin/release.sh +++ b/bin/release.sh @@ -243,7 +243,7 @@ build_project() { return fi - make build REFRESH_DEPS=1 REFRESH_L10N=1 STRICT_L10N=1 + make build REFRESH_DEPS=1 REFRESH_L10N=1 REFRESH_JS_BUILD=1 STRICT_L10N=1 print_success "Build completed successfully" } From 863b738731ed8dc344726ffd3f29c81bc80a760a Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 19 Dec 2025 15:19:32 -0500 Subject: [PATCH 2/3] docs: Direct Claude to use `make` targets Follow project practice of using the `makefile` as the de facto script interface. --- CLAUDE.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a3198d65..c518655c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,10 +98,13 @@ npm run clean:l10n # Clean only translations directory ```bash # Build Swift package -swift build +make build-swift-package + +# Build Swift package (force refresh of npm deps/translations/build if needed) +make build-swift-package REFRESH_DEPS=1 REFRESH_L10N=1 REFRESH_JS_BUILD=1 # Run Swift tests -swift test +make test-swift-package ``` ### Android Development From 2f0ae428ec85818494a6d073134dec6f0ca3204c Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 19 Dec 2025 15:20:08 -0500 Subject: [PATCH 3/3] style: Format existing documentation --- docs/preloading.md | 76 +++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/docs/preloading.md b/docs/preloading.md index a8734046..8e8560af 100644 --- a/docs/preloading.md +++ b/docs/preloading.md @@ -42,6 +42,7 @@ The preloading system consists of several interconnected components: The `EditorService` actor coordinates fetching all editor dependencies concurrently: **Swift** + ```swift let service = EditorService(configuration: config) let dependencies = try await service.prepare { progress in @@ -50,40 +51,43 @@ let dependencies = try await service.prepare { progress in ``` **Kotlin** + ```kotlin // TBD ``` The `prepare` method fetches these resources in parallel: -- Editor settings (theme styles, block settings) -- Asset bundles (JavaScript and CSS files) -- Preload list (API responses for editor initialization) + +- Editor settings (theme styles, block settings) +- Asset bundles (JavaScript and CSS files) +- Preload list (API responses for editor initialization) ### EditorPreloadList The `EditorPreloadList` struct contains pre-fetched API responses that are serialized to JSON and injected into the editor's JavaScript runtime: -| Property | API Endpoint | Description | -|----------|--------------|-------------| -| `postData` | `/wp/v2/posts/{id}?context=edit` | The post being edited (existing posts only) | -| `postTypeData` | `/wp/v2/types/{type}?context=edit` | Schema for the current post type | -| `postTypesData` | `/wp/v2/types?context=view` | All available post types | -| `activeThemeData` | `/wp/v2/themes?context=edit&status=active` | Active theme information | -| `settingsOptionsData` | `OPTIONS /wp/v2/settings` | Site settings schema | +| Property | API Endpoint | Description | +| --------------------- | ------------------------------------------ | ------------------------------------------- | +| `postData` | `/wp/v2/posts/{id}?context=edit` | The post being edited (existing posts only) | +| `postTypeData` | `/wp/v2/types/{type}?context=edit` | Schema for the current post type | +| `postTypesData` | `/wp/v2/types?context=view` | All available post types | +| `activeThemeData` | `/wp/v2/themes?context=edit&status=active` | Active theme information | +| `settingsOptionsData` | `OPTIONS /wp/v2/settings` | Site settings schema | ### EditorURLCache The `EditorURLCache` provides disk-based caching for API responses, keyed by URL and HTTP method. It supports three cache policies via `EditorCachePolicy`: -| Policy | Behavior | -|--------|----------| -| `.ignore` | Never use cached responses (force fresh data) | +| Policy | Behavior | +| ----------------------- | --------------------------------------------------- | +| `.ignore` | Never use cached responses (force fresh data) | | `.maxAge(TimeInterval)` | Use cached responses younger than the specified age | -| `.always` | Always use cached responses regardless of age | +| `.always` | Always use cached responses regardless of age | Example: **Swift** + ```swift // Cache responses for up to 1 hour let service = EditorService( @@ -93,6 +97,7 @@ let service = EditorService( ``` **Kotlin** + ```kotlin // TBD ``` @@ -158,12 +163,16 @@ The `GBKitGlobal` struct packages all configuration and preload data, then injec ```javascript window.GBKit = { - siteURL: "https://example.com", - siteApiRoot: "https://example.com/wp-json", - authHeader: "Bearer ...", - preloadData: { /* serialized EditorPreloadList */ }, - editorSettings: { /* theme styles, colors, etc. */ }, - // ... other configuration + siteURL: 'https://example.com', + siteApiRoot: 'https://example.com/wp-json', + authHeader: 'Bearer ...', + preloadData: { + /* serialized EditorPreloadList */ + }, + editorSettings: { + /* theme styles, colors, etc. */ + }, + // ... other configuration }; ``` @@ -174,11 +183,11 @@ The `@wordpress/api-fetch` package includes a preloading middleware that interce ```javascript // In src/utils/api-fetch.js export function configureApiFetch() { - const { preloadData } = getGBKit(); + const { preloadData } = getGBKit(); - apiFetch.use( - apiFetch.createPreloadingMiddleware(preloadData ?? defaultPreloadData) - ); + apiFetch.use( + apiFetch.createPreloadingMiddleware( preloadData ?? defaultPreloadData ) + ); } ``` @@ -193,8 +202,8 @@ When Gutenberg makes an API request: Only certain headers are preserved in preload responses to match WordPress core's behavior: -- `Accept` - Content type negotiation -- `Link` - REST API discovery and pagination +- `Accept` - Content type negotiation +- `Link` - REST API discovery and pagination This filtering is performed by `EditorURLResponse.asPreloadResponse()`. @@ -205,6 +214,7 @@ This filtering is performed by `EditorURLResponse.asPreloadResponse()`. `EditorService` automatically cleans up old asset bundles once per day: **Swift** + ```swift try await onceEvery(.seconds(86_400)) { try await self.cleanup() @@ -212,6 +222,7 @@ try await onceEvery(.seconds(86_400)) { ``` **Kotlin** + ```kotlin //tbd ``` @@ -219,6 +230,7 @@ try await onceEvery(.seconds(86_400)) { ### Manual Cache Control **Swift** + ```swift // Clear unused resources (keeps most recent) try await service.cleanup() @@ -228,6 +240,7 @@ try await service.purge() ``` **Kotlin** + ```kotlin //tbd ``` @@ -261,11 +274,11 @@ that contain everything the editor needs, the progress bar will never be display `EditorDependencies` contains all pre-fetched resources needed to initialize the editor instantly. -| Property | Type | Description | -|----------|------|-------------| -| `editorSettings` | `EditorSettings` | Theme styles, colors, typography, block settings | -| `assetBundle` | `EditorAssetBundle` | Cached JavaScript/CSS for plugins/themes | -| `preloadList` | `EditorPreloadList?` | Pre-fetched API responses | +| Property | Type | Description | +| ---------------- | -------------------- | ------------------------------------------------ | +| `editorSettings` | `EditorSettings` | Theme styles, colors, typography, block settings | +| `assetBundle` | `EditorAssetBundle` | Cached JavaScript/CSS for plugins/themes | +| `preloadList` | `EditorPreloadList?` | Pre-fetched API responses | ### Obtaining Dependencies @@ -326,3 +339,4 @@ class PostListViewController: UIViewController { navigationController?.pushViewController(editor, animated: true) } } +```