Updated the files.
This commit is contained in:
parent
1553e6b971
commit
753967d4f5
23418 changed files with 3784666 additions and 0 deletions
8
my-app/node_modules/@angular/ssr/esm2022/index.mjs
generated
vendored
Executable file
8
my-app/node_modules/@angular/ssr/esm2022/index.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export * from './public_api';
|
||||
8
my-app/node_modules/@angular/ssr/esm2022/public_api.mjs
generated
vendored
Executable file
8
my-app/node_modules/@angular/ssr/esm2022/public_api.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export { CommonEngine } from './src/common-engine';
|
||||
137
my-app/node_modules/@angular/ssr/esm2022/src/common-engine.mjs
generated
vendored
Executable file
137
my-app/node_modules/@angular/ssr/esm2022/src/common-engine.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
|
||||
import * as fs from 'node:fs';
|
||||
import { dirname, join, normalize, resolve } from 'node:path';
|
||||
import { URL } from 'node:url';
|
||||
import { InlineCriticalCssProcessor } from './inline-css-processor';
|
||||
import { noopRunMethodAndMeasurePerf, printPerformanceLogs, runMethodAndMeasurePerf, } from './peformance-profiler';
|
||||
const SSG_MARKER_REGEXP = /ng-server-context=["']\w*\|?ssg\|?\w*["']/;
|
||||
/**
|
||||
* A common engine to use to server render an application.
|
||||
*/
|
||||
export class CommonEngine {
|
||||
options;
|
||||
templateCache = new Map();
|
||||
inlineCriticalCssProcessor;
|
||||
pageIsSSG = new Map();
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
this.inlineCriticalCssProcessor = new InlineCriticalCssProcessor({
|
||||
minify: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Render an HTML document for a specific URL with specified
|
||||
* render options
|
||||
*/
|
||||
async render(opts) {
|
||||
const enablePerformanceProfiler = this.options?.enablePerformanceProfiler;
|
||||
const runMethod = enablePerformanceProfiler
|
||||
? runMethodAndMeasurePerf
|
||||
: noopRunMethodAndMeasurePerf;
|
||||
let html = await runMethod('Retrieve SSG Page', () => this.retrieveSSGPage(opts));
|
||||
if (html === undefined) {
|
||||
html = await runMethod('Render Page', () => this.renderApplication(opts));
|
||||
if (opts.inlineCriticalCss !== false) {
|
||||
const { content, errors, warnings } = await runMethod('Inline Critical CSS', () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.inlineCriticalCss(html, opts));
|
||||
html = content;
|
||||
// eslint-disable-next-line no-console
|
||||
warnings?.forEach((m) => console.warn(m));
|
||||
// eslint-disable-next-line no-console
|
||||
errors?.forEach((m) => console.error(m));
|
||||
}
|
||||
}
|
||||
if (enablePerformanceProfiler) {
|
||||
printPerformanceLogs();
|
||||
}
|
||||
return html;
|
||||
}
|
||||
inlineCriticalCss(html, opts) {
|
||||
return this.inlineCriticalCssProcessor.process(html, {
|
||||
outputPath: opts.publicPath ?? (opts.documentFilePath ? dirname(opts.documentFilePath) : ''),
|
||||
});
|
||||
}
|
||||
async retrieveSSGPage(opts) {
|
||||
const { publicPath, documentFilePath, url } = opts;
|
||||
if (!publicPath || !documentFilePath || url === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const { pathname } = new URL(url, 'resolve://');
|
||||
// Do not use `resolve` here as otherwise it can lead to path traversal vulnerability.
|
||||
// See: https://portswigger.net/web-security/file-path-traversal
|
||||
const pagePath = join(publicPath, pathname, 'index.html');
|
||||
if (this.pageIsSSG.get(pagePath)) {
|
||||
// Serve pre-rendered page.
|
||||
return fs.promises.readFile(pagePath, 'utf-8');
|
||||
}
|
||||
if (!pagePath.startsWith(normalize(publicPath))) {
|
||||
// Potential path traversal detected.
|
||||
return undefined;
|
||||
}
|
||||
if (pagePath === resolve(documentFilePath) || !(await exists(pagePath))) {
|
||||
// View matches with prerender path or file does not exist.
|
||||
this.pageIsSSG.set(pagePath, false);
|
||||
return undefined;
|
||||
}
|
||||
// Static file exists.
|
||||
const content = await fs.promises.readFile(pagePath, 'utf-8');
|
||||
const isSSG = SSG_MARKER_REGEXP.test(content);
|
||||
this.pageIsSSG.set(pagePath, isSSG);
|
||||
return isSSG ? content : undefined;
|
||||
}
|
||||
async renderApplication(opts) {
|
||||
const moduleOrFactory = this.options?.bootstrap ?? opts.bootstrap;
|
||||
if (!moduleOrFactory) {
|
||||
throw new Error('A module or bootstrap option must be provided.');
|
||||
}
|
||||
const extraProviders = [
|
||||
{ provide: ɵSERVER_CONTEXT, useValue: 'ssr' },
|
||||
...(opts.providers ?? []),
|
||||
...(this.options?.providers ?? []),
|
||||
];
|
||||
let document = opts.document;
|
||||
if (!document && opts.documentFilePath) {
|
||||
document = await this.getDocument(opts.documentFilePath);
|
||||
}
|
||||
const commonRenderingOptions = {
|
||||
url: opts.url,
|
||||
document,
|
||||
};
|
||||
return isBootstrapFn(moduleOrFactory)
|
||||
? renderApplication(moduleOrFactory, {
|
||||
platformProviders: extraProviders,
|
||||
...commonRenderingOptions,
|
||||
})
|
||||
: renderModule(moduleOrFactory, { extraProviders, ...commonRenderingOptions });
|
||||
}
|
||||
/** Retrieve the document from the cache or the filesystem */
|
||||
async getDocument(filePath) {
|
||||
let doc = this.templateCache.get(filePath);
|
||||
if (!doc) {
|
||||
doc = await fs.promises.readFile(filePath, 'utf-8');
|
||||
this.templateCache.set(filePath, doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
async function exists(path) {
|
||||
try {
|
||||
await fs.promises.access(path, fs.constants.F_OK);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function isBootstrapFn(value) {
|
||||
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
|
||||
return typeof value === 'function' && !('ɵmod' in value);
|
||||
}
|
||||
181
my-app/node_modules/@angular/ssr/esm2022/src/inline-css-processor.mjs
generated
vendored
Executable file
181
my-app/node_modules/@angular/ssr/esm2022/src/inline-css-processor.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import Critters from 'critters';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
/**
|
||||
* Pattern used to extract the media query set by Critters in an `onload` handler.
|
||||
*/
|
||||
const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
|
||||
/**
|
||||
* Name of the attribute used to save the Critters media query so it can be re-assigned on load.
|
||||
*/
|
||||
const CSP_MEDIA_ATTR = 'ngCspMedia';
|
||||
/**
|
||||
* Script text used to change the media value of the link tags.
|
||||
*
|
||||
* NOTE:
|
||||
* We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
|
||||
* because this does not always fire on Chome.
|
||||
* See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
|
||||
*/
|
||||
const LINK_LOAD_SCRIPT_CONTENT = [
|
||||
'(() => {',
|
||||
` const CSP_MEDIA_ATTR = '${CSP_MEDIA_ATTR}';`,
|
||||
' const documentElement = document.documentElement;',
|
||||
' const listener = (e) => {',
|
||||
' const target = e.target;',
|
||||
` if (!target || target.tagName !== 'LINK' || !target.hasAttribute(CSP_MEDIA_ATTR)) {`,
|
||||
' return;',
|
||||
' }',
|
||||
' target.media = target.getAttribute(CSP_MEDIA_ATTR);',
|
||||
' target.removeAttribute(CSP_MEDIA_ATTR);',
|
||||
// Remove onload listener when there are no longer styles that need to be loaded.
|
||||
' if (!document.head.querySelector(`link[${CSP_MEDIA_ATTR}]`)) {',
|
||||
` documentElement.removeEventListener('load', listener);`,
|
||||
' }',
|
||||
' };',
|
||||
// We use an event with capturing (the true parameter) because load events don't bubble.
|
||||
` documentElement.addEventListener('load', listener, true);`,
|
||||
'})();',
|
||||
].join('\n');
|
||||
class CrittersExtended extends Critters {
|
||||
optionsExtended;
|
||||
resourceCache;
|
||||
warnings = [];
|
||||
errors = [];
|
||||
initialEmbedLinkedStylesheet;
|
||||
addedCspScriptsDocuments = new WeakSet();
|
||||
documentNonces = new WeakMap();
|
||||
constructor(optionsExtended, resourceCache) {
|
||||
super({
|
||||
logger: {
|
||||
warn: (s) => this.warnings.push(s),
|
||||
error: (s) => this.errors.push(s),
|
||||
info: () => { },
|
||||
},
|
||||
logLevel: 'warn',
|
||||
path: optionsExtended.outputPath,
|
||||
publicPath: optionsExtended.deployUrl,
|
||||
compress: !!optionsExtended.minify,
|
||||
pruneSource: false,
|
||||
reduceInlineStyles: false,
|
||||
mergeStylesheets: false,
|
||||
// Note: if `preload` changes to anything other than `media`, the logic in
|
||||
// `embedLinkedStylesheetOverride` will have to be updated.
|
||||
preload: 'media',
|
||||
noscriptFallback: true,
|
||||
inlineFonts: true,
|
||||
});
|
||||
this.optionsExtended = optionsExtended;
|
||||
this.resourceCache = resourceCache;
|
||||
// We can't use inheritance to override `embedLinkedStylesheet`, because it's not declared in
|
||||
// the `Critters` .d.ts which means that we can't call the `super` implementation. TS doesn't
|
||||
// allow for `super` to be cast to a different type.
|
||||
this.initialEmbedLinkedStylesheet = this.embedLinkedStylesheet;
|
||||
this.embedLinkedStylesheet = this.embedLinkedStylesheetOverride;
|
||||
}
|
||||
async readFile(path) {
|
||||
let resourceContent = this.resourceCache.get(path);
|
||||
if (resourceContent === undefined) {
|
||||
resourceContent = await readFile(path, 'utf-8');
|
||||
this.resourceCache.set(path, resourceContent);
|
||||
}
|
||||
return resourceContent;
|
||||
}
|
||||
/**
|
||||
* Override of the Critters `embedLinkedStylesheet` method
|
||||
* that makes it work with Angular's CSP APIs.
|
||||
*/
|
||||
embedLinkedStylesheetOverride = async (link, document) => {
|
||||
if (link.getAttribute('media') === 'print' && link.next?.name === 'noscript') {
|
||||
// Workaround for https://github.com/GoogleChromeLabs/critters/issues/64
|
||||
// NB: this is only needed for the webpack based builders.
|
||||
const media = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
|
||||
if (media) {
|
||||
link.removeAttribute('onload');
|
||||
link.setAttribute('media', media[1]);
|
||||
link?.next?.remove();
|
||||
}
|
||||
}
|
||||
const returnValue = await this.initialEmbedLinkedStylesheet(link, document);
|
||||
const cspNonce = this.findCspNonce(document);
|
||||
if (cspNonce) {
|
||||
const crittersMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
|
||||
if (crittersMedia) {
|
||||
// If there's a Critters-generated `onload` handler and the file has an Angular CSP nonce,
|
||||
// we have to remove the handler, because it's incompatible with CSP. We save the value
|
||||
// in a different attribute and we generate a script tag with the nonce that uses
|
||||
// `addEventListener` to apply the media query instead.
|
||||
link.removeAttribute('onload');
|
||||
link.setAttribute(CSP_MEDIA_ATTR, crittersMedia[1]);
|
||||
this.conditionallyInsertCspLoadingScript(document, cspNonce, link);
|
||||
}
|
||||
// Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
|
||||
// a way of doing that at the moment so we fall back to doing it any time a `link` tag is
|
||||
// inserted. We mitigate it by only iterating the direct children of the `<head>` which
|
||||
// should be pretty shallow.
|
||||
document.head.children.forEach((child) => {
|
||||
if (child.tagName === 'style' && !child.hasAttribute('nonce')) {
|
||||
child.setAttribute('nonce', cspNonce);
|
||||
}
|
||||
});
|
||||
}
|
||||
return returnValue;
|
||||
};
|
||||
/**
|
||||
* Finds the CSP nonce for a specific document.
|
||||
*/
|
||||
findCspNonce(document) {
|
||||
if (this.documentNonces.has(document)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.documentNonces.get(document);
|
||||
}
|
||||
// HTML attribute are case-insensitive, but the parser used by Critters is case-sensitive.
|
||||
const nonceElement = document.querySelector('[ngCspNonce], [ngcspnonce]');
|
||||
const cspNonce = nonceElement?.getAttribute('ngCspNonce') || nonceElement?.getAttribute('ngcspnonce') || null;
|
||||
this.documentNonces.set(document, cspNonce);
|
||||
return cspNonce;
|
||||
}
|
||||
/**
|
||||
* Inserts the `script` tag that swaps the critical CSS at runtime,
|
||||
* if one hasn't been inserted into the document already.
|
||||
*/
|
||||
conditionallyInsertCspLoadingScript(document, nonce, link) {
|
||||
if (this.addedCspScriptsDocuments.has(document)) {
|
||||
return;
|
||||
}
|
||||
if (document.head.textContent.includes(LINK_LOAD_SCRIPT_CONTENT)) {
|
||||
// Script was already added during the build.
|
||||
this.addedCspScriptsDocuments.add(document);
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('nonce', nonce);
|
||||
script.textContent = LINK_LOAD_SCRIPT_CONTENT;
|
||||
// Prepend the script to the head since it needs to
|
||||
// run as early as possible, before the `link` tags.
|
||||
document.head.insertBefore(script, link);
|
||||
this.addedCspScriptsDocuments.add(document);
|
||||
}
|
||||
}
|
||||
export class InlineCriticalCssProcessor {
|
||||
options;
|
||||
resourceCache = new Map();
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
}
|
||||
async process(html, options) {
|
||||
const critters = new CrittersExtended({ ...this.options, ...options }, this.resourceCache);
|
||||
const content = await critters.process(html);
|
||||
return {
|
||||
content,
|
||||
errors: critters.errors.length ? critters.errors : undefined,
|
||||
warnings: critters.warnings.length ? critters.warnings : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
50
my-app/node_modules/@angular/ssr/esm2022/src/peformance-profiler.mjs
generated
vendored
Executable file
50
my-app/node_modules/@angular/ssr/esm2022/src/peformance-profiler.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
const PERFORMANCE_MARK_PREFIX = '🅰️';
|
||||
export function printPerformanceLogs() {
|
||||
let maxWordLength = 0;
|
||||
const benchmarks = [];
|
||||
for (const { name, duration } of performance.getEntriesByType('measure')) {
|
||||
if (!name.startsWith(PERFORMANCE_MARK_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
// `🅰️:Retrieve SSG Page` -> `Retrieve SSG Page:`
|
||||
const step = name.slice(PERFORMANCE_MARK_PREFIX.length + 1) + ':';
|
||||
if (step.length > maxWordLength) {
|
||||
maxWordLength = step.length;
|
||||
}
|
||||
benchmarks.push([step, `${duration.toFixed(1)}ms`]);
|
||||
performance.clearMeasures(name);
|
||||
}
|
||||
/* eslint-disable no-console */
|
||||
console.log('********** Performance results **********');
|
||||
for (const [step, value] of benchmarks) {
|
||||
const spaces = maxWordLength - step.length + 5;
|
||||
console.log(step + ' '.repeat(spaces) + value);
|
||||
}
|
||||
console.log('*****************************************');
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
export async function runMethodAndMeasurePerf(label, asyncMethod) {
|
||||
const labelName = `${PERFORMANCE_MARK_PREFIX}:${label}`;
|
||||
const startLabel = `start:${labelName}`;
|
||||
const endLabel = `end:${labelName}`;
|
||||
try {
|
||||
performance.mark(startLabel);
|
||||
return await asyncMethod();
|
||||
}
|
||||
finally {
|
||||
performance.mark(endLabel);
|
||||
performance.measure(labelName, startLabel, endLabel);
|
||||
performance.clearMarks(startLabel);
|
||||
performance.clearMarks(endLabel);
|
||||
}
|
||||
}
|
||||
export function noopRunMethodAndMeasurePerf(label, asyncMethod) {
|
||||
return asyncMethod();
|
||||
}
|
||||
351
my-app/node_modules/@angular/ssr/fesm2022/ssr.mjs
generated
vendored
Executable file
351
my-app/node_modules/@angular/ssr/fesm2022/ssr.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,351 @@
|
|||
import { ɵSERVER_CONTEXT as _SERVER_CONTEXT, renderApplication, renderModule } from '@angular/platform-server';
|
||||
import * as fs from 'node:fs';
|
||||
import { dirname, join, normalize, resolve } from 'node:path';
|
||||
import { URL } from 'node:url';
|
||||
import Critters from 'critters';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
/**
|
||||
* Pattern used to extract the media query set by Critters in an `onload` handler.
|
||||
*/
|
||||
const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
|
||||
/**
|
||||
* Name of the attribute used to save the Critters media query so it can be re-assigned on load.
|
||||
*/
|
||||
const CSP_MEDIA_ATTR = 'ngCspMedia';
|
||||
/**
|
||||
* Script text used to change the media value of the link tags.
|
||||
*
|
||||
* NOTE:
|
||||
* We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
|
||||
* because this does not always fire on Chome.
|
||||
* See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
|
||||
*/
|
||||
const LINK_LOAD_SCRIPT_CONTENT = [
|
||||
'(() => {',
|
||||
` const CSP_MEDIA_ATTR = '${CSP_MEDIA_ATTR}';`,
|
||||
' const documentElement = document.documentElement;',
|
||||
' const listener = (e) => {',
|
||||
' const target = e.target;',
|
||||
` if (!target || target.tagName !== 'LINK' || !target.hasAttribute(CSP_MEDIA_ATTR)) {`,
|
||||
' return;',
|
||||
' }',
|
||||
' target.media = target.getAttribute(CSP_MEDIA_ATTR);',
|
||||
' target.removeAttribute(CSP_MEDIA_ATTR);',
|
||||
// Remove onload listener when there are no longer styles that need to be loaded.
|
||||
' if (!document.head.querySelector(`link[${CSP_MEDIA_ATTR}]`)) {',
|
||||
` documentElement.removeEventListener('load', listener);`,
|
||||
' }',
|
||||
' };',
|
||||
// We use an event with capturing (the true parameter) because load events don't bubble.
|
||||
` documentElement.addEventListener('load', listener, true);`,
|
||||
'})();',
|
||||
].join('\n');
|
||||
class CrittersExtended extends Critters {
|
||||
optionsExtended;
|
||||
resourceCache;
|
||||
warnings = [];
|
||||
errors = [];
|
||||
initialEmbedLinkedStylesheet;
|
||||
addedCspScriptsDocuments = new WeakSet();
|
||||
documentNonces = new WeakMap();
|
||||
constructor(optionsExtended, resourceCache) {
|
||||
super({
|
||||
logger: {
|
||||
warn: (s) => this.warnings.push(s),
|
||||
error: (s) => this.errors.push(s),
|
||||
info: () => { },
|
||||
},
|
||||
logLevel: 'warn',
|
||||
path: optionsExtended.outputPath,
|
||||
publicPath: optionsExtended.deployUrl,
|
||||
compress: !!optionsExtended.minify,
|
||||
pruneSource: false,
|
||||
reduceInlineStyles: false,
|
||||
mergeStylesheets: false,
|
||||
// Note: if `preload` changes to anything other than `media`, the logic in
|
||||
// `embedLinkedStylesheetOverride` will have to be updated.
|
||||
preload: 'media',
|
||||
noscriptFallback: true,
|
||||
inlineFonts: true,
|
||||
});
|
||||
this.optionsExtended = optionsExtended;
|
||||
this.resourceCache = resourceCache;
|
||||
// We can't use inheritance to override `embedLinkedStylesheet`, because it's not declared in
|
||||
// the `Critters` .d.ts which means that we can't call the `super` implementation. TS doesn't
|
||||
// allow for `super` to be cast to a different type.
|
||||
this.initialEmbedLinkedStylesheet = this.embedLinkedStylesheet;
|
||||
this.embedLinkedStylesheet = this.embedLinkedStylesheetOverride;
|
||||
}
|
||||
async readFile(path) {
|
||||
let resourceContent = this.resourceCache.get(path);
|
||||
if (resourceContent === undefined) {
|
||||
resourceContent = await readFile(path, 'utf-8');
|
||||
this.resourceCache.set(path, resourceContent);
|
||||
}
|
||||
return resourceContent;
|
||||
}
|
||||
/**
|
||||
* Override of the Critters `embedLinkedStylesheet` method
|
||||
* that makes it work with Angular's CSP APIs.
|
||||
*/
|
||||
embedLinkedStylesheetOverride = async (link, document) => {
|
||||
if (link.getAttribute('media') === 'print' && link.next?.name === 'noscript') {
|
||||
// Workaround for https://github.com/GoogleChromeLabs/critters/issues/64
|
||||
// NB: this is only needed for the webpack based builders.
|
||||
const media = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
|
||||
if (media) {
|
||||
link.removeAttribute('onload');
|
||||
link.setAttribute('media', media[1]);
|
||||
link?.next?.remove();
|
||||
}
|
||||
}
|
||||
const returnValue = await this.initialEmbedLinkedStylesheet(link, document);
|
||||
const cspNonce = this.findCspNonce(document);
|
||||
if (cspNonce) {
|
||||
const crittersMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
|
||||
if (crittersMedia) {
|
||||
// If there's a Critters-generated `onload` handler and the file has an Angular CSP nonce,
|
||||
// we have to remove the handler, because it's incompatible with CSP. We save the value
|
||||
// in a different attribute and we generate a script tag with the nonce that uses
|
||||
// `addEventListener` to apply the media query instead.
|
||||
link.removeAttribute('onload');
|
||||
link.setAttribute(CSP_MEDIA_ATTR, crittersMedia[1]);
|
||||
this.conditionallyInsertCspLoadingScript(document, cspNonce, link);
|
||||
}
|
||||
// Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
|
||||
// a way of doing that at the moment so we fall back to doing it any time a `link` tag is
|
||||
// inserted. We mitigate it by only iterating the direct children of the `<head>` which
|
||||
// should be pretty shallow.
|
||||
document.head.children.forEach((child) => {
|
||||
if (child.tagName === 'style' && !child.hasAttribute('nonce')) {
|
||||
child.setAttribute('nonce', cspNonce);
|
||||
}
|
||||
});
|
||||
}
|
||||
return returnValue;
|
||||
};
|
||||
/**
|
||||
* Finds the CSP nonce for a specific document.
|
||||
*/
|
||||
findCspNonce(document) {
|
||||
if (this.documentNonces.has(document)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.documentNonces.get(document);
|
||||
}
|
||||
// HTML attribute are case-insensitive, but the parser used by Critters is case-sensitive.
|
||||
const nonceElement = document.querySelector('[ngCspNonce], [ngcspnonce]');
|
||||
const cspNonce = nonceElement?.getAttribute('ngCspNonce') || nonceElement?.getAttribute('ngcspnonce') || null;
|
||||
this.documentNonces.set(document, cspNonce);
|
||||
return cspNonce;
|
||||
}
|
||||
/**
|
||||
* Inserts the `script` tag that swaps the critical CSS at runtime,
|
||||
* if one hasn't been inserted into the document already.
|
||||
*/
|
||||
conditionallyInsertCspLoadingScript(document, nonce, link) {
|
||||
if (this.addedCspScriptsDocuments.has(document)) {
|
||||
return;
|
||||
}
|
||||
if (document.head.textContent.includes(LINK_LOAD_SCRIPT_CONTENT)) {
|
||||
// Script was already added during the build.
|
||||
this.addedCspScriptsDocuments.add(document);
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('nonce', nonce);
|
||||
script.textContent = LINK_LOAD_SCRIPT_CONTENT;
|
||||
// Prepend the script to the head since it needs to
|
||||
// run as early as possible, before the `link` tags.
|
||||
document.head.insertBefore(script, link);
|
||||
this.addedCspScriptsDocuments.add(document);
|
||||
}
|
||||
}
|
||||
class InlineCriticalCssProcessor {
|
||||
options;
|
||||
resourceCache = new Map();
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
}
|
||||
async process(html, options) {
|
||||
const critters = new CrittersExtended({ ...this.options, ...options }, this.resourceCache);
|
||||
const content = await critters.process(html);
|
||||
return {
|
||||
content,
|
||||
errors: critters.errors.length ? critters.errors : undefined,
|
||||
warnings: critters.warnings.length ? critters.warnings : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const PERFORMANCE_MARK_PREFIX = '🅰️';
|
||||
function printPerformanceLogs() {
|
||||
let maxWordLength = 0;
|
||||
const benchmarks = [];
|
||||
for (const { name, duration } of performance.getEntriesByType('measure')) {
|
||||
if (!name.startsWith(PERFORMANCE_MARK_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
// `🅰️:Retrieve SSG Page` -> `Retrieve SSG Page:`
|
||||
const step = name.slice(PERFORMANCE_MARK_PREFIX.length + 1) + ':';
|
||||
if (step.length > maxWordLength) {
|
||||
maxWordLength = step.length;
|
||||
}
|
||||
benchmarks.push([step, `${duration.toFixed(1)}ms`]);
|
||||
performance.clearMeasures(name);
|
||||
}
|
||||
/* eslint-disable no-console */
|
||||
console.log('********** Performance results **********');
|
||||
for (const [step, value] of benchmarks) {
|
||||
const spaces = maxWordLength - step.length + 5;
|
||||
console.log(step + ' '.repeat(spaces) + value);
|
||||
}
|
||||
console.log('*****************************************');
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
async function runMethodAndMeasurePerf(label, asyncMethod) {
|
||||
const labelName = `${PERFORMANCE_MARK_PREFIX}:${label}`;
|
||||
const startLabel = `start:${labelName}`;
|
||||
const endLabel = `end:${labelName}`;
|
||||
try {
|
||||
performance.mark(startLabel);
|
||||
return await asyncMethod();
|
||||
}
|
||||
finally {
|
||||
performance.mark(endLabel);
|
||||
performance.measure(labelName, startLabel, endLabel);
|
||||
performance.clearMarks(startLabel);
|
||||
performance.clearMarks(endLabel);
|
||||
}
|
||||
}
|
||||
function noopRunMethodAndMeasurePerf(label, asyncMethod) {
|
||||
return asyncMethod();
|
||||
}
|
||||
|
||||
const SSG_MARKER_REGEXP = /ng-server-context=["']\w*\|?ssg\|?\w*["']/;
|
||||
/**
|
||||
* A common engine to use to server render an application.
|
||||
*/
|
||||
class CommonEngine {
|
||||
options;
|
||||
templateCache = new Map();
|
||||
inlineCriticalCssProcessor;
|
||||
pageIsSSG = new Map();
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
this.inlineCriticalCssProcessor = new InlineCriticalCssProcessor({
|
||||
minify: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Render an HTML document for a specific URL with specified
|
||||
* render options
|
||||
*/
|
||||
async render(opts) {
|
||||
const enablePerformanceProfiler = this.options?.enablePerformanceProfiler;
|
||||
const runMethod = enablePerformanceProfiler
|
||||
? runMethodAndMeasurePerf
|
||||
: noopRunMethodAndMeasurePerf;
|
||||
let html = await runMethod('Retrieve SSG Page', () => this.retrieveSSGPage(opts));
|
||||
if (html === undefined) {
|
||||
html = await runMethod('Render Page', () => this.renderApplication(opts));
|
||||
if (opts.inlineCriticalCss !== false) {
|
||||
const { content, errors, warnings } = await runMethod('Inline Critical CSS', () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.inlineCriticalCss(html, opts));
|
||||
html = content;
|
||||
// eslint-disable-next-line no-console
|
||||
warnings?.forEach((m) => console.warn(m));
|
||||
// eslint-disable-next-line no-console
|
||||
errors?.forEach((m) => console.error(m));
|
||||
}
|
||||
}
|
||||
if (enablePerformanceProfiler) {
|
||||
printPerformanceLogs();
|
||||
}
|
||||
return html;
|
||||
}
|
||||
inlineCriticalCss(html, opts) {
|
||||
return this.inlineCriticalCssProcessor.process(html, {
|
||||
outputPath: opts.publicPath ?? (opts.documentFilePath ? dirname(opts.documentFilePath) : ''),
|
||||
});
|
||||
}
|
||||
async retrieveSSGPage(opts) {
|
||||
const { publicPath, documentFilePath, url } = opts;
|
||||
if (!publicPath || !documentFilePath || url === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const { pathname } = new URL(url, 'resolve://');
|
||||
// Do not use `resolve` here as otherwise it can lead to path traversal vulnerability.
|
||||
// See: https://portswigger.net/web-security/file-path-traversal
|
||||
const pagePath = join(publicPath, pathname, 'index.html');
|
||||
if (this.pageIsSSG.get(pagePath)) {
|
||||
// Serve pre-rendered page.
|
||||
return fs.promises.readFile(pagePath, 'utf-8');
|
||||
}
|
||||
if (!pagePath.startsWith(normalize(publicPath))) {
|
||||
// Potential path traversal detected.
|
||||
return undefined;
|
||||
}
|
||||
if (pagePath === resolve(documentFilePath) || !(await exists(pagePath))) {
|
||||
// View matches with prerender path or file does not exist.
|
||||
this.pageIsSSG.set(pagePath, false);
|
||||
return undefined;
|
||||
}
|
||||
// Static file exists.
|
||||
const content = await fs.promises.readFile(pagePath, 'utf-8');
|
||||
const isSSG = SSG_MARKER_REGEXP.test(content);
|
||||
this.pageIsSSG.set(pagePath, isSSG);
|
||||
return isSSG ? content : undefined;
|
||||
}
|
||||
async renderApplication(opts) {
|
||||
const moduleOrFactory = this.options?.bootstrap ?? opts.bootstrap;
|
||||
if (!moduleOrFactory) {
|
||||
throw new Error('A module or bootstrap option must be provided.');
|
||||
}
|
||||
const extraProviders = [
|
||||
{ provide: _SERVER_CONTEXT, useValue: 'ssr' },
|
||||
...(opts.providers ?? []),
|
||||
...(this.options?.providers ?? []),
|
||||
];
|
||||
let document = opts.document;
|
||||
if (!document && opts.documentFilePath) {
|
||||
document = await this.getDocument(opts.documentFilePath);
|
||||
}
|
||||
const commonRenderingOptions = {
|
||||
url: opts.url,
|
||||
document,
|
||||
};
|
||||
return isBootstrapFn(moduleOrFactory)
|
||||
? renderApplication(moduleOrFactory, {
|
||||
platformProviders: extraProviders,
|
||||
...commonRenderingOptions,
|
||||
})
|
||||
: renderModule(moduleOrFactory, { extraProviders, ...commonRenderingOptions });
|
||||
}
|
||||
/** Retrieve the document from the cache or the filesystem */
|
||||
async getDocument(filePath) {
|
||||
let doc = this.templateCache.get(filePath);
|
||||
if (!doc) {
|
||||
doc = await fs.promises.readFile(filePath, 'utf-8');
|
||||
this.templateCache.set(filePath, doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
async function exists(path) {
|
||||
try {
|
||||
await fs.promises.access(path, fs.constants.F_OK);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function isBootstrapFn(value) {
|
||||
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
|
||||
return typeof value === 'function' && !('ɵmod' in value);
|
||||
}
|
||||
|
||||
export { CommonEngine };
|
||||
//# sourceMappingURL=ssr.mjs.map
|
||||
1
my-app/node_modules/@angular/ssr/fesm2022/ssr.mjs.map
generated
vendored
Executable file
1
my-app/node_modules/@angular/ssr/fesm2022/ssr.mjs.map
generated
vendored
Executable file
File diff suppressed because one or more lines are too long
55
my-app/node_modules/@angular/ssr/index.d.ts
generated
vendored
Executable file
55
my-app/node_modules/@angular/ssr/index.d.ts
generated
vendored
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
import { ApplicationRef } from '@angular/core';
|
||||
import { StaticProvider } from '@angular/core';
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
/**
|
||||
* A common engine to use to server render an application.
|
||||
*/
|
||||
export declare class CommonEngine {
|
||||
private options?;
|
||||
private readonly templateCache;
|
||||
private readonly inlineCriticalCssProcessor;
|
||||
private readonly pageIsSSG;
|
||||
constructor(options?: CommonEngineOptions | undefined);
|
||||
/**
|
||||
* Render an HTML document for a specific URL with specified
|
||||
* render options
|
||||
*/
|
||||
render(opts: CommonEngineRenderOptions): Promise<string>;
|
||||
private inlineCriticalCss;
|
||||
private retrieveSSGPage;
|
||||
private renderApplication;
|
||||
/** Retrieve the document from the cache or the filesystem */
|
||||
private getDocument;
|
||||
}
|
||||
|
||||
export declare interface CommonEngineOptions {
|
||||
/** A method that when invoked returns a promise that returns an `ApplicationRef` instance once resolved or an NgModule. */
|
||||
bootstrap?: Type<{}> | (() => Promise<ApplicationRef>);
|
||||
/** A set of platform level providers for all requests. */
|
||||
providers?: StaticProvider[];
|
||||
/** Enable request performance profiling data collection and printing the results in the server console. */
|
||||
enablePerformanceProfiler?: boolean;
|
||||
}
|
||||
|
||||
export declare interface CommonEngineRenderOptions {
|
||||
/** A method that when invoked returns a promise that returns an `ApplicationRef` instance once resolved or an NgModule. */
|
||||
bootstrap?: Type<{}> | (() => Promise<ApplicationRef>);
|
||||
/** A set of platform level providers for the current request. */
|
||||
providers?: StaticProvider[];
|
||||
url?: string;
|
||||
document?: string;
|
||||
documentFilePath?: string;
|
||||
/**
|
||||
* Reduce render blocking requests by inlining critical CSS.
|
||||
* Defaults to true.
|
||||
*/
|
||||
inlineCriticalCss?: boolean;
|
||||
/**
|
||||
* Base path location of index file.
|
||||
* Defaults to the 'documentFilePath' dirname when not provided.
|
||||
*/
|
||||
publicPath?: string;
|
||||
}
|
||||
|
||||
export { }
|
||||
42
my-app/node_modules/@angular/ssr/package.json
generated
vendored
Executable file
42
my-app/node_modules/@angular/ssr/package.json
generated
vendored
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "@angular/ssr",
|
||||
"version": "17.1.3",
|
||||
"description": "Angular server side rendering utilities",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/angular/angular-cli",
|
||||
"keywords": [
|
||||
"angular",
|
||||
"ssr",
|
||||
"universal"
|
||||
],
|
||||
"ng-add": {
|
||||
"save": "dependencies"
|
||||
},
|
||||
"dependencies": {
|
||||
"critters": "0.0.20",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^17.0.0",
|
||||
"@angular/core": "^17.0.0"
|
||||
},
|
||||
"schematics": "./schematics/collection.json",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular-cli.git"
|
||||
},
|
||||
"module": "./fesm2022/ssr.mjs",
|
||||
"typings": "./index.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": {
|
||||
"default": "./package.json"
|
||||
},
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"esm2022": "./esm2022/index.mjs",
|
||||
"esm": "./esm2022/index.mjs",
|
||||
"default": "./fesm2022/ssr.mjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
my-app/node_modules/@angular/ssr/schematics/collection.json
generated
vendored
Executable file
9
my-app/node_modules/@angular/ssr/schematics/collection.json
generated
vendored
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"schematics": {
|
||||
"ng-add": {
|
||||
"description": "Adds Angular SSR to the application without affecting any templates",
|
||||
"factory": "./ng-add/index",
|
||||
"schema": "./ng-add/schema.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
my-app/node_modules/@angular/ssr/schematics/ng-add/index.d.ts
generated
vendored
Executable file
10
my-app/node_modules/@angular/ssr/schematics/ng-add/index.d.ts
generated
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { Schema as SSROptions } from './schema';
|
||||
declare const _default: (options: SSROptions) => import("@angular-devkit/schematics").Rule;
|
||||
export default _default;
|
||||
11
my-app/node_modules/@angular/ssr/schematics/ng-add/index.js
generated
vendored
Executable file
11
my-app/node_modules/@angular/ssr/schematics/ng-add/index.js
generated
vendored
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
"use strict";
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const schematics_1 = require("@angular-devkit/schematics");
|
||||
exports.default = (options) => (0, schematics_1.externalSchematic)('@schematics/angular', 'ssr', options);
|
||||
9
my-app/node_modules/@angular/ssr/schematics/ng-add/index.mjs
generated
vendored
Executable file
9
my-app/node_modules/@angular/ssr/schematics/ng-add/index.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { externalSchematic } from '@angular-devkit/schematics';
|
||||
export default (options) => externalSchematic('@schematics/angular', 'ssr', options);
|
||||
10
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.d.ts
generated
vendored
Executable file
10
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.d.ts
generated
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
export interface Schema {
|
||||
/**
|
||||
* The name of the project.
|
||||
*/
|
||||
project: string;
|
||||
/**
|
||||
* Skip installing dependency packages.
|
||||
*/
|
||||
skipInstall?: boolean;
|
||||
}
|
||||
4
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.js
generated
vendored
Executable file
4
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.js
generated
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
"use strict";
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
|
||||
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
22
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.json
generated
vendored
Executable file
22
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.json
generated
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "SchematicsAngularNgAddSSR",
|
||||
"title": "Angular SSR Options Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The name of the project.",
|
||||
"$default": {
|
||||
"$source": "projectName"
|
||||
}
|
||||
},
|
||||
"skipInstall": {
|
||||
"description": "Skip installing dependency packages.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": ["project"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
3
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.mjs
generated
vendored
Executable file
3
my-app/node_modules/@angular/ssr/schematics/ng-add/schema.mjs
generated
vendored
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
|
||||
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
|
||||
export {};
|
||||
3
my-app/node_modules/@angular/ssr/schematics/package.json
generated
vendored
Executable file
3
my-app/node_modules/@angular/ssr/schematics/package.json
generated
vendored
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
0
my-app/node_modules/@angular/ssr/schematics/schematics.externs.js
generated
vendored
Executable file
0
my-app/node_modules/@angular/ssr/schematics/schematics.externs.js
generated
vendored
Executable file
Loading…
Add table
Add a link
Reference in a new issue