Photo by Yancy Min on Unsplash

Getting Webpack 5/Laravel-Mix public path dynamically set to your Shopify assets folder (Vue.js/Others)

If you are using Webpack 5.x with ES6 imports to dynamically load modules for Vue.js/React.js components or similar optionally with Laravel-Mix then you may need to dynamically set the Webpack import path to the Shopify CDN asset_url folder, so that Webpack can find your dynamic module .js chunked files.

The problem

When you use Webpack to generate chunked files or multiple bundle files using a standard setup Webpack would usually output your chunked files to assets/app/<filename>.js. When working with Shopify this will not be allowed to be uploaded to their CDN using Theme Kit. It will fail with a subfolders not allowed error. You must generate the chunks in the same directory as app.js, the root assets folder. You can do it in Mix using the following in the webpack.mix.js:

mix.webpackConfig({
output: {
chunkFilename: 'assets/[name].chunk.[chunkhash:5].js'
}
});

This will then allow you to do the following command to upload all of the chunks and your entry point & library files as well:

theme deploy assets/app.js assets/manifest.js assets/vendor.js assets/*chunk*.js -e=<store theme>

Once they are on the Shopify CDN, Webpack needs to know the exact URL and complete path to that folder, without that Webpack will look for the chunked files in the default root path of the Shopify store, not the full asset_url path that Shopify needs us to use.

The Solution

Using a Liquid snippet we can get the value and pass it to Webpack at the right point.

Create a snippet in your theme’s snippets folder called webpack-public-path.liquid and paste in the following, this will allow us to set a global JavaScript variable on the window object with the value being the correct path:

{% capture asset_path %}{{ 'app.js' | asset_url }}{% endcapture %}
{% assign webpack_path = asset_path | split: 'app.js' | first | replace: 'assets/', '' %}
<script type="text/javascript">
// This will set the path into the global JavaScript scope so it can be picked up further down the path of execution. window.__webpack_public_path__ = '{{ webpack_path }}';
</script>

This will use your existing app.js asset url and then strip away the path so that Webpack can add assets/chunk-name.js to the end of the URL to include your async components or other dynamically loaded data:

//cdn.shopify.com/s/files/1/xxxx/xxxx/xxxx/x/xx/

Once Webpack modifies the URL to load in any further imports, it would become:

//cdn.shopify.com/s/files/1/xxxx/xxxx/xxxx/x/xx/assets/chunk.js 

Next you need to tell Webpack that it needs to look in the correct place for the value before importing its dependancies. If you are using the same Laravel-Mix setup as I am, then it would need to be defined in the top of app.js.

Updating The Entry Point

You need to ensure it is at the top of all the lines in the file, due to the way this is carried out Webpack recommends you create a file called public-path.js in the same folder as your app.js and then include it as follows:

import "./public-path";
import Vue from 'Vue';
import _ from 'lodash-es';
// Using an anonymous function you can split the components out into // the chunked files, only loading the components in use on the page // being rendered, increasing performance both on the browser and
// the network.
Vue.component('test', () => import('./test.vue'));

Setting The Public Path Variable

The contents of the public-path.js file just needs to be this one line which grabs the value we set earlier in the page’s execution:

__webpack_public_path__ = window.__webpack_public_path__;

Once you have completed this, you need to add a render tag to theme.liquid to load up your Liquid snippet at the right time, above the entry point of your Webpack bundles.

<head>{% render 'meta' %}{% render 'webpack-public-path' %}{{ content_for_header }} </head><body><script src="{{ 'manifest.js' | asset_url }}" defer></script>
<script src="{{ 'vendor.js' | asset_url }}" defer></script>
<script src="{{ 'app.js' | asset_url }}" defer></script>
</body>

Now Webpack should be able to find all of your async loading chunks in the correct asset path folder. You can check the value yourself if required by adding this line temporarily to the top of the public-path.js file below the assignment of the variable. It should then show up in your browser console when visiting your store.

__webpack_public_path__ = window.__webpack_public_path__;// Make sure to remove this line afterwards.
console.log(__webpack_public_path__);

Finishing Up (Dynamic Imports)

Next, if you have not yet changed all of your library, dependancy & internal imports to use async loading, then you may need to setup the correct Babel plugins as well, you can do this by installing these packages:

npm i --save @babel/preset-env@latest @babel/plugin-syntax-dynamic-import@latest

Then create or edit your .babelrc configuration file to define the plugins in the settings used.

{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}

Laravel-Mix & Webpack should now use these Babel plugins to poly-fill the feature so that it works on all modern browsers, as its a bleeding edge feature of JavaScript at the moment. You should see an increase in performance and a dramatic reduction of the entry point file in favour of the many split-chunk files that you can now use on your theme.

Some libraries you use like jQuery, Lodash and others will possibly have different syntax for ES6 style importing or they will have separate packages for their ES6 versions, like lodash-es on npm.

So your require statements for these modules can now be changed to import statements, install the lodash-es package from npm and then change:

var _ = require('lodash');

To:

import _ from 'lodash-es';

A Note On Uploading (Shopify Theme Kit)

When using Shopify Theme Kit to deploy your theme files and assets, you need to now remember instead of being able to just execute:

theme deploy assets/app.js assets/manifest.js assets/vendor.js -e=<store theme>

You now have to make sure that all chunks get uploaded everytime you run npm run prod, using the following wildcard will help you upload all your Webpack files once done, without potentially doing *.js and catching non-Webpack JavaScript assets:

theme deploy assets/app.js assets/*chunk*.js assets/manifest.js assets/vendor.js -e=<store theme>

Versions of software used:

  • Webpack: 5.x
  • Laravel-Mix: 6.x
  • Babel: 7.x
  • Shopify Theme Kit: 1.1.x

References / Docs:

Software/Web Development with data science, multiple disciplines and cynicism thrown in.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store