chore: add rollup-glob

This commit is contained in:
Ou 2024-10-25 03:06:03 +08:00
parent ae57261fb9
commit 3535cdb84a
13 changed files with 177 additions and 83 deletions

View File

@ -62,6 +62,7 @@
"@iconify-json/ph": "^1.2.1",
"@ourongxing/eslint-config": "3.2.3-beta.6",
"@ourongxing/tsconfig": "^0.0.4",
"@rollup/pluginutils": "^5.1.3",
"@tanstack/react-query": "^5.59.9",
"@tanstack/router-devtools": "^1.64.0",
"@tanstack/router-plugin": "^1.64.0",
@ -75,11 +76,13 @@
"eslint": "^9.12.0",
"eslint-plugin-react-hooks": "^5.1.0-rc-77f43893-20241010",
"eslint-plugin-react-refresh": "^0.4.12",
"fast-glob": "^3.3.2",
"favicons-scraper": "^1.3.2",
"lint-staged": "^15.2.10",
"mlly": "^1.7.2",
"mockdate": "^3.0.5",
"pnpm-patch-i": "^0.4.1",
"rollup": "^4.24.0",
"simple-git-hooks": "^2.11.1",
"tsx": "^4.19.1",
"typescript": "^5.6.3",

9
pnpm-lock.yaml generated
View File

@ -120,6 +120,9 @@ importers:
'@ourongxing/tsconfig':
specifier: ^0.0.4
version: 0.0.4
'@rollup/pluginutils':
specifier: ^5.1.3
version: 5.1.3(rollup@4.24.0)
'@tanstack/react-query':
specifier: ^5.59.9
version: 5.59.16(react@18.3.1)
@ -159,6 +162,9 @@ importers:
eslint-plugin-react-refresh:
specifier: ^0.4.12
version: 0.4.13(eslint@9.13.0(jiti@2.3.3))
fast-glob:
specifier: ^3.3.2
version: 3.3.2
favicons-scraper:
specifier: ^1.3.2
version: 1.3.2
@ -174,6 +180,9 @@ importers:
pnpm-patch-i:
specifier: ^0.4.1
version: 0.4.1
rollup:
specifier: ^4.24.0
version: 4.24.0
simple-git-hooks:
specifier: ^2.11.1
version: 2.11.1

View File

@ -2,7 +2,7 @@ import process from "node:process"
import { TTL } from "@shared/consts"
import type { SourceID, SourceResponse } from "@shared/types"
import { sources } from "@shared/sources"
import { sourcesGetters } from "#/sources"
import { getters } from "#/getters"
import { useCache } from "#/hooks/useCache"
export default defineEventHandler(async (event): Promise<SourceResponse> => {
@ -10,7 +10,7 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
let id = getRouterParam(event, "id") as SourceID
const query = getQuery(event)
const latest = query.latest !== undefined && query.latest !== "false"
const isValid = (id: SourceID) => !id || !sources[id] || !sourcesGetters[id]
const isValid = (id: SourceID) => !id || !sources[id] || !getters[id]
if (isValid(id)) {
const redirectID = sources?.[id]?.redirect
@ -54,7 +54,7 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
}
}
const data = (await sourcesGetters[id]()).slice(0, 30)
const data = (await getters[id]()).slice(0, 30)
logger.success(`fetch ${id} latest`)
if (cacheTable) {
if (event.context.waitUntil) event.context.waitUntil(cacheTable.set(id, data))

16
server/getters.ts Normal file
View File

@ -0,0 +1,16 @@
import { typeSafeObjectEntries } from "@shared/type.util"
import type { SourceID } from "@shared/types"
import * as x from "glob:./sources/{*.ts,**/index.ts}"
import type { SourceGetter } from "./types"
export const getters = (function () {
const getters = {} as Record<SourceID, SourceGetter>
typeSafeObjectEntries(x).forEach(([id, x]) => {
if (x.default instanceof Function) {
Object.assign(getters, { [id]: x.default })
} else {
Object.assign(getters, x.default)
}
})
return getters
})()

22
server/glob.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
/* eslint-disable */
declare module 'glob:./sources/{*.ts,**/index.ts}' {
export const _36kr: typeof import('./sources/_36kr')
export const cankaoxiaoxi: typeof import('./sources/cankaoxiaoxi')
export const cls: typeof import('./sources/cls/index')
export const coolapk: typeof import('./sources/coolapk/index')
export const douyin: typeof import('./sources/douyin')
export const fastbull: typeof import('./sources/fastbull')
export const gelonghui: typeof import('./sources/gelonghui')
export const ithome: typeof import('./sources/ithome')
export const sputniknewscn: typeof import('./sources/sputniknewscn')
export const thepaper: typeof import('./sources/thepaper')
export const tieba: typeof import('./sources/tieba')
export const toutiao: typeof import('./sources/toutiao')
export const v2ex: typeof import('./sources/v2ex')
export const wallstreetcn: typeof import('./sources/wallstreetcn')
export const weibo: typeof import('./sources/weibo')
export const xueqiu: typeof import('./sources/xueqiu')
export const zaobao: typeof import('./sources/zaobao')
export const zhihu: typeof import('./sources/zhihu')
}

View File

@ -20,19 +20,21 @@ interface Res {
}[]
}
export default defineSource(async () => {
const url = "https://api.coolapk.com/v6/page/dataList?url=%2Ffeed%2FstatList%3FcacheExpires%3D300%26statType%3Dday%26sortField%3Ddetailnum%26title%3D%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&title=%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&subTitle=&page=1"
const r: Res = await $fetch(url, {
headers: await genHeaders(),
})
if (!r.data.length) throw new Error("Failed to fetch")
return r.data.filter(k => k.id).map(i => ({
id: i.id,
title: i.editor_title || load(i.message).text().split("\n")[0],
url: i.shareUrl,
extra: {
info: i.targetRow?.subTitle,
// date: new Date(i.dateline * 1000).getTime(),
},
})).slice(0, 30)
export default defineSource({
coolapk: async () => {
const url = "https://api.coolapk.com/v6/page/dataList?url=%2Ffeed%2FstatList%3FcacheExpires%3D300%26statType%3Dday%26sortField%3Ddetailnum%26title%3D%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&title=%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&subTitle=&page=1"
const r: Res = await $fetch(url, {
headers: await genHeaders(),
})
if (!r.data.length) throw new Error("Failed to fetch")
return r.data.filter(k => k.id).map(i => ({
id: i.id,
title: i.editor_title || load(i.message).text().split("\n")[0],
url: i.shareUrl,
extra: {
info: i.targetRow?.subTitle,
// date: new Date(i.dateline * 1000).getTime(),
},
}))
},
})

View File

@ -17,12 +17,11 @@ export default defineSource(async () => {
cookie: cookie.join("; "),
},
})
return res.data.word_list
.map((k) => {
return {
id: k.sentence_id,
title: k.word,
url: `https://www.douyin.com/hot/${k.sentence_id}`,
}
})
return res.data.word_list.map((k) => {
return {
id: k.sentence_id,
title: k.word,
url: `https://www.douyin.com/hot/${k.sentence_id}`,
}
})
})

View File

@ -1,41 +0,0 @@
import type { DisabledSourceID, SourceID } from "@shared/types"
import weibo from "./weibo"
import zaobao from "./zaobao"
import v2ex from "./v2ex"
import ithome from "./ithome"
import zhihu from "./zhihu"
import cankaoxiaoxi from "./cankaoxiaoxi"
import coolapk from "./coolapk"
import kr36 from "./36kr"
import wallstreetcn from "./wallstreetcn"
import douyin from "./douyin"
import toutiao from "./toutiao"
import cls from "./cls"
import sputniknewscn from "./sputniknewscn"
import xueqiu from "./xueqiu"
import gelonghui from "./gelonghui"
import tieba from "./tieba"
import thepaper from "./thepaper"
import fastbull from "./fastbull"
import type { SourceGetter } from "#/types"
export const sourcesGetters = {
weibo,
zaobao,
...v2ex,
ithome,
zhihu,
coolapk,
cankaoxiaoxi,
thepaper,
sputniknewscn,
...fastbull,
...wallstreetcn,
...xueqiu,
gelonghui,
douyin,
...cls,
toutiao,
tieba,
...kr36,
} as Record<SourceID, SourceGetter> & Partial<Record<DisabledSourceID, SourceGetter>>

View File

@ -19,19 +19,21 @@ interface Res {
}[]
}
export default defineSource(async () => {
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=20&desktop=true"
const res: Res = await $fetch(url)
return res.data
.slice(0, 30)
.map((k) => {
return {
id: k.target.id,
title: k.target.title,
extra: {
icon: k.card_label?.night_icon,
},
url: `https://www.zhihu.com/question/${k.target.id}`,
}
})
export default defineSource({
zhihu: async () => {
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=20&desktop=true"
const res: Res = await $fetch(url)
return res.data
.slice(0, 30)
.map((k) => {
return {
id: k.target.id,
title: k.target.title,
extra: {
icon: k.card_label?.night_icon,
},
url: `https://www.zhihu.com/question/${k.target.id}`,
}
})
},
})

78
tools/rollup-glob.ts Normal file
View File

@ -0,0 +1,78 @@
import path from "node:path"
import { writeFile } from "node:fs/promises"
import type { Plugin } from "rollup"
import glob from "fast-glob"
import type { FilterPattern } from "@rollup/pluginutils"
import { createFilter, normalizePath } from "@rollup/pluginutils"
import { projectDir } from "../shared/dir"
const ID_PREFIX = "glob:"
const root = path.join(projectDir, "server")
type GlobMap = Record<string /* name:pattern */, string[]>
export function RollopGlob(): Plugin {
const map: GlobMap = {}
const include: FilterPattern = []
const exclude: FilterPattern = []
const filter = createFilter(include, exclude)
return {
name: "rollup-glob",
resolveId(id, src) {
if (!id.startsWith(ID_PREFIX)) return
if (!src || !filter(src)) return
return `${id}:${encodeURIComponent(src)}`
},
async load(id) {
if (!id.startsWith(ID_PREFIX)) return
const [_, pattern, encodePath] = id.split(":")
const currentPath = decodeURIComponent(encodePath)
const files = (
await glob(pattern, {
cwd: currentPath ? path.dirname(currentPath) : root,
absolute: true,
})
)
.map(file => normalizePath(file))
.filter(file => file !== normalizePath(currentPath))
.sort()
map[pattern] = files
const contents = files.map((file) => {
const r = file.replace("/index", "")
const name = path.basename(r, path.extname(r))
return `export * as ${name} from '${file}'\n`
}).join("\n")
await writeTypeDeclaration(map, path.join(root, "glob"))
return `${contents}\n`
},
}
}
async function writeTypeDeclaration(map: GlobMap, filename: string) {
function relatePath(filepath: string) {
return normalizePath(path.relative(path.dirname(filename), filepath))
}
let declare = `/* eslint-disable */\n\n`
const sortedEntries = Object.entries(map).sort(([a], [b]) =>
a.localeCompare(b),
)
for (const [_idx, [id, files]] of sortedEntries.entries()) {
declare += `declare module '${ID_PREFIX}${id}' {\n`
for (const file of files) {
const relative = `./${relatePath(file)}`.replace(/\.tsx?$/, "")
const r = file.replace("/index", "")
const fileName = path.basename(r, path.extname(r))
declare += ` export const ${fileName}: typeof import('${relative}')\n`
}
declare += `}\n`
}
await writeFile(`${filename}.d.ts`, declare, "utf-8")
}

View File

@ -8,5 +8,5 @@
"@shared/*": ["shared/*"]
}
},
"include": ["server", "*.config.*", "shared", "test", "scripts", "dist/.nitro/types"]
"include": ["server", "*.config.*", "shared", "test", "scripts", "tools", "dist/.nitro/types"]
}

View File

@ -10,6 +10,7 @@ import dotenv from "dotenv"
import type { VitePWAOptions } from "vite-plugin-pwa"
import { VitePWA } from "vite-plugin-pwa"
import { projectDir } from "./shared/dir"
import { RollopGlob } from "./tools/rollup-glob"
dotenv.config({
path: join(projectDir, ".env.server"),
@ -62,6 +63,9 @@ const nitroOption: Parameters<typeof nitro>[0] = {
experimental: {
database: true,
},
rollupConfig: {
plugins: [RollopGlob()],
},
sourceMap: false,
database: {
default: {