Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit25eaaa2

Browse files
devversionangular-robot[bot]
authored andcommitted
fix(@angular-devkit/build-angular): downlevel class properties when targeting Safari <=v15
The Angular compiler is dependent on static fields being attached touser-defined classes. e.g. `static ecmp = defineComponent`.These static fields sometimes rely on variables from outside of theclass. e.g. the Angular compiler generates constants for contentprojection that are then accessed in the static field initializer.Surprisingly such access to these variables may break in Safari <=v15when a page is loaded without devtools open. The bug (already solved inv16 of Safari)- is very subtle, hard to re-reproduce but basicallyvariable scope tracking is broken. This bug is triggered by additionalparenthesis in the initializer expression. See:https://bugs.webkit.org/show_bug.cgi?id=236843.The TypeScript compiler may generate such additional parenthesis whenit tries to adjust the `this` context when invoking methods, such as fordefining animations in the `ecmp` definition.More details can be found here:#24355 (comment)To ensure Angular applications are not subject to this bug whentargeting Safari <=v15. v15 Safari, both for iOS and Mac is still part ofthe default CLI browserslist with `last 2 Safari majors` (at time ofwriting).Note that it is important that the Babel plugin properly handles thedownleveling of static block-defined members. TypeScript will transformstatic fields, like `static ecmp` into `static { this.ecmp = X }` when`useDefineForClassFields = false` (which is the case for CLI apps). Theclass properties plugin from Babel seems to handle this in an acceptableway. Unlike actual static fields, Babel will not use helpers like`defineProperty` for such extracted static blocks though. e.g.See repro:https://gist.github.com/devversion/dec0dea26e348c509921bf62079b60be```jsclass Test { x = true; static b = true; static { this.a = true; }}// intoclass X { constructor() { _defineProperty(this, "x", true); }}_defineProperty(X, "b", true);X.a = true;```note that in practice TypeScript with `useDefineForClassFields = false`will put non-static members into the constructor as normal assignmentsregardless- so there would be no change by the Babel plugin.Fixes#24355.
1 parent49b313f commit25eaaa2

File tree

3 files changed

+90
-12
lines changed

3 files changed

+90
-12
lines changed

‎packages/angular_devkit/build_angular/src/babel/presets/application.ts‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,26 @@ import type {
1414
makeLocalePlugin,
1515
}from'@angular/localize/tools';
1616
import{strictasassert}from'assert';
17+
importbrowserslistfrom'browserslist';
1718
import*asfsfrom'fs';
1819
import*aspathfrom'path';
1920

21+
/**
22+
* List of browsers which are affected by a WebKit bug where class field
23+
* initializers might have incorrect variable scopes.
24+
*
25+
* See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033
26+
* See: https://github.com/WebKit/WebKit/commit/e8788a34b3d5f5b4edd7ff6450b80936bff396f2
27+
*/
28+
constsafariClassFieldScopeBugBrowsers=newSet(
29+
browserslist([
30+
// Safari <15 is technically not supported via https://angular.io/guide/browser-support,
31+
// but we apply the workaround if forcibly selected.
32+
'Safari <=15',
33+
'iOS <=15',
34+
]),
35+
);
36+
2037
exporttypeDiagnosticReporter=(type:'error'|'warning'|'info',message:string)=>void;
2138

2239
/**
@@ -45,7 +62,6 @@ export interface ApplicationPresetOptions {
4562
linkerPluginCreator:typeofimport('@angular/compiler-cli/linker/babel').createEs2015LinkerPlugin;
4663
};
4764

48-
forcePresetEnv?:boolean;
4965
forceAsyncTransformation?:boolean;
5066
instrumentCode?:{
5167
includedBasePath:string;
@@ -171,13 +187,26 @@ export default function (api: unknown, options: ApplicationPresetOptions) {
171187
);
172188
}
173189

174-
if(options.forcePresetEnv){
190+
// Applications code ES version can be controlled using TypeScript's `target` option.
191+
// However, this doesn't effect libraries and hence we use preset-env to downlevel ES features
192+
// based on the supported browsers in browserslist.
193+
if(options.supportedBrowsers){
194+
constincludePlugins:string[]=[];
195+
196+
// If a Safari browser affected by the class field scope bug is selected, we
197+
// downlevel class properties by ensuring the class properties Babel plugin
198+
// is always included- regardless of the preset-env targets.
199+
if(options.supportedBrowsers.some((b)=>safariClassFieldScopeBugBrowsers.has(b))){
200+
includePlugins.push('@babel/plugin-proposal-class-properties');
201+
}
202+
175203
presets.push([
176204
require('@babel/preset-env').default,
177205
{
178206
bugfixes:true,
179207
modules:false,
180208
targets:options.supportedBrowsers,
209+
include:includePlugins,
181210
exclude:['transform-typeof-symbol'],
182211
},
183212
]);

‎packages/angular_devkit/build_angular/src/babel/webpack-loader.ts‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ export default custom<ApplicationPresetOptions>(() => {
7979

8080
constcustomOptions:ApplicationPresetOptions={
8181
forceAsyncTransformation:false,
82-
forcePresetEnv:false,
8382
angularLinker:undefined,
8483
i18n:undefined,
8584
instrumentCode:undefined,
@@ -105,14 +104,6 @@ export default custom<ApplicationPresetOptions>(() => {
105104
shouldProcess=true;
106105
}
107106

108-
// Analyze for ES target processing
109-
if(customOptions.supportedBrowsers?.length){
110-
// Applications code ES version can be controlled using TypeScript's `target` option.
111-
// However, this doesn't effect libraries and hence we use preset-env to downlevel ES fetaures
112-
// based on the supported browsers in browserlist.
113-
customOptions.forcePresetEnv=true;
114-
}
115-
116107
// Application code (TS files) will only contain native async if target is ES2017+.
117108
// However, third-party libraries can regardless of the target option.
118109
// APF packages with code in [f]esm2015 directories is downlevelled to ES2015 and
@@ -121,7 +112,9 @@ export default custom<ApplicationPresetOptions>(() => {
121112
!/[\\/][_f]?esm2015[\\/]/.test(this.resourcePath)&&source.includes('async');
122113

123114
shouldProcess||=
124-
customOptions.forceAsyncTransformation||customOptions.forcePresetEnv||false;
115+
customOptions.forceAsyncTransformation||
116+
customOptions.supportedBrowsers!==undefined||
117+
false;
125118

126119
// Analyze for i18n inlining
127120
if(
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import{expectFileToExist,readFile,writeFile}from'../../utils/fs';
2+
import{ng}from'../../utils/process';
3+
import{updateJsonFile}from'../../utils/project';
4+
5+
constunexpectedStaticFieldErrorMessage=
6+
'Found unexpected static field. This indicates that the Safari <=v15 '+
7+
'workaround for a scope variable tracking is not working. '+
8+
'See: https://github.com/angular/angular-cli/pull/24357';
9+
10+
exportdefaultasyncfunction(){
11+
awaitupdateJsonFile('angular.json',(workspace)=>{
12+
constbuild=workspace.projects['test-project'].architect.build;
13+
build.defaultConfiguration=undefined;
14+
build.options={
15+
...build.options,
16+
optimization:false,
17+
outputHashing:'none',
18+
};
19+
});
20+
21+
// Matches two types of static fields that indicate that the Safari bug
22+
// may still occur. With the workaround this should not appear in bundles.
23+
// - static { this.ecmp = bla }
24+
// - static #_ = this.ecmp = bla
25+
conststaticIndicatorRegex=/static\s+(\{|#[_\d]+\s+=)/;
26+
27+
awaitng('build');
28+
awaitexpectFileToExist('dist/test-project/main.js');
29+
constmainContent=awaitreadFile('dist/test-project/main.js');
30+
31+
// TODO: This default cause can be removed in the future when Safari v15
32+
// is longer included in the default browserlist configuration of CLI apps.
33+
if(staticIndicatorRegex.test(mainContent)){
34+
thrownewError(unexpectedStaticFieldErrorMessage);
35+
}
36+
37+
awaitwriteFile('.browserslistrc','last 1 chrome version');
38+
39+
awaitng('build');
40+
awaitexpectFileToExist('dist/test-project/main.js');
41+
constmainContentChromeLatest=awaitreadFile('dist/test-project/main.js');
42+
43+
if(!staticIndicatorRegex.test(mainContentChromeLatest)){
44+
thrownewError('Expected static fields to be used when Safari <=v15 is not targeted.');
45+
}
46+
47+
awaitwriteFile('.browserslistrc','Safari <=15');
48+
49+
awaitng('build');
50+
awaitexpectFileToExist('dist/test-project/main.js');
51+
constmainContentSafari15Explicit=awaitreadFile('dist/test-project/main.js');
52+
53+
if(staticIndicatorRegex.test(mainContentSafari15Explicit)){
54+
thrownewError(unexpectedStaticFieldErrorMessage);
55+
}
56+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp