perf: optimize blog assets for low bandwidth
This commit is contained in:
@@ -8,12 +8,16 @@ server {
|
|||||||
gzip on;
|
gzip on;
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_min_length 1024;
|
gzip_min_length 1024;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_proxied any;
|
||||||
gzip_types
|
gzip_types
|
||||||
application/javascript
|
application/javascript
|
||||||
application/json
|
application/json
|
||||||
|
application/rss+xml
|
||||||
application/xml
|
application/xml
|
||||||
image/svg+xml
|
image/svg+xml
|
||||||
text/css
|
text/css
|
||||||
|
text/javascript
|
||||||
text/plain
|
text/plain
|
||||||
text/xml;
|
text/xml;
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -26,7 +26,7 @@ function formatBytes(bytes) {
|
|||||||
async function optimizeImage(inputPath, outputPath, options = {}) {
|
async function optimizeImage(inputPath, outputPath, options = {}) {
|
||||||
const { width, quality = 80, outputExt = '.webp' } = options;
|
const { width, quality = 80, outputExt = '.webp' } = options;
|
||||||
const originalSize = await getFileSize(inputPath);
|
const originalSize = await getFileSize(inputPath);
|
||||||
|
|
||||||
let pipeline = sharp(inputPath);
|
let pipeline = sharp(inputPath);
|
||||||
|
|
||||||
if (width) {
|
if (width) {
|
||||||
@@ -40,19 +40,20 @@ async function optimizeImage(inputPath, outputPath, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await pipeline.toBuffer();
|
const buffer = await pipeline.toBuffer();
|
||||||
|
const optimizedSize = buffer.length;
|
||||||
// For standard images, if webp is larger, we'll keep it if it's the specific target like bgImage
|
|
||||||
// but for general article images we might skip.
|
// Threshold: webp must be at least 10% smaller than original
|
||||||
const isTargeted = inputPath.includes(SOURCE_DIR);
|
const isTargeted = inputPath.includes(SOURCE_DIR);
|
||||||
if (!isTargeted && buffer.length >= originalSize && outputExt === '.webp') {
|
const reductionRatio = (originalSize - optimizedSize) / originalSize;
|
||||||
console.log(`Skipping ${path.basename(inputPath)}: Optimized size (${formatBytes(buffer.length)}) >= original (${formatBytes(originalSize)})`);
|
|
||||||
|
if (!isTargeted && reductionRatio < 0.1 && outputExt === '.webp') {
|
||||||
|
console.log(`Skipping ${path.basename(inputPath)}: Reduction ${ (reductionRatio * 100).toFixed(2) }% < 10% threshold`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||||
await fs.writeFile(outputPath, buffer);
|
await fs.writeFile(outputPath, buffer);
|
||||||
const optimizedSize = buffer.length;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputPath,
|
inputPath,
|
||||||
outputPath,
|
outputPath,
|
||||||
@@ -60,7 +61,7 @@ async function optimizeImage(inputPath, outputPath, options = {}) {
|
|||||||
outputName: path.basename(outputPath),
|
outputName: path.basename(outputPath),
|
||||||
originalSize,
|
originalSize,
|
||||||
optimizedSize,
|
optimizedSize,
|
||||||
reduction: ((originalSize - optimizedSize) / originalSize * 100).toFixed(2) + '%'
|
reduction: (reductionRatio * 100).toFixed(2) + '%'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +90,16 @@ async function main() {
|
|||||||
if (res) results.push(res);
|
if (res) results.push(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Optimize Article Images (In-place WebP generation)
|
// 4. Optimize Cover Images in Public Assets
|
||||||
|
const coverImages = await glob('src/.vuepress/public/assets/images/cover*.{jpg,jpeg,png}');
|
||||||
|
console.log(`Found ${coverImages.length} cover images to process...`);
|
||||||
|
for (const img of coverImages) {
|
||||||
|
const outputWebp = img.replace(path.extname(img), '.webp');
|
||||||
|
const res = await optimizeImage(img, outputWebp, { quality: 80 });
|
||||||
|
if (res) results.push(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Optimize Article Images (In-place WebP generation)
|
||||||
const articleImages = await glob('src/**/*.{jpg,jpeg,png}', {
|
const articleImages = await glob('src/**/*.{jpg,jpeg,png}', {
|
||||||
ignore: ['src/.vuepress/dist/**', 'src/.vuepress/public/**']
|
ignore: ['src/.vuepress/dist/**', 'src/.vuepress/public/**']
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export default defineUserConfig({
|
|||||||
|
|
||||||
head: [['link', { rel: 'icon', href: '/favicon.png' }]],
|
head: [['link', { rel: 'icon', href: '/favicon.png' }]],
|
||||||
|
|
||||||
|
shouldPrefetch: false,
|
||||||
|
shouldPreload: false,
|
||||||
|
|
||||||
theme,
|
theme,
|
||||||
bundler: viteBundler({
|
bundler: viteBundler({
|
||||||
viteOptions: {
|
viteOptions: {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -116,7 +116,14 @@ export default hopeTheme(
|
|||||||
blog: true,
|
blog: true,
|
||||||
|
|
||||||
// 启用本地搜索
|
// 启用本地搜索
|
||||||
search: true,
|
search: {
|
||||||
|
isSearchable: (page) =>
|
||||||
|
!page.path.includes('/work/log/') &&
|
||||||
|
!page.path.includes('/demo/') &&
|
||||||
|
!page.path.includes('/accounts/') &&
|
||||||
|
!page.path.includes('SpringSecurity'),
|
||||||
|
maxSuggestions: 10,
|
||||||
|
},
|
||||||
|
|
||||||
// 组件配置
|
// 组件配置
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 账号密码管理
|
title: 账号密码管理
|
||||||
index: false
|
index: false
|
||||||
search: false
|
|
||||||
navbar: false
|
navbar: false
|
||||||
sidebar: false
|
sidebar: false
|
||||||
breadcrumb: false
|
breadcrumb: false
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
icon: circle-info
|
icon: circle-info
|
||||||
cover: /assets/images/cover3.jpg
|
cover: /assets/images/cover3.webp
|
||||||
date: 2025-05-06
|
date: 2025-05-06
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user