Issues with Module Initialization and Webpack HMR Inlined Styles on First Page Load

Hi everyone,

I’m having an issue with my Sage setup (using Bud.js for asset bundling) where my JavaScript modules are initializing before Webpack-inlined styles are fully applied on the first page load. This causes problems when my modules depend on accurate DOM dimensions (e.g., calculating heights or applying animations).

My Setup:

  • Framework: Sage 10 + Acorn v4
  • Build Tool: Bud.js
    *** Scss ***

The Problem:

On the first page load, Webpack dynamically injects styles into <style> tags, but my JavaScript modules (e.g., StickyHeader, Swiper) are initialized before those styles are fully applied. As a result:

  1. My DOM dimensions are inaccurate when calculated in the modules.
  2. Styles appear to be applied after the modules have already initialized.

What I’m Looking For:

I want to ensure that my JavaScript modules are initialized only after Webpack has fully injected and applied styles during the initial page load in development mode. Is there a reliable way to hook into Webpack or Bud.js to detect when the styles are applied?

Post some code

Hi @ben,

Here is a empty boilerplate of whats look like my app.js

import domReady from '@roots/sage/client/dom-ready';
import Swiper from './app/Swiper';
import StickyHeader from './app/StickyHeader';

/**
 * Active modules
 */
const ActiveModules = {
    Swiper,
    StickyHeader,
};

/**
 * Initialize all modules
 */
const initializeModules = () => {
    for (const [key, module] of Object.entries(ActiveModules)) {
        if (!module.init) {
            console.warn(`Module ${key} is missing the init method`);
            continue;
        }
        module.init();
    }
};

/**
 * Application entrypoint
 */
domReady(() => {
    // Initialize all modules
    initializeModules();
});

const hmr = import.meta.webpackHot;
if (hmr) {
    hmr.accept(console.error);
}

Here is my bud.config.js

import globImporter from 'node-sass-glob-importer';
import fs from 'fs';
import ThemeConfig from './theme.config.js';
import ExtractEditorRules from './resources/scripts/postcss-extract-editor.js';
import path from 'path';

const __CUR_DIR__ = fs.realpathSync('.').split('/').pop();

/**
 * Compiler configuration
 *
 * @see {@link https://roots.io/sage/docs sage documentation}
 * @see {@link https://bud.js.org/learn/config bud.js configuration guide}
 *
 * @type {import('@roots/bud').Config}
 */
export default async (bud) =>
{
    /**
     * Development settings
     */
    bud.when(bud.isDevelopment, () =>
    {
        bud.devtool('source-map');
        bud.postcss.setSourceMap(true);
    });

    /**
     * Production settings
     */
    bud.when(bud.isProduction, () =>
    {
        bud.devtool(false);
        bud.minimize();
    });

    /**
     * Add our custom PostCSS plugin for extracting `@editor` rules into a separate css file.
     */
    bud.postcss.setPlugin(
        'extract-editor',
        ExtractEditorRules({
            outputFile: bud.path('editor-rules.css'),
        })
    );
    bud.postcss.use(plugins => [...plugins, 'extract-editor']);

    /**
     * Add sass glob importer
     */
    bud.tap(bud =>
    {
        /** @ts-ignore */
        bud.build.items.sass.setOptions(
            {
                sourceMap: true,
                sassOptions: {
                    importer: globImporter(),
                    silenceDeprecations: ['legacy-js-api'],
                }
            }
        )
    });

    /**
     * Add svg source asset module
     * 
     * @see {@link https://webpack.js.org/guides/asset-modules/}
     */
    bud.hooks.on(`build.module.rules.oneOf`, (rules = []) =>
    {
        rules.push({
            test: /\.svg$/,
            resourceQuery: (/source/),
            type: 'asset/source'
        })

        return rules
    })

    /**
     * Add svgr/webpack loader
     * @see {@link https://react-svgr.com/docs/webpack/}
     */
    bud.hooks.on(`build.module.rules.oneOf`, (rules = []) =>
    {
        rules.unshift({
            test: /\.svg$/,
            issuer: /\.[jt]sx?$/, // Matches JavaScript or TypeScript files
            use: [
                {
                    loader: `@svgr/webpack`,
                    options: {
                        // icon: true,
                    },
                },
            ],
        });

        return rules;
    });

    // We need to set the issuer for the inline-svg and svg rules to avoid conflicts with the svgr loader
    bud.build.rules.svg.setIssuer(/^(?!.*\.(js|ts|jsx)$).*$/);
    bud.build.rules['inline-svg'].setIssuer(/^(?!.*\.(js|ts|jsx)$).*$/);

    /**
     * Application assets & entrypoints
     *
     * @see {@link https://bud.js.org/reference/bud.entry}
     * @see {@link https://bud.js.org/reference/bud.assets}
     */
    bud
        .entry('app', ['@scripts/app', '@styles/app'])
        .entry('editor', ['@scripts/editor', '@styles/editor'])
        .assets(['images', 'fonts']);

    /**
     * Exclude external wp dependencies
     * @see {@link https://bud.js.org/extensions/bud-preset-wordpress#excluding-dependencies}
     */
    bud.wp.setExclude([
        'lodash',
    ]);

    /**
     * Set public path
     * 
     * @see {@link https://bud.js.org/reference/bud.setPublicPath}
     */
    bud.setPublicPath(`/wp-content/themes/${__CUR_DIR__}/public/`);

    /**
     * Add aliases
     * 
     * @see {@link https://bud.js.org/reference/bud.alias}
     */
    bud.alias('@node', fs.realpathSync('node_modules'));
    bud.alias('@vendor', fs.realpathSync('vendor'));

    /**
     * Development server settings
     *
     * @see {@link https://bud.js.org/reference/bud.setUrl}
     * @see {@link https://bud.js.org/reference/bud.setProxyUrl}
     * @see {@link https://bud.js.org/reference/bud.watch}
     */
    bud
        .setUrl('http://localhost:3005')
        .setProxyUrl('http://localhost/')
        .watch(['resources/views', 'resources/scripts', 'app'], {
            usePolling: false,
        });

    /**
     * Theme configuration
     */
    bud.wpjson
        .setSettings(ThemeConfig.wpSettings)
        .setStyles(ThemeConfig.wpStyles)
        .useTailwindColors()
        .useTailwindFontFamily()
        .useTailwindFontSize()
        .useTailwindSpacing();
};

That’s a lot. Post a minimal reproduction.

Hi @Ben,

That’s a lot. Post a minimal reproduction

I’m not sure where the problem lies, so I can’t be more specific. Since the bundling process is quite complex, I’ve shared all the relevant code. The bud.config.js file is a particularly important piece, which is why I included it in its entirety.

Thanks for your support and time !