UNPKG

vite-plugin-font

Version:

An automatic Web Font optimization plugin that supports many platforms such as Vite, Next, Nuxt, and more.

121 lines (115 loc) 4.49 kB
import { proto, fontSplit, decodeReporter } from 'cn-font-split'; import path from 'path'; import fs from 'fs-extra'; import url from 'url'; import crypto from 'crypto'; import { createChineseCrossPlatformFallbackCss } from 'cn-font-metrics'; export function getFileName(id: string) { return path.basename(id).replace(/\./g, '_'); } function chunk(arr?: number[], size = 500) { if (arr) { let result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; } else { return; } } export type BundlePluginConfig = Omit<Partial<proto.InputTemplate>, 'input' | 'subsets'> & { cacheDir?: string; server?: boolean; subsetChunkSize?: number }; export class BundlePlugin { public subsets: number[][] | undefined = undefined; constructor( public config: BundlePluginConfig, public key = 'default', ) {} /** 获取正确的缓存文件夹的位置 */ getCachedPath(p: string) { return path.resolve(this.config.cacheDir!, getFileName(p)); } /** 创建 CSS 封装层源代码 */ async createSourceCode(p: string) { const resolvedPath = this.getCachedPath(p); const reporter = decodeReporter( await fs.promises.readFile(resolvedPath + '/reporter.bin'), ); const metrics = await fs.readJSON(resolvedPath + '/metrics.json'); const details = { ...reporter.toObject(), ...metrics }; const code = Object.entries(details) .map(([k, v]) => { return `export const ${k} = ${JSON.stringify(v)};`; }) .join('\n'); const resultCSS = decodeURI(url.pathToFileURL(resolvedPath).pathname); const key = (Math.random() * 100000).toFixed(0); return ( `import '${resultCSS}/metrics.css?t=${key}';\n` + `import '${resultCSS}/result.css?t=${key}';\n` + code ); } /** 检查整个系统的缓存 */ async checkCache(resolvedPath: string) { const isFullCached = await Promise.all([ fs.exists(resolvedPath), fs.exists(path.resolve(resolvedPath, 'result.css')), fs.exists(path.resolve(resolvedPath, 'metrics.css')), fs.exists(path.resolve(resolvedPath, 'metrics.json')), fs.exists(path.resolve(resolvedPath, 'reporter.bin')), ]); return isFullCached.every((i) => i); } /** 重新进行预构建字体 */ async prebuild(filePath: string, mode: 'full' | 'subsets' = 'full') { const resolvedPath = this.getCachedPath(filePath); const isCached = await this.checkCache(resolvedPath); if (!isCached && this.config.server !== false) { console.log( 'vite-plugin-font | font pre-building | ' + resolvedPath, ); const FontPath = filePath.split('?')[0]; const onlySubset = mode !== 'full'; await fontSplit({ ...this.config, input: FontPath, outDir: resolvedPath, reporter: true, languageAreas: !onlySubset as false, autoSubset: true, fontFeature: true, reduceMins: !onlySubset, subsetRemainChars: !onlySubset, subsets: onlySubset ? chunk(this.subsets?.flat(), this.config.subsetChunkSize) : undefined, silent: true, }).catch((e) => { console.error(e); }); await this.createCSSFontFallback(FontPath, resolvedPath); } else { console.log('vite-plugin-font | using cache | ' + resolvedPath); } } /** 写入 CSS 字体的 fallback 选项,减少布局抖动 */ async createCSSFontFallback(FontPath: string, resolvedPath: string) { const hash = crypto.createHash('md5').update(FontPath).digest('hex'); const { fontFamilyString, css } = await createChineseCrossPlatformFallbackCss( FontPath, ` ${this.key} ${hash.slice(0, 6)}`, ); await fs.promises.writeFile( path.resolve(resolvedPath, 'metrics.css'), css, ); await fs.promises.writeFile( path.resolve(resolvedPath, 'metrics.json'), JSON.stringify({ fontFamilyFallback: fontFamilyString }), ); } }