feat: init

This commit is contained in:
Innei
2025-06-05 13:27:09 +08:00
commit f841d2ada2
181 changed files with 27788 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
const sortObjectKeys = (obj) => {
if (typeof obj !== 'object' || obj === null) {
return obj
}
if (Array.isArray(obj)) {
return obj.map((element) => sortObjectKeys(element))
}
return Object.keys(obj)
.sort()
.reduce((acc, key) => {
acc[key] = sortObjectKeys(obj[key])
return acc
}, {})
}
/**
* @type {import("eslint").ESLint.Plugin}
*/
export default {
rules: {
'recursive-sort': {
meta: {
type: 'layout',
fixable: 'code',
},
create(context) {
return {
Program(node) {
if (context.getFilename().endsWith('.json')) {
const sourceCode = context.getSourceCode()
const text = sourceCode.getText()
try {
const json = JSON.parse(text)
const sortedJson = sortObjectKeys(json)
const sortedText = JSON.stringify(sortedJson, null, 2)
if (text.trim() !== sortedText.trim()) {
context.report({
node,
message: 'JSON keys are not sorted recursively',
fix(fixer) {
return fixer.replaceText(node, sortedText)
},
})
}
} catch (error) {
context.report({
node,
message: `Invalid JSON: ${error.message}`,
})
}
}
},
}
},
},
},
}

View File

@@ -0,0 +1,98 @@
import type { Plugin } from 'vite'
import { cleanupOldOGImages } from '../scripts/cleanup-og-images.js'
import { generateFavicons } from '../scripts/generate-favicon.js'
import { generateOGImage } from '../scripts/generate-og-image.js'
interface OGImagePluginOptions {
title?: string
description?: string
siteName?: string
siteUrl?: string
}
export function ogImagePlugin(options: OGImagePluginOptions = {}): Plugin {
const {
title = 'Photo Gallery',
description = 'Beautiful photo collection and gallery',
siteName = 'Photo Gallery',
siteUrl,
} = options
let ogImagePath = ''
return {
name: 'og-image-plugin',
async buildStart() {
// 在构建开始时生成 OG 图片
const timestamp = Date.now()
const fileName = `og-image-${timestamp}.png`
try {
// 生成 favicon
await generateFavicons()
// 生成 OG 图片
await generateOGImage({
title,
description,
outputPath: fileName,
includePhotos: true,
photoCount: 4,
})
ogImagePath = `/${fileName}`
console.info(`🖼️ OG image generated: ${ogImagePath}`)
// 清理旧的 OG 图片
await cleanupOldOGImages(3)
} catch (error) {
console.error('Failed to generate OG image:', error)
}
},
transformIndexHtml: {
order: 'pre',
handler(html) {
if (!ogImagePath) {
console.warn('⚠️ No OG image path available')
return html
}
// 生成 meta 标签
const metaTags = `
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="${siteUrl}" />
<meta property="og:title" content="${title}" />
<meta property="og:description" content="${description}" />
<meta property="og:image" content="${siteUrl}${ogImagePath}" />
<meta property="og:site_name" content="${siteName}" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="${siteUrl}" />
<meta property="twitter:title" content="${title}" />
<meta property="twitter:description" content="${description}" />
<meta property="twitter:image" content="${siteUrl}${ogImagePath}" />
<!-- Additional meta tags -->
<meta name="description" content="${description}" />
<meta name="author" content="${siteName}" />
<meta name="generator" content="Vite + React" />
<meta name="robots" content="index, follow" />
<meta name="theme-color" content="#0a0a0a" />
<meta name="msapplication-TileColor" content="#0a0a0a" />
<!-- Favicon and app icons -->
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="shortcut icon" href="/favicon.ico" />
`
// 在 </head> 标签前插入 meta 标签
return html.replace('</head>', `${metaTags}\n </head>`)
},
},
}
}

47
plugins/vite/deps.ts Normal file
View File

@@ -0,0 +1,47 @@
import type { Plugin, UserConfig } from 'vite'
export function createDependencyChunksPlugin(dependencies: string[][]): Plugin {
return {
name: 'dependency-chunks',
config(config: UserConfig) {
config.build = config.build || {}
config.build.rollupOptions = config.build.rollupOptions || {}
config.build.rollupOptions.output =
config.build.rollupOptions.output || {}
const { output } = config.build.rollupOptions
const outputConfig = Array.isArray(output) ? output[0] : output
outputConfig.assetFileNames = 'assets/[name].[hash:6][extname]'
outputConfig.chunkFileNames = (chunkInfo) => {
return chunkInfo.name.startsWith('vendor/')
? '[name]-[hash].js'
: 'assets/[name]-[hash].js'
}
outputConfig.manualChunks = (id: string, { getModuleInfo }) => {
const moduleInfo = getModuleInfo(id)
if (
moduleInfo?.dynamicImporters?.length &&
moduleInfo?.importers?.length
) {
return null
}
const matchedDep = dependencies.findIndex((dep) => {
return dep.some((d) => {
const pattern = `/node_modules/${d}/`
return (
id.includes(pattern) && !id.includes(`${pattern}node_modules/`)
)
})
})
if (matchedDep !== -1) {
return `vendor/${matchedDep}`
}
return null
}
},
}
}