Files
WooNooW/includes/Frontend/Assets.php
Dwindi Ramadhana 447ca501c7 fix: Generate Vite manifest for customer SPA loading
Problem: Customer SPA stuck on 'Loading...' message after installation
Root Cause: Vite build wasn't generating manifest.json, causing WordPress asset loader to fall back to direct app.js loading without proper module configuration

Solution:
1. Added manifest: true to both SPA vite configs
2. Updated Assets.php to look for manifest in correct location (.vite/manifest.json)
3. Rebuilt both SPAs with manifest generation

Changes:
- customer-spa/vite.config.ts: Added manifest: true
- admin-spa/vite.config.ts: Added manifest: true
- includes/Frontend/Assets.php: Updated manifest path from 'manifest.json' to '.vite/manifest.json'

Build Output:
- Customer SPA: dist/.vite/manifest.json generated
- Admin SPA: dist/.vite/manifest.json generated
- Production zip: 10M (includes manifest files)

Result:
 Customer SPA now loads correctly via manifest
 Admin SPA continues to work
 Proper asset loading with CSS and JS from manifest
 Production package ready for deployment
2025-12-30 17:26:18 +07:00

347 lines
13 KiB
PHP

<?php
namespace WooNooW\Frontend;
/**
* Frontend Assets Manager
* Handles loading of customer-spa assets
*/
class Assets {
/**
* Initialize
*/
public static function init() {
add_action('wp_enqueue_scripts', [self::class, 'enqueue_assets'], 20);
add_action('wp_head', [self::class, 'add_inline_config'], 5);
add_action('wp_enqueue_scripts', [self::class, 'dequeue_conflicting_scripts'], 100);
add_filter('script_loader_tag', [self::class, 'add_module_type'], 10, 3);
}
/**
* Add type="module" to customer-spa scripts
*/
public static function add_module_type($tag, $handle, $src) {
// Add type="module" to our Vite scripts
if (strpos($handle, 'woonoow-customer') !== false) {
$tag = str_replace('<script ', '<script type="module" ', $tag);
}
return $tag;
}
/**
* Enqueue customer-spa assets
*/
public static function enqueue_assets() {
// Only load on pages with WooNooW shortcodes or in full SPA mode
if (!self::should_load_assets()) {
return;
}
// Check if dev mode is enabled
$is_dev = defined('WOONOOW_CUSTOMER_DEV') && WOONOOW_CUSTOMER_DEV;
if ($is_dev) {
// Dev mode: Load from Vite dev server
$dev_server = 'https://woonoow.local:5174';
// Vite client for HMR
wp_enqueue_script(
'woonoow-customer-vite',
$dev_server . '/@vite/client',
[],
null,
false // Load in header
);
// Main entry point
wp_enqueue_script(
'woonoow-customer-spa',
$dev_server . '/src/main.tsx',
['woonoow-customer-vite'],
null,
false // Load in header
);
error_log('WooNooW Customer: Loading from Vite dev server at ' . $dev_server);
error_log('WooNooW Customer: Scripts enqueued - vite client and main.tsx');
} else {
// Production mode: Load from build
$plugin_url = plugin_dir_url(dirname(dirname(__FILE__)));
$dist_path = plugin_dir_path(dirname(dirname(__FILE__))) . 'customer-spa/dist/';
// Check if build exists
if (!file_exists($dist_path)) {
error_log('WooNooW: customer-spa build not found. Run: cd customer-spa && npm run build');
return;
}
// Load manifest to get hashed filenames (Vite puts it in .vite/manifest.json)
$manifest_file = $dist_path . '.vite/manifest.json';
if (file_exists($manifest_file)) {
$manifest = json_decode(file_get_contents($manifest_file), true);
// Enqueue main JS
if (isset($manifest['src/main.tsx'])) {
$main_js = $manifest['src/main.tsx']['file'];
wp_enqueue_script(
'woonoow-customer-spa',
$plugin_url . 'customer-spa/dist/' . $main_js,
[],
null,
true
);
}
// Enqueue main CSS
if (isset($manifest['src/main.tsx']['css'])) {
foreach ($manifest['src/main.tsx']['css'] as $css_file) {
wp_enqueue_style(
'woonoow-customer-spa',
$plugin_url . 'customer-spa/dist/' . $css_file,
[],
null
);
}
}
} else {
// Fallback for production build without manifest
wp_enqueue_script(
'woonoow-customer-spa',
$plugin_url . 'customer-spa/dist/app.js',
[],
null,
true
);
wp_enqueue_style(
'woonoow-customer-spa',
$plugin_url . 'customer-spa/dist/app.css',
[],
null
);
}
}
}
/**
* Add inline config and scripts to page head
*/
public static function add_inline_config() {
if (!self::should_load_assets()) {
return;
}
// Get Customer SPA settings
$spa_settings = get_option('woonoow_customer_spa_settings', []);
$default_settings = [
'mode' => 'disabled',
'layout' => 'modern',
'colors' => [
'primary' => get_option('woonoow_primary_color', '#111827'), // Gray-900 from Store Details
'secondary' => '#6B7280', // Gray-500
'accent' => '#10B981',
],
'typography' => [
'preset' => 'professional',
],
];
$theme_settings = array_replace_recursive($default_settings, $spa_settings);
// Get appearance settings and preload them
$appearance_settings = get_option('woonoow_appearance_settings', []);
if (empty($appearance_settings)) {
// Use defaults from AppearanceController
$appearance_settings = \WooNooW\Admin\AppearanceController::get_default_settings();
}
// Get WooCommerce currency settings
$currency_settings = [
'code' => get_woocommerce_currency(),
'symbol' => get_woocommerce_currency_symbol(),
'position' => get_option('woocommerce_currency_pos', 'left'),
'thousandSeparator' => wc_get_price_thousand_separator(),
'decimalSeparator' => wc_get_price_decimal_separator(),
'decimals' => wc_get_price_decimals(),
];
// Get store logo from WooNooW Store Details (Settings > Store Details)
$logo_url = get_option('woonoow_store_logo', '');
// Get user billing/shipping data if logged in
$user_data = [
'isLoggedIn' => is_user_logged_in(),
'id' => get_current_user_id(),
];
if (is_user_logged_in()) {
$customer = new \WC_Customer(get_current_user_id());
$user_data['email'] = $customer->get_email();
$user_data['billing'] = [
'first_name' => $customer->get_billing_first_name(),
'last_name' => $customer->get_billing_last_name(),
'email' => $customer->get_billing_email(),
'phone' => $customer->get_billing_phone(),
'address_1' => $customer->get_billing_address_1(),
'city' => $customer->get_billing_city(),
'state' => $customer->get_billing_state(),
'postcode' => $customer->get_billing_postcode(),
'country' => $customer->get_billing_country(),
];
$user_data['shipping'] = [
'first_name' => $customer->get_shipping_first_name(),
'last_name' => $customer->get_shipping_last_name(),
'address_1' => $customer->get_shipping_address_1(),
'city' => $customer->get_shipping_city(),
'state' => $customer->get_shipping_state(),
'postcode' => $customer->get_shipping_postcode(),
'country' => $customer->get_shipping_country(),
];
}
$config = [
'apiUrl' => rest_url('woonoow/v1'),
'apiRoot' => rest_url('woonoow/v1'),
'nonce' => wp_create_nonce('wp_rest'),
'siteUrl' => get_site_url(),
'siteTitle' => get_bloginfo('name'),
'storeName' => get_bloginfo('name'),
'storeLogo' => $logo_url,
'user' => $user_data,
'theme' => $theme_settings,
'currency' => $currency_settings,
'appearanceSettings' => $appearance_settings,
];
?>
<script type="text/javascript">
window.woonoowCustomer = <?php echo wp_json_encode($config); ?>;
</script>
<?php
// If dev mode, output scripts directly
$is_dev = defined('WOONOOW_CUSTOMER_DEV') && WOONOOW_CUSTOMER_DEV;
if ($is_dev) {
$dev_server = 'https://woonoow.local:5174';
?>
<script type="module">
import RefreshRuntime from '<?php echo $dev_server; ?>/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" crossorigin src="<?php echo $dev_server; ?>/@vite/client"></script>
<script type="module" crossorigin src="<?php echo $dev_server; ?>/src/main.tsx"></script>
<?php
error_log('WooNooW Customer: Scripts output directly in head with React Refresh preamble');
}
}
/**
* Check if we should load customer-spa assets
*/
private static function should_load_assets() {
global $post;
// Get Customer SPA settings
$spa_settings = get_option('woonoow_customer_spa_settings', []);
$mode = isset($spa_settings['mode']) ? $spa_settings['mode'] : 'disabled';
// If disabled, don't load
if ($mode === 'disabled') {
// Still check for shortcodes
if ($post && has_shortcode($post->post_content, 'woonoow_shop')) {
return true;
}
if ($post && has_shortcode($post->post_content, 'woonoow_cart')) {
return true;
}
if ($post && has_shortcode($post->post_content, 'woonoow_checkout')) {
return true;
}
return false;
}
// Full SPA mode - load on all WooCommerce pages
if ($mode === 'full') {
if (function_exists('is_shop') && is_shop()) {
return true;
}
if (function_exists('is_product') && is_product()) {
return true;
}
if (function_exists('is_cart') && is_cart()) {
return true;
}
if (function_exists('is_checkout') && is_checkout()) {
return true;
}
if (function_exists('is_account_page') && is_account_page()) {
return true;
}
return false;
}
// Checkout-Only mode - load only on specific pages
if ($mode === 'checkout_only') {
$checkout_pages = isset($spa_settings['checkoutPages']) ? $spa_settings['checkoutPages'] : [];
if (!empty($checkout_pages['checkout']) && function_exists('is_checkout') && is_checkout() && !is_order_received_page()) {
return true;
}
if (!empty($checkout_pages['thankyou']) && function_exists('is_order_received_page') && is_order_received_page()) {
return true;
}
if (!empty($checkout_pages['account']) && function_exists('is_account_page') && is_account_page()) {
return true;
}
if (!empty($checkout_pages['cart']) && function_exists('is_cart') && is_cart()) {
return true;
}
return false;
}
// Check if current page has WooNooW shortcodes
if ($post && has_shortcode($post->post_content, 'woonoow_shop')) {
return true;
}
if ($post && has_shortcode($post->post_content, 'woonoow_cart')) {
return true;
}
if ($post && has_shortcode($post->post_content, 'woonoow_checkout')) {
return true;
}
if ($post && has_shortcode($post->post_content, 'woonoow_account')) {
return true;
}
return false;
}
/**
* Dequeue conflicting scripts when SPA is active
*/
public static function dequeue_conflicting_scripts() {
if (!self::should_load_assets()) {
return;
}
// Dequeue WooCommerce scripts that conflict with SPA
wp_dequeue_script('wc-cart-fragments');
wp_dequeue_script('woocommerce');
wp_dequeue_script('wc-add-to-cart');
wp_dequeue_script('wc-add-to-cart-variation');
// Dequeue WordPress block scripts that cause errors in SPA
wp_dequeue_script('wp-block-library');
wp_dequeue_script('wp-block-navigation');
wp_dequeue_script('wp-interactivity');
wp_dequeue_script('wp-interactivity-router');
// Keep only essential WooCommerce styles, dequeue others if needed
// wp_dequeue_style('woocommerce-general');
// wp_dequeue_style('woocommerce-layout');
// wp_dequeue_style('woocommerce-smallscreen');
}
}