Chapter 04

静态资源与 CSS 处理

弄清 public 和 src 的边界,掌握 ?url/?raw/?inline 查询后缀,以及 CSS Modules + PostCSS + Tailwind 的最佳组合。

4.1 public vs src/assets

这是第一次用 Vite 最容易混淆的概念:

位置是否参与构建URL典型用途
public/❌ 不参与/foo.pngfavicon、robots.txt、不需要处理的第三方脚本
src/assets/✅ 参与import 生成哈希文件名业务用图片/字体/SVG,需要缓存与 tree-shaking
// src 里的资源必须 import,否则不会被打包
import logoUrl from './assets/logo.png';
<img src={logoUrl} />

// public 里的资源用绝对路径,不需要 import
<img src="/favicon.png" />
public 里的文件不会被处理

不做 hash、不做压缩、不检查是否存在。所以业务图片应该放 src/,只有那些"必须保持固定路径"的文件才放 public/。

4.2 资源导入的三种形态

// 1. 默认:返回解析后的 URL(生产带 hash)
import imgUrl from './img.png';
// imgUrl === "/assets/img-Dh8a3K.png"

// 2. ?url 显式要 URL(等价默认)
import imgUrl from './img.png?url';

// 3. ?raw 把内容作为字符串 inline
import shader from './shader.glsl?raw';
// shader === "precision mediump float; void main(){..."

// 4. ?inline 强制 base64 data URL(即使超过阈值)
import iconDataUrl from './icon.svg?inline';

4.3 动态 URL:import.meta.url

相对路径变量化时,不能直接拼字符串:

// ❌ 错:Vite 静态分析不到 './img1.png',不会打包
const url = `./assets/${name}.png`;

// ✅ 对:用 new URL + import.meta.url,Rollup 会识别
const url = new URL(`./assets/${name}.png`, import.meta.url).href;
import.meta.url 是什么?

ESM 标准 API,返回当前模块所在的 URL。在 Vite 中,它会被替换成构建后的正确路径。这是构造运行时相对路径的唯一安全方法。

4.4 Glob 导入:import.meta.glob

一次性导入一堆文件?webpack 里要用 require.context;Vite 提供官方等价物:

// 懒加载(默认)
const modules = import.meta.glob('./pages/*.vue');
// modules = { './pages/a.vue': () => import('./pages/a.vue'), ... }

// 急切加载:eager: true
const modules = import.meta.glob('./pages/*.vue', { eager: true });

// 拿原始文本
const docs = import.meta.glob('./docs/*.md', { query: '?raw', import: 'default' });

4.5 CSS 基本用法

// 直接 import 会把 CSS 注入页面
import './app.css';

// 拿到 CSS 字符串(通常不用)
import css from './app.css?inline';

4.6 CSS Modules

文件名后缀 .module.css/.module.scss 自动启用:

/* Button.module.css */
.primary { background: #646cff; color: white; }
.disabled { opacity: 0.5; }
import styles from './Button.module.css';

function Button({ disabled, children }) {
  return (
    <button className={`${styles.primary} ${disabled ? styles.disabled : ''}`}>
      {children}
    </button>
  );
}

构建后类名会变成 _primary_x7f92 这样的全局唯一哈希,避免冲突。

4.7 CSS Modules 配置

css: {
  modules: {
    // 类名格式(对 local 字段有效)
    generateScopedName: '[name]__[local]__[hash:base64:5]',
    // exportLocalsConvention: 小驼峰访问 —— styles.primaryButton 对应 .primary-button
    localsConvention: 'camelCaseOnly',
  },
}

4.8 预处理器:Sass / Less / Stylus

安装对应预处理器即用,Vite 自动识别后缀:

$ npm i -D sass
// App.vue
<style lang="scss">
  $primary: #646cff;
  .btn { background: $primary; }
</style>
css: {
  preprocessorOptions: {
    scss: {
      // 每个 scss 自动注入
      additionalData: `@use "@/styles/vars" as *;`,
    },
  },
},

4.9 PostCSS

Vite 内置 PostCSS 支持,在项目根放 postcss.config.js 即可:

// postcss.config.js
export default {
  plugins: {
    'postcss-preset-env': {},
    'autoprefixer': {},
  },
};

4.10 Tailwind CSS 集成

Tailwind v4(2025 年 GA)有官方 Vite 插件,集成比 v3 更干净:

$ npm i -D tailwindcss @tailwindcss/vite
// vite.config.ts
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
  plugins: [tailwindcss()],
});
/* src/index.css */
@import "tailwindcss";

然后在组件里直接写 utility 类:

<button className="px-4 py-2 rounded bg-indigo-500 text-white hover:bg-indigo-600">
  Click
</button>

4.11 字体文件

字体放 src/assets,通过 CSS @font-face 引用:

@font-face {
  font-family: 'Inter';
  src: url('@/assets/fonts/Inter.woff2') format('woff2');
  font-display: swap;
}

Vite 会把 woff2 当资源处理,加上 hash、生产路径正确。

4.12 SVG 的多种玩法

// 1. 作为 URL(默认)
import iconUrl from './icon.svg';

// 2. 作为 raw SVG 字符串
import svgText from './icon.svg?raw';

// 3. 作为 React 组件(需 vite-plugin-svgr)
import Icon from './icon.svg?react';
<Icon width={24} />

4.13 JSON 导入

// 默认:整个对象
import pkg from '../package.json';

// 按字段(可 tree-shake)
import { version } from '../package.json';

4.14 小结