vite 预加载插件
vite-plugin-simplest-sorted-preload.ts
import fs from 'fs';
import path from 'path';
import { globSync } from 'glob';
// 资源优先级排序数组(越靠前优先级越高)
const PRIORITY_ORDER = [
'html', // 页面文件
'css', // 样式文件
'js', // 脚本文件
'svg', // 矢量图
'png', // 位图
'jpg',
'jpeg',
'webp',
'gif', // 动图
'json', // 数据文件
'woff', // 字体文件
'woff2',
'ttf',
'eot'
];
export default function preloadInjectorPlugin(options: any = {}) {
const {
outputDir = 'dist',
manifestName = 'preload-manifest.json',
globPattern = '**/*.+(html|css|js)',
ignore = ['index.html'],
// 预加载脚本注入位置的选择器
injectSelector = '</head>'
} = options;
const manifestPath = path.resolve(outputDir, manifestName);
return {
name: 'preload-injector',
closeBundle() {
try {
if (!fs.existsSync(outputDir)) {
console.warn(`目标目录不存在: ${outputDir}`);
return;
}
// 扫描文件并生成清单
const files = globSync(globPattern, {
cwd: outputDir,
nodir: true,
ignore,
});
// 处理文件路径和排序
const sortedFiles = files
.map(file => {
const normalizedPath = file.replace(/\\/g, '/');
const ext = path.extname(normalizedPath).slice(1).toLowerCase() || 'unknown';
const priorityIndex = PRIORITY_ORDER.includes(ext)
? PRIORITY_ORDER.indexOf(ext)
: PRIORITY_ORDER.length;
return {
path: normalizedPath,
priorityIndex
};
})
.sort((a, b) => {
if (a.priorityIndex !== b.priorityIndex) {
return a.priorityIndex - b.priorityIndex;
}
return a.path.localeCompare(b.path);
});
// 写入清单文件
const manifest = {
generatedAt: new Date().toISOString(),
totalFiles: sortedFiles.length,
files: sortedFiles.map(item => item.path)
};
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
console.log(`已生成预加载清单: ${manifestPath}`);
} catch (error) {
console.error('生成预加载清单失败:', error);
}
},
// 注入预加载脚本到index.html
transformIndexHtml(html: string) {
if (process.env.NODE_ENV === 'development') return html
// 生成预加载脚本
const preloadScript = `
<script>
// 闲时预加载逻辑
(function() {
// 检查浏览器是否支持requestIdleCallback
const useIdleCallback = typeof requestIdleCallback !== 'undefined';
// 加载预加载清单并处理
function loadPreloadManifest() {
fetch('${manifestName}')
.then(response => {
if (!response.ok) throw new Error('清单加载失败');
return response.json();
})
.then(manifest => {
// 处理预加载队列
handlePreloadQueue(manifest.files);
})
.catch(error => {
console.error('预加载清单处理失败:', error);
});
}
// 处理预加载队列
function handlePreloadQueue(files) {
if (!files || files.length === 0) return;
// 创建预加载元素的函数
function createPreloadElement(filePath) {
const ext = filePath.split('.').pop().toLowerCase();
const link = document.createElement('link');
link.rel = 'preload';
link.crossOrigin = 'anonymous';
link.href = filePath;
// 根据文件类型设置适当的as属性
if (ext === 'css') {
link.as = 'style';
} else if (ext === 'js') {
link.as = 'script';
} else if (['png', 'jpg', 'jpeg', 'webp', 'svg', 'gif'].includes(ext)) {
link.as = 'image';
} else if (['woff', 'woff2', 'ttf', 'eot'].includes(ext)) {
link.as = 'font';
link.crossOrigin = 'anonymous';
}
return link;
}
// 预加载队列处理器
let index = 0;
function processNext() {
if (index >= files.length) return;
const file = files[index];
const link = createPreloadElement(file);
// 监听加载完成事件,继续处理下一个
link.onload = link.onerror = () => {
index++;
// 继续处理下一个资源,使用setTimeout避免阻塞
setTimeout(processNext, 0);
};
document.head.appendChild(link);
}
// 使用闲时加载或延迟加载
if (useIdleCallback) {
requestIdleCallback(processNext, { timeout: 3000 });
} else {
// 不支持requestIdleCallback的浏览器使用延迟加载
setTimeout(processNext, 1000);
}
}
// 页面加载完成后再加载清单,避免影响首屏
if (document.readyState === 'complete') {
loadPreloadManifest();
} else {
window.addEventListener('load', loadPreloadManifest);
}
})();
</script>
`;
// 将脚本注入到指定位置
return html.replace(injectSelector, `${preloadScript}\n${injectSelector}`);
}
};
}
vite.config.ts
import simplestSortedPreloadManifestPlugin from './src/plugins/vite-plugin-simplest-sorted-preload';
// https://vite.dev/config/
export default defineConfig({
base: './',
plugins: [
vue(),
// 使用方式
simplestSortedPreloadManifestPlugin({
outputDir: 'dist',
manifestName: 'resource-manifest.json', // 生成相对于 outputDir 目录的清单文件名
})
]
})

浙公网安备 33010602011771号