Files
CyyWordpress/inc/class-cyywordpress-assets.php
2026-04-29 19:04:20 +08:00

151 lines
4.0 KiB
PHP

<?php
/**
* Asset registration and enqueueing class.
*
* Reads the Vite manifest (assets/.vite/manifest.json) in production
* and falls back to direct paths in development mode.
*
* @package CyyWordpress
*/
if ( ! class_exists( 'CyyWordpress_Assets' ) ) :
/**
* Handles all script / style enqueueing for the theme.
*/
class CyyWordpress_Assets {
/** Absolute path to the compiled assets directory. */
private const ASSETS_DIR = __DIR__ . '/../assets';
/** URL to the compiled assets directory. */
private static string $assets_url = '';
/** Parsed Vite manifest. */
private static array $manifest = [];
/**
* Registers hooks.
*/
public static function init(): void {
self::$assets_url = get_template_directory_uri() . '/assets';
self::load_manifest();
add_action( 'wp_enqueue_scripts', array( static::class, 'enqueue_frontend' ) );
add_action( 'admin_enqueue_scripts', array( static::class, 'enqueue_admin' ) );
add_filter( 'script_loader_tag', array( static::class, 'add_defer_attr' ), 10, 2 );
}
/**
* Loads and caches the Vite manifest if it exists.
*/
private static function load_manifest(): void {
$manifest_path = self::ASSETS_DIR . '/.vite/manifest.json';
if ( file_exists( $manifest_path ) ) {
$json = file_get_contents( $manifest_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions
if ( $json ) {
self::$manifest = json_decode( $json, true ) ?? [];
}
}
}
/**
* Returns the hashed asset URL from the Vite manifest
* or falls back to the raw path.
*
* @param string $entry The entry key (e.g. "src/ts/main.ts").
* @param string $type 'js' or 'css'.
*/
private static function asset_url( string $entry, string $type = 'js' ): string {
if ( ! empty( self::$manifest[ $entry ][ 'file' ] ) ) {
return self::$assets_url . '/' . self::$manifest[ $entry ][ 'file' ];
}
// Dev fallback.
return self::$assets_url . "/$type/" . basename( $entry, '.ts' ) . '.' . $type;
}
/**
* Returns the filemtime-based version string for a local file,
* used for cache-busting in development.
*
* @param string $path Relative path from assets dir.
*/
private static function file_version( string $path ): string {
$abs = self::ASSETS_DIR . '/' . ltrim( $path, '/' );
return file_exists( $abs ) ? (string) filemtime( $abs ) : _S_VERSION;
}
/**
* Enqueue front-end styles and scripts.
*/
public static function enqueue_frontend(): void {
// Main CSS.
$css_url = self::asset_url( 'src/ts/main.ts', 'css' );
wp_enqueue_style(
'cyywordpress-main',
$css_url,
array(),
_S_VERSION
);
wp_style_add_data( 'cyywordpress-main', 'rtl', 'replace' );
// Legacy style.css (theme header required by WP).
wp_enqueue_style(
'cyywordpress-style',
get_stylesheet_uri(),
array( 'cyywordpress-main' ),
_S_VERSION
);
// Main JS.
$js_url = self::asset_url( 'src/ts/main.ts', 'js' );
wp_enqueue_script(
'cyywordpress-main',
$js_url,
array(),
_S_VERSION,
true
);
// Font Awesome (local).
wp_enqueue_style(
'font-awesome',
get_template_directory_uri() . '/node_modules/@fortawesome/fontawesome-free/css/all.min.css',
array(),
'6.5.2'
);
// Comment reply: only when needed.
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
wp_enqueue_script( 'comment-reply' );
}
}
/**
* Enqueue admin / Customizer scripts.
*/
public static function enqueue_admin(): void {
// Customizer preview script is handled via customize_preview_init hook.
}
/**
* Add `defer` attribute to all theme JS files.
*
* @param string $tag The <script> HTML tag.
* @param string $handle The registered script handle.
*/
public static function add_defer_attr( string $tag, string $handle ): string {
$defer_handles = array( 'cyywordpress-main' );
if ( in_array( $handle, $defer_handles, true ) ) {
return str_replace( ' src=', ' defer src=', $tag );
}
return $tag;
}
}
endif;