Skip to content

Support [build].watch for theme app extensions#7392

Open
erikmay wants to merge 1 commit intoShopify:mainfrom
erikmay:feat/theme-extension-build-watch
Open

Support [build].watch for theme app extensions#7392
erikmay wants to merge 1 commit intoShopify:mainfrom
erikmay:feat/theme-extension-build-watch

Conversation

@erikmay
Copy link
Copy Markdown

@erikmay erikmay commented Apr 24, 2026

Summary

Theme app extensions currently use the base BaseSchema (which has no build field) and don't implement devSessionWatchConfig. As a result, any [build] block in shopify.extension.toml is silently stripped by zod, and the CLI's dev watcher falls back to globbing **/* under the extension directory.

This is painful when an external build tool (Vite, esbuild, tsc --watch) writes multiple output files per build — each write fires an "Extension changed" event, triggering redundant theme-check and bundle cycles. A single manual bun run theme-extension:build during shopify app dev can produce 8+ "Extension changed" events and 2 full bundle cycles.

This PR makes the theme spec support [build].watch the same way function extensions already do:

  • Extend BaseSchema into ThemeExtensionSchema with build: { watch: string | string[] }.
  • Add devSessionWatchConfig to the theme spec that restricts watched paths to build.watch + locales/**.json + **.toml.

Motivation

Authors who use a separate source directory + build tool (common when writing Tailwind, TypeScript, or templated Liquid that compiles into blocks/) have no way to tell the CLI "only rebundle when the build is fully done." With this change, they can point the CLI at a sentinel file written last by their build tool:

name = "my-theme-extension"
type = "theme"
uid = "..."

[build]
watch = [".build-stamp"]

and get exactly one bundle cycle per build.

Changes

  • packages/app/src/cli/models/extensions/specifications/theme.ts: extend schema with build.watch, add devSessionWatchConfig.
  • Added a changeset (@shopify/app patch).

Test plan

  • Theme app extension without [build] in its toml — dev watcher behaves exactly as before (watches whole extension dir).
  • Theme app extension with [build] watch = [".build-stamp"] and an external build tool that touches .build-stamp last — shopify app dev only triggers one bundle cycle per build, instead of one per file write.
  • Theme app extension with [build] watch = ["src/**"] — only changes under src/ trigger the watcher.
  • locales/**.json and **.toml still trigger bundle cycles when build.watch is set (parity with function extensions).

Theme extensions currently use the base `dd` schema which has no `build`
field, and don't implement `devSessionWatchConfig`. Any `[build]` block
in `shopify.extension.toml` is silently stripped by zod, and the CLI's
dev watcher falls back to globbing `**/*` under the extension directory.

That cascade is painful when an external build tool (Vite, esbuild,
tsc --watch) writes multiple output files per build — each write fires
"Extension changed", triggering redundant theme-check and bundle cycles.

This mirrors the function extension pattern:

- Extend `BaseSchema` into `ThemeExtensionSchema` with
  `build: { watch: string | string[] }`.
- Add `devSessionWatchConfig` to the theme spec that restricts watched
  paths to `build.watch` + `locales/**.json` + `**.toml` (dropping the
  `**/!(.)*.graphql` entry from the function version since theme
  extensions don't use GraphQL).

With this change, authors can point the CLI at a sentinel file written
by their build tool after all outputs are on disk — one CLI trigger per
full build instead of N.
@erikmay erikmay requested review from a team as code owners April 24, 2026 08:51
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