You're absolutely right to question that. I did test in Incognito (and cleared LiteSpeed/Cloudflare cache), and here's what I observed:
On a fresh visit to the homepage → product.jsdoes NOT load
On a fresh visit to a product page → product.jsloads correctly
So far so good your expectation matches reality.
BUT — the confusion came from LiteSpeed’s ESI (Edge Side Includes) + Cloudflare APO. When APO is enabled, it caches the entire HTML response per URL, including enqueued scripts. My worry was:
"If the first cached version of /shop/ (an archive) was served to a visitor who then clicked into a product, would the archive page now have the script baked in for everyone?"
Turns out: No. WordPress still runs wp_enqueue_scripts on every request, even when serving a cached page — because the cache is at the HTML level, but PHP (and thus is_product()) runs before the cached HTML is sent if the cache is bypassed or invalidated.
LiteSpeed + APO only cache static HTML, but script enqueues happen in PHP, so they’re evaluated per request. The <script> tag only appears when is_product() is true.
So my original code is actually safe and cache-friendly.
Lesson learned: I overthought it. wp_enqueue_scripts + is_product() is the best practice.
Thanks for pushing me to double-check - this saved me from a complicated workaround!