Autoload JS for custom blocks

Hey there,

<?= js('@auto') ?>

only works for page-templates, right?

Is there a way to load some JS only if there is a specific custom block on a page?

For example, I tried to create a file like `assets/js/snippets/blocks/007-logos.js` for my custom block `site/snippets/blocks/007-logos.php` but it doesn’t load. Is there any approach?
I need the JS to load in the footer of the page, not where the block is placed on the page.

I have a workaround checking for what kinds of blocks are on a page in the page template and then add some JS, but it feels dirty.

Thank you.

That is correct.

For the moment, you will have to stick with this, but a cool new feature called “template stacks” is coming to Kirby 6, which will allow you to push stuff from snippets, blocks etc. into a template, see feat: Template stacks by distantnative · Pull Request #7867 · getkirby/kirby · GitHub

Is that because whatever you are doing relies on reading the DOM? You might not actually need the JS to be in the footer. I know thats common practice and its certainly where i put stuff.

If it was me it wouldnt do the check for what kind of blocks of have been used at all. I would just set my build system up to compile js for blocks (i use Esbuild) and then just load that in a script tag at the bottom of the custom block that needs it. That way the DOM elements are parsed by the browser before the JS gets loaded and executed. I agree it is tidier to have all that stuff nice and tidy dowin in the footer but ti think really you just need to the JS to come after the HTML elements that it depends on. I think sticking it it in the last line of the custom block snippet is fine in this case.

sounds promising, thank you.

yes because of the DOM. I’m using Bootstrap 4 and jQuery in this (quite old and complex) project. This whole thing needs a make-over actually. Will take a look into Esbuild, thanks for your thought on this.

If t helps, here is is my own Esbuild setup, but i do use SCSS. you can sinplify it a bit if you roll with vanilla css. This iconfig is bascially my starting point for any project. Note that you can have multiple entry points in the css and js blocks. I would normally do that based on template names so they get auto load it, but in the case of blocks, you can multiple entry points for the JS / CSS needed for each block instead.

import * as dotenv from "dotenv";
dotenv.config();
import * as esbuild from "esbuild";
import postcss from "postcss";
import autoprefixer from "autoprefixer";
import { sassPlugin } from "esbuild-sass-plugin";
import browserSync from "browser-sync";
import copyStaticFiles from "esbuild-copy-static-files";

// ======================================================================
// Configuration
// ======================================================================

// Local domain name fro browserSync
const domain = process.env.LOCAL;

// Common esbuild options
let commonOptions = {
  outdir: "public/assets",
  bundle: process.env.NODE_ENV === "development" ? false : true,
  minify: process.env.NODE_ENV === "development" ? false : true,
  sourcemap: process.env.NODE_ENV === "development" ? false : true,
  plugins: [
    sassPlugin({
      async transform(source) {
        const { css } = await postcss([autoprefixer]).process(source, {
          from: undefined, // This is needed to prevent postcss from trying to resolve the file path
        });
        return css;
      },
    }),
    copyStaticFiles({
      src: "./src/static",
      dest: "./public/assets/",
      preserveTimestamps: true,
      recursive: true,
    }),
  ],
};

// CSS esbuild options
let cssOptions = {
  entryPoints: {
    "css/site": "./src/scss/site.scss",
  },
  ...commonOptions,
};

// JS esbuild options
let jsOptions = {
  entryPoints: {
    "js/site": "./src/js/site.js",
  },
  ...commonOptions,
};

// ======================================================================
// Development Build
// ======================================================================

if (process.env.NODE_ENV === "development") {
  let js = await esbuild.context(jsOptions);
  let css = await esbuild.context(cssOptions);

  await js.watch();
  await css.watch();

  const browserSyncInstance = browserSync.create();

  browserSyncInstance.init({
    proxy: domain, // Set the proxy to the valet domain
    open: "local", // Automatically open the valet domain in the browser
    reloadOnRestart: true, // Reload the browser when we restart the server
    notify: true, // I don't want to see the BrowserSync notification in the browser
    ui: false, // Disable the BrowserSync UI
    snippetOptions: {
      // This solves https://github.com/BrowserSync/browser-sync/issues/1600
      rule: {
        match: /<\/head>/u,
        fn(snippet, match) {
          const {
            groups: { src },
          } = /src='(?<src>[^']+)'/u.exec(snippet);

          return `<script src="${src}" async></script>${match}`;
        },
      },
    },
  });

  browserSyncInstance
    .watch([
      "public/assets/js/**/*.js",
      "public/assets/css/**/*.css",
      "public/site/**/*.php",
      "public/content/**/*.txt",
    ])
    .on("change", browserSyncInstance.reload);

  console.log("Watching assets...");
}

// ======================================================================
// Production Build
// ======================================================================

if (process.env.NODE_ENV === "production") {
  await esbuild.build(jsOptions);
  await esbuild.build(cssOptions);
  console.log("Assets built!");
}

I use dot env to trigger whether stuff gets minified or not. The .env file just has this in it

LOCAL = "yourlocaldomain.test"

I use Herd for local development.