How to ensure css and js assets are not loaded multiple times?

Hi there,

I am a brand new Pinegrow user who signed up this week (coming from Cwicly), I have been learning Pinegrow by reading as much as possible and also watching lots of videos online (big shout out to Adam Lowe, amazing videos!), and I am now ready to start building my first custom blocks on a test site using the Pinegrow Wordpress plugin.

But one question I have which I have not seen answered anywhere (which may be my fault, maybe I have missed it in the documentation or on a video or maybe I just donā€™t understandā€¦), is regarding how to ensure that I do not load the same css or js libraries multiple times on one page. I will explain further:

  • I have seen some videos on youtube where ā€˜automatic.cssā€™ is used to style a custom block, and so the stylesheet for ACSS is loaded in the head of a page if we use that custom clock on a page. All good so farā€¦

But what happens when I develop a 2nd custom block, which also uses ACSS, and I want to use both of these blocks on the same page? This would then load ACSS twice in the head of the document, which I of course do not want as this will affect performance and may even cause a conflict.

  • Another example, lets say I want to use the Uikit framework (Installation - UIkit) to build some js components (lets say a slideshow) and make them into a custom block. And then I want to build an accordion custom block as well.

Uikit loads a css file in the and also needs a js script before the closing tag.

What happens if I want to use my slideshow block and my accordion block on the same page, I will then load 2 identical css files and 2 identical js files which will cause a conflict.

So, how does Pinegrow handle this situation? How can I prevent duplicate loading of css and / or js assets into a page?

Many thanks in advance for your help,

Keith

Here are few thoughts from my end on this topic, but in a non-wp context. There might be other solutions too.

Generally assets (js/css) files are cached, so ā€œdownloadingā€ twice is not really a concern, as the second call to the asset will generally be picked from the cache. So, loading two js files of 100kb doesnā€™t always end up ā€œdownloadingā€ 200kb (of course, there are always exceptional scenarios, but they are uncommon). Same applies to css files too. To beat this caching is why build tools like webpack, vite add a ā€œhashā€ to these assets.

The real concern is the ā€œre-executionā€ of the second redundant script tag, unless the script is protected against double-inclusion. For eg, jQuery doesnā€™t, function foo(){} in the script file will be executed twice, and might attach duplicate event listeners. The same concern for css, as the second style tag, will end up overriding the first one. So, avoiding double inclusion of js/lib files must be avoid for this reason, not necessary to save data download.

What you are attempting to do with two blocks is similar to the concepts of ā€œislandsā€, lately popularised by js frameworks like Astro & Iles. These modern frameworks use module scripts (type=ā€œmoduleā€) instead of the traditional classic scripts.

Module scripts are different to classic scripts:

  • Singleton (loaded only once, even if imported multiple times)
  • Deferred by default (executed after HTML content is parsed, so can be used anywhere within the document. Nice to co-locate them with itā€™s content.
  • Strict mode - all objects (let, const, var, function, class) are private unless explicitly exported.
  • Uses modern ES module syntax, can be broken down into smaller chunks and import/export nicely. No file protocol, so external module imports (src=ā€œ./my-module.jsā€) wonā€™t work if the page is opened directly in the browser, without a live-server.

For eg, if you add the below two islands to two blocks, and check your network tab in your browser, you will notice that the petite-vue will be downloaded & executed only once (singleton). Not sure if uikit provides a module script via cdn.

So, for javascript, you could use module scripts (type=ā€œmoduleā€), but I believe when using it with WP, you probably have to ā€œDo not enqueueā€ it as currently Pinegrow WP builder doesnā€™t differentiate module vs classic scripts and ignores the type (type=ā€œmoduleā€) when exporting theme/plugin.

With regards to how to avoid double css download & execution (cascading) in the WP block context, Iā€™m not sure about it, lets wait to hear more from the community on this topic. Obviously, double download of css import could be avoided by using javascript, but thatā€™s not very clean. And Iā€™m not very familiar with WP blocks world, so I better donā€™t say anything inaccurate.

Island-1:

    <script type="module" data-pg-name="Pt-App-Hero" id="hero-app">
      import { createApp } from 'https://unpkg.com/petite-vue?module'

      const state = {
        // state exposed to all expressions within v-scope regions
        count: 0,
        get oddOrEven() {
          return this.count % 2 === 0 ? 'even' : 'odd' // computed property
        },
        // methods
        increment() {
          this.count++
        },
        decrement() {
          this.count--
        },
      }

      createApp(state).mount('div#hero-island')
    </script>
    <div
      id="hero-island"
      data-pg-name="Pt-Island-Hero"
      v-scope="{}"
      style="
        padding: 20px;
        margin: 20px;
        border-radius: 4px;
        border-width: 2px;
        outline: 1px solid #cccccc;
      "
      data-pg-collapsed
    >
      <div
        style="
          display: flex;
          margin: 8px;
          padding: 8px;
          justify-content: center;
          align-items: center;
        "
      >
        <p style="text-align: center; width: 50%; min-width: 400px">
          Hello, I&apos;m within an island, and the root tag of the island
          <code>div#hero-island</code> has an exclusive app mounted by
          <code>script#hero-app</code>, and marked as a
          <code>v-scope</code> region. Any sprinkles of interactions within this
          <code>v-scope</code> region are managed by this exclusive app.
        </p>
      </div>
      <div
        style="
          display: flex;
          margin: 8px;
          padding: 8px;
          justify-content: center;
          align-items: center;
        "
      >
        <button
          v-on:click="decrement"
          style="margin-left: 4px; margin-right: 4px"
        >
          ā¬‡ļø
        </button>
        <button
          v-on:click="increment"
          style="margin-left: 4px; margin-right: 4px"
        >
          ā¬†ļø
        </button>
        <div v-cloak style="text-align: left; width: 180px; margin-left: 4px">
          <span>Count is: </span>
          <span style="width: 30px; display: inline-block; text-align: center"
            >{{count}}</span
          ><span v-cloak>({{oddOrEven}})</span>
        </div>
      </div>
    </div>

Island-2:

    <script type="module" data-pg-name="Pt-App-Feature" id="feature-app">
      import { createApp } from 'https://unpkg.com/petite-vue?module'
      const state = {
        // state exposed to all expressions within v-scope regions
        msg: 'Happy Life!',
      }

      createApp(state).mount('div#feature-island')
    </script>
    <div
      id="feature-island"
      data-pg-name="Pt-Island-Feature"
      v-scope="{}"
      style="
        padding: 20px;
        margin: 20px;
        border-radius: 4px;
        border-width: 2px;
        outline: 1px solid #cccccc;
      "
    >
      <div
        style="
          display: flex;
          margin: 8px;
          padding: 8px;
          justify-content: center;
          align-items: center;
        "
      >
        <p style="text-align: center; width: 50%; min-width: 400px">
          Hello, I&apos;m within an island, and the root tag of the island
          <code>div#feature-island</code> has an exclusive app mounted by
          <code>script#feature-app</code>, and marked as a
          <code>v-scope</code> region. Any sprinkles of interactions within this
          <code>v-scope</code> region are managed by this exclusive app.
        </p>
      </div>
      <div
        style="
          display: flex;
          margin: 8px;
          padding: 8px;
          justify-content: center;
          align-items: center;
          flex-direction: column;
        "
      >
        <input v-model="msg" style="text-align: center" /><span
          v-cloak
          style="margin: 8px"
          >{{msg}}</span
        >
      </div>
    </div>

1 Like

Great answer, @TechAkayy

The answer is somewhat covered (buried?) in this document. Although they are specifically talking about enqueuing styles, the same applies to enqueuing scripts. I tested this with two blocks running alpine.js, and it was only enqueued once.

@matjaz please correct me if Iā€™m wrong

2 Likes

Firstly - wow and thank you for such a detailed and substantial answer! I really appreciate you taking the time to help me. You clearly know what you are talking about and are much more knowledgeable on development than I am!

But yes as you have mentioned I am looking at this purely from a Wordpress perspective - Iā€™m here at Pinegrow as it is a non-code solution that allows me to build Gutenberg blocks for the Wordpress block editor. But I think I have managed to understand what you are saying (but only at a very basic level haha). Iā€™m just hoping that there is a way for me to be able to do this for Wordpress :pray:

1 Like

Adam - you are brilliant, thank you so much I will have a read through and see if I can work it out :slight_smile:
I am so impressed with this community!

2 Likes

Donā€™t feel bad for not understanding everything @TechAkayy says. Iā€™m convinced that heā€™s actually an advanced AI from the future who has to dumb down his thoughts for us ā€œmere mortals.ā€

2 Likes

Ha ha haā€¦ Iā€™m definitely human :slight_smile: Thanks both for the kind words.

I kinda try to ā€œrecordā€ some of my thoughts in our forum replies, and save them at my end, and hope they can be helpful in the future when users search them.

Here is another post on a very much related topic, where I downloaded my thoughts - Add controls for WP 6.3ā€™s new asynch & defer attributes - #3 by TechAkayy, which again revolves around the support (in our wp builder) for module scripts for the modern browsers when it becomes common in WP world in the future.

3 Likes

@iamkeef

Summary :

If a JS script or a CSS file needs to be loaded at the template level, it will be loaded every time the template is used, unless you use conditionals. See: Including Scripts and Styles | Pinegrow Web Editor

If the script (or CSS) needs to be loaded at the block level, just like stylesheets, as documented in the URL we provided, when two blocks use the exact same JS or CSS, it will only be loaded once. See: Custom Blocks Styling and Scripts | Pinegrow Web Editor

On top of that, from the project settings, (as documented) you can setup your blocks so only styles and scripts that are displayed on the frontend are loaded.

I have revised (a bit) our documentation to make it clearer.

2 Likes

thank you very much @Emmanuel - I understand now :slight_smile:
And thanks again @adamslowe and @TechAkayy for taking the time to help me

2 Likes