Code splitting (modular) of javascript file inside Theme app extension block

-Malek
Shopify Partner
6 0 4

Hello

I used 

npm init @shopify/app@latest

to create a Shopify node app, then I generated Theme App Extension usin

npm run generate extension

In my extension I did create a block called home-page.liquid under /blocks and I created main.js and main.css under /assets folder
Here is my architecture

 

Capture d’écran 2022-12-28 152509.png

 

Problem:
There are so many lines of code in main.js and main.css. so I tried splitting the code into multiple files using:

  1. module bundler (vite/webpack) but I could not because the theme app extension does not allow you to add custom folders or files other than its architecture check this docs
  2. using modular programming directly by splitting code into multiple javascript files but when
    I import files it does not work, here is an example of using helper.js file

Capture d’écran 2022-12-28 153711.png

 

And then I import it into main.js file

Capture d’écran 2022-12-28 154109.png

 

Is there a solution to code splitting?
Also to implement Typescript


Thank you in advance

 

Replies 10 (10)

mitchelldirt
Shopify Partner
9 1 4

Bit of a late response here, but I figure this could be useful for anyone else checking this post out.

 

I struggled with this same issue and recently decided to use rollup js to bundle up my js files (you could also implement ts though in my case I didn’t).

 


After figuring it out I made a blog post so that others would be able to do something similar 🙂 you could probably do something similar with vite and then have it build to your extensions folder every time you save, but I didn’t end up doing that since it was just complicating what already worked.

 

 Blog Post: https://www.mitchellmudd.dev/blog/implementing-code-splitting-shopify-theme-extensions-with-rollup 

 

Hope this helps someone 😊

Web Developer
wristbands
Shopify Partner
20 3 8

Thank you Mitchelldirt, this is exactly what I needed! My only critique for your tutorial is that you have to add "type": "module" to the package.json file in order for it to compile with `npm run build`.

 

I wish it was possible to "natively" use multiple javascript files in a theme app extension, but I'm glad I was able to use your workaround (it was the only workaround I could find!).

mitchelldirt
Shopify Partner
9 1 4

Ahhhhh yes - forgot to add that when I was writing the blog post. I updated the post and the linked source code 🙂 

 

Thanks for letting me know and I'm glad that I was able to help you out!

Web Developer
wristbands
Shopify Partner
20 3 8

Hey @mitchelldirt, I had a follow-up question. How do I update the rollup.config.js file to work with multiple app blocks? Like I want to give each app block its own corresponding javascript file, instead of bundling everything into a single javascript file called "app.js" as it's doing currently.

wristbands
Shopify Partner
20 3 8

I've figured out a solution that works with multiple app blocks. I edited the rollup.config.js and <app-block-name>.liquid files slightly and am using the SystemJS loader.

 

First, create your new app block and corresponding JavaScript file. For the sake of this tutorial, let's call the new app block "block2.liquid" and corresponding JavaScript file "app2.js".

 

Then, edit your rollup.config.js to look like:

 

import commonjs from "@rollup/plugin-commonjs";
import copy from "rollup-plugin-copy";

const input = {
  app: "./assets/app.js",
  app2: "./assets/app2.js",
};

export default {
  input,
  output: {
    dir: "../../extensions/code-splitting-example/assets",
    format: "system",
  },
  plugins: [
    commonjs(),
    copy({
      targets: [
        { src: "./assets/*.css", dest: "../../extensions/code-splitting-example/assets" },
        { src: "./assets/system.js", dest: "../../extensions/code-splitting-example/assets" },
        {
          src: "./blocks/*.liquid",
          dest: "../../extensions/code-splitting-example/blocks",
        },
      ],
    }),
  ],
};

 

You can see I made 3 changes: I added the path to the new JavaScript file associated with my second app block ("./assets/app2.js), I set the output format to "system" so that it uses the SystemJS format, and I added a copy target which copies a new "system.js" file to my "extensions" directory.

 

To download this new system.js file, go to the SystemJS Readme - Overview, and download the loader of your choosing. I chose the regular system.js loader (option 2). Then, rename the downloaded file to "system.js", and place it in the folder "dev-extensions/code-splitting-example/assets".

 

In each of your <app-block-name>.liquid files, paste the following code at the top (replace "app2.js" in the code below with whatever you called the JavaScript file associated with your given app block):

 

<script src="{{ 'system.js' | asset_url }}" defer></script>
<script type="systemjs-module" src="{{ 'app2.js' | asset_url }}" defer></script>

 

 

After making the above changes, running `npm run build` in the "dev-extensions/code-splitting-example" directory should generate files in a format that still works with Shopify Theme App Extensions, even when you have multiple app blocks.

twilson90
Shopify Partner
32 0 19

Hi,

I'm trying to set something similar up with my shopify theme but I've run into a snag when it comes to dynamic imports.

It's hard to explain, but basically I want a big chunk of code to load but only when the user clicks a button.

The only neat way of doing this with rollup and everything else is to use dynamic es imports in my source code, which gets converted into SystemJS.import()s.
However, Shopify likes to append a long string of numbers to version every time you upload an asset (ie, code).
SystemJS doesn't know this and tries to load the original version of the uploaded script, which has long since changed. Not only that but it also has a dependency check that causes it to also load the original uploaded version of the calling script!

chrome_48eDxRclKO.png

 

Those ?v= arguments are important.

Shopify never make it easy....

 

I could change it so rollup outputs the files using the default template `[name]-[hash].js`, but this doesn't address the issue of SystemJS attempting to load dependencies (app.system.js) because it doesn't recognize that 'app.system.js?v=38838934239889749121708712258' is the actual dependency and is already loaded.

 

Any ideas?

 

EDIT

 

The problem is a bit worse than I thought. forget dynamic imports.

I have a component called 'flipdown' which is embedded in its own section type.

In sections/flipdown.liquid:

 

 

 

<script type="systemjs-module" src="{{ "flipdown.system.js" | asset_url }}" defer></script>
{{ "flipdown.css" | asset_url | stylesheet_tag }}
<div data-component="timer" data-date-end="{{ date }}"></div>

 

 
But even this ends up downloading and running the original uploaded version of `app.system.js`

AlexAngelov
Shopify Partner
2 0 0

Whats up with this difficult stuff? Why should I install a bundler, ffs?
You have two files - lets call them main.js and helper.js
In helper.js you can define some helper functions and then exporting them:

 

export const numberFormat(number) => {
return `Number: ${number}`;
}

 

In main.js you call you first import the function:

 

import { numberFormat } from './helper.js';

 

Lastly, when you use the script tag, you must use it like so: (note the type="module")

 

<script type="module" src="{{ 'main.js' | asset_url }}" defer></script>

 


The css is similar, but easier.
You have the styles in main.css and other.css. Then in main.css:

 

@import url('./other.css');

 

 The referenced stylesheet in the schema should be of course main.css.

Please dont go confuse people with complicated solutions! This is basic java script and css in its finest! Of course, the bundler has advantages such as minification and obfuscation.
However, if you are going to do this, the chances are you have a big extension and then you can just use a framework, which at build outputs all its files in the asset folder.

wristbands
Shopify Partner
20 3 8

@AlexAngelov, you're right that it shouldn't be this complicated, but unfortunately it is. At least from my experience, your example code above does not work with Shopify theme app extensions. Myself and others have had to come up with these stupid workarounds in order to be able to separate our code into multiple files and reuse code.

 

But if you have any simpler solution that you've tested and verified that it works with theme app extensions, please do share it here!

AlexAngelov
Shopify Partner
2 0 0

I posted this because I literally implemented it a few hours ago. It works fine, and there is no reason it shouldn’t - because that’s how JavaScript works as of ECMA6. 
In my solution I have two files - one with the main function and one with shared functions - which I am using in the react admin and in the theme extension (hence why I needed the code split).

wristbands
Shopify Partner
20 3 8

@AlexAngelov Oh that is awesome to hear! Maybe they fixed the issue sometime after I experienced it about a month and a half ago? Cause I swear the code you posted is exactly what I first tried (cause you're right; that way of doing it should normally work). Maybe I'll try again to see if it's working for me now. Thanks for your reply and bringing this to my attention.