mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
feat: init
This commit is contained in:
60
plugins/eslint-recursive-sort.js
Normal file
60
plugins/eslint-recursive-sort.js
Normal 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}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
98
plugins/og-image-plugin.ts
Normal file
98
plugins/og-image-plugin.ts
Normal 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
47
plugins/vite/deps.ts
Normal 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
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user