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",
|
"estree-walker": "^3.0.1",
|
||||||
"execa": "^6.1.0",
|
"execa": "^6.1.0",
|
||||||
"fast-glob": "^3.2.11",
|
"fast-glob": "^3.2.11",
|
||||||
"fast-xml-parser": "^4.0.7",
|
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"html-entities": "^2.3.3",
|
"html-entities": "^2.3.3",
|
||||||
"html-escaper": "^3.0.3",
|
"html-escaper": "^3.0.3",
|
||||||
|
|
|
@ -775,7 +775,11 @@ export type GetHydrateCallback = () => Promise<
|
||||||
* Docs: https://docs.astro.build/reference/api-reference/#getstaticpaths
|
* Docs: https://docs.astro.build/reference/api-reference/#getstaticpaths
|
||||||
*/ export interface GetStaticPathsOptions {
|
*/ export interface GetStaticPathsOptions {
|
||||||
paginate: PaginateFunction;
|
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 };
|
export type GetStaticPathsItem = { params: Params; props?: Props };
|
||||||
|
@ -986,51 +990,6 @@ export type SerializedRouteData = Omit<RouteData, 'generate' | 'pattern'> & {
|
||||||
|
|
||||||
export type RuntimeMode = 'development' | 'production';
|
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 type SSRError = Error & vite.ErrorPayload['err'];
|
||||||
|
|
||||||
export interface SSRElement {
|
export interface SSRElement {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { fileURLToPath } from 'url';
|
||||||
import * as colors from 'kleur/colors';
|
import * as colors from 'kleur/colors';
|
||||||
import { debug } from '../logger/core.js';
|
import { debug } from '../logger/core.js';
|
||||||
import { preload as ssrPreload } from '../render/dev/index.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 { callGetStaticPaths, RouteCache, RouteCacheEntry } from '../render/route-cache.js';
|
||||||
import { removeTrailingForwardSlash } from '../path.js';
|
import { removeTrailingForwardSlash } from '../path.js';
|
||||||
import { isBuildingToSSR } from '../util.js';
|
import { isBuildingToSSR } from '../util.js';
|
||||||
|
@ -114,32 +113,6 @@ export async function collectPagesData(
|
||||||
debug('build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
|
debug('build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
|
||||||
throw err;
|
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
|
const finalPaths = result.staticPaths
|
||||||
.map((staticPath) => staticPath.params && route.generate(staticPath.params))
|
.map((staticPath) => staticPath.params && route.generate(staticPath.params))
|
||||||
.filter((staticPath) => {
|
.filter((staticPath) => {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export interface SSROptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
/** "development" or "production" */
|
/** "development" or "production" */
|
||||||
mode: RuntimeMode;
|
mode: RuntimeMode;
|
||||||
/** production website, needed for some RSS functions */
|
/** production website */
|
||||||
origin: string;
|
origin: string;
|
||||||
/** the web request (needed for dynamic routes) */
|
/** the web request (needed for dynamic routes) */
|
||||||
pathname: string;
|
pathname: string;
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,7 +5,6 @@ import type {
|
||||||
GetStaticPathsResultKeyed,
|
GetStaticPathsResultKeyed,
|
||||||
Params,
|
Params,
|
||||||
RouteData,
|
RouteData,
|
||||||
RSS,
|
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
import { LogOptions, warn, debug } from '../logger/core.js';
|
import { LogOptions, warn, debug } from '../logger/core.js';
|
||||||
|
|
||||||
|
@ -16,8 +15,6 @@ import {
|
||||||
validateGetStaticPathsResult,
|
validateGetStaticPathsResult,
|
||||||
} from '../routing/validation.js';
|
} from '../routing/validation.js';
|
||||||
|
|
||||||
type RSSFn = (...args: any[]) => any;
|
|
||||||
|
|
||||||
interface CallGetStaticPathsOptions {
|
interface CallGetStaticPathsOptions {
|
||||||
mod: ComponentInstance;
|
mod: ComponentInstance;
|
||||||
route: RouteData;
|
route: RouteData;
|
||||||
|
@ -34,18 +31,15 @@ export async function callGetStaticPaths({
|
||||||
ssr,
|
ssr,
|
||||||
}: CallGetStaticPathsOptions): Promise<RouteCacheEntry> {
|
}: CallGetStaticPathsOptions): Promise<RouteCacheEntry> {
|
||||||
validateGetStaticPathsModule(mod, { ssr });
|
validateGetStaticPathsModule(mod, { ssr });
|
||||||
const resultInProgress = {
|
|
||||||
rss: [] as RSS[],
|
|
||||||
};
|
|
||||||
|
|
||||||
let staticPaths: GetStaticPathsResult = [];
|
let staticPaths: GetStaticPathsResult = [];
|
||||||
if (mod.getStaticPaths) {
|
if (mod.getStaticPaths) {
|
||||||
staticPaths = (
|
staticPaths = (
|
||||||
await mod.getStaticPaths({
|
await mod.getStaticPaths({
|
||||||
paginate: generatePaginateFunction(route),
|
paginate: generatePaginateFunction(route),
|
||||||
rss: (data) => {
|
rss() {
|
||||||
resultInProgress.rss.push(data);
|
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();
|
).flat();
|
||||||
}
|
}
|
||||||
|
@ -61,14 +55,12 @@ export async function callGetStaticPaths({
|
||||||
validateGetStaticPathsResult(keyedStaticPaths, logging);
|
validateGetStaticPathsResult(keyedStaticPaths, logging);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
rss: resultInProgress.rss,
|
|
||||||
staticPaths: keyedStaticPaths,
|
staticPaths: keyedStaticPaths,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouteCacheEntry {
|
export interface RouteCacheEntry {
|
||||||
staticPaths: GetStaticPathsResultKeyed;
|
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