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

9
pnpm-lock.yaml generated
View File

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

View File

@ -2,7 +2,7 @@ import process from "node:process"
import { TTL } from "@shared/consts" import { TTL } from "@shared/consts"
import type { SourceID, SourceResponse } from "@shared/types" import type { SourceID, SourceResponse } from "@shared/types"
import { sources } from "@shared/sources" import { sources } from "@shared/sources"
import { sourcesGetters } from "#/sources" import { getters } from "#/getters"
import { useCache } from "#/hooks/useCache" import { useCache } from "#/hooks/useCache"
export default defineEventHandler(async (event): Promise<SourceResponse> => { 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 let id = getRouterParam(event, "id") as SourceID
const query = getQuery(event) const query = getQuery(event)
const latest = query.latest !== undefined && query.latest !== "false" 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)) { if (isValid(id)) {
const redirectID = sources?.[id]?.redirect 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`) logger.success(`fetch ${id} latest`)
if (cacheTable) { if (cacheTable) {
if (event.context.waitUntil) event.context.waitUntil(cacheTable.set(id, data)) 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,7 +20,8 @@ interface Res {
}[] }[]
} }
export default defineSource(async () => { 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 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, { const r: Res = await $fetch(url, {
headers: await genHeaders(), headers: await genHeaders(),
@ -34,5 +35,6 @@ export default defineSource(async () => {
info: i.targetRow?.subTitle, info: i.targetRow?.subTitle,
// date: new Date(i.dateline * 1000).getTime(), // date: new Date(i.dateline * 1000).getTime(),
}, },
})).slice(0, 30) }))
},
}) })

View File

@ -17,8 +17,7 @@ export default defineSource(async () => {
cookie: cookie.join("; "), cookie: cookie.join("; "),
}, },
}) })
return res.data.word_list return res.data.word_list.map((k) => {
.map((k) => {
return { return {
id: k.sentence_id, id: k.sentence_id,
title: k.word, title: k.word,

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,7 +19,8 @@ interface Res {
}[] }[]
} }
export default defineSource(async () => { export default defineSource({
zhihu: async () => {
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=20&desktop=true" const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=20&desktop=true"
const res: Res = await $fetch(url) const res: Res = await $fetch(url)
return res.data return res.data
@ -34,4 +35,5 @@ export default defineSource(async () => {
url: `https://www.zhihu.com/question/${k.target.id}`, 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/*"] "@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 type { VitePWAOptions } from "vite-plugin-pwa"
import { VitePWA } from "vite-plugin-pwa" import { VitePWA } from "vite-plugin-pwa"
import { projectDir } from "./shared/dir" import { projectDir } from "./shared/dir"
import { RollopGlob } from "./tools/rollup-glob"
dotenv.config({ dotenv.config({
path: join(projectDir, ".env.server"), path: join(projectDir, ".env.server"),
@ -62,6 +63,9 @@ const nitroOption: Parameters<typeof nitro>[0] = {
experimental: { experimental: {
database: true, database: true,
}, },
rollupConfig: {
plugins: [RollopGlob()],
},
sourceMap: false, sourceMap: false,
database: { database: {
default: { default: {