🚀 Font subsetting plugin for Vite, Webpack, Rollup & Rspack - 基于项目实际使用字符自动子集化字体并生成 WOFF2
- ✅ Vite - 原生支持,Chrome DevTools 验证
- ✅ Webpack - 完全支持,无 deprecation 警告
- ✅ Rollup - 完全支持,库打包优化
- ✅ Rspack - 兼容支持,使用 Webpack 适配器
| 场景 | 原始字体 | 子集化后 | 压缩比 | 加载速度提升 |
|---|---|---|---|---|
| 中文字体 | 10.2 MB | 28.5 KB | 99.7% | ⚡️ 358x |
| 日文字体 | 8.7 MB | 22.1 KB | 99.7% | ⚡️ 394x |
| 韩文字体 | 6.8 MB | 18.3 KB | 99.7% | ⚡️ 372x |
| 混合字体 | 12.4 MB | 31.7 KB | 99.7% | ⚡️ 391x |
💡 视觉冲击:从整包字体到只包含项目字符的子集,体积减少 99.7%!
- 企业官网 - 静态内容,字符集固定
- 产品展示页 - 文案确定,追求极致加载速度
- H5 营销页面 - 移动端友好,字体大小至关重要
- 管理后台 - 字符集有限,性能提升明显
- 文档网站 - 技术文档,字符相对固定
- 纯动态新闻站 - 每篇文章字符集不同
- UGC 内容平台 - 用户生成内容,字符不可预测
- 在线编辑器 - 实时输入任意字符
- 🇨🇳 中文 - 支持 3500+ 常用汉字,涵盖 GB2312、GBK、UTF-8
- 🇯🇵 日文 - 支持平假名、片假名、常用汉字
- 🇰🇷 韩文 - 支持韩文字母、常用汉字组合
- 自动扫描字符集:从源码文件中收集实际用到的字符,避免整包字体。
- 按字体生成子集:使用
subset-font生成.woff2子集文件。 - 自动生成 CSS:在字体所在目录生成统一的
font.css,包含@font-face声明。 - 按目录合并:同一目录下多字体会合并到一个
font.css中,避免互相覆盖。 - 可选自动注入:构建时可自动把生成的
font.css注入 HTML,无需手动引入。 - 仅在构建阶段运行:默认只在生产构建时执行,不影响开发速度。
推荐使用 scoped 包名:
npm install @fe-fast/vite-plugin-font-subset --save-dev
# 或者
pnpm add @fe-fast/vite-plugin-font-subset -D
yarn add @fe-fast/vite-plugin-font-subset -D// vite.config.js
import { defineConfig } from 'vite'
import fontSubsetPlugin from '@fe-fast/vite-plugin-font-subset'
export default defineConfig({
plugins: [
fontSubsetPlugin({
fonts: [
{
src: 'src/fonts/SourceHanSansCN-Medium.otf',
family: 'Source Han Sans CN',
weight: 400,
style: 'normal'
}
],
scanDirs: ['src/**/*.{vue,js,ts,jsx,tsx,json,scss,less,css}'],
outputDir: 'subset',
generateCss: true,
injectCss: true,
extraChars: '①②③④⑤⑥⑦⑧⑨⑩',
enabled: true
})
]
})// webpack.config.js
import FontSubsetPlugin from '@fe-fast/vite-plugin-font-subset/webpack'
module.exports = {
plugins: [
new FontSubsetPlugin({
fonts: [
{
src: 'src/fonts/SourceHanSansCN-Medium.otf',
family: 'Source Han Sans CN',
weight: 400,
style: 'normal'
}
],
scanDirs: ['src/**/*.{vue,js,ts,jsx,tsx,json,scss,less,css}'],
outputDir: 'subset',
generateCss: true,
injectCss: true,
extraChars: '①②③④⑤⑥⑦⑧⑨⑩',
enabled: true
})
]
}// rollup.config.js
import fontSubsetPlugin from '@fe-fast/vite-plugin-font-subset/rollup'
export default {
plugins: [
fontSubsetPlugin({
fonts: [
{
src: 'src/fonts/SourceHanSansCN-Medium.otf',
family: 'Source Han Sans CN',
weight: 400,
style: 'normal'
}
],
scanDirs: ['src/**/*.{vue,js,ts,jsx,tsx,json,scss,less,css}'],
outputDir: 'subset',
generateCss: true,
extraChars: '①②③④⑤⑥⑦⑧⑨⑩',
enabled: true
})
]
}// rspack.config.js
// Rspack 兼容 Webpack 插件 API
import FontSubsetPlugin from '@fe-fast/vite-plugin-font-subset/webpack'
export default {
plugins: [
new FontSubsetPlugin({
fonts: [
{
src: 'src/fonts/SourceHanSansCN-Medium.otf',
family: 'Source Han Sans CN',
weight: 400,
style: 'normal'
}
],
scanDirs: ['src/**/*.{vue,js,ts,jsx,tsx,json,scss,less,css}'],
outputDir: 'subset',
generateCss: true,
injectCss: true,
extraChars: '①②③④⑤⑥⑦⑧⑨⑩',
enabled: true
})
]
}export default defineConfig({
plugins: [
fontSubset({
// 必填:需要子集化的字体列表
fonts: [
{
// 源字体文件路径,可以是相对项目根目录的相对路径或绝对路径
src: 'src/assets/fonts/SourceHanSansCN-Regular.ttf',
// 在 CSS 中使用的 font-family 名称
family: 'Source Han Sans CN',
// 可选:字重,默认 400
weight: 400,
// 可选:样式,默认 'normal'
style: 'normal'
}
],
// 可选:扫描哪些文件来收集字符集
// 默认:['src/**/*.{vue,js,ts,jsx,tsx,json,scss,less,css}']
scanDirs: ['src/**/*.{vue,ts,tsx,js,jsx}'],
// 可选:子集文件输出目录(相对于字体源文件目录)
// 默认:'subset'
outputDir: 'subset',
// 可选:是否生成 font.css,默认 true
generateCss: true,
// 可选:是否自动把 font.css 注入 HTML,默认 true
injectCss: true,
// 可选:额外需要保留的字符(例如接口动态返回、运行时生成的字符)
extraChars: '①②③',
// 可选:字符集预设,用于覆盖动态文案(可选)
// 支持值:'common-3500' | 'gb2312-basic' | 'punctuation'
preset: null,
// 可选:是否启用插件,默认 true(但仅在 build 时真正执行)
enabled: true
})
]
})以上配置在构建时会:
- 扫描
scanDirs中的所有文件,收集出现过的字符。 - 对
fonts中每个字体:- 在字体所在目录下创建
outputDir目录(默认为subset)。 - 在该目录生成子集字体:
<原文件名>.woff2。
- 在字体所在目录下创建
- 在字体所在目录(
src同级目录)生成一个font.css,内容类似:
/* 此文件由 vite-plugin-font-subset 自动生成,请勿手动修改 */
@font-face {
font-family: 'Source Han Sans CN';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./subset/SourceHanSansCN-Regular.woff2') format('woff2');
}同一目录下多个字体会合并到同一个
font.css中,并按 family / weight / style 排序,保证生成内容稳定。
默认情况下,插件会在构建时自动把生成的 font.css 注入到最终的 HTML 中,无需手动引入。直接在 CSS 中使用即可:
body {
font-family: 'Source Han Sans CN', system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', sans-serif;
}如果你将 injectCss 设为 false,或需要自定义加载顺序/作用域,可以手动引入生成的 font.css:
在入口文件中:
// main.ts
import '@/assets/fonts/font.css'或在 index.html 中:
<link rel="stylesheet" href="/src/assets/fonts/font.css" />字体子集化插件默认只扫描静态源码中的字符,但实际项目中常有动态文案:
// 静态文案 ✅ - 会被扫描到
<h1>欢迎来到我的网站</h1>
// 动态文案 ❌ - 接口返回,构建时未知
<p>{{ apiResponse.message }}</p> // "操作成功"
<span>{{ userStatus }}</span> // "处理中"| 方案 | 字体大小 | 动态文案处理 | 视觉一致性 | 推荐场景 |
|---|---|---|---|---|
| CSS 回退 | ~50-150 KB | 系统字体兜底 | 默认推荐 - 大多数项目 | |
| 预设字符集 | ~300-500 KB | 完全覆盖 | ✅ 完全一致 | 品牌官网、设计系统 |
/* 在你的样式中配置字体回退 */
body {
font-family: 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', sans-serif;
}效果:
- 静态文案 "Hello World" → 使用子集字体 ✅
- 动态文案 "操作成功" → 子集字体缺字 → 回退到系统字体 ✅
// vite.config.js
fontSubsetPlugin({
fonts: [...],
preset: 'common-3500' // 添加3500常用字
})可用预设:
| 预设名 | 字符数 | 说明 | 预计大小 |
|---|---|---|---|
common-3500 |
3,594 | 常用3500字,覆盖99%日常用字 | ~300 KB |
gb2312-basic |
6,857 | GB2312基础汉字,几乎所有简体中文 | ~500 KB |
punctuation |
~200 | CJK标点符号 | ~10 KB |
选择 CSS 回退(默认):
- ✅ 追求极致性能
- ✅ 动态文案较少
- ✅ 可以接受部分字体不一致
选择预设字符集:
- ✅ 品牌视觉一致性要求高
- ✅ 动态文案内容丰富
- ✅ 可以接受额外的带宽成本
💡 重要提示:即使使用预设字符集,仍建议在 CSS 中保留字体回退列表,这是最佳实践。
预设功能是完全可选的,现有项目无需任何修改即可继续使用。如需启用预设,只需添加 preset 配置项即可。
interface FontConfig {
src: string // 源字体路径,支持绝对路径或相对于 Vite 项目根目录的相对路径
family: string // font-family 名称
weight?: number // 字重,默认 400
style?: string // 样式,默认 'normal'
}- 建议把字体放在
src/assets/fonts之类的目录下,方便管理。 - 同一个目录下多个字体会共享一个
font.css。
- 类型:
string[] - 默认:
['src/**/*.{vue,js,ts,jsx,tsx,json,scss,less,css}'] - 作用:告诉插件需要扫描哪些文件来收集字符。
扫描范围越大,构建时间可能越长。可以根据项目实际情况适当收紧,例如只扫描
.vue和.ts文件。
- 类型:
string - 默认:
'subset' - 作用:子集字体输出目录,相对于字体源文件所在目录。
- 类型:
boolean - 默认:
true - 作用:是否在字体目录生成
font.css。如果你有自己的一套 CSS 管理方式,也可以关掉,然后在构建后手动处理生成的.woff2。
- 类型:
boolean - 默认:
true - 作用:是否在构建阶段自动把生成的
font.css注入到最终 HTML。关闭后需自行在入口或 HTML 中引入。
- 类型:
string - 默认:
'' - 作用:额外需要保留的字符,用于处理:
- 接口动态返回但源码中不存在的字符;
- 富文本 / Markdown / 国际化等场景。
- 类型:
boolean - 默认:
true - 说明:
- 插件只会在
vite build时执行,开发环境(vite dev)不会触发实际子集化。 - 可以通过环境变量控制是否启用,例如:
- 插件只会在
fontSubset({
enabled: process.env.NODE_ENV === 'production'
})- 构建时间:首次扫描和子集化会增加一定构建时间,取决于扫描文件数量和字体文件大小。
- 动态内容:如果运行时会产生大量源码中不存在的字符(例如富文本编辑器),请务必通过
extraChars或调整scanDirs进行补充。 - 多入口 / 多字体目录:
- 同一目录下的字体会共用一个
font.css; - 不同目录会生成各自的
font.css,请分别在对应入口中引入。
- 同一目录下的字体会共用一个
- 运行环境:Node.js 16+(推荐 18+)
- Vite 版本:
peerDependencies:^4.0.0 || ^5.0.0
@fe-fast/vite-plugin-font-subset is a Vite plugin that generates WOFF2 font subsets
based on the actual characters used in your project. It scans your source files,
subsets the specified fonts using subset-font,
and generates a font.css file with corresponding @font-face rules.
- Automatically collect characters from your source files.
- Generate
.woff2subsets for each configured font. - Emit a merged
font.cssin each font directory to avoid overwrite issues. - Optionally inject generated
font.cssinto HTML during build. - Run only during
vite build, keepingvite devfast.
npm install @fe-fast/vite-plugin-font-subset --save-dev
# or
pnpm add @fe-fast/vite-plugin-font-subset -D
yarn add @fe-fast/vite-plugin-font-subset -DThe legacy name
vite-plugin-font-subsetis still compatible, but@fe-fast/vite-plugin-font-subsetis the recommended package.
import { defineConfig } from 'vite'
import fontSubset from '@fe-fast/vite-plugin-font-subset'
export default defineConfig({
plugins: [
fontSubset({
fonts: [
{
src: 'src/assets/fonts/SourceHanSansCN-Regular.ttf',
family: 'Source Han Sans CN',
weight: 400,
style: 'normal'
}
],
scanDirs: ['src/**/*.{vue,ts,tsx,js,jsx}'],
outputDir: 'subset',
generateCss: true,
injectCss: true,
extraChars: '①②③',
enabled: true
})
]
})During build, the plugin will:
- Scan all files matched by
scanDirsand collect all unique characters. - Subset each font defined in
fontsinto a.woff2file underoutputDir. - Generate a
font.cssfile next to the source font file with@font-facerules.
By default, the plugin automatically injects generated font.css into the final
HTML during build, so you don't need to manually import it.
If you set injectCss to false or need custom ordering/scope, you can import
font.css manually in your entry or index.html. Then you can use the configured
font-family in your styles:
body {
font-family: 'Source Han Sans CN', system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', sans-serif;
}fonts: list of fonts to subset. Each item hassrc,family, optionalweight,style.scanDirs: glob patterns to search for used characters.outputDir: folder name relative to the font file directory where subsets are written.generateCss: whether to emitfont.cssautomatically.injectCss: whether to inject generatedfont.cssinto HTML during build.extraChars: extra characters to keep that may not appear in source files.enabled: whether the plugin is enabled; it only runs duringvite build.
- A larger
scanDirsrange may increase build time. - For runtime-only characters (API responses, user input, rich text, etc.),
please use
extraCharsor adjustscanDirs. - Fonts in the same directory share a single
font.css; fonts in different directories will have their ownfont.css.
查看 demo 目录获取完整的 Vite + Vue 示例项目。
# 克隆仓库后
cd demo
npm install
# 下载字体文件到 demo/src/fonts/ 目录
# 推荐:思源黑体 SourceHanSansCN-Regular.otf
# 运行开发服务器
npm run dev
# 构建生产版本
npm run build详细说明请查看 demo/README.md