Fix web/extension build ordering for deploy and dev#7320
Open
MitchLillie wants to merge 1 commit intomainfrom
Open
Fix web/extension build ordering for deploy and dev#7320MitchLillie wants to merge 1 commit intomainfrom
MitchLillie wants to merge 1 commit intomainfrom
Conversation
Extensions like the admin module copy output from web builds (e.g. dist/) into the bundle via include_assets. Both deploy and dev ran web builds concurrently with extension builds, causing a race condition where dist/ was empty or missing when the admin extension tried to copy it. Deploy: split the single renderConcurrent call into two sequential phases — web builds first, then extension builds. Dev: wait for the web dev process to populate static_root before building extensions. We cannot run commands.build ourselves because it races with the concurrent web dev process on the same output directory. Fixes: index_missing: index.html must be present in the bundle.
d588506 to
98607cc
Compare
elanalynn
approved these changes
Apr 15, 2026
Contributor
elanalynn
left a comment
There was a problem hiding this comment.
🎩 'd. Both dev and deploy work without running build manually.
| import EventEmitter from 'events' | ||
| import {Writable} from 'stream' | ||
|
|
||
| const POLL_TIMEOUT_MS = 30_000 |
Contributor
There was a problem hiding this comment.
Polling isn't ideal, but it does work.
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.
Problem
When deploying or running dev with an app that has
[admin] static_root, the web build (e.g. Vite) and extension builds run concurrently. The admin extension'sinclude_assetsstep copies files fromstatic_root(e.g.dist/), but the web build may not have finished populating it yet. Vite'semptyOutDir: truedeletesdist/at the start of its build, so the admin extension either sees a missing or empty directory and skips the copy. The result:This affects both
shopify app deployandshopify app dev.Fixes https://github.com/shop/issues-admin-extensibility/issues/2411
Supersedes #7315 and #7305
Root Cause
The admin extension's
include_assetsbuild step has an implicit dependency on web build output (static_root→dist/), but the build system runs everything concurrently:Deploy (
bundle.ts):renderConcurrent([webBuildProcesses, extensionBuildProcesses].flat())— one flat concurrent batch.Dev (
app-event-watcher.ts): All processes (web dev, app watcher) start viaPromise.all. The watcher immediately builds all extensions on start, while the web dev process is still starting up.Solution
Deploy (
bundle.ts)Split the single
renderConcurrentcall into two sequential phases:Extensions within each phase still run concurrently with each other.
Dev (
app-event-watcher.ts)Added
waitForStaticRoots()which polls until admin'sstatic_rootdirectory is populated before building extensions.We cannot use the same "build first" approach as deploy because the web dev process (
commands.dev) runs as a concurrent sibling process. Runningcommands.buildin the watcher races with the dev process on the same output directory (both write todist/, and Vite'semptyOutDircauses them to clobber each other).Instead, we wait for the web dev process to produce its initial output (up to 30s, polling every 200ms), then proceed with extension builds. No-op when no admin extension references a
static_root.Testing
callOrderarray to proveweb-build-endhappens beforeextension-build-startTophatting
Prerequisites
dev upHOSTED_APPS=1 pnpm shopify app init --name test-fix-index-html --path="$HOME/tmp"Reset state between tests
Test dev
HOSTED_APPS=1 pnpm shopify app dev --path="$HOME/tmp/test-fix-index-html"Before fix:
index_missing: index.html must be present in the bundle.After fix: Dev session starts successfully. The watcher waits briefly for the web dev process to populate
dist/, then builds the admin extension.Test deploy
HOSTED_APPS=1 pnpm shopify app deploy --path="$HOME/tmp/test-fix-index-html"Before fix: Same
index_missingrace condition (intermittent).After fix: Web builds complete before extension builds start. No race condition.
Edge cases
static_rootconfigured —waitForStaticRootsis a no-op,copyConfigKeyEntryskips