@@ -9,15 +9,55 @@ import type { NormalizedStoriesSpecifier, StoryIndex } from 'storybook/internal/
99import { debounce } from 'es-toolkit/function' ;
1010import type { Polka } from 'polka' ;
1111import { SitemapStream , streamToPromise } from 'sitemap' ;
12- import invariant from 'tiny-invariant' ;
1312
13+ import { logger } from '../../node-logger' ;
14+ import type { BuildStaticStandaloneOptions } from '../build-static' ;
1415import type { StoryIndexGenerator } from './StoryIndexGenerator' ;
1516import type { ServerChannel } from './get-server-channel' ;
1617import { getServerAddresses } from './server-address' ;
1718import { watchStorySpecifiers } from './watch-story-specifiers' ;
1819import { watchConfig } from './watchConfig' ;
1920
2021export const DEBOUNCE = 100 ;
22+ let cachedSitemap : Buffer | undefined ;
23+
24+ async function generateSitemap (
25+ initializedStoryIndexGenerator : Promise < StoryIndexGenerator > ,
26+ networkAddress : string ,
27+ isProduction : boolean
28+ ) : Promise < Buffer | undefined > {
29+ const generator = await initializedStoryIndexGenerator ;
30+ const index = await generator . getIndex ( ) ;
31+
32+ const smStream = new SitemapStream ( { hostname : networkAddress } ) ;
33+ // const smStream = new SitemapStream();
34+ const pipeline = isProduction ? smStream : smStream . pipe ( createGzip ( ) ) ;
35+
36+ const now = new Date ( ) ;
37+
38+ // static settings pages; those with backlinks to storybook.js.org are the most important.
39+ smStream . write ( { url : './?path=/settings/about' , lastmod : now , priority : 1 } ) ;
40+ smStream . write ( { url : './?path=/settings/whats-new' , lastmod : now , priority : 1 } ) ;
41+ smStream . write ( { url : './?path=/settings/guide' , lastmod : now , priority : 0.3 } ) ;
42+ smStream . write ( { url : './?path=/settings/shortcuts' , lastmod : now , priority : 0.3 } ) ;
43+ smStream . write ( { url : './index.json' , lastmod : now , priority : 0.3 } ) ;
44+ smStream . write ( { url : './project.json' , lastmod : now , priority : 0.3 } ) ;
45+
46+ // entries from story index; we prefer indexing docs over stories
47+ const entries = Object . values ( index . entries || { } ) ;
48+ for ( const entry of entries ) {
49+ if ( entry . type === 'docs' ) {
50+ smStream . write ( { url : `./?path=/docs/${ entry . id } ` , lastmod : now , priority : 0.9 } ) ;
51+ } else if ( entry . type === 'story' && entry . subtype !== 'test' ) {
52+ smStream . write ( { url : `./?path=/story/${ entry . id } ` , lastmod : now , priority : 0.5 } ) ;
53+ }
54+ }
55+
56+ smStream . end ( ) ;
57+
58+ // await and cache the gzipped buffer
59+ return streamToPromise ( pipeline ) ;
60+ }
2161
2262export async function extractStoriesJson (
2363 outputFile : string ,
@@ -29,14 +69,42 @@ export async function extractStoriesJson(
2969 await writeFile ( outputFile , JSON . stringify ( transform ? transform ( storyIndex ) : storyIndex ) ) ;
3070}
3171
72+ export async function extractSitemap (
73+ outputFile : string ,
74+ initializedStoryIndexGenerator : Promise < StoryIndexGenerator > ,
75+ options : BuildStaticStandaloneOptions
76+ ) {
77+ const { siteUrl } = options ;
78+
79+ if ( options . ignorePreview ) {
80+ logger . info ( `Not building preview` ) ;
81+ } else {
82+ }
83+
84+ // Can't generate a sitemap for the static build without knowing the host.
85+ if ( ! siteUrl ) {
86+ logger . info ( `Not building sitemap (\`siteUrl\` option not set).` ) ;
87+ return ;
88+ }
89+
90+ logger . info ( 'Building sitemap..' ) ;
91+ const sitemapBuffer = await generateSitemap (
92+ initializedStoryIndexGenerator ,
93+ siteUrl . endsWith ( '/' ) ? siteUrl : `${ siteUrl } /` ,
94+ true
95+ ) ;
96+ if ( sitemapBuffer ) {
97+ await writeFile ( outputFile , sitemapBuffer . toString ( 'utf-8' ) ) ;
98+ }
99+ }
100+
32101export function useStoriesJson ( {
33102 app,
34103 initializedStoryIndexGenerator,
35104 workingDir = process . cwd ( ) ,
36105 configDir,
37106 serverChannel,
38107 normalizedStories,
39- options,
40108} : {
41109 app : Polka ;
42110 initializedStoryIndexGenerator : Promise < StoryIndexGenerator > ;
@@ -46,8 +114,6 @@ export function useStoriesJson({
46114 normalizedStories : NormalizedStoriesSpecifier [ ] ;
47115 options : Options ;
48116} ) {
49- let cachedSitemap : Buffer | undefined ;
50-
51117 const maybeInvalidate = debounce ( ( ) => serverChannel . emit ( STORY_INDEX_INVALIDATED ) , DEBOUNCE , {
52118 edges : [ 'leading' , 'trailing' ] ,
53119 } ) ;
@@ -79,7 +145,13 @@ export function useStoriesJson({
79145 res . end ( err instanceof Error ? err . toString ( ) : String ( err ) ) ;
80146 }
81147 } ) ;
148+ }
82149
150+ export function useSitemap (
151+ app : Polka ,
152+ initializedStoryIndexGenerator : Promise < StoryIndexGenerator > ,
153+ options : Options
154+ ) {
83155 app . use ( '/sitemap.xml' , async ( req , res ) => {
84156 try {
85157 res . setHeader ( 'Content-Type' , 'application/xml' ) ;
@@ -91,40 +163,18 @@ export function useStoriesJson({
91163 return ;
92164 }
93165
94- const generator = await initializedStoryIndexGenerator ;
95- const index = await generator . getIndex ( ) ;
166+ const { port = 80 , host } = options ;
96167
97- const { port, host, initialPath } = options ;
98- invariant ( port , 'expected options to have a port' ) ;
99168 const proto = options . https ? 'https' : 'http' ;
100- const { networkAddress } = getServerAddresses ( port , host , proto , initialPath ) ;
101-
102- const smStream = new SitemapStream ( { hostname : networkAddress } ) ;
103- const pipeline = smStream . pipe ( createGzip ( ) ) ;
104-
105- const now = new Date ( ) ;
106-
107- // static settings pages; those with backlinks to storybook.js.org are the most important.
108- smStream . write ( { url : '/?path=/settings/about' , lastmod : now , priority : 1 } ) ;
109- smStream . write ( { url : '/?path=/settings/whats-new' , lastmod : now , priority : 1 } ) ;
110- smStream . write ( { url : '/?path=/settings/guide' , lastmod : now , priority : 0.3 } ) ;
111- smStream . write ( { url : '/?path=/settings/shortcuts' , lastmod : now , priority : 0.3 } ) ;
112-
113- // entries from story index; we prefer indexing docs over stories
114- const entries = Object . values ( index . entries || { } ) ;
115- for ( const entry of entries ) {
116- if ( entry . type === 'docs' ) {
117- smStream . write ( { url : `/?path=/docs/${ entry . id } ` , lastmod : now , priority : 0.9 } ) ;
118- } else if ( entry . type === 'story' && entry . subtype !== 'test' ) {
119- smStream . write ( { url : `/?path=/story/${ entry . id } ` , lastmod : now , priority : 0.5 } ) ;
120- }
121- }
122-
123- smStream . end ( ) ;
124-
125- // await and cache the gzipped buffer
126- cachedSitemap = await streamToPromise ( pipeline ) ;
127- res . end ( cachedSitemap ) ;
169+ const { networkAddress } = getServerAddresses ( port , host , proto ) ;
170+
171+ const sitemapBuffer = await generateSitemap (
172+ initializedStoryIndexGenerator ,
173+ networkAddress ,
174+ false
175+ ) ;
176+ cachedSitemap = sitemapBuffer ;
177+ res . end ( sitemapBuffer ) ;
128178 } catch ( err ) {
129179 res . statusCode = 500 ;
130180 res . end ( err instanceof Error ? err . toString ( ) : String ( err ) ) ;
0 commit comments