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:
Ben Holmes 2022-05-31 12:08:09 -04:00 committed by GitHub
parent b795a085f0
commit d145b8689c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 323 additions and 564 deletions

View 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.

View file

@ -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",

View file

@ -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 {

View file

@ -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) => {

View file

@ -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

View file

@ -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[];
} }
/** /**

View file

@ -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 users inputs to see if its 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;
};
}

File diff suppressed because it is too large Load diff