doc-site
Architecture

How the build works

How TypeScript and CSS in src become hashed assets that Liquid can load, and why vite-tag.liquid is generated.

How the build works

Liquid has no module system, and Shopify's assets/ directory is flat. Vite works around both by compiling src/ into hashed files in assets/ and generating the Liquid snippet that points at them.

A left to right pipeline: src/entrypoints is compiled by Vite, which writes hashed bundles into assets/, records them in vite-manifest.json, and rewrites vite-tag.liquid. A Liquid section renders vite-tag, which emits a script or stylesheet tag pointing at the Shopify CDN copy of the file.

The pieces

FileRole
src/entrypoints/**Every file here becomes a separately loadable bundle
vite.config.jsVite configuration, including the Shopify plugin and a cleanup plugin
assets/*-XXXXXXXX.js and .cssCompiled output, committed to git
assets/vite-manifest.jsonIndex of the build, written by Vite
snippets/vite-tag.liquidGenerated lookup table from entrypoint path to hashed file
src/entrypoints/theme.styles.cssTailwind output, produced by PostCSS before Vite runs

Only files directly under src/entrypoints/ and its subdirectories become entrypoints. Entrypoints import files from src/components/, src/lib/ and src/@types/, and those files never load on their own.

What npm run build does

npm run build

It runs two steps in order.

First npm run build:css runs PostCSS over src/assets/theme_in.css, which is three Tailwind directives, and writes the result to src/entrypoints/theme.styles.css. Tailwind scans ./**/*.liquid and ./src/**/*.{js,ts,jsx,tsx} for class names, so a class used only in Liquid is still generated.

Then vite build compiles every entrypoint. Before it starts, a cleanup plugin reads the previous assets/vite-manifest.json and deletes each hashed file it lists, so assets/ never accumulates old bundles. After the bundle is written, the same plugin sweeps assets/ again for orphaned files whose name matches a current output's hash pattern.

vite-plugin-shopify writes the manifest and rewrites snippets/vite-tag.liquid.

How Liquid loads a bundle

A Liquid file asks for an entrypoint by its path relative to src/entrypoints/:

{%- render 'vite-tag' with 'mini-cart/elements/mini-cart.ts' -%}
{%- render 'vite-tag' with 'mini-cart/elements/mini-cart.css' -%}

vite-tag.liquid is a long chain of elsif branches. Each one matches an entrypoint path and emits the right tag, plus modulepreload links for that bundle's shared chunks:

{% elsif path == '/src/entrypoints/cart-reward-banner.ts' or path == 'cart-reward-banner.ts' %}
  <script src='{{ 'cart-reward-banner-Cd_n0SIT.js' | asset_url }}' type='module' crossorigin='anonymous'></script>
  <link rel='modulepreload' href='{{ 'core-D-K-kGrY.js' | asset_url }}' crossorigin='anonymous'>

asset_url turns the filename into a Shopify CDN URL. The browser never sees the repository path.

Never edit snippets/vite-tag.liquid. The next build overwrites it, and your change is lost with no error.

Loading a bundle only where it is used

Most sections render vite-tag themselves rather than relying on the layout. A carousel section loads its own bundle, so a page without that carousel does not download it.

layout/theme.liquid loads only what every page needs: the Tailwind output, the theme stylesheet, the menu styles, and a few global behaviours.

Adding a section to a page adds its JavaScript to that page. That is intended. It is also why a bundle can work on the homepage and appear broken on a landing page that never rendered the section that loads it.

Development mode

npm run dev starts four processes under run-pty, defined in run-pty.json.

ProcessWhat it does
viteVite dev server on https://localhost:5173 with HTTPS and CORS
cssPostCSS in watch mode, rebuilding the Tailwind output
shopifyshopify theme dev on http://127.0.0.1:9292
tunnelA Cloudflare quick tunnel giving the Shopify preview an HTTPS URL

In development vite-plugin-shopify rewrites vite-tag.liquid to point at the Vite dev server instead of assets/, so hot module replacement works. This is why snippets/vite-tag.liquid shows up as modified in git status while the dev server runs. Run npm run build before committing to restore the production form.

The tunnel rewrites its origin Host header to 127.0.0.1:9292, because Shopify's local proxy rejects the public trycloudflare.com hostname with a JSON 400.

Why assets are committed

The build output is committed to git rather than generated during deployment.

A developer who pulls only the theme from Shopify still gets working, readable code, because the build does not minify JavaScript or CSS and source maps embed the original source. Someone who inherits this store without the repository can still read what is running.

The cost is that every build produces a diff in assets/. A pull request touching one entrypoint will show changes to several hashed files, and that is normal.

A pre-commit hook runs npm run build on every commit, so the committed output stays in step with src/. It is configured under simple-git-hooks in package.json.

Failure modes

SymptomCause
Feature does nothing, console shows a 404 for a hashed assetvite-tag.liquid was committed while it pointed at the dev server, or the build output was not committed
A change to src/ has no effect on the storefrontThe build was not run, or was run but not pushed
A hand edit to assets/ revertsThe build overwrote it. Change the source in src/ instead
Tailwind class missing in productionThe class is built dynamically, so Tailwind never saw the string. Add it to safelist in tailwind.config.cjs

Diagnosis steps are in Feature does nothing and the console shows a 404.

Source map

ConcernFile
Vite configuration and asset cleanupvite.config.js
Entrypointssrc/entrypoints/
Tailwind configurationtailwind.config.cjs
PostCSS pipelinepostcss.config.cjs
Tailwind entry filesrc/assets/theme_in.css
TypeScript configuration and path aliasestsconfig.json
Dev process definitionsrun-pty.json
Tunnel startupscripts/start-cloudflare-tunnel.mjs
Files excluded from theme pushes.shopifyignore

On this page