AgentSkillsCN

dev-assets-vite-asset-loading

采用 Vite 6 的资产加载模式,避免因 `?import` 查询参数而导致的污染问题。当您处理静态资产(FBX 模型、图像、字体),或需应对 Vite 6 新增的资产处理机制,又或需要从 public 目录与 src/assets 目录分别加载资产时,可选用此模式。

SKILL.md
--- frontmatter
name: dev-assets-vite-asset-loading
description: Vite 6 asset loading patterns that avoid '?import' query parameter pollution. Use when working with static assets (FBX models, images, fonts), dealing with Vite 6's new asset handling, or loading assets from public vs src/assets directories.
category: assets

Vite 6 Asset Loading Patterns

When to Use

  • Working with static assets (FBX models, images, fonts)
  • Dealing with Vite 6's new asset handling
  • Avoiding '?import' query parameter pollution
  • Loading assets from public directory vs src/assets

Quick Start

For FBX Models (public/ directory)

typescript
import { useFBX } from '@react-three/drei';

// CORRECT - Uses public directory with absolute path
function CharacterModel({ characterType }: { characterType: string }) {
  const fbx = useFBX(`/assets/${characterType}.fbx`);
  return <primitive object={fbx} />;
}

// For public assets that need URL
const imageUrl = new URL('/assets/images/splash.png', import.meta.url);

For CSS/JS Assets (src/assets/ directory)

typescript
// For assets that need processing
import backgroundImage from '@/assets/images/background.jpg';

// For CSS imports
import '@/styles/global.css';

Anti-Patterns

DON'T: Use import statements for public directory assets

typescript
import characterModel from '/assets/character.fbx'; // Creates '?import' query

DO: Use absolute paths for public assets

typescript
// In JSX
<img src="/assets/logo.png" alt="Logo" />

// Or with URL constructor
const assetUrl = new URL('/assets/data.json', import.meta.url);

DON'T: Mix public and src/assets references arbitrarily

typescript
// Bad - inconsistent approach
function MixedAsset() {
  // This should be in public/ with absolute path
  const localAsset = require('@/assets/icon.png');
  // This should be imported properly
  const publicAsset = '/assets/icon.png';
}

DO: Consistent directory strategy

typescript
// All public assets use absolute paths
function ConsistentAssets() {
  return (
    <div>
      <img src="/assets/public-image.jpg" alt="Public" />
      <Model assetUrl="/assets/model.fbx" />
    </div>
  );
}

Asset Directory Strategy

Public Directory (public/)

Use for:

  • Static files that won't change (favicons, fonts)
  • Third-party assets (Blaster Kit, Animated Characters)
  • Files served as-is without processing

Reference pattern:

typescript
// Always use absolute paths starting with /
const assetPath = '/assets/Blaster Kit/Models/FBX format/rifle.fbx';
const fbx = useFBX(assetPath);

Src Assets Directory (src/assets/)

Use for:

  • Application-specific assets
  • Assets that need processing (images, CSS)
  • Assets bundled with your code

Import pattern:

typescript
import processedImage from '@/assets/images/hero.png';
import styles from '@/styles/component.module.css';

Vite 6 Specific Considerations

Base URL Configuration

javascript
// vite.config.js
export default defineConfig({
  base: '/your-app-path/', // Important for public assets
  assetsInclude: ['**/*.fbx', '**/*.glb'],
  // ... other config
});

Asset Optimization

  • Vite 6 automatically optimizes images, fonts, and media
  • Use modern formats (WebP, AVIF when supported)
  • Consider lazy loading for non-critical assets

Cache Busting

typescript
// For cache busting on public assets
const versionedAsset = `/assets/image.jpg?v=${APP_VERSION}`;

// Or for dynamic assets
const dynamicAsset = new URL(`/assets/${assetId}.png?theme=${theme}`, import.meta.url);

Environment-Specific Paths

Development

typescript
// Absolute paths work in dev
const devAsset = '/assets/development-only.json';

Production

typescript
// Ensure base path is correct for production deployment
const prodAsset = `${import.meta.env.BASE_URL}assets/production.json`;

FBX Model Loading Pattern

Sequential Loading (Recommended)

typescript
import { useFBX, useProgress } from '@react-three/drei';
import { Suspense } from 'react';

function SequentialLoader({ characters }: { characters: string[] }) {
  return (
    <Suspense fallback={<LoadingScreen />}>
      {characters.map((char, index) => (
        <CharacterModel key={index} characterType={char} />
      ))}
    </Suspense>
  );
}

function CharacterModel({ characterType }: { characterType: string }) {
  const fbx = useFBX(`/assets/${characterType}.fbx`);

  // Process and return the model
  return (
    <primitive
      object={fbx}
      position={[0, 0, 0]}
      scale={[1, 1, 1]}
    />
  );
}

Loading State Management

typescript
function AssetLoader() {
  const { active, progress, errors, item } = useProgress();

  if (errors.length > 0) {
    return <div>Error loading assets</div>;
  }

  return (
    <div>
      {active && <div>Loading: {item} - {progress.toFixed(0)}%</div>}
    </div>
  );
}

Reference