DIYgod/RSSHub · error · Error
JavaScript file not found.
Error message
JavaScript file not found.
What it means
Thrown when the scoop.sh index HTML contains no <script type="module" crossorigin src="..."> tag from which the route extracts the JS bundle URL. The route scrapes the JS bundle to recover the embedded Azure Search API key, so a missing module script aborts the whole flow.
Source
Thrown at lib/routes/scoop/apps.tsx:52
export const handler = async (ctx: Context): Promise<Data> => {
const { query = 's=2&d=1&n=true&dm=true&o=true' } = ctx.req.param();
const limit = Number(ctx.req.query('limit') ?? '50');
const baseUrl = 'https://scoop.sh';
const apiBaseUrl = 'https://scoopsearch.search.windows.net';
const targetUrl: string = new URL(`/#/apps?${query}`, baseUrl).href;
const apiUrl: string = new URL('indexes/apps/docs/search', apiBaseUrl).href;
const targetResponse = await ofetch(targetUrl);
const $: CheerioAPI = load(targetResponse);
const language = $('html').attr('lang') ?? 'en';
const scriptRegExp = /<script type="module" crossorigin src="(.*?)"><\/script>/;
const scriptUrl: string = scriptRegExp.test(targetResponse) ? new URL(targetResponse.match(scriptRegExp)?.[1], baseUrl).href : '';
if (!scriptUrl) {
throw new Error('JavaScript file not found.');
}
const scriptResponse = await ofetch(scriptUrl, {
parseResponse: (txt) => txt,
});
const key: string = scriptResponse.match(/VITE_APP_AZURESEARCH_KEY:"(.*?)"/)?.[1];
if (!key) {
throw new Error('Key not found.');
}
const isOffcial = !query.includes('o=false');
const isDistinct = !query.includes('dm=false');
const sort: string = query.match(/s=(\d+)/)?.[1] ?? '2';
const desc: string = query.match(/d=(\d+)/)?.[1] ?? '1';
const response = await ofetch(apiUrl, {View on GitHub (pinned to bed535e087)
Solutions
- View-source https://scoop.sh and confirm the module script tag's current attributes.
- Update scriptRegExp to match the new tag shape (e.g. dropped 'crossorigin', changed type).
- If an anti-bot page is served, add browser-like headers via config.trueUA.
- Detect a non-HTML response early and throw a clearer 'unexpected response type' error.
Example fix
// before
const scriptRegExp = /<script type="module" crossorigin src="(.*?)"><\/script>/;
const scriptUrl: string = scriptRegExp.test(targetResponse) ? new URL(targetResponse.match(scriptRegExp)?.[1], baseUrl).href : '';
if (!scriptUrl) {
throw new Error('JavaScript file not found.');
}
// after — tolerate the optional crossorigin attribute
const scriptRegExp = /<script[^>]*type="module"[^>]*src="([^"]+)"[^>]*><\/script>/;
const scriptUrl = new URL(targetResponse.match(scriptRegExp)?.[1] ?? '', baseUrl).href;
if (!scriptUrl) {
throw new Error('JavaScript module bundle not found in scoop.sh index HTML');
} Defensive patterns
Strategy: fallback
Validate before calling
// Tolerate attribute changes by matching the module script more loosely.
function extractScriptUrl(html: string, baseUrl: string): string {
const m = html.match(/<script[^>]*\btype="module"[^>]*\bsrc="([^"]+)"/);
return m ? new URL(m[1], baseUrl).href : '';
} Type guard
const isHtmlWithModuleScript = (s: string): boolean =>
/<script[^>]*type="module"[^>]*src=/.test(s); Try / catch
let scriptUrl = extractScriptUrl(targetResponse, baseUrl);
if (!scriptUrl) {
// retry once with a browser UA in case an anti-bot page was served
const retry = await ofetch(targetUrl, { headers: { 'user-agent': config.trueUA }, parseResponse: (t) => t });
scriptUrl = extractScriptUrl(retry, baseUrl);
}
if (!scriptUrl) throw new Error('JavaScript module bundle not found in scoop.sh index HTML'); Prevention
- Match the module script by the stable 'type="module"' attribute rather than the volatile 'crossorigin'.
- Send config.trueUA to avoid lightweight anti-bot interstitials.
- Snapshot the index HTML in a fixture to catch structural changes.
When it happens
Trigger: ofetch(targetUrl) returns HTML where scriptRegExp.test(targetResponse) is false — i.e. the Vite module script tag is absent (renamed attribute, different bundler, or an anti-bot page was returned). scriptUrl stays '' and the guard throws.
Common situations: scoop.sh rebuilt its frontend and changed the script tag attributes (no longer 'module crossorigin'); a CDN/anti-bot layer returned a challenge page instead of the app shell; the response was gzip/binary and the regex ran against a non-string (mitigated by ofetch default text handling).
Related errors
- Unable to extract creator ID
- Key not found.
- Failed to retrieve JSON beatmap info from osu! website
- Cannot find the script with data-iso-key="_0"
- 无法获取最新的 year 和 issue
AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12).
Data as JSON: /api/errors/61631c3e8183e6e8.
Report an issue: GitHub.