vite-plugin-font
Version:
An automatic Web Font optimization plugin that supports many platforms such as Vite, Next, Nuxt, and more.
114 lines (109 loc) • 3.79 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const cnFontSplit = require('cn-font-split');
const path = require('path');
const fs = require('fs-extra');
const url = require('url');
const crypto = require('crypto');
const cnFontMetrics = require('cn-font-metrics');
function getFileName(id) {
return path.basename(id).replace(/\./g, "_");
}
function chunk(arr, 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;
}
}
class BundlePlugin {
constructor(config, key = "default") {
this.config = config;
this.key = key;
this.subsets = undefined;
}
/** 获取正确的缓存文件夹的位置 */
getCachedPath(p) {
return path.resolve(this.config.cacheDir, getFileName(p));
}
/** 创建 CSS 封装层源代码 */
async createSourceCode(p) {
const resolvedPath = this.getCachedPath(p);
const reporter = cnFontSplit.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() * 1e5).toFixed(0);
return `import '${resultCSS}/metrics.css?t=${key}';
import '${resultCSS}/result.css?t=${key}';
` + code;
}
/** 检查整个系统的缓存 */
async checkCache(resolvedPath) {
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, mode = "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 cnFontSplit.fontSplit({
...this.config,
input: FontPath,
outDir: resolvedPath,
reporter: true,
languageAreas: !onlySubset,
autoSubset: true,
fontFeature: true,
reduceMins: !onlySubset,
subsetRemainChars: !onlySubset,
subsets: onlySubset ? chunk(this.subsets?.flat()) : 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, resolvedPath) {
const hash = crypto.createHash("md5").update(FontPath).digest("hex");
const { fontFamilyString, css } = await cnFontMetrics.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 })
);
}
}
exports.BundlePlugin = BundlePlugin;
exports.getFileName = getFileName;