Search code examples
vue.jsviteshopify-app

How to set Vite config for dynamic imports (code-splitting) when entry file and chunks are on a different domain?


I'm looking for help to resolve some code-splitting issues in a unique deployment setup (Vue + Vite + Shopify app).

Background and setup

  • I have a Vue app that dynamically loads and renders blocks of content (like from a CMS) on a page. So for example, if the page includes a carousel block and text block, there’s a <component :is=“…” /> for each block type.
  • There are several block types and I don’t want to put them in a single bundle so I have some code splitting on block SFCs. So I have something like this in my entry file to async import those globally…
blockTypes.forEach((blockType) => {
  app.component(
    blockType,
    defineAsyncComponent(() => import(`./blocks/${blockType}.vue`))
  )
})

So far, this is all fairly straightforward when I have complete control over the deployment.

Enter Shopify development

This is where it gets tricky.

The Vue application is deployed as a Shopify theme extension block for a Shopify app. The main thing that’s relevant here is that Shopify handles the deployment and sets some constraints.

  1. All assets must be in a flat /assets/ folder (I have this part working with rollupOptions in the vite config)
rollupOptions: {
    input: './src/main.ts',
    output: {
      entryFileNames: 'assets/genesis-block.js',
      chunkFileNames: 'assets/[name]-[hash].js',
      assetFileNames: 'assets/block.[ext]'
  }
}

  1. The domain for the HTML is [some-store].myshopify.com; but the assets are put on a CDN cdn.shopify.com/extensions/[theme_extension_uuid]/assets/* (and the theme_extension_uuid is not known at build time).

  2. Also, the asset URL includes a cache buster param when injected in the html, like this… cdn.shopify.com/extensions/[theme_extension_uuid]/assets/index.js?v=1726188069

Now when loading the page... things get weird:

  • script tag loads the entry file (with cache buster param) — this loads, of course
  • then the entry file tries to load a chunk, but from the HTML domain (not the CDN), this 404s
  • but somehow the entry file keeps going and loads the same chunk, but now from the CDN domain — this works 🤷
  • now the chunk itself references the entry file, but without the cache buster param — so this works, but requests the entry file again (not cached)
  • and it looks like there are pairs of 404 + 200 responses for each chunk

Worse, for CSS chunks

  • the first chunk loads correctly (because it's specified in the HTML)
  • but for the dynamically loaded CSS, these are ONLY requested from the HTML domain — so only 404s for these

I get a mess of console errors for each 404, and a lot of red in the network tab.

network tab (entry file is 'gen-block.js')

How to configure for this case?

Is this a vite or @vitejs/plugin-vue concern, or something I should solve for with rollup options? I’m not sure how to move forward.

I also put this question in the vite repo discussion


It works fine when running the app locally. The main issue is when deployed on a different domain (with a path not known at build time) as the window location.

I've tried different ways of splitting code with defineAsyncComponent, and different rollupOptions in vite config.


Solution

  • The solution was to use Vite's renderBuildUrl feature. It took some trial and error with the code outlined here, but looks like I got it working for both JS and CSS chunks.

    // vite.config
    experimental: {
      renderBuiltUrl(filename, { hostType }) {
        if (hostType === 'js') {
          return {
            runtime: `window.__toCdnUrl(${JSON.stringify(filename)})`
          }
        } else {
          return { relative: true }
        }
      }
    },
    

    Then to resolve the cache busting issue with the entry file, I created a simple proxy module to get the CDN URL at runtime and import the entry file. So this proxy is loaded from the script tag instead of the entry file.

    const loaderPathFile = 'assets/gen-lp-loader.js'
    const loaderSrc = document.querySelector('#MainContent script').src
    const [shopifyCdnUrl] = loaderSrc.split(loaderPathFile)
    window.__toCdnUrl = (filename) => {
      return `${shopifyCdnUrl}${filename}`
    }
    
    // entry file
    import './gen-block.js'
    

    Additional context here: https://github.com/vitejs/vite/discussions/18097