# Classic Theme Assets Build

> Build or audit frontend asset loading for classic PHP WordPress themes on WP 7.1. Covers `wp_enqueue_scripts`, child-theme-safe `get_theme_file_uri()` and `get_theme_file_path()`, cache-busting with `filemtime()`, classic scripts and script modules, loading strategy/fetch priority, conditional enqueues, `comment-reply`, RTL style data, inline data with `wp_json_encode()` and `wp_add_inline_script()`, build output folders, preload/resource hints, and common mistakes such as hardcoded tags, enqueues inside templates, and invalid file paths.

- Skill: `lonsdale201/classic-theme-assets-build` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lonsdale201/classic-theme-assets-build`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lonsdale201/classic-theme-assets-build/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: Lonsdale201 (https://skillmd.com/u/lonsdale201)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/lonsdale201/classic-theme-assets-build

---


# Classic Theme Assets and Build Output

Use this when adding or reviewing CSS, JavaScript, fonts, images, build artifacts, cache busting, or conditional frontend assets in a classic PHP theme.

This skill is for the frontend theme layer. Admin, editor, and block-specific asset loading are separate concerns.

## When to Use This Skill

- Adding `assets/css/*.css`, `assets/js/*.js`, build output, or a bundler manifest.
- Replacing hardcoded `<link>` or `<script>` tags in `header.php` or `footer.php`.
- Adding conditional CSS/JS for navigation, comments, templates, sliders, galleries, maps, or page-specific UI.
- Reviewing cache busting, defer/async, RTL styles, inline data, or asset paths in a classic theme.

## Hook and Path Rules

Frontend theme assets load on `wp_enqueue_scripts`.

```php
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue_assets' );

function mytheme_enqueue_assets() {
	$theme = wp_get_theme();

	wp_enqueue_style(
		'mytheme-style',
		get_stylesheet_uri(),
		array(),
		$theme->get( 'Version' )
	);
}
```

Rules:

- Use `wp_enqueue_scripts` for public frontend CSS/JS.
- Use `admin_enqueue_scripts` only for admin screens.
- Use `customize_controls_enqueue_scripts` and `customize_preview_init` for Customizer-specific assets.
- Use `get_theme_file_uri()` and `get_theme_file_path()` for theme files that may be overridden by a child theme.
- Use `get_stylesheet_uri()` for the active theme's root `style.css`.
- Do not hardcode theme URLs or filesystem paths.
- Do not enqueue assets inside `header.php`, `footer.php`, template parts, or loops.

## Cache Busting

Use `filemtime()` for local built files when the file exists. Fall back to the theme version.

```php
function mytheme_asset_version( $relative_path ) {
	$path = get_theme_file_path( $relative_path );

	if ( file_exists( $path ) ) {
		return (string) filemtime( $path );
	}

	return wp_get_theme()->get( 'Version' );
}
```

Example:

```php
wp_enqueue_style(
	'mytheme-main',
	get_theme_file_uri( 'assets/css/main.css' ),
	array(),
	mytheme_asset_version( 'assets/css/main.css' )
);
```

Rules:

- Call `filemtime()` on filesystem paths, never URLs.
- Keep version values deterministic. Do not use `time()` for production cache busting.
- If a bundler writes a manifest, parse it once and fail gracefully when an entry is missing.

## JavaScript Loading

WP 7.1 retains the modern `$args` array for `wp_enqueue_script()` (introduced
in 6.3). It supports `strategy`, `in_footer`, and, since 6.9,
`fetchpriority`.

```php
wp_enqueue_script(
	'mytheme-navigation',
	get_theme_file_uri( 'assets/js/navigation.js' ),
	array(),
	mytheme_asset_version( 'assets/js/navigation.js' ),
	array(
		'strategy'  => 'defer',
		'in_footer' => true,
	)
);
```

Rules:

- Use `strategy => 'defer'` when the script does not need to block parsing.
- Use `strategy => 'async'` only for scripts that are order-independent.
- Keep dependencies accurate. WordPress may adjust strategies to preserve dependency order.
- Prefer small, focused frontend scripts over one global file that runs on every page.
- Do not pass the old boolean footer parameter in new code unless maintaining legacy style.

### Script modules

Use `wp_enqueue_script_module()` for a real ES module/import-map graph rather
than printing a hand-written `<script type="module">`. Dependencies are module
IDs; array entries can mark `import` as `static` or `dynamic`. Since WordPress
6.9 the args array supports `in_footer` and `fetchpriority`, and WordPress 7.0+
supports `wp_set_script_module_translations()`.

Do not list classic script handles as module dependencies. If a classic script
must dynamically import registered modules, use the `module_dependencies`
classic-script argument available in WordPress 7.0+, or redesign one side of
the boundary. Use the `wp-plugin-assets-loading` skill for the complete module
contract and compatibility pattern.

## Inline Data

Use `wp_add_inline_script()` for boot data and encode PHP data with `wp_json_encode()`.

```php
$data = array(
	'ajaxUrl' => admin_url( 'admin-ajax.php' ),
	'nonce'   => wp_create_nonce( 'mytheme_public_action' ),
);

wp_add_inline_script(
	'mytheme-navigation',
	'window.MyTheme = ' . wp_json_encode( $data ) . ';',
	'before'
);
```

Rules:

- Do not include `<script>` tags in `wp_add_inline_script()`; core strips/warns about them.
- Do not use `wp_localize_script()` for arbitrary config objects. Use it for translation-style localization.
- Do not expose privileged data or long-lived secrets to frontend JavaScript.
- Any nonce exposed to the frontend must still be paired with capability checks server-side where permissions matter.

## Conditional Enqueues

Load assets only where they are needed.

```php
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
	wp_enqueue_script( 'comment-reply' );
}

if ( is_page_template( 'page-templates/landing.php' ) ) {
	wp_enqueue_style(
		'mytheme-landing',
		get_theme_file_uri( 'assets/css/landing.css' ),
		array( 'mytheme-main' ),
		mytheme_asset_version( 'assets/css/landing.css' )
	);
}
```

Good gating signals:

- `is_singular()`, `is_archive()`, `is_search()`, `is_front_page()`, `is_home()`.
- `is_page_template( 'page-templates/example.php' )`.
- `has_nav_menu( 'primary' )` for navigation behavior.
- `comments_open()` and `get_option( 'thread_comments' )` for `comment-reply`.

Avoid running expensive queries just to decide whether to enqueue a file.

## RTL, Preload, and Resource Hints

If the theme ships an RTL replacement stylesheet, register it with style data:

```php
wp_style_add_data( 'mytheme-main', 'rtl', 'replace' );
```

Use resource hints sparingly:

- `wp_resource_hints` can add DNS-prefetch/preconnect URLs.
- `wp_preload_resources` can add preload links for critical resources.
- Do not preload everything. Preload only resources needed very early.

For fonts:

- Prefer self-hosted fonts when licensing allows.
- Declare `font-display` deliberately.
- Avoid loading multiple remote font families and weights by default.

## Build Output Layout

A practical classic theme layout:

```text
mytheme/
|-- assets/
|   |-- src/
|   |   |-- js/
|   |   `-- css/
|   |-- css/
|   |   `-- main.css
|   |-- js/
|   |   `-- navigation.js
|   `-- manifest.json
```

Rules:

- Public files enqueued by WordPress must exist in predictable paths.
- Source files may live under `assets/src/`, but templates should reference built public files.
- Do not require Node, Vite, webpack, or npm at runtime.
- Keep source maps out of production packages unless the project explicitly allows them.

## Review Checklist

- Frontend assets are hooked to `wp_enqueue_scripts`.
- No hardcoded asset tags in `header.php`/`footer.php`.
- Handles are unique and prefixed with the theme slug.
- Local asset versions use `filemtime()` on paths or the theme version.
- Script dependencies and loading strategy are explicit.
- `comment-reply` loads only when needed.
- RTL style data is registered when RTL files exist.
- Inline data uses `wp_json_encode()` and `wp_add_inline_script()`.
- Conditional enqueues do not run heavy database work.
- Build output paths exist and are child-theme-safe when override support is intended.

## Common Mistakes

- `filemtime( get_theme_file_uri( ... ) )`, which passes a URL to a filesystem function.
- Adding `<script>` inside `wp_add_inline_script()`.
- Enqueueing every page-specific asset globally.
- Using `time()` as a version in production.
- Loading `comment-reply` on every page.
- Forgetting that `get_template_directory_uri()` always points to the parent theme.
- Breaking child theme overrides by using parent-only path helpers for overridable files.

## References

- Official documentation: <https://developer.wordpress.org/themes/core-concepts/including-assets/>
- Official documentation: <https://developer.wordpress.org/reference/hooks/wp_enqueue_scripts/>
- Verified source paths:
  - `wp-includes/script-loader.php`
  - `wp-includes/functions.wp-scripts.php`
  - `wp-includes/functions.wp-styles.php`
  - `wp-includes/class-wp-scripts.php`
  - `wp-includes/class-wp-styles.php`
  - `wp-includes/link-template.php`
  - `wp-includes/general-template.php`
  - `wp-content/themes/storefront/inc/class-storefront.php`
  - `wp-content/themes/generatepress/inc/general.php`

