Bud config for outputting multiple css files (from scss for ACF Blocks)

Hey thanks for all the hard work getting sage 10 + bud + acorn out the door :tada: really impressive!

I might be going about this all wrong? Hoping someone can point me in the right direction.

How would I add a directory of scss files to the Bud config to compile into separate CSS files (that will be enqueued by ACF Composer)? In pre-bud sage we have this in our Mix config:

// webpack.mix.js
const glob = require('glob');
...
// Export individual custom ACF Block .css files
glob.sync('resources/styles/blocks/custom/**/[^_]*.scss').forEach((file) => {
  mix.sass(file, 'styles/blocks');
});
...
// app/Blocks/ExampleBlock.php
...
public function enqueue() {
  wp_enqueue_style('sage/example-block.css', asset('styles/blocks/example-block.css')->uri(), false, null);
}

Any help/pointers very much appreciated! :slight_smile:

1 Like

ahah! I realised I was just missing a dot ./ in my relative styles path :man_facepalming: will post what I have in a sec in case it helps anyone or if anyone can improve on it

1 Like
// bud.config.js
const path = require('path');
const glob = require('glob');
...

const blockStyleFiles = {};
glob.sync('./resources/styles/blocks/**/[^_]*.scss').forEach((file) => {
  const name = path.basename(file).split('.')[0];
  blockStyleFiles[name] = file;
});

app
  .entry({
    ...{
      app: ['@scripts/app', '@styles/app'],
      editor: ['@scripts/editor', '@styles/editor'],
    },
    ...blockStyleFiles,
  })
// app/Blocks/ExampleBlock.php
...
public function enqueue() {
    bundle('example-block')->enqueue();
}

Couldn’t seem to get the alias @styles/blocks/**/[^_]*.scss to work with glob, guessing there’s some bud magic behind the scenes, but the relative path seems to work - any/all suggestions on how to tidy this up always appreciated


Edit: converted the blockStyleFiles array β†’ object for the files to compile out separately + added enqueue snippet for ACF Block

2 Likes

Nice, thank you for sharing!

1 Like

I’m using ACF-Composer and here’s what the relevant pieces of my bud.config looks like

const glob = require('glob');
const path = require('path');

String.prototype.toKebabCase = function () {
  return this.match(
    /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g,
  ).join('-');
};

/**
   * Get list of entrypoints for blocks
   */
  const blocks = Object.assign(
    ...glob.sync('app/Blocks/*.php').map((block) => {
      const name = path.basename(block, '.php').toKebabCase().toLowerCase();
      const files = glob.sync(`./resources/{scripts,styles}/blocks/${name}.{js,scss}`);
      if (files.length !== 0) {
        return {[name]: files};
      }
    }),
  );

app.entry({
      app: ['scripts/app.js', 'styles/app.scss'],
      editor: ['scripts/editor.js', 'styles/editor.scss'],
      // ACF-Composer Blocks
      ...blocks,
    })
3 Likes

Personally, I’d keep the blocks grouped by domain. It’s easier to work with.

My example uses this structure:

β”œβ”€β”€ app
β”‚   β”œβ”€β”€ blocks
β”‚   β”‚   β”œβ”€β”€ block-a.php
β”‚   β”‚   └── block-b.php
β”œβ”€β”€ resources
β”‚   β”œβ”€β”€ blocks
β”‚   β”‚   β”œβ”€β”€ block-a
β”‚   β”‚   β”‚   β”œβ”€β”€ index.css
β”‚   β”‚   β”‚   └── index.js
β”‚   β”‚   └── block-b
β”‚   β”‚       β”œβ”€β”€ index.css
β”‚   β”‚       └── index.js

Here is the asset mapper I came up with:

const {basename, join} = require('node:path');
const {globby} = require('@roots/bud-support');

const mappedAssets = async (dir) => {
  const assets = await globby.globby(`app/${dir}/*`);
  const rel = (s) => join(dir, basename(s, '.php'));
  const entry = (a, c) => ({...a, [c]: [c]});

  return assets.map(rel).reduce(entry, {});
};

And here is how it’s used:

app.entry({
  app: ['@scripts/app', '@styles/app'],
  editor: ['@scripts/editor', '@styles/editor'],  
   ...(await mappedAssets('blocks')),
});

You’ll end up with a dist directory that looks like this:

public
β”œβ”€β”€ blocks
β”‚   β”œβ”€β”€ block-a.4480df.js
β”‚   └── block-b.4623bf.js
5 Likes

amazing! thanks @kellymears :raised_hands: I’ll give this a go tomorrow

You can also enqueue stylesheets automatically when blocks are used. Haven’t actually tried it with acf blocks but works with core and custom ones


    // @see https://make.wordpress.org/core/2021/12/15/using-multiple-stylesheets-per-block/
    $manifest = config('assets.manifests.theme.assets');
    collect(json_decode(file_get_contents($manifest), true))
        ->keys()
        ->filter(fn ($file) => Str::startsWith($file, '/styles/blocks/'))
        ->map(fn ($file) => asset($file))
        ->each(function (Asset $asset) {
            $filename = pathinfo(basename($asset->path()), PATHINFO_FILENAME);
            [$collection, $blockName] = explode('-', $filename, 2);
            wp_enqueue_block_style("$collection/$blockName", [
                'handle' => "sage/block/$filename",
                'src' => $asset->uri(),
                'path' => $asset->path(),
            ]);
        });

5 Likes

I would like to have the same structure and I was wondering how you achieved this in combination with acf-composer, or are you not using that?

Since it expects the block template to be in /resources/views/blocks?
Should I use the $view variable or the render function?

The latter results in an error:

# Declaration of App\Blocks\MyBlock::render() must be compatible with Log1x\AcfComposer\Block::render($block, $content = '', $preview = false, $post_id = 0)

Thanks!

I think Kelly was referring to a vanilla structure for blocks. What are you trying to achieve? Moving the blocks folder outside of views/? Right now it will always expect them to be in views/ because that’s what Laravel’s view() function defaults to.

For putting them inside of a folder you should be able to set the $view property to e.g. blocks.block-a.index to look for index.blade.php inside of the resources/views/block-a folder.

2 Likes

Thanks for you reply!

I think my goal is/was to have a separate folder for each ACF block in the resources folder, containing it’s own blade template, script & style files, instead of spreading those around in the views, scripts & styles folders.

If Laravel expects the block template to be in the views folder (which I understand completely), then it already defeats this purpose.

aha! this was the missing piece of the puzzle for us, we’ve got all assets in their own folders but views were always left in views/blocks/* - going to have a play with $view :+1:

Semi-related, wondering if it’d be nicer to move each block (views + assets) to a plugin? :thinking:

This is technically already possible (unless it got broken at some point) but I haven’t actually used the implementation myself on production so it’s not battle tested or mentioned anywhere.

I will try to get the example repo for it polished/tested and made public when I can.

2 Likes

Thank you Brandon, I’ll wait for your example too. I suppose I missed something moving our blocks to a plugin (Proposal: AcfComposer - relative path calculation Β· Issue #125 Β· Log1x/acf-composer Β· GitHub)

Following up on this, since you’re also the creator of the marvellous log1x/poet :slight_smile:

Would it also be possible to set the location of the block view in the poet registering a block functionality, similar to the $view variable in log1x/acf-composer?

config/poet.php:

return [
	'block' => [
		'domain/native-block'
	],
];

That way I can truly have this structure for native & acf blocks:

β”œβ”€β”€ app
β”‚   β”œβ”€β”€ Blocks
β”‚   β”‚   β”œβ”€β”€ AcfBlockA.php
β”‚   β”‚   └── AcfBlockB.php
β”œβ”€β”€ resources
β”‚   β”œβ”€β”€ views
β”‚   β”‚   β”œβ”€β”€ blocks
β”‚   β”‚   β”‚   β”œβ”€β”€ acf-block-a
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ index.css
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   β”‚   β”‚   └── index.blade.php
β”‚   β”‚   β”‚   └── acf-block-b
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ index.css
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   β”‚   β”‚   └── index.blade.php
β”‚   β”‚   β”‚   └── native-block
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ index.css
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ index.js
β”‚   β”‚   β”‚   β”‚   └── index.blade.php

Right now the native-block view needs to be in the root of the views folder.

Thanks!

I think you can accomplish this with:

'block' => [
    'domain/native-block' => [
        'view' => 'blocks.native-block.index',
    ],
],
1 Like

Brilliant! I misinterpreted that function.
Thanks!

@Log1x is there a way to generate with acf-composer a nested resources/views/blocks/acf-block-a foldering like the one mentioned before?
Thx

Updated my example for more recent versions of bud.js:

Project structure:

β”œβ”€β”€ app
β”‚   β”œβ”€β”€ blocks
β”‚   β”‚   β”œβ”€β”€ block-a.php
β”‚   β”‚   └── block-b.php
β”œβ”€β”€ resources
β”‚   β”œβ”€β”€ blocks
β”‚   β”‚   β”œβ”€β”€ block-a
β”‚   β”‚   β”‚   β”œβ”€β”€ index.css
β”‚   β”‚   β”‚   └── index.js
β”‚   β”‚   └── block-b
β”‚   β”‚       β”œβ”€β”€ index.css
β”‚   β”‚       └── index.js

Utility function:

import {basename, join} from 'node:path';
import glob from '@roots/bud-support/globby';

const mappedAssets = async (dir) => {
  const assets = await glob(`app/${dir}/*`);
  const rel = (s) => join(dir, basename(s, '.php'));
  const entry = (a, c) => ({...a, [c]: [c]});

  return assets.map(rel).reduce(entry, {});
};

bud.config.js:

  app
    .entry('app', ['@scripts/app', '@styles/app'])
    .entry('editor', ['@scripts/editor', '@styles/editor'])
    .entry(await mappedAssets('blocks'))

Output:

public
β”œβ”€β”€ css
β”‚   └── blocks
β”‚       └── block-a.ef46db.css
└── js
    └── blocks
        └── block-a.ac6f89.js

entrypoints.json:

{
  "blocks/block-a": {
    "js": [
      "js/runtime.c039ed.js",
      "js/blocks/block-a.ac6f89.js"
    ],
    "css": [
      "css/blocks/block-a.ef46db.css"
    ],
    "dependencies": []
  },
  "blocks/block-b": {
    "js": [
      "js/runtime.c039ed.js",
      "js/blocks/block-b.606d10.js"
    ],
    "dependencies": []
  }
}
2 Likes