Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a new pull request by comparing changes across two branches #1118

Merged
merged 11 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,8 @@ jobs:
uses: angular/dev-infra/github-actions/bazel/configure-remote@8eed650ff101cb2be3949febba829b722e89ff80
- name: Install node modules
run: yarn install --immutable
- name: Run module tests
run: yarn bazel test //modules/...
- name: Run package tests
run: yarn bazel test //packages/...
- name: Run module and package tests
run: yarn bazel test //modules/... //packages/...

e2e:
strategy:
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,8 @@ jobs:
uses: angular/dev-infra/github-actions/bazel/configure-remote@8eed650ff101cb2be3949febba829b722e89ff80
- name: Install node modules
run: yarn install --immutable
- name: Run module tests
run: yarn bazel test //modules/...
- name: Run package tests
run: yarn bazel test //packages/...
- name: Run module and package tests
run: yarn bazel test //modules/... //packages/...

e2e:
strategy:
Expand Down
4 changes: 2 additions & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
/CONTRIBUTING.md
.yarn/
dist/
third_party/
third_party/github.com/
/tests/legacy-cli/e2e/assets/
/tools/test/*.json
/tools/test/*.json
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"@angular/router": "19.0.0-next.1",
"@angular/service-worker": "19.0.0-next.1",
"@babel/core": "7.25.2",
"@babel/generator": "7.25.4",
"@babel/generator": "7.25.5",
"@babel/helper-annotate-as-pure": "7.24.7",
"@babel/helper-split-export-declaration": "7.24.7",
"@babel/plugin-syntax-import-attributes": "7.24.7",
Expand All @@ -83,6 +83,7 @@
"@bazel/buildifier": "7.1.2",
"@bazel/concatjs": "patch:@bazel/concatjs@npm%3A5.8.1#~/.yarn/patches/@bazel-concatjs-npm-5.8.1-1bf81df846.patch",
"@bazel/jasmine": "patch:@bazel/jasmine@npm%3A5.8.1#~/.yarn/patches/@bazel-jasmine-npm-5.8.1-3370fee155.patch",
"@bazel/runfiles": "^5.8.1",
"@discoveryjs/json-ext": "0.6.1",
"@inquirer/confirm": "3.1.22",
"@inquirer/prompts": "5.3.8",
Expand Down Expand Up @@ -196,14 +197,15 @@
"terser": "5.31.6",
"tree-kill": "1.2.2",
"ts-node": "^10.9.1",
"tslib": "2.6.3",
"tslib": "2.7.0",
"typescript": "5.5.4",
"undici": "6.19.8",
"unenv": "^1.10.0",
"verdaccio": "5.32.1",
"verdaccio-auth-memory": "^10.0.0",
"vite": "5.4.2",
"watchpack": "2.4.2",
"webpack": "5.93.0",
"webpack": "5.94.0",
"webpack-dev-middleware": "7.4.2",
"webpack-dev-server": "5.0.4",
"webpack-merge": "6.0.1",
Expand Down
1 change: 1 addition & 0 deletions packages/angular/build/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ ts_library(
"@npm//browserslist",
"@npm//critters",
"@npm//esbuild",
"@npm//esbuild-wasm",
"@npm//fast-glob",
"@npm//https-proxy-agent",
"@npm//listr2",
Expand Down
19 changes: 19 additions & 0 deletions packages/angular/build/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @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.dev/license
*/

// The `bundled_critters` causes issues with module mappings in Bazel,
// leading to unexpected behavior with esbuild. Specifically, the problem occurs
// when esbuild resolves to a different module or version than expected, due to
// how Bazel handles module mappings.
//
// This change aims to resolve esbuild types correctly and maintain consistency
// in the Bazel build process.

declare module 'esbuild' {
export * from 'esbuild-wasm';
}
97 changes: 52 additions & 45 deletions packages/angular/build/src/utils/index-file/inline-critical-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,46 @@ const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
const CSP_MEDIA_ATTR = 'ngCspMedia';

/**
* Script text used to change the media value of the link tags.
* Script that dynamically updates the `media` attribute of `<link>` tags based on a custom attribute (`CSP_MEDIA_ATTR`).
*
* NOTE:
* We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
* because this does not always fire on Chome.
* because load events are not always triggered reliably on Chrome.
* See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
*
* The script:
* - Ensures the event target is a `<link>` tag with the `CSP_MEDIA_ATTR` attribute.
* - Updates the `media` attribute with the value of `CSP_MEDIA_ATTR` and then removes the attribute.
* - Removes the event listener when all relevant `<link>` tags have been processed.
* - Uses event capturing (the `true` parameter) since load events do not bubble up the DOM.
*/
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');
const LINK_LOAD_SCRIPT_CONTENT = `
(() => {
const CSP_MEDIA_ATTR = '${CSP_MEDIA_ATTR}';
const documentElement = document.documentElement;

// Listener for load events on link tags.
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);

if (!document.head.querySelector(\`link[\${CSP_MEDIA_ATTR}]\`)) {
documentElement.removeEventListener('load', listener);
}
};

documentElement.addEventListener('load', listener, true);
})();
`.trim();

export interface InlineCriticalCssProcessOptions {
outputPath: string;
Expand Down Expand Up @@ -85,22 +95,22 @@ interface PartialDocument {
querySelector(selector: string): PartialHTMLElement | null;
}

/** Signature of the `Critters.embedLinkedStylesheet` method. */
type EmbedLinkedStylesheetFn = (
link: PartialHTMLElement,
document: PartialDocument,
) => Promise<unknown>;
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */

class CrittersExtended extends Critters {
// We use Typescript declaration merging because `embedLinkedStylesheet` it's not declared in
// the `Critters` types which means that we can't call the `super` implementation.
interface CrittersBase {
embedLinkedStylesheet(link: PartialHTMLElement, document: PartialDocument): Promise<unknown>;
}
class CrittersBase extends Critters {}
/* eslint-enable @typescript-eslint/no-unsafe-declaration-merging */

class CrittersExtended extends CrittersBase {
readonly warnings: string[] = [];
readonly errors: string[] = [];
private initialEmbedLinkedStylesheet: EmbedLinkedStylesheetFn;
private addedCspScriptsDocuments = new WeakSet<PartialDocument>();
private documentNonces = new WeakMap<PartialDocument, string | null>();

// Inherited from `Critters`, but not exposed in the typings.
protected declare embedLinkedStylesheet: EmbedLinkedStylesheetFn;

constructor(
private readonly optionsExtended: InlineCriticalCssProcessorOptions &
InlineCriticalCssProcessOptions,
Expand All @@ -119,17 +129,11 @@ class CrittersExtended extends Critters {
reduceInlineStyles: false,
mergeStylesheets: false,
// Note: if `preload` changes to anything other than `media`, the logic in
// `embedLinkedStylesheetOverride` will have to be updated.
// `embedLinkedStylesheet` will have to be updated.
preload: 'media',
noscriptFallback: true,
inlineFonts: true,
});

// 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;
}

public override readFile(path: string): Promise<string> {
Expand All @@ -142,7 +146,10 @@ class CrittersExtended extends Critters {
* Override of the Critters `embedLinkedStylesheet` method
* that makes it work with Angular's CSP APIs.
*/
private embedLinkedStylesheetOverride: EmbedLinkedStylesheetFn = async (link, document) => {
override async embedLinkedStylesheet(
link: PartialHTMLElement,
document: PartialDocument,
): Promise<unknown> {
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.
Expand All @@ -154,7 +161,7 @@ class CrittersExtended extends Critters {
}
}

const returnValue = await this.initialEmbedLinkedStylesheet(link, document);
const returnValue = await super.embedLinkedStylesheet(link, document);
const cspNonce = this.findCspNonce(document);

if (cspNonce) {
Expand Down Expand Up @@ -182,7 +189,7 @@ class CrittersExtended extends Critters {
}

return returnValue;
};
}

/**
* Finds the CSP nonce for a specific document.
Expand Down
11 changes: 9 additions & 2 deletions packages/angular/ssr/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ ts_library(
),
module_name = "@angular/ssr",
deps = [
"//packages/angular/ssr/third_party/critters:bundled_critters_lib",
"@npm//@angular/common",
"@npm//@angular/core",
"@npm//@angular/platform-server",
"@npm//@angular/router",
"@npm//@types/node",
"@npm//critters",
],
)

Expand All @@ -32,8 +32,15 @@ ng_package(
package_name = "@angular/ssr",
srcs = [
":package.json",
"//packages/angular/ssr/third_party/critters:bundled_critters_lib",
],
externals = [
"express",
"../../third_party/critters",
],
nested_packages = [
"//packages/angular/ssr/schematics:npm_package",
],
nested_packages = ["//packages/angular/ssr/schematics:npm_package"],
tags = ["release-package"],
deps = [
":ssr",
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/ssr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"save": "dependencies"
},
"dependencies": {
"critters": "0.0.24",
"tslib": "^2.3.0"
},
"peerDependencies": {
Expand All @@ -29,6 +28,7 @@
"@angular/platform-browser": "19.0.0-next.1",
"@angular/platform-server": "19.0.0-next.1",
"@angular/router": "19.0.0-next.1",
"@bazel/runfiles": "^5.8.1",
"zone.js": "^0.15.0"
},
"schematics": "./schematics/collection.json",
Expand Down
24 changes: 20 additions & 4 deletions packages/angular/ssr/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Hooks } from './hooks';
import { getAngularAppManifest } from './manifest';
import { ServerRouter } from './routes/router';
import { REQUEST, REQUEST_CONTEXT, RESPONSE_INIT } from './tokens';
import { InlineCriticalCssProcessor } from './utils/inline-critical-css';
import { renderAngular } from './utils/ng';

/**
Expand Down Expand Up @@ -52,6 +53,11 @@ export class AngularServerApp {
*/
private router: ServerRouter | undefined;

/**
* The `inlineCriticalCssProcessor` is responsible for handling critical CSS inlining.
*/
private inlineCriticalCssProcessor: InlineCriticalCssProcessor | undefined;

/**
* Renders a response for the given HTTP request using the server application.
*
Expand Down Expand Up @@ -177,10 +183,20 @@ export class AngularServerApp {
html = await hooks.run('html:transform:pre', { html });
}

return new Response(
await renderAngular(html, manifest.bootstrap(), new URL(request.url), platformProviders),
responseInit,
);
html = await renderAngular(html, manifest.bootstrap(), new URL(request.url), platformProviders);

if (manifest.inlineCriticalCss) {
// Optionally inline critical CSS.
this.inlineCriticalCssProcessor ??= new InlineCriticalCssProcessor((path: string) => {
const fileName = path.split('/').pop() ?? path;

return this.assets.getServerAsset(fileName);
});

html = await this.inlineCriticalCssProcessor.process(html);
}

return new Response(html, responseInit);
}
}

Expand Down
Loading
Loading