Skip to content

Commit 5d9165f

Browse files
committed
feat(prerender): add more hooks to prerender config
Added afterSerializeTemplate(), beforeSerializeTemplate(), and loadTemplate() hooks to prerender config
1 parent 51131c6 commit 5d9165f

4 files changed

Lines changed: 157 additions & 91 deletions

File tree

src/declarations/stencil-private.ts

Lines changed: 1 addition & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
OptimizeCssOutput,
2323
OutputTargetWww,
2424
PageReloadStrategy,
25+
PrerenderConfig,
2526
PrerenderRequest,
2627
PrerenderResults,
2728
StyleDoc,
@@ -1525,42 +1526,6 @@ export interface InMemoryFileSystem {
15251526
getMemoryStats(): string;
15261527
}
15271528

1528-
export interface HydrateDocumentOptions {
1529-
canonicalUrl?: string;
1530-
constrainTimeouts?: boolean;
1531-
clientHydrateAnnotations?: boolean;
1532-
cookie?: string;
1533-
direction?: string;
1534-
excludeComponents?: string[];
1535-
language?: string;
1536-
maxHydrateCount?: number;
1537-
referrer?: string;
1538-
removeScripts?: boolean;
1539-
removeUnusedStyles?: boolean;
1540-
resourcesUrl?: string;
1541-
timeout?: number;
1542-
title?: string;
1543-
url?: string;
1544-
userAgent?: string;
1545-
}
1546-
1547-
export interface SerializeDocumentOptions extends HydrateDocumentOptions {
1548-
afterHydrate?(document: any): any | Promise<any>;
1549-
approximateLineWidth?: number;
1550-
beforeHydrate?(document: any): any | Promise<any>;
1551-
prettyHtml?: boolean;
1552-
removeAttributeQuotes?: boolean;
1553-
removeBooleanAttributeQuotes?: boolean;
1554-
removeEmptyAttributes?: boolean;
1555-
removeHtmlComments?: boolean;
1556-
}
1557-
1558-
export interface HydrateFactoryOptions extends SerializeDocumentOptions {
1559-
serializeToHtml: boolean;
1560-
destroyWindow: boolean;
1561-
destroyDocument: boolean;
1562-
}
1563-
15641529
export interface HydrateResults {
15651530
diagnostics: Diagnostic[];
15661531
url: string;
@@ -1732,53 +1697,6 @@ export interface PrerenderManager {
17321697
maxConcurrency: number;
17331698
}
17341699

1735-
export interface PrerenderHydrateOptions extends SerializeDocumentOptions {
1736-
addModulePreloads?: boolean;
1737-
inlineExternalStyleSheets?: boolean;
1738-
minifyStyleElements?: boolean;
1739-
minifyScriptElements?: boolean;
1740-
}
1741-
1742-
export interface PrerenderConfig {
1743-
afterHydrate?(document?: Document, url?: URL): any | Promise<any>;
1744-
beforeHydrate?(document?: Document, url?: URL): any | Promise<any>;
1745-
canonicalUrl?(url?: URL): string | null;
1746-
entryUrls?: string[];
1747-
filterAnchor?(attrs: { [attrName: string]: string }, base?: URL): boolean;
1748-
filterUrl?(url?: URL, base?: URL): boolean;
1749-
filePath?(url?: URL, filePath?: string): string;
1750-
hydrateOptions?(url?: URL): PrerenderHydrateOptions;
1751-
normalizeUrl?(href?: string, base?: URL): URL;
1752-
robotsTxt?(opts: RobotsTxtOpts): string | RobotsTxtResults;
1753-
sitemapXml?(opts: SitemapXmpOpts): string | SitemapXmpResults;
1754-
trailingSlash?: boolean;
1755-
}
1756-
1757-
export interface RobotsTxtOpts {
1758-
urls: string[];
1759-
sitemapUrl: string;
1760-
baseUrl: string;
1761-
dir: string;
1762-
}
1763-
1764-
export interface RobotsTxtResults {
1765-
content: string;
1766-
filePath: string;
1767-
url: string;
1768-
}
1769-
1770-
export interface SitemapXmpOpts {
1771-
urls: string[];
1772-
baseUrl: string;
1773-
dir: string;
1774-
}
1775-
1776-
export interface SitemapXmpResults {
1777-
content: string;
1778-
filePath: string;
1779-
url: string;
1780-
}
1781-
17821700
/**
17831701
* Generic node that represents all of the
17841702
* different types of nodes we'd see when rendering

src/declarations/stencil-public-compiler.ts

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,14 @@ export interface StencilConfig {
139139
rollupConfig?: RollupConfig;
140140

141141
/**
142-
* Sets if the ES5 build must be generated or not. It defaults to `false` in dev mode, and `true` in production mode.
143-
* Notice that Stencil always generates a modern build too, this setting will just disable the additional `es5` build.
142+
* Sets if the ES5 build should be generated or not. It defaults to `false` in dev mode, and `true` in
143+
* production mode. Notice that Stencil always generates a modern build too, whereas this setting
144+
* will either disable es5 builds entirely with `false`, or always create es5 builds (even in dev mode)
145+
* when set to `true`. Basically if the app does not need to run on legacy browsers
146+
* (IE11 and Edge 18 and below), it's safe to set `buildEs5` to `false`, which will also speed up
147+
* production build times. In addition to not creating es5 builds, apps may also be interested in
148+
* disabling any unnecessary runtime when support legacy browsers. See
149+
* [https://stenciljs.com/docs/config-extras](/docs/config-extras) for more information.
144150
*/
145151
buildEs5?: boolean;
146152

@@ -267,7 +273,9 @@ export interface ConfigExtras {
267273
cssVarsShim?: boolean;
268274

269275
/**
270-
* Dynamic `import()` shim. This is only needed for Edge 18 and below, and Firefox 67 and below.
276+
* Dynamic `import()` shim. This is only needed for Edge 18 and below, and Firefox 67
277+
* and below. If you do not need to support Edge 18 and below (Edge before it moved
278+
* to Chromium) then it's recommended to set `dynamicImportShim` to `false`.
271279
* Defaults to `true`.
272280
*/
273281
dynamicImportShim?: boolean;
@@ -296,7 +304,8 @@ export interface ConfigExtras {
296304
* If enabled `true`, the runtime will check if the shadow dom shim is required. However,
297305
* if it's determined that shadow dom is already natively supported by the browser then
298306
* it does not request the shim. Setting to `false` will avoid all shadow dom tests.
299-
* Defaults to `true`.
307+
* If the app does not need to support IE11 or Edge 18 it's recommended to set `shadowDomShim` to
308+
* `false`. Defaults to `true`.
300309
*/
301310
shadowDomShim?: boolean;
302311

@@ -474,6 +483,130 @@ export type TaskCommand = 'build' | 'docs' | 'generate' | 'help' | 'prerender' |
474483

475484
export type PageReloadStrategy = 'hmr' | 'pageReload' | null;
476485

486+
export interface PrerenderConfig {
487+
afterHydrate?(document?: Document, url?: URL): any | Promise<any>;
488+
beforeHydrate?(document?: Document, url?: URL): any | Promise<any>;
489+
/**
490+
* Runs after the template Document object has serialize into an
491+
* HTML formatted string. Returns an HTML string to be used as the
492+
* base template for all prerendered pages.
493+
*/
494+
afterSerializeTemplate?(html: string): Promise<string>;
495+
/**
496+
* Runs before the template Document object is serialize into an
497+
* HTML formatted string. Returns the Document to be serialized which
498+
* will become the base template html for all prerendered pages.
499+
*/
500+
beforeSerializeTemplate?(document: Document): Promise<Document>;
501+
/**
502+
* A custom function to be used to generate the canonical `<link>` tag
503+
* which goes in the `<head>` of every prerendered page. Returning `null`
504+
* will not add a canonical url tag to the page.
505+
*/
506+
canonicalUrl?(url?: URL): string | null;
507+
/**
508+
* URLs to start the prerender crawling from. By default the root URL of `/` is used.
509+
*/
510+
entryUrls?: string[];
511+
/**
512+
* Return `true` the given `<a>` element should be crawled or not.
513+
*/
514+
filterAnchor?(attrs: { [attrName: string]: string }, base?: URL): boolean;
515+
/**
516+
* Return `true` if the given URL should be prerendered or not.
517+
*/
518+
filterUrl?(url?: URL, base?: URL): boolean;
519+
/**
520+
* Returns the file path which the prerendered HTML content
521+
* should be written to.
522+
*/
523+
filePath?(url?: URL, filePath?: string): string;
524+
/**
525+
* Returns the hydrate options to use for each individual prerendered page.
526+
*/
527+
hydrateOptions?(url?: URL): PrerenderHydrateOptions;
528+
/**
529+
* Returns the template file's content. The template is the base
530+
* HTML used for all prerendered pages.
531+
*/
532+
loadTemplate?(filePath?: string): Promise<string>;
533+
normalizeUrl?(href?: string, base?: URL): URL;
534+
robotsTxt?(opts: RobotsTxtOpts): string | RobotsTxtResults;
535+
sitemapXml?(opts: SitemapXmpOpts): string | SitemapXmpResults;
536+
/**
537+
* If the prerenndered URLs should have a trailing "/"" or not. Defaults to `false`.
538+
*/
539+
trailingSlash?: boolean;
540+
}
541+
542+
export interface HydrateDocumentOptions {
543+
canonicalUrl?: string;
544+
constrainTimeouts?: boolean;
545+
clientHydrateAnnotations?: boolean;
546+
cookie?: string;
547+
direction?: string;
548+
excludeComponents?: string[];
549+
language?: string;
550+
maxHydrateCount?: number;
551+
referrer?: string;
552+
removeScripts?: boolean;
553+
removeUnusedStyles?: boolean;
554+
resourcesUrl?: string;
555+
timeout?: number;
556+
title?: string;
557+
url?: string;
558+
userAgent?: string;
559+
}
560+
561+
export interface SerializeDocumentOptions extends HydrateDocumentOptions {
562+
afterHydrate?(document: any): any | Promise<any>;
563+
approximateLineWidth?: number;
564+
beforeHydrate?(document: any): any | Promise<any>;
565+
prettyHtml?: boolean;
566+
removeAttributeQuotes?: boolean;
567+
removeBooleanAttributeQuotes?: boolean;
568+
removeEmptyAttributes?: boolean;
569+
removeHtmlComments?: boolean;
570+
}
571+
572+
export interface HydrateFactoryOptions extends SerializeDocumentOptions {
573+
serializeToHtml: boolean;
574+
destroyWindow: boolean;
575+
destroyDocument: boolean;
576+
}
577+
578+
export interface PrerenderHydrateOptions extends SerializeDocumentOptions {
579+
addModulePreloads?: boolean;
580+
inlineExternalStyleSheets?: boolean;
581+
minifyStyleElements?: boolean;
582+
minifyScriptElements?: boolean;
583+
}
584+
585+
export interface RobotsTxtOpts {
586+
urls: string[];
587+
sitemapUrl: string;
588+
baseUrl: string;
589+
dir: string;
590+
}
591+
592+
export interface RobotsTxtResults {
593+
content: string;
594+
filePath: string;
595+
url: string;
596+
}
597+
598+
export interface SitemapXmpOpts {
599+
urls: string[];
600+
baseUrl: string;
601+
dir: string;
602+
}
603+
604+
export interface SitemapXmpResults {
605+
content: string;
606+
filePath: string;
607+
url: string;
608+
}
609+
477610
export interface CompilerSystem {
478611
events?: BuildEvents;
479612
details?: SystemDetails;

src/prerender/prerender-main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ async function runPrerenderOutputTarget(
135135
return;
136136
}
137137

138-
const templateHtml = await generateTemplateHtml(diagnostics, manager.isDebug, srcIndexHtmlPath, outputTarget, hydrateOpts);
138+
const templateHtml = await generateTemplateHtml(prerenderConfig, diagnostics, manager.isDebug, srcIndexHtmlPath, outputTarget, hydrateOpts);
139139
if (diagnostics.length > 0 || typeof templateHtml !== 'string') {
140140
return;
141141
}

src/prerender/prerender-template-html.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { promisify } from 'util';
77
const readFile = promisify(fs.readFile);
88

99
export async function generateTemplateHtml(
10+
prerenderConfig: d.PrerenderConfig,
1011
diagnostics: d.Diagnostic[],
1112
isDebug: boolean,
1213
srcIndexHtmlPath: string,
@@ -19,8 +20,14 @@ export async function generateTemplateHtml(
1920
if (typeof srcIndexHtmlPath !== 'string') {
2021
srcIndexHtmlPath = outputTarget.indexHtml;
2122
}
22-
const templateHtml = await readFile(srcIndexHtmlPath, 'utf8');
23-
const doc = createDocument(templateHtml);
23+
let templateHtml: string;
24+
if (typeof prerenderConfig.loadTemplate === 'function') {
25+
templateHtml = await prerenderConfig.loadTemplate(srcIndexHtmlPath);
26+
} else {
27+
templateHtml = await readFile(srcIndexHtmlPath, 'utf8');
28+
}
29+
30+
let doc = createDocument(templateHtml);
2431

2532
if (hydrateOpts.inlineExternalStyleSheets && !isDebug) {
2633
try {
@@ -46,7 +53,15 @@ export async function generateTemplateHtml(
4653
}
4754
}
4855

49-
return serializeNodeToHtml(doc);
56+
if (typeof prerenderConfig.beforeSerializeTemplate === 'function') {
57+
doc = await prerenderConfig.beforeSerializeTemplate(doc);
58+
}
59+
60+
let html = serializeNodeToHtml(doc);
61+
if (typeof prerenderConfig.afterSerializeTemplate === 'function') {
62+
html = await prerenderConfig.afterSerializeTemplate(html);
63+
}
64+
return html;
5065
} catch (e) {
5166
catchError(diagnostics, e);
5267
}

0 commit comments

Comments
 (0)