diff --git a/.changeset/silly-crews-smile.md b/.changeset/silly-crews-smile.md
new file mode 100644
index 00000000..ba54a755
--- /dev/null
+++ b/.changeset/silly-crews-smile.md
@@ -0,0 +1,5 @@
+---
+"vite-plugin-shopify": minor
+---
+
+Add snippetAssetFile option for URL-only output
diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md
index a8946996..56eee13c 100644
--- a/docs/guide/configuration.md
+++ b/docs/guide/configuration.md
@@ -51,6 +51,13 @@ Additional files to use as entry points (accepts an array of file paths or glob
Specifies the file name of the snippet that loads your assets.
+## snippetAssetFile
+
+- **Type:** `boolean | string`
+- **Default:** `false`
+
+This snippet outputs the URL for the given entrypoint.
+
## versionNumbers
- **Type:** `boolean`
diff --git a/examples/vite-shopify-example/.gitignore b/examples/vite-shopify-example/.gitignore
index b25ba532..696daa29 100644
--- a/examples/vite-shopify-example/.gitignore
+++ b/examples/vite-shopify-example/.gitignore
@@ -1,2 +1,3 @@
/assets
/snippets/vite.liquid
+/snippets/vite-asset.liquid
diff --git a/examples/vite-shopify-example/layout/theme.liquid b/examples/vite-shopify-example/layout/theme.liquid
index 2c32d035..dfb79eab 100755
--- a/examples/vite-shopify-example/layout/theme.liquid
+++ b/examples/vite-shopify-example/layout/theme.liquid
@@ -11,8 +11,9 @@
+
{%- liquid
- render 'vite' with 'theme.scss', preload_stylesheet: true
+ render 'vite' with 'theme.scss'
render 'vite' with 'theme.ts'
render 'vite' with '@/foo.ts'
render 'vite' with '~/bar.ts'
diff --git a/examples/vite-shopify-example/vite.config.ts b/examples/vite-shopify-example/vite.config.ts
index 92d5bd52..0e984808 100644
--- a/examples/vite-shopify-example/vite.config.ts
+++ b/examples/vite-shopify-example/vite.config.ts
@@ -16,6 +16,7 @@ export default defineConfig({
shopify({
tunnel: true,
snippetFile: 'vite.liquid',
+ snippetAssetFile: 'vite-asset.liquid',
additionalEntrypoints: [
'frontend/foo.ts', // relative to sourceCodeDir
'frontend/bar.ts',
diff --git a/packages/vite-plugin-shopify/README.md b/packages/vite-plugin-shopify/README.md
index df834067..52460b86 100644
--- a/packages/vite-plugin-shopify/README.md
+++ b/packages/vite-plugin-shopify/README.md
@@ -44,6 +44,8 @@ export default {
additionalEntrypoints: [],
// Specifies the file name of the snippet that loads your assets
snippetFile: 'vite-tag.liquid',
+ // This snippet outputs the URL for the given entrypoint
+ snippetAssetFile: false,
// Specifies whether to append version numbers to your production-ready asset URLs in `snippetFile`
versionNumbers: false,
// Enables the creation of Cloudflare tunnels during dev, allowing previews from any device
@@ -127,6 +129,8 @@ You can pass the `preload_stylesheet` variable to the `vite-tag` snippet to enab
{% render 'vite-tag' with 'theme.scss', preload_stylesheet: true %}
```
+Alternatively, use `snippetAssetFile: 'vite-asset.liquid'` to get just the URL: `{% render 'vite-asset', entry: 'theme.scss' %}`
+
### Import aliases
For convenience, `~/` and `@/` are aliased to your `frontend` folder, which simplifies imports:
diff --git a/packages/vite-plugin-shopify/src/constants.ts b/packages/vite-plugin-shopify/src/constants.ts
index 19f58d58..288d0e0d 100644
--- a/packages/vite-plugin-shopify/src/constants.ts
+++ b/packages/vite-plugin-shopify/src/constants.ts
@@ -17,3 +17,5 @@ export const CSS_EXTENSIONS_REGEX = new RegExp(
export const hotReloadScriptId = 'hot-reload-client'
export const hotReloadScriptUrl = 'https://cdn.jsdelivr.net/npm/@shopify/theme-hot-reload/dist/theme-hot-reload.min.js'
+
+export const snippetAssetFile = 'vite-asset.liquid'
diff --git a/packages/vite-plugin-shopify/src/html.ts b/packages/vite-plugin-shopify/src/html.ts
index 6d798ea9..9a76b237 100644
--- a/packages/vite-plugin-shopify/src/html.ts
+++ b/packages/vite-plugin-shopify/src/html.ts
@@ -6,7 +6,7 @@ import createDebugger from 'debug'
import startTunnel from '@shopify/plugin-cloudflare/hooks/tunnel'
import { renderInfo, isTTY } from '@shopify/cli-kit/node/ui'
-import { CSS_EXTENSIONS_REGEX, KNOWN_CSS_EXTENSIONS, hotReloadScriptId, hotReloadScriptUrl } from './constants'
+import { CSS_EXTENSIONS_REGEX, KNOWN_CSS_EXTENSIONS, hotReloadScriptId, hotReloadScriptUrl, snippetAssetFile } from './constants'
import type { Options, DevServerUrl, FrontendURLResult } from './types'
import type { TunnelClient } from '@shopify/cli-kit/node/plugins/tunnel'
@@ -21,8 +21,20 @@ export default function shopifyHTML (options: Required): Plugin {
const viteTagSnippetPath = path.resolve(options.themeRoot, `snippets/${options.snippetFile}`)
const viteTagSnippetName = options.snippetFile.replace(/\.[^.]+$/, '')
- const viteTagSnippetPrefix = (config: ResolvedConfig): string =>
- viteTagDisclaimer + viteTagEntryPath(config.resolve.alias, options.entrypointsDir, viteTagSnippetName)
+ const viteTagSnippetPrefix = (config: ResolvedConfig, snippetName = viteTagSnippetName): string =>
+ viteTagDisclaimer + viteTagEntryPath(config.resolve.alias, options.entrypointsDir, snippetName)
+
+ const resolvedSnippetAssetFile = typeof options.snippetAssetFile === 'string'
+ ? options.snippetAssetFile
+ : options.snippetAssetFile
+ ? snippetAssetFile
+ : ''
+ const viteAssetSnippetPath = resolvedSnippetAssetFile
+ ? path.resolve(options.themeRoot, `snippets/${resolvedSnippetAssetFile}`)
+ : ''
+ const viteAssetSnippetName = resolvedSnippetAssetFile
+ ? path.parse(resolvedSnippetAssetFile).name
+ : ''
return {
name: 'vite-plugin-shopify-html',
@@ -76,8 +88,14 @@ export default function shopifyHTML (options: Required): Plugin {
tunnelUrl, options.entrypointsDir, reactPlugin, options.themeHotReload
)
+ const viteAssetSnippetContent = viteAssetSnippetName && (viteTagSnippetPrefix(config, viteAssetSnippetName) + viteAssetSnippetDev(
+ tunnelUrl, options.entrypointsDir
+ ))
+
// Write vite-tag with a Cloudflare Tunnel URL
fs.writeFileSync(viteTagSnippetPath, viteTagSnippetContent)
+
+ viteAssetSnippetContent && fs.writeFileSync(viteAssetSnippetPath, viteAssetSnippetContent)
})()
}, 100)
@@ -87,8 +105,14 @@ export default function shopifyHTML (options: Required): Plugin {
: viteDevServerUrl, options.entrypointsDir, reactPlugin, options.themeHotReload
)
+ const viteAssetSnippetContent = viteAssetSnippetName && (viteTagSnippetPrefix(config, viteAssetSnippetName) + viteAssetSnippetDev(
+ frontendUrl !== '' ? frontendUrl : viteDevServerUrl, options.entrypointsDir
+ ))
+
// Write vite-tag snippet for development server
fs.writeFileSync(viteTagSnippetPath, viteTagSnippetContent)
+
+ viteAssetSnippetContent && fs.writeFileSync(viteAssetSnippetPath, viteAssetSnippetContent)
}
})
@@ -125,6 +149,7 @@ export default function shopifyHTML (options: Required): Plugin {
}
const assetTags: string[] = []
+ const assetUrls: string[] = []
const manifest = JSON.parse(
fs.readFileSync(manifestFilePath, 'utf8')
) as Manifest
@@ -174,18 +199,24 @@ export default function shopifyHTML (options: Required): Plugin {
}
assetTags.push(viteEntryTag(entryPaths, tagsForEntry.join('\n '), assetTags.length === 0))
+ assetUrls.push(viteEntryTag(entryPaths, `{{ ${assetUrl(file, options.versionNumbers)} }}`, assetUrls.length === 0))
}
// Generate entry tag for bundled "style.css" file when cssCodeSplit is false
if (src === 'style.css' && !config.build.cssCodeSplit) {
assetTags.push(viteEntryTag([src], stylesheetTag(file, options.versionNumbers), false))
+ assetUrls.push(viteEntryTag([src], `{{ ${assetUrl(file, options.versionNumbers)} }}`, false))
}
})
const viteTagSnippetContent = viteTagSnippetPrefix(config) + assetTags.join('\n') + '\n{% endif %}\n'
+ const viteAssetSnippetContent = viteAssetSnippetName && viteTagSnippetPrefix(config, viteAssetSnippetName) + assetUrls.join('\n') + '\n{% endif %}\n'
+
// Write vite-tag snippet for production build
fs.writeFileSync(viteTagSnippetPath, viteTagSnippetContent)
+
+ viteAssetSnippetContent && fs.writeFileSync(viteAssetSnippetPath, viteAssetSnippetContent)
}
}
}
@@ -245,6 +276,19 @@ const scriptTag = (fileName: string, versionNumbers: boolean): string =>
const stylesheetTag = (fileName: string, versionNumbers: boolean): string =>
`{{ ${assetUrl(fileName, versionNumbers)} | stylesheet_tag: preload: preload_stylesheet }}`
+// Generate vite-asset snippet for development
+const viteAssetSnippetDev = (assetHost: string, entrypointsDir: string): string =>
+ `{% liquid
+ assign path_prefix = path | slice: 0
+ if path_prefix == '/'
+ assign file_url_prefix = '${assetHost}'
+ else
+ assign file_url_prefix = '${assetHost}/${entrypointsDir}/'
+ endif
+ assign file_url = path | prepend: file_url_prefix
+ echo file_url
+%}`
+
// Generate vite-tag snippet for development
const viteTagSnippetDev = (assetHost: string, entrypointsDir: string, reactPlugin: Plugin | undefined, themeHotReload: boolean): string =>
`{% liquid
diff --git a/packages/vite-plugin-shopify/src/options.ts b/packages/vite-plugin-shopify/src/options.ts
index 7786caab..911fe5b5 100644
--- a/packages/vite-plugin-shopify/src/options.ts
+++ b/packages/vite-plugin-shopify/src/options.ts
@@ -10,6 +10,7 @@ export const resolveOptions = (
const entrypointsDir = options.entrypointsDir ?? normalizePath(path.join(sourceCodeDir, 'entrypoints'))
const additionalEntrypoints = options.additionalEntrypoints ?? []
const snippetFile = options.snippetFile ?? 'vite-tag.liquid'
+ const snippetAssetFile = options.snippetAssetFile ?? false
const versionNumbers = options.versionNumbers ?? false
const tunnel = options.tunnel ?? false
const themeHotReload = options.themeHotReload ?? true
@@ -20,6 +21,7 @@ export const resolveOptions = (
entrypointsDir,
additionalEntrypoints,
snippetFile,
+ snippetAssetFile,
versionNumbers,
tunnel,
themeHotReload
diff --git a/packages/vite-plugin-shopify/src/types.ts b/packages/vite-plugin-shopify/src/types.ts
index 181c23a9..3b8d1640 100644
--- a/packages/vite-plugin-shopify/src/types.ts
+++ b/packages/vite-plugin-shopify/src/types.ts
@@ -34,6 +34,13 @@ export interface Options {
*/
snippetFile?: string
+ /**
+ * This snippet outputs the URL for the given entrypoint.
+ *
+ * @default false
+ */
+ snippetAssetFile?: boolean | string
+
/**
* Specifies whether to append version numbers to your production-ready asset URLs in {@link snippetFile}.
*
diff --git a/packages/vite-plugin-shopify/test/__fixtures__/snippets/vite-asset.liquid b/packages/vite-plugin-shopify/test/__fixtures__/snippets/vite-asset.liquid
new file mode 100644
index 00000000..90cf9bb6
--- /dev/null
+++ b/packages/vite-plugin-shopify/test/__fixtures__/snippets/vite-asset.liquid
@@ -0,0 +1,15 @@
+{% comment %}
+ IMPORTANT: This snippet is automatically generated by vite-plugin-shopify.
+ Do not attempt to modify this file directly, as any changes will be overwritten by the next build.
+{% endcomment %}
+{% liquid
+ assign entry = entry | default: vite-asset
+ assign path = entry | replace: '@@/', '../../resources/js/' | replace: '~/', '../' | replace: '@/', '../'
+%}
+{% if path == "/test/__fixtures__/frontend/entrypoints/customers.js" or path == "customers.js" %}
+ {{ 'customers-MM9Bv3NP.js' | asset_url }}
+{% elsif path == "/test/__fixtures__/frontend/entrypoints/theme.css" or path == "theme.css" %}
+ {{ 'theme-y6Yj_vm2.css' | asset_url }}
+{% elsif path == "/test/__fixtures__/frontend/entrypoints/theme.js" or path == "theme.js" %}
+ {{ 'theme-Df-cUSaP.js' | asset_url }}
+{% endif %}
diff --git a/packages/vite-plugin-shopify/test/__snapshots__/html.test.ts.snap b/packages/vite-plugin-shopify/test/__snapshots__/html.test.ts.snap
index e187c17e..50dc89a4 100644
--- a/packages/vite-plugin-shopify/test/__snapshots__/html.test.ts.snap
+++ b/packages/vite-plugin-shopify/test/__snapshots__/html.test.ts.snap
@@ -184,3 +184,24 @@ exports[`vite-plugin-shopify:html > builds out .liquid files for development wit
{% endif %}
"
`;
+
+exports[`vite-plugin-shopify:html > builds out vite-asset snippet for development 1`] = `
+"{% comment %}
+ IMPORTANT: This snippet is automatically generated by vite-plugin-shopify.
+ Do not attempt to modify this file directly, as any changes will be overwritten by the next build.
+{% endcomment %}
+{% liquid
+ assign entry = entry | default: vite-asset
+ assign path = entry | replace: '~/', '../' | replace: '@/', '../' | replace: '@@/', '../../resources/js/'
+%}
+{% liquid
+ assign path_prefix = path | slice: 0
+ if path_prefix == '/'
+ assign file_url_prefix = 'http://localhost:5173'
+ else
+ assign file_url_prefix = 'http://localhost:5173/test/__fixtures__/frontend/entrypoints/'
+ endif
+ assign file_url = path | prepend: file_url_prefix
+ echo file_url
+%}"
+`;
diff --git a/packages/vite-plugin-shopify/test/__snapshots__/index.test.ts.snap b/packages/vite-plugin-shopify/test/__snapshots__/index.test.ts.snap
index 617268cc..38f2e219 100644
--- a/packages/vite-plugin-shopify/test/__snapshots__/index.test.ts.snap
+++ b/packages/vite-plugin-shopify/test/__snapshots__/index.test.ts.snap
@@ -68,3 +68,41 @@ exports[`vite-plugin-shopify > builds out .liquid files for production without m
{% endif %}
"
`;
+
+exports[`vite-plugin-shopify > builds out vite-asset snippet for production 1`] = `
+"{% comment %}
+ IMPORTANT: This snippet is automatically generated by vite-plugin-shopify.
+ Do not attempt to modify this file directly, as any changes will be overwritten by the next build.
+{% endcomment %}
+{% liquid
+ assign entry = entry | default: vite-asset
+ assign path = entry | replace: '@@/', '../../resources/js/' | replace: '~/', '../' | replace: '@/', '../'
+%}
+{% if path == "/test/__fixtures__/frontend/entrypoints/customers.js" or path == "customers.js" %}
+ {{ 'customers-MM9Bv3NP.js' | asset_url | split: '?' | first }}
+{% elsif path == "/test/__fixtures__/frontend/entrypoints/theme.css" or path == "theme.css" %}
+ {{ 'theme-y6Yj_vm2.css' | asset_url | split: '?' | first }}
+{% elsif path == "/test/__fixtures__/frontend/entrypoints/theme.js" or path == "theme.js" %}
+ {{ 'theme-Df-cUSaP.js' | asset_url | split: '?' | first }}
+{% endif %}
+"
+`;
+
+exports[`vite-plugin-shopify > builds out vite-asset snippet for production with version numbers 1`] = `
+"{% comment %}
+ IMPORTANT: This snippet is automatically generated by vite-plugin-shopify.
+ Do not attempt to modify this file directly, as any changes will be overwritten by the next build.
+{% endcomment %}
+{% liquid
+ assign entry = entry | default: vite-asset
+ assign path = entry | replace: '@@/', '../../resources/js/' | replace: '~/', '../' | replace: '@/', '../'
+%}
+{% if path == "/test/__fixtures__/frontend/entrypoints/customers.js" or path == "customers.js" %}
+ {{ 'customers-MM9Bv3NP.js' | asset_url }}
+{% elsif path == "/test/__fixtures__/frontend/entrypoints/theme.css" or path == "theme.css" %}
+ {{ 'theme-y6Yj_vm2.css' | asset_url }}
+{% elsif path == "/test/__fixtures__/frontend/entrypoints/theme.js" or path == "theme.js" %}
+ {{ 'theme-Df-cUSaP.js' | asset_url }}
+{% endif %}
+"
+`;
diff --git a/packages/vite-plugin-shopify/test/config.test.ts b/packages/vite-plugin-shopify/test/config.test.ts
index 850a8f77..b95c7d15 100644
--- a/packages/vite-plugin-shopify/test/config.test.ts
+++ b/packages/vite-plugin-shopify/test/config.test.ts
@@ -120,17 +120,20 @@ describe('resolveOptions', () => {
expect(options.entrypointsDir).toBe('frontend/entrypoints')
expect(options.additionalEntrypoints).toEqual([])
expect(options.snippetFile).toEqual('vite-tag.liquid')
+ expect(options.snippetAssetFile).toBe(false)
})
it('accepts a partial configuration', () => {
const options = resolveOptions({
themeRoot: 'shopify',
- sourceCodeDir: 'src'
+ sourceCodeDir: 'src',
+ snippetAssetFile: true
})
expect(options.themeRoot).toBe('shopify')
expect(options.sourceCodeDir).toBe('src')
expect(options.entrypointsDir).toBe('src/entrypoints')
+ expect(options.snippetAssetFile).toBe(true)
})
})
diff --git a/packages/vite-plugin-shopify/test/html.test.ts b/packages/vite-plugin-shopify/test/html.test.ts
index 19e4df78..1ea57a29 100644
--- a/packages/vite-plugin-shopify/test/html.test.ts
+++ b/packages/vite-plugin-shopify/test/html.test.ts
@@ -193,4 +193,31 @@ describe('vite-plugin-shopify:html', () => {
vi.useRealTimers()
})
+
+ it('builds out vite-asset snippet for development', async () => {
+ const options = resolveOptions({
+ themeRoot: 'test/__fixtures__',
+ sourceCodeDir: 'test/__fixtures__/frontend',
+ snippetAssetFile: 'vite-asset.liquid'
+ })
+
+ const { configureServer } = html(options)
+
+ const viteServer = await (
+ await createServer({
+ logLevel: 'silent',
+ configFile: path.join(__dirname, '__fixtures__', 'vite.config.js')
+ })
+ ).listen()
+
+ configureServer(viteServer)
+
+ viteServer.httpServer?.emit('listening')
+
+ const assetSnippet = await fs.readFile(path.join(__dirname, '__fixtures__', 'snippets', 'vite-asset.liquid'), { encoding: 'utf8' })
+
+ await viteServer.close()
+
+ expect(assetSnippet).toMatchSnapshot()
+ })
})
diff --git a/packages/vite-plugin-shopify/test/index.test.ts b/packages/vite-plugin-shopify/test/index.test.ts
index f1c00634..281ba6bf 100644
--- a/packages/vite-plugin-shopify/test/index.test.ts
+++ b/packages/vite-plugin-shopify/test/index.test.ts
@@ -69,4 +69,51 @@ describe('vite-plugin-shopify', () => {
expect(tagsHtml).toMatchSnapshot()
})
+
+ it('builds out vite-asset snippet for production', async () => {
+ await build({
+ logLevel: 'silent',
+ plugins: [
+ shopify({
+ themeRoot: path.join(__dirname, '__fixtures__'),
+ sourceCodeDir: path.join(__dirname, '__fixtures__', 'frontend'),
+ snippetFile: 'vite-tag.liquid',
+ snippetAssetFile: 'vite-asset.liquid'
+ })
+ ],
+ resolve: {
+ alias: {
+ '@@': normalizePath(path.resolve(path.join(__dirname, '__fixtures__', 'resources', 'js')))
+ }
+ }
+ })
+
+ const assetSnippet = await fs.readFile(path.join(__dirname, '__fixtures__', 'snippets', 'vite-asset.liquid'), { encoding: 'utf8' })
+
+ expect(assetSnippet).toMatchSnapshot()
+ })
+
+ it('builds out vite-asset snippet for production with version numbers', async () => {
+ await build({
+ logLevel: 'silent',
+ plugins: [
+ shopify({
+ themeRoot: path.join(__dirname, '__fixtures__'),
+ sourceCodeDir: path.join(__dirname, '__fixtures__', 'frontend'),
+ snippetFile: 'vite-tag.liquid',
+ snippetAssetFile: 'vite-asset.liquid',
+ versionNumbers: true
+ })
+ ],
+ resolve: {
+ alias: {
+ '@@': normalizePath(path.resolve(path.join(__dirname, '__fixtures__', 'resources', 'js')))
+ }
+ }
+ })
+
+ const assetSnippet = await fs.readFile(path.join(__dirname, '__fixtures__', 'snippets', 'vite-asset.liquid'), { encoding: 'utf8' })
+
+ expect(assetSnippet).toMatchSnapshot()
+ })
})