-
Notifications
You must be signed in to change notification settings - Fork 9.1k
feat: deploy RSSHub to Cloudflare Workers #20804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
d74b6cf
feat: deploy RSSHub to Cloudflare Workers
DIYgod e1c7b3d
feat(worker): add puppeteer and request-rewriter support
DIYgod bfb5fb6
fix(cache): restore synchronous initialization for tests
DIYgod 1561f22
test(worker): add automated Worker integration tests
DIYgod ab32758
refactor(worker): use test routes for Worker integration tests
DIYgod 7cdcd3b
fix(test): make header-generator test less flaky
DIYgod 1898d37
fix(test): exclude worker tests from vitest coverage run
DIYgod 51ee6d0
test(worker): add automated Worker integration tests
DIYgod 3c94494
fix(worker): fix routes.js alias path in worker build config
DIYgod 31b6ed2
ci: trigger CI re-run
DIYgod 8d0a53a
fix(ci): build worker routes before worker-build
DIYgod ae6bb33
fix(test): increase timeout for Worker integration tests
DIYgod File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,8 @@ node_modules | |
| tmp | ||
| dist | ||
| dist-lib | ||
| dist-worker | ||
| .wrangler | ||
|
|
||
| Session.vim | ||
| combined.log | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // Worker-specific app configuration | ||
| // This is a simplified version of app-bootstrap.tsx for Cloudflare Workers | ||
| // Heavy middleware and API routes are excluded | ||
|
|
||
| import { Hono } from 'hono'; | ||
| import { jsxRenderer } from 'hono/jsx-renderer'; | ||
| import { trimTrailingSlash } from 'hono/trailing-slash'; | ||
|
|
||
| import { errorHandler, notFoundHandler } from '@/errors'; | ||
| import accessControl from '@/middleware/access-control'; | ||
| import debug from '@/middleware/debug'; | ||
| import header from '@/middleware/header'; | ||
| import mLogger from '@/middleware/logger'; | ||
| import template from '@/middleware/template'; | ||
| import trace from '@/middleware/trace'; | ||
| import registry from '@/registry'; | ||
| import { setBrowserBinding } from '@/utils/puppeteer'; | ||
|
|
||
| // Define Worker environment bindings | ||
| type Bindings = { | ||
| BROWSER?: any; // Browser Rendering API binding | ||
| }; | ||
|
|
||
| const app = new Hono<{ Bindings: Bindings }>(); | ||
|
|
||
| // Set browser binding for puppeteer | ||
| app.use(async (c, next) => { | ||
| if (c.env?.BROWSER) { | ||
| setBrowserBinding(c.env.BROWSER); | ||
| } | ||
| await next(); | ||
| }); | ||
|
|
||
| app.use(trimTrailingSlash()); | ||
|
|
||
| // Cloudflare Workers handles compression at the edge, no need for compress() | ||
|
|
||
| app.use( | ||
| jsxRenderer(({ children }) => <>{children}</>, { | ||
| docType: '<?xml version="1.0" encoding="UTF-8"?>', | ||
| stream: {}, | ||
| }) | ||
| ); | ||
| app.use(mLogger); | ||
| app.use(trace); | ||
|
|
||
| // Heavy middleware excluded in Worker build: | ||
| // - sentry: @sentry/node | ||
| // - antiHotlink: cheerio | ||
| // - parameter: cheerio, sanitize-html, @postlight/parser | ||
| // - cache: ioredis | ||
|
|
||
| app.use(accessControl); | ||
| app.use(debug); | ||
| app.use(template); | ||
| app.use(header); | ||
|
|
||
| app.route('/', registry); | ||
|
|
||
| // API routes not available in Worker environment | ||
|
|
||
| app.notFound(notFoundHandler); | ||
| app.onError(errorHandler); | ||
|
|
||
| export default app; |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| // No-op shim for dotenv/config in Cloudflare Workers | ||
| // Environment variables are set via wrangler.toml or wrangler secrets | ||
| // No need to load from .env file |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| // Shim for node:module in Cloudflare Workers | ||
| // Provides a createRequire that returns pre-imported modules | ||
|
|
||
| import * as assert from 'node:assert'; | ||
| import * as async_hooks from 'node:async_hooks'; | ||
| import * as buffer from 'node:buffer'; | ||
| import * as child_process from 'node:child_process'; | ||
| import * as console_module from 'node:console'; | ||
| import * as constants from 'node:constants'; | ||
| import * as crypto from 'node:crypto'; | ||
| import * as diagnostics_channel from 'node:diagnostics_channel'; | ||
| import * as dns from 'node:dns'; | ||
| // For events, we need the default export (EventEmitter class) for CJS compatibility | ||
| // CJS require('events') returns EventEmitter class directly | ||
| import events, * as eventsNamespace from 'node:events'; | ||
| // Pre-import Node.js builtins that CJS modules might require | ||
| import * as fs from 'node:fs'; | ||
| import * as fs_promises from 'node:fs/promises'; | ||
| import * as http from 'node:http'; | ||
| import * as https from 'node:https'; | ||
| import * as net from 'node:net'; | ||
| import * as os from 'node:os'; | ||
| import path from 'node:path'; | ||
| import * as perf_hooks from 'node:perf_hooks'; | ||
| import * as process from 'node:process'; | ||
| import * as punycode from 'node:punycode'; | ||
Check warningCode scanning / ESLint disallow deprecated APIs Warning
'punycode' module was deprecated since v7.0.0. Use 'https://www.npmjs.com/package/punycode' instead.
|
||
| import * as querystring from 'node:querystring'; | ||
| import * as readline from 'node:readline'; | ||
| import * as stream from 'node:stream'; | ||
| import * as stream_promises from 'node:stream/promises'; | ||
| import * as stream_web from 'node:stream/web'; | ||
| import * as string_decoder from 'node:string_decoder'; | ||
| import * as timers from 'node:timers'; | ||
| import * as timers_promises from 'node:timers/promises'; | ||
| import * as tls from 'node:tls'; | ||
| import * as tty from 'node:tty'; | ||
| import * as url from 'node:url'; | ||
| // eslint-disable-next-line unicorn/import-style -- need full util module for CJS compatibility | ||
| import * as util from 'node:util'; | ||
|
||
| import * as util_types from 'node:util/types'; | ||
| import * as worker_threads from 'node:worker_threads'; | ||
| import * as zlib from 'node:zlib'; | ||
|
|
||
| // VM shim for Cloudflare Workers | ||
| // JSDOM and some other libraries require vm module | ||
| class ScriptShim { | ||
| private code: string; | ||
| constructor(code: string) { | ||
| this.code = code; | ||
| } | ||
| runInContext() { | ||
| throw new Error('vm.Script.runInContext is not supported in Workers'); | ||
| } | ||
| runInNewContext() { | ||
| throw new Error('vm.Script.runInNewContext is not supported in Workers'); | ||
| } | ||
| runInThisContext() { | ||
| throw new Error('vm.Script.runInThisContext is not supported in Workers'); | ||
| } | ||
| } | ||
|
|
||
| const vmShim = { | ||
| createContext: (sandbox?: object) => sandbox || {}, | ||
| runInContext: () => { | ||
| throw new Error('vm.runInContext is not supported in Workers'); | ||
| }, | ||
| runInNewContext: () => { | ||
| throw new Error('vm.runInNewContext is not supported in Workers'); | ||
| }, | ||
| runInThisContext: () => { | ||
| throw new Error('vm.runInThisContext is not supported in Workers'); | ||
| }, | ||
| Script: ScriptShim, | ||
| isContext: () => false, | ||
| compileFunction: () => { | ||
| throw new Error('vm.compileFunction is not supported in Workers'); | ||
| }, | ||
| }; | ||
|
|
||
| // Create a CJS-compatible events module | ||
| // In CJS, require('events') returns EventEmitter class directly (the default export) | ||
| // but also has named exports attached to it | ||
| const eventsModule = Object.assign(events, eventsNamespace); | ||
|
|
||
| // Map of module names to their exports | ||
| const builtinModules: Record<string, unknown> = { | ||
| fs, | ||
| path, | ||
|
|
||
| util, | ||
| stream, | ||
| events: eventsModule, | ||
| buffer, | ||
| crypto, | ||
| http, | ||
| https, | ||
| url, | ||
| querystring, | ||
| zlib, | ||
| os, | ||
| assert, | ||
| tty, | ||
| net, | ||
| dns, | ||
| child_process, | ||
| string_decoder, | ||
| timers, | ||
| process, | ||
| perf_hooks, | ||
| async_hooks, | ||
| worker_threads, | ||
| tls, | ||
| readline, | ||
|
|
||
| punycode, | ||
|
|
||
| constants, | ||
| diagnostics_channel, | ||
| console: console_module, | ||
| vm: vmShim, | ||
| // Also support node: prefix | ||
| 'node:fs': fs, | ||
| 'node:path': path, | ||
| 'node:util': util, | ||
| 'node:stream': stream, | ||
| 'node:events': eventsModule, | ||
| 'node:buffer': buffer, | ||
| 'node:crypto': crypto, | ||
| 'node:http': http, | ||
| 'node:https': https, | ||
| 'node:url': url, | ||
| 'node:querystring': querystring, | ||
| 'node:zlib': zlib, | ||
| 'node:os': os, | ||
| 'node:assert': assert, | ||
| 'node:tty': tty, | ||
| 'node:net': net, | ||
| 'node:dns': dns, | ||
| 'node:child_process': child_process, | ||
| 'node:string_decoder': string_decoder, | ||
| 'node:timers': timers, | ||
| 'node:process': process, | ||
| 'node:perf_hooks': perf_hooks, | ||
| 'node:async_hooks': async_hooks, | ||
| 'node:worker_threads': worker_threads, | ||
| 'node:tls': tls, | ||
| 'node:readline': readline, | ||
| 'node:punycode': punycode, | ||
| 'node:constants': constants, | ||
| 'node:diagnostics_channel': diagnostics_channel, | ||
| 'node:console': console_module, | ||
| 'node:fs/promises': fs_promises, | ||
| 'fs/promises': fs_promises, | ||
| 'node:stream/promises': stream_promises, | ||
| 'stream/promises': stream_promises, | ||
| 'node:stream/web': stream_web, | ||
| 'stream/web': stream_web, | ||
| 'node:util/types': util_types, | ||
| 'util/types': util_types, | ||
| 'node:timers/promises': timers_promises, | ||
| 'timers/promises': timers_promises, | ||
| 'node:vm': vmShim, | ||
| }; | ||
|
|
||
| export function createRequire(_filename: string | URL) { | ||
| return function require(id: string): unknown { | ||
| if (id in builtinModules) { | ||
| return builtinModules[id]; | ||
| } | ||
| // For non-builtin modules, throw an error | ||
| throw new Error(`require() is not available in Workers. Attempted to require: ${id}`); | ||
| }; | ||
| } | ||
|
|
||
| export default { | ||
| createRequire, | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| // No-op shim for @sentry/node in Cloudflare Workers | ||
| export const withScope = (callback: (scope: unknown) => void) => callback({}); | ||
| export const captureException = () => {}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Worker-specific cache module - no-op implementation | ||
| // This file is used instead of index.ts when building for Cloudflare Workers | ||
|
|
||
| import { config } from '@/config'; | ||
|
|
||
| import type CacheModule from './base'; | ||
|
|
||
| const globalCache: { | ||
| get: (key: string) => Promise<string | null | undefined> | string | null | undefined; | ||
| set: (key: string, value?: string | Record<string, any>, maxAge?: number) => any; | ||
| } = { | ||
| get: () => null, | ||
| set: () => null, | ||
| }; | ||
|
|
||
| // No-op cache module for Worker | ||
| const cacheModule: CacheModule = { | ||
| init: () => null, | ||
| get: () => null, | ||
| set: () => null, | ||
| status: { | ||
| available: false, | ||
| }, | ||
| clients: {}, | ||
| }; | ||
|
|
||
| export default { | ||
| ...cacheModule, | ||
| /** | ||
| * Try to get the cache. If the cache does not exist, the `getValueFunc` function will be called to get the data, and the data will be cached. | ||
| * @param key The key used to store and retrieve the cache. You can use `:` as a separator to create a hierarchy. | ||
| * @param getValueFunc A function that returns data to be cached when a cache miss occurs. | ||
| * @param maxAge The maximum age of the cache in seconds. This should left to the default value in most cases which is `CACHE_CONTENT_EXPIRE`. | ||
| * @param refresh Whether to renew the cache expiration time when the cache is hit. `true` by default. | ||
| * @returns | ||
| */ | ||
| tryGet: async <T extends string | Record<string, any>>(key: string, getValueFunc: () => Promise<T>, _maxAge = config.cache.contentExpire, _refresh = true) => { | ||
| if (typeof key !== 'string') { | ||
| throw new TypeError('Cache key must be a string'); | ||
| } | ||
| // In Worker environment, always call getValueFunc since cache is not available | ||
| const value = await getValueFunc(); | ||
| return value; | ||
| }, | ||
| globalCache, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // No-op shim for directory-import in Cloudflare Workers | ||
| // directoryImport is only used in dev mode, Worker builds use pre-built routes | ||
|
|
||
| export type DirectoryImportOptions = { | ||
| targetDirectoryPath: string; | ||
| importPattern?: RegExp; | ||
| includeSubdirectories?: boolean; | ||
| }; | ||
|
|
||
| export const directoryImport = (_options: DirectoryImportOptions): Record<string, unknown> => { | ||
| // This should never be called in Worker builds | ||
| // Worker builds use pre-built routes from routes-worker.js | ||
| throw new Error('directoryImport is not available in Worker builds'); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| // Runtime detection of Cloudflare Workers environment | ||
| // Workers have specific global objects like caches and WebSocketPair | ||
| export const isWorker = globalThis.caches !== undefined && (globalThis as unknown as Record<string, unknown>).WebSocketPair !== undefined; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // In Worker build, isWorker is always true | ||
| export const isWorker = true; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / ESLint
disallow deprecated APIs Warning