How to add PostCSS plugins to Bud?

Hi,

I try to include the postcss-inline-svg plugin with Bud (v5.0.0-next.18 ).

With Laravel Mix I included it like this:

mix
   ...
  .options({
    ...
    postCss: [
      ...
      require('postcss-inline-svg')(),
      ...
    ]
  });

Using the dev-main branche of Sage I included: bud-postcss and changed the bud.config to the one below. But it’s not working, I get: TypeError: config.entry(...).postcss.setPlugin(...).assets is not a function

How to include PostCSS plugins to Bud?

module.exports = async (config) =>
  config
    /**
     * Application entrypoints
     *
     * Paths are relative to your resources directory
     */
    .entry({
      app: ['scripts/app.js', 'styles/app.css'],
      editor: ['scripts/editor.js', 'styles/editor.css'],
      customizer: 'scripts/customizer.js',
    })

    .postcss.setPlugin('postcss-inline-svg', [
        require.resolve('postcss-inline-svg'),
        {}
    ])

    /**
     * These files should be processed as part of the build
     * even if they are not explicitly imported in application assets.
     */
    .assets(['resources/images'])

    /**
     * These files will trigger a full page reload
     * when modified.
     */
    .watch([
      'tailwind.config.js',
      'resources/views/*.blade.php',
      'app/View/**/*.php',
    ])

    /**
     * Target URL to be proxied by the dev server.
     *
     * This is your local dev server.
     */
    .proxy({target: 'http://example.test'});

You should be able to skip adding bud-postcss since it’s already included with Sage

Your Bud config changes should also be correct, and should work properly once you remove bud-postcss

The problem here is that you are chaining bud.assets off bud.postcss, but bud.postcss returns itself rather than bud.

you can either break the chain:

bud
  .entry(...)
  .postcss.setPlugin(...)

bud.assets(...)

or, you can use bud.tap to make a new scope to do your postcss config:

bud
  .entry(...)
  .tap(bud => {
    bud.postcss.setPlugin(...)
  }) // bud.tap returns bud
  .assets(...) // so chain can keep going

if you prefer, you can destructure this (because of bud’s context binding) to be:

bud
  .entry(...)
  .tap(({postcss}) => postcss.setPlugin(...))
  .assets(...)

if you add the @roots/bud-postcss import to the docblock at the top of the config you will get helpful intellisense:

/**
 * @typedef {import('@roots/bud-postcss')}
 * @typedef {import('@roots/bud').Bud} Bud
 *
 * @param {Bud} app
 */

4 Likes

Thanks @kellymears! Adding the @roots/bud-postcss import to the docblock is really helping too.

Bud compiles, but it’s not parsing svg-load() and svg-inline() when I add:

.tap(bud => {
  bud.postcss
    .setPlugin('postcss-inline-svg', [
      require.resolve('postcss-inline-svg'),
      {},
    ])
})

I think it has something to do with the order of PostCSS plugins, or am I doing something wrong? With the config below using .setPlugins it parses correctly:

.tap(bud => {
  bud.postcss
    .setPlugins({
      'postcss-import': require.resolve('postcss-import'),
      'tailwindcss': require.resolve('tailwindcss'),
      'postcss-nested': require.resolve('postcss-nested'),
      'postcss-inline-svg': [
        require.resolve('postcss-inline-svg'),
        {},
      ],
      'postcss-preset-env': [
        require.resolve('postcss-preset-env'),
        {
          stage: 1,
          features: {
            'focus-within-pseudo-class': false,
          },
        },
      ],
    });
})

Is there a way to set the order of the PostCSS plugins?

PS I want to use SCSS too, so I added @roots/bud-sass too. I have added my test setup to Github here:

1 Like

nice! thank you for the repo. i was able to reproduce your issue and i have a fix. allow me to walk you through the steps i took.

Check .budfiles/bud/webpack.config.js

If bud made it all the way to the dashboard it means bud has created a diagnostic file at this location. The file has a representation of the configuration bud is giving to webpack.

The first example

When you set your plugin with setPlugin in the first example:

.tap(bud => {
  bud.postcss
    .setPlugin('postcss-inline-svg', [
      require.resolve('postcss-inline-svg'),
      {},
    ])
})

this is the output you’ll find in the postcss rule:

Object {
  "loader": "[modules]/postcss-loader/dist/cjs.js",
  "options": Object {
    "postcssOptions": Object {
      "plugins": Array [
        "[modules]/postcss-import/index.js",
        "[modules]/tailwindcss/nesting/index.js",
        "[modules]/tailwindcss/lib/index.js",
        Array [
          "[modules]/postcss-inline-svg/lib/index.js",
          Object {},
        ],
        Array [
          "[modules]/postcss-preset-env/dist/index.js",
          Object {
            "features": Object {
              "focus-within-pseudo-class": false,
            },
            "stage": 1,
          },
        ],
      ],
    },
    "sourceMap": true,
  },
}

This is correct so we know postcss.setPlugins works.

The second example

Running your second example

bud.tap(bud => {
  bud.postcss
    .setPlugins({
      'postcss-import': require.resolve('postcss-import'),
      'tailwindcss': require.resolve('tailwindcss'),
      'postcss-nested': require.resolve('postcss-nested'),
      'postcss-inline-svg': [
        require.resolve('postcss-inline-svg'),
        {},
      ],
      'postcss-preset-env': [
        require.resolve('postcss-preset-env'),
        {
          stage: 1,
          features: {
            'focus-within-pseudo-class': false,
          },
        },
      ],
    });
})

also gives us what we ask for.

Both seem to be what was specified. So, bud is doing its job so far but we aren’t getting what we want. I think there is an issue combining tailwindcss-nesting with postcss-inline-svg. Fixing this feels like an issue for one of those two repositories.

Removing an extension

I noticed you aren’t using tailwindcss in your stylesheet. We can remove it:

bud.tap(({postcss}) => postcss
  .remove('tailwindcss')
  .remove('tailwindcss-nesting')
)

And now it’s a matter of keeping postcss-import and adding postcss-nested back in.

Inserting it into the right position is tricky, but we can do it with a reducer (it’s basically like dealing with state in redux). Here is the function I came up with:

/**
 * @param {Bud} instance
 */
const mergePlugin = ({postcss}) => {
  const reducer = (a, [k, v]) => ({...a, [k]: v});

  postcss.remove('tailwindcss').remove('tailwindcss-nesting');

  const plugins = {
    ...postcss.getEntries().slice(0, 1).reduce(reducer, {}),
    ['postcss-nested']: ['postcss-nested', {}],
    ['postcss-inline-svg']: ['postcss-inline-svg', {}],
    ...postcss.getEntries().slice(2).reduce(reducer, {}),
  };

  postcss.setPlugins(plugins);
};

This works and can be applied with .tap(mergePlugin) You could also do what you’re doing in the first example and overwrite everything. Just leave out tailwindcss and its nesting plugin.

I don’t know what’s up with tailwindcss and postcss-inline-svg. Postcss doesn’t have the best reporting, and so it’s hard to say what is going on.

Of note, in 5.1.0 svg will automatically be converted to data urls if you install @roots/bud-imagemin. I wouldn’t install it until 5.1.0, though.

2 Likes

Thank you for your explanation! I got it working. I will try @roots/bud-imagemin when Bud 5.1.0 is out.

1 Like

I am trying to do similar thing here
trying to add postcss-nested plugin with bud but it does not seem to be working.

Here is the error I get

./resources/styles/partials/_base.scss @apply is not supported within nested at-rules like @include. You can fix this by un-nesting @include.

  153 |     @apply flex flex-col w-full p-8;
  154 |     @include respond(md) {
> 155 |       @apply flex-row p-0;
      |       ^
  156 |     }

and below is my bud config file.

/**
 * @typedef {import('@roots/bud').Bud} Bud
 *
 * @param {Bud} config
 */

module.exports = async (config) =>
  config
    /**
     * Application entrypoints
     *
     * Paths are relative to your resources directory
     */
    .entry({
      app: ['scripts/app.js', 'styles/app.css'],
      editor: ['scripts/editor.js', 'styles/editor.css'],
    })

    /**
     * Add postcss plugin with bud
     */
    .tap(bud => {
      bud.postcss.setPlugin('postcss-nested', [
        require.resolve('postcss-nested'), // the plugin module path
        {}, // options
      ])
    })


    /**
     * These files should be processed as part of the build
     * even if they are not explicitly imported in application assets.
     */
    .assets(['resources/images'])

    /**
     * These files will trigger a full page reload
     * when modified.
     */
    .watch([
      'tailwind.config.js',
      'resources/views/**/*.blade.php',
      'app/View/**/*.php',
    ])

    /**
     * Target URL to be proxied by the dev server.
     *
     * This is your local dev server.
     */
    .proxy('http://website.local');

Any suggestions here on what I am doing wrong?
Thanks

@stealth123 this topic is solved, let’s keep your open topic as the place of discussion for your issue :pray: