# Assets Management

Acorn's asset management system is meant to make handling your assets easier and more fluent. It also integrates with the build process, automatically handling things like cache-busting filenames, manifests, chunking, etc.

# Helper Functions

Although you can interact directly with the underlying systems (opens new window) if you like, Acorn makes some helper functions available to you in the Roots namespace:

# asset()

asset(string $asset, ?string $manifest): Asset - This returns an Asset object, which you can chain off of in several ways. \Roots\asset() is the function to use if you want to deal with a single file, such as an image in your theme.

$name = 'sandwich.png';

echo asset($name)->uri(); // https://tasty.food/themes/kitchen/assets/sandwich.png
echo asset($name)->path(); // /srv/www/site/app/themes/kitchen/assets/sandwich.png

// When treated as a string, and Asset will return the URI of its asset:
echo asset($name); // https://tasty.food/theme/kitchen/assets/sandwich.png

Asset has many methods:

  • uri(): string - The public URI (which you may know colloquially as a URL) of the asset.
  • path(): string - The filesystem path of the asset on your server.
  • exists(): bool - Whether the asset exists.
  • contents(): string - The raw content of the asset. Useful for SVGs and the like; less useful for a PNG.
  • base64(): string - The raw content, but encoded in base64.
  • dataUrl(): string - The asset in data URL form, like you might use in CSS.
  • dataUri(): string - Alias of dataUrl().
  • contentType(): string|false - The data type of the asset if it can be determined; false otherwise.
  • mimeType(): string|false - The MIME type of the asset, if it can be determined; false otherwise.
  • file(): \SplFileInfo - An SplFileInfo (opens new window) instance of the asset. If you don't know what that is, you probably don't need it.

# bundle()

bundle(string $bundle, ?string $manifest = null): Bundle - This returns a Bundle object, which you can use to enqueue and interact with your JS and CSS dependencies.

$name = 'app';

// Enqueues all of the JS and CSS dependencies in the `app` bundle.
add_action('wp_enqueue_scripts', function() {
  bundle($name)->enqueue();
}, 100);

// Enqueues only the CSS dependencies in the `app` bundle.
add_action('wp_enqueue_scripts' function() {
  bundle($name)->enqueueCss();
}, 100);

The above is pretty standard--what you might be used to doing with core functions like wp_enqueue_style(), albeit somewhat more concise.

# Usage

If you're using Sage 10, loading of basic assets is already set up for you:

module.exports = async (app) => {
  app
    .entry({
      app: ['@scripts/app', '@styles/app'],
    })
    .assets('images')
};

bud.config.js (opens new window)

This configures Bud to load your JS entrypoint (resources/scripts/app.js) and your CSS entrypoint (resources/styles/app.css) and compile them into the app bundle, which will be

add_action('wp_enqueue_scripts', function () {
    bundle('app')->enqueue();
}, 100);

app/setup.php (opens new window)

This enqueues that app bundle, loading your CSS and JS.

If you need to handle multiple bundles, that might look something like this:

// bud.config.js
module.exports = async (app) => {
  app
    .entry({
      app: ['@scripts/app', '@styles/app'],
      editor: ['@scripts/editor', '@styles/editor'],
      gallery: ['@scripts/gallery', '@scripts/app', '@styles/gallery', '@styles/app'],
    })
    .assets('images')
};
add_action('wp_enqueue_scripts', function () {
    bundle(is_page_template('gallery.blade.php') ? 'gallery' : 'app')->enqueue();
}, 100);
add_action('enqueue_block_editor_assets', function () {
    bundle('editor')->enqueue();
}, 100);

# A Note About Runtimes

You may have noticed when looking at the Acorn source or the output on your pages, that when used with Bud Acorn will inline some JS called a "runtime." If you're familiar with modern webpack, you probably already know what a runtime is, and you can skip this section--what Bud generates is exactly what you're expecting.

If you don't know what a runtime is, the simple version is that it's some inline JavaScript that loads other JavaScript. It's generated by webpack and helps with code deduplication, since webpack knows everything in every one of your modules and can load this as needed and appropriately. Because of this, you generally only want to have a single entry point per page--just include whatever you need when defining the entry point, even if it overlaps with modules required by another entrypoint, and let webpack worry about the rest.

Important caveat here: Webpack and Bud do this by building an understanding of what modules you use where. In order to do this, you need to use the import syntax consistently and reliable so that they can parse what is used where.

An example? Sure!

Let's say you have two pages:

  • Home, which will display an image carousel and has several accordions
  • About, which has an image carousel

In this extremely contrived example, you might create the following entrypoints:

  • app - This includes the carousel library, and generalized code to activate the carousel on the page
  • home - This includes the accordion library, some code to activate it on the homepage, as well as the carousel library and code to activate it on the homepage

Then you have some enqueuing logic that looks like this pseudo-code:

bundle('app')->enqueue();

if (is_home()) {
  bundle('home')->enqueue();
}

This means that on your Home page, you'll be enqueuing two entrypoints. Because of the way your entrypoints are structured, the carousel library will be downloaded twice. Instead, you should let webpack handle de-duplicating your dependencies with something like this:

  • app - This includes generalized code used across the site
  • home - The imports app, and also includes the accordion library, some code to activate it on the homepage, and some code to activate the carousel on the homepage
bundle(is_home() ? 'home' : 'app')->enqueue();

Webpack and Bud will now handle loading only the libraries necessary for each entrypoint, and only one entrypoint is loaded per page.

This example is just an introduction to how this might work, and why it works. You're encouraged to read through the Bud docs (opens new window) for a deeper understanding of the systems involved.

Page authors:

Ben
Ben Word
Sponsor us on GitHub to help us grow 🌱