Chore: remove rss helper from getStaticPaths
(#3462)
* chore: remove rss() with helpful error message * docs: add context on "getStaticPaths" removal * chore: changeset * deps: remove fast-xml-parser from core! * chore: update lockfile
This commit is contained in:
parent
b795a085f0
commit
d145b8689c
9 changed files with 323 additions and 564 deletions
5
.changeset/modern-readers-ring.md
Normal file
5
.changeset/modern-readers-ring.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Remove the rss() helper from getStaticPaths. Using rss() now prints an error pointing to the new @astrojs/rss documentation.
|
|
@ -101,7 +101,6 @@
|
|||
"estree-walker": "^3.0.1",
|
||||
"execa": "^6.1.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"fast-xml-parser": "^4.0.7",
|
||||
"gray-matter": "^4.0.3",
|
||||
"html-entities": "^2.3.3",
|
||||
"html-escaper": "^3.0.3",
|
||||
|
|
|
@ -775,7 +775,11 @@ export type GetHydrateCallback = () => Promise<
|
|||
* Docs: https://docs.astro.build/reference/api-reference/#getstaticpaths
|
||||
*/ export interface GetStaticPathsOptions {
|
||||
paginate: PaginateFunction;
|
||||
rss: (...args: any[]) => any;
|
||||
/**
|
||||
* The RSS helper has been removed from getStaticPaths! Try the new @astrojs/rss package instead.
|
||||
* @see https://docs.astro.build/en/guides/rss/
|
||||
*/
|
||||
rss(): never;
|
||||
}
|
||||
|
||||
export type GetStaticPathsItem = { params: Params; props?: Props };
|
||||
|
@ -986,51 +990,6 @@ export type SerializedRouteData = Omit<RouteData, 'generate' | 'pattern'> & {
|
|||
|
||||
export type RuntimeMode = 'development' | 'production';
|
||||
|
||||
/**
|
||||
* RSS
|
||||
* Docs: https://docs.astro.build/reference/api-reference/#rss
|
||||
*/
|
||||
export interface RSS {
|
||||
/** (required) Title of the RSS Feed */
|
||||
title: string;
|
||||
/** (required) Description of the RSS Feed */
|
||||
description: string;
|
||||
/** Specify arbitrary metadata on opening <xml> tag */
|
||||
xmlns?: Record<string, string>;
|
||||
/**
|
||||
* If false (default), does not include XSL stylesheet.
|
||||
* If true, automatically includes 'pretty-feed-v3'.
|
||||
* If a string value, specifies a local custom XSL stylesheet, for example '/custom-feed.xsl'.
|
||||
*/
|
||||
stylesheet?: string | boolean;
|
||||
/** Specify custom data in opening of file */
|
||||
customData?: string;
|
||||
/**
|
||||
* Specify where the RSS xml file should be written.
|
||||
* Relative to final build directory. Example: '/foo/bar.xml'
|
||||
* Defaults to '/rss.xml'.
|
||||
*/
|
||||
dest?: string;
|
||||
/** Return data about each item */
|
||||
items: {
|
||||
/** (required) Title of item */
|
||||
title: string;
|
||||
/** (required) Link to item */
|
||||
link: string;
|
||||
/** Publication date of item */
|
||||
pubDate?: Date;
|
||||
/** Item description */
|
||||
description?: string;
|
||||
/** Append some other XML-valid data to this item */
|
||||
customData?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type RSSFunction = (args: RSS) => RSSResult;
|
||||
|
||||
export type FeedResult = { url: string; content?: string };
|
||||
export type RSSResult = { xml: FeedResult; xsl?: FeedResult };
|
||||
|
||||
export type SSRError = Error & vite.ErrorPayload['err'];
|
||||
|
||||
export interface SSRElement {
|
||||
|
|
|
@ -8,7 +8,6 @@ import { fileURLToPath } from 'url';
|
|||
import * as colors from 'kleur/colors';
|
||||
import { debug } from '../logger/core.js';
|
||||
import { preload as ssrPreload } from '../render/dev/index.js';
|
||||
import { generateRssFunction } from '../render/rss.js';
|
||||
import { callGetStaticPaths, RouteCache, RouteCacheEntry } from '../render/route-cache.js';
|
||||
import { removeTrailingForwardSlash } from '../path.js';
|
||||
import { isBuildingToSSR } from '../util.js';
|
||||
|
@ -114,32 +113,6 @@ export async function collectPagesData(
|
|||
debug('build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
|
||||
throw err;
|
||||
});
|
||||
const rssFn = generateRssFunction(astroConfig.site, route);
|
||||
for (const rssCallArg of result.rss) {
|
||||
const rssResult = rssFn(rssCallArg);
|
||||
if (rssResult.xml) {
|
||||
const { url, content } = rssResult.xml;
|
||||
if (content) {
|
||||
const rssFile = new URL(url.replace(/^\/?/, './'), astroConfig.outDir);
|
||||
if (assets[fileURLToPath(rssFile)]) {
|
||||
throw new Error(
|
||||
`[getStaticPaths] RSS feed ${url} already exists.\nUse \`rss(data, {url: '...'})\` to choose a unique, custom URL. (${route.component})`
|
||||
);
|
||||
}
|
||||
assets[fileURLToPath(rssFile)] = content;
|
||||
}
|
||||
}
|
||||
if (rssResult.xsl?.content) {
|
||||
const { url, content } = rssResult.xsl;
|
||||
const stylesheetFile = new URL(url.replace(/^\/?/, './'), astroConfig.outDir);
|
||||
if (assets[fileURLToPath(stylesheetFile)]) {
|
||||
throw new Error(
|
||||
`[getStaticPaths] RSS feed stylesheet ${url} already exists.\nUse \`rss(data, {stylesheet: '...'})\` to choose a unique, custom URL. (${route.component})`
|
||||
);
|
||||
}
|
||||
assets[fileURLToPath(stylesheetFile)] = content;
|
||||
}
|
||||
}
|
||||
const finalPaths = result.staticPaths
|
||||
.map((staticPath) => staticPath.params && route.generate(staticPath.params))
|
||||
.filter((staticPath) => {
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface SSROptions {
|
|||
logging: LogOptions;
|
||||
/** "development" or "production" */
|
||||
mode: RuntimeMode;
|
||||
/** production website, needed for some RSS functions */
|
||||
/** production website */
|
||||
origin: string;
|
||||
/** the web request (needed for dynamic routes) */
|
||||
pathname: string;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,7 +5,6 @@ import type {
|
|||
GetStaticPathsResultKeyed,
|
||||
Params,
|
||||
RouteData,
|
||||
RSS,
|
||||
} from '../../@types/astro';
|
||||
import { LogOptions, warn, debug } from '../logger/core.js';
|
||||
|
||||
|
@ -16,8 +15,6 @@ import {
|
|||
validateGetStaticPathsResult,
|
||||
} from '../routing/validation.js';
|
||||
|
||||
type RSSFn = (...args: any[]) => any;
|
||||
|
||||
interface CallGetStaticPathsOptions {
|
||||
mod: ComponentInstance;
|
||||
route: RouteData;
|
||||
|
@ -34,18 +31,15 @@ export async function callGetStaticPaths({
|
|||
ssr,
|
||||
}: CallGetStaticPathsOptions): Promise<RouteCacheEntry> {
|
||||
validateGetStaticPathsModule(mod, { ssr });
|
||||
const resultInProgress = {
|
||||
rss: [] as RSS[],
|
||||
};
|
||||
|
||||
let staticPaths: GetStaticPathsResult = [];
|
||||
if (mod.getStaticPaths) {
|
||||
staticPaths = (
|
||||
await mod.getStaticPaths({
|
||||
paginate: generatePaginateFunction(route),
|
||||
rss: (data) => {
|
||||
resultInProgress.rss.push(data);
|
||||
},
|
||||
rss() {
|
||||
throw new Error('The RSS helper has been removed from getStaticPaths! Try the new @astrojs/rss package instead. See https://docs.astro.build/en/guides/rss/')
|
||||
}
|
||||
})
|
||||
).flat();
|
||||
}
|
||||
|
@ -61,14 +55,12 @@ export async function callGetStaticPaths({
|
|||
validateGetStaticPathsResult(keyedStaticPaths, logging);
|
||||
}
|
||||
return {
|
||||
rss: resultInProgress.rss,
|
||||
staticPaths: keyedStaticPaths,
|
||||
};
|
||||
}
|
||||
|
||||
export interface RouteCacheEntry {
|
||||
staticPaths: GetStaticPathsResultKeyed;
|
||||
rss: RSS[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
import type { RSSFunction, RSS, RSSResult, RouteData } from '../../@types/astro';
|
||||
|
||||
import { XMLValidator } from 'fast-xml-parser';
|
||||
import { PRETTY_FEED_V3 } from './pretty-feed.js';
|
||||
import { createCanonicalURL, isValidURL } from './util.js';
|
||||
|
||||
/** Validates getStaticPaths.rss */
|
||||
export function validateRSS(args: GenerateRSSArgs): void {
|
||||
const { rssData, srcFile } = args;
|
||||
if (!rssData.title) throw new Error(`[${srcFile}] rss.title required`);
|
||||
if (!rssData.description) throw new Error(`[${srcFile}] rss.description required`);
|
||||
if ((rssData as any).item)
|
||||
throw new Error(`[${srcFile}] \`item: Function\` should be \`items: Item[]\``);
|
||||
if (!Array.isArray(rssData.items))
|
||||
throw new Error(`[${srcFile}] rss.items should be an array of items`);
|
||||
}
|
||||
|
||||
type GenerateRSSArgs = { site: string; rssData: RSS; srcFile: string };
|
||||
|
||||
/** Generate RSS 2.0 feed */
|
||||
export function generateRSS(args: GenerateRSSArgs): string {
|
||||
validateRSS(args);
|
||||
const { srcFile, rssData, site } = args;
|
||||
if ((rssData as any).item)
|
||||
throw new Error(
|
||||
`[${srcFile}] rss() \`item()\` function was deprecated, and is now \`items: object[]\`.`
|
||||
);
|
||||
|
||||
let xml = `<?xml version="1.0" encoding="UTF-8"?>`;
|
||||
if (typeof rssData.stylesheet === 'string') {
|
||||
xml += `<?xml-stylesheet href="${rssData.stylesheet}" type="text/xsl"?>`;
|
||||
}
|
||||
xml += `<rss version="2.0"`;
|
||||
|
||||
// xmlns
|
||||
if (rssData.xmlns) {
|
||||
for (const [k, v] of Object.entries(rssData.xmlns)) {
|
||||
xml += ` xmlns:${k}="${v}"`;
|
||||
}
|
||||
}
|
||||
xml += `>`;
|
||||
xml += `<channel>`;
|
||||
|
||||
// title, description, customData
|
||||
xml += `<title><![CDATA[${rssData.title}]]></title>`;
|
||||
xml += `<description><![CDATA[${rssData.description}]]></description>`;
|
||||
xml += `<link>${createCanonicalURL(site).href}</link>`;
|
||||
if (typeof rssData.customData === 'string') xml += rssData.customData;
|
||||
// items
|
||||
for (const result of rssData.items) {
|
||||
xml += `<item>`;
|
||||
// validate
|
||||
if (typeof result !== 'object')
|
||||
throw new Error(
|
||||
`[${srcFile}] rss.items expected an object. got: "${JSON.stringify(result)}"`
|
||||
);
|
||||
if (!result.title)
|
||||
throw new Error(
|
||||
`[${srcFile}] rss.items required "title" property is missing. got: "${JSON.stringify(
|
||||
result
|
||||
)}"`
|
||||
);
|
||||
if (!result.link)
|
||||
throw new Error(
|
||||
`[${srcFile}] rss.items required "link" property is missing. got: "${JSON.stringify(
|
||||
result
|
||||
)}"`
|
||||
);
|
||||
xml += `<title><![CDATA[${result.title}]]></title>`;
|
||||
// If the item's link is already a valid URL, don't mess with it.
|
||||
const itemLink = isValidURL(result.link)
|
||||
? result.link
|
||||
: createCanonicalURL(result.link, site).href;
|
||||
xml += `<link>${itemLink}</link>`;
|
||||
xml += `<guid>${itemLink}</guid>`;
|
||||
if (result.description) xml += `<description><![CDATA[${result.description}]]></description>`;
|
||||
if (result.pubDate) {
|
||||
// note: this should be a Date, but if user provided a string or number, we can work with that, too.
|
||||
if (typeof result.pubDate === 'number' || typeof result.pubDate === 'string') {
|
||||
result.pubDate = new Date(result.pubDate);
|
||||
} else if (result.pubDate instanceof Date === false) {
|
||||
throw new Error('[${filename}] rss.item().pubDate must be a Date');
|
||||
}
|
||||
xml += `<pubDate>${result.pubDate.toUTCString()}</pubDate>`;
|
||||
}
|
||||
if (typeof result.customData === 'string') xml += result.customData;
|
||||
xml += `</item>`;
|
||||
}
|
||||
|
||||
xml += `</channel></rss>`;
|
||||
|
||||
// validate user’s inputs to see if it’s valid XML
|
||||
const isValid = XMLValidator.validate(xml);
|
||||
if (isValid !== true) {
|
||||
// If valid XML, isValid will be `true`. Otherwise, this will be an error object. Throw.
|
||||
throw new Error(isValid as any);
|
||||
}
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
export function generateRSSStylesheet() {
|
||||
return PRETTY_FEED_V3;
|
||||
}
|
||||
|
||||
/** Generated function to be run */
|
||||
export function generateRssFunction(site: string | undefined, route: RouteData): RSSFunction {
|
||||
return function rssUtility(args: RSS): RSSResult {
|
||||
if (!site) {
|
||||
throw new Error(
|
||||
`[${route.component}] rss() tried to generate RSS but "site" missing in astro.config.mjs`
|
||||
);
|
||||
}
|
||||
let result: RSSResult = {} as any;
|
||||
const { dest, ...rssData } = args;
|
||||
const feedURL = dest || '/rss.xml';
|
||||
if (rssData.stylesheet === true) {
|
||||
rssData.stylesheet = feedURL.replace(/\.xml$/, '.xsl');
|
||||
result.xsl = {
|
||||
url: rssData.stylesheet,
|
||||
content: generateRSSStylesheet(),
|
||||
};
|
||||
} else if (typeof rssData.stylesheet === 'string') {
|
||||
result.xsl = {
|
||||
url: rssData.stylesheet,
|
||||
};
|
||||
}
|
||||
result.xml = {
|
||||
url: feedURL,
|
||||
content: generateRSS({ rssData, site, srcFile: route.component }),
|
||||
};
|
||||
return result;
|
||||
};
|
||||
}
|
551
pnpm-lock.yaml
551
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue