|
6 | 6 | * found in the LICENSE file at https://angular.dev/license |
7 | 7 | */ |
8 | 8 |
|
9 | | -import {ConstantPool} from '@angular/compiler'; |
| 9 | +import {ConstantPool, ViewEncapsulation} from '@angular/compiler'; |
10 | 10 | import ts from 'typescript'; |
11 | 11 |
|
12 | 12 | import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../../cycles'; |
@@ -67,11 +67,17 @@ function setup( |
67 | 67 | program: ts.Program, |
68 | 68 | options: ts.CompilerOptions, |
69 | 69 | host: ts.CompilerHost, |
70 | | - opts: {compilationMode: CompilationMode; usePoisonedData?: boolean} = { |
71 | | - compilationMode: CompilationMode.FULL, |
72 | | - }, |
| 70 | + opts: { |
| 71 | + compilationMode?: CompilationMode; |
| 72 | + usePoisonedData?: boolean; |
| 73 | + externalRuntimeStyles?: boolean; |
| 74 | + } = {}, |
73 | 75 | ) { |
74 | | - const {compilationMode, usePoisonedData} = opts; |
| 76 | + const { |
| 77 | + compilationMode = CompilationMode.FULL, |
| 78 | + usePoisonedData, |
| 79 | + externalRuntimeStyles = false, |
| 80 | + } = opts; |
75 | 81 | const checker = program.getTypeChecker(); |
76 | 82 | const reflectionHost = new TypeScriptReflectionHost(checker); |
77 | 83 | const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null); |
@@ -145,6 +151,7 @@ function setup( |
145 | 151 | /* forbidOrphanRenderering */ false, |
146 | 152 | /* enableBlockSyntax */ true, |
147 | 153 | /* enableLetSyntax */ true, |
| 154 | + externalRuntimeStyles, |
148 | 155 | /* localCompilationExtraImportsTracker */ null, |
149 | 156 | jitDeclarationRegistry, |
150 | 157 | /* i18nPreserveSignificantWhitespace */ true, |
@@ -359,6 +366,187 @@ runInEachFileSystem(() => { |
359 | 366 | expect(compileResult).toEqual([]); |
360 | 367 | }); |
361 | 368 |
|
| 369 | + it('should populate externalStyles from styleUrl when externalRuntimeStyles is enabled', () => { |
| 370 | + const {program, options, host} = makeProgram([ |
| 371 | + { |
| 372 | + name: _('/node_modules/@angular/core/index.d.ts'), |
| 373 | + contents: 'export const Component: any;', |
| 374 | + }, |
| 375 | + { |
| 376 | + name: _('/myStyle.css'), |
| 377 | + contents: '<div>hello world</div>', |
| 378 | + }, |
| 379 | + { |
| 380 | + name: _('/entry.ts'), |
| 381 | + contents: ` |
| 382 | + import {Component} from '@angular/core'; |
| 383 | +
|
| 384 | + @Component({ |
| 385 | + template: '', |
| 386 | + styleUrl: '/myStyle.css', |
| 387 | + styles: ['a { color: red; }', 'b { color: blue; }'], |
| 388 | + }) class TestCmp {} |
| 389 | + `, |
| 390 | + }, |
| 391 | + ]); |
| 392 | + const {reflectionHost, handler} = setup(program, options, host, { |
| 393 | + externalRuntimeStyles: true, |
| 394 | + }); |
| 395 | + const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration); |
| 396 | + const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp)); |
| 397 | + if (detected === undefined) { |
| 398 | + return fail('Failed to recognize @Component'); |
| 399 | + } |
| 400 | + const {analysis} = handler.analyze(TestCmp, detected.metadata); |
| 401 | + expect(analysis?.resources.styles.size).toBe(2); |
| 402 | + expect(analysis?.meta.externalStyles).toEqual(['/myStyle.css']); |
| 403 | + }); |
| 404 | + |
| 405 | + it('should populate externalStyles from styleUrls when externalRuntimeStyles is enabled', () => { |
| 406 | + const {program, options, host} = makeProgram([ |
| 407 | + { |
| 408 | + name: _('/node_modules/@angular/core/index.d.ts'), |
| 409 | + contents: 'export const Component: any;', |
| 410 | + }, |
| 411 | + { |
| 412 | + name: _('/myStyle.css'), |
| 413 | + contents: '<div>hello world</div>', |
| 414 | + }, |
| 415 | + { |
| 416 | + name: _('/entry.ts'), |
| 417 | + contents: ` |
| 418 | + import {Component} from '@angular/core'; |
| 419 | +
|
| 420 | + @Component({ |
| 421 | + template: '', |
| 422 | + styleUrls: ['/myStyle.css', '/myOtherStyle.css'], |
| 423 | + styles: ['a { color: red; }', 'b { color: blue; }'], |
| 424 | + }) class TestCmp {} |
| 425 | + `, |
| 426 | + }, |
| 427 | + ]); |
| 428 | + const {reflectionHost, handler} = setup(program, options, host, { |
| 429 | + externalRuntimeStyles: true, |
| 430 | + }); |
| 431 | + const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration); |
| 432 | + const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp)); |
| 433 | + if (detected === undefined) { |
| 434 | + return fail('Failed to recognize @Component'); |
| 435 | + } |
| 436 | + const {analysis} = handler.analyze(TestCmp, detected.metadata); |
| 437 | + expect(analysis?.resources.styles.size).toBe(2); |
| 438 | + expect(analysis?.meta.externalStyles).toEqual(['/myStyle.css', '/myOtherStyle.css']); |
| 439 | + }); |
| 440 | + |
| 441 | + it('should keep default emulated view encapsulation with styleUrls when externalRuntimeStyles is enabled', () => { |
| 442 | + const {program, options, host} = makeProgram([ |
| 443 | + { |
| 444 | + name: _('/node_modules/@angular/core/index.d.ts'), |
| 445 | + contents: 'export const Component: any;', |
| 446 | + }, |
| 447 | + { |
| 448 | + name: _('/myStyle.css'), |
| 449 | + contents: '<div>hello world</div>', |
| 450 | + }, |
| 451 | + { |
| 452 | + name: _('/entry.ts'), |
| 453 | + contents: ` |
| 454 | + import {Component} from '@angular/core'; |
| 455 | +
|
| 456 | + @Component({ |
| 457 | + template: '', |
| 458 | + styleUrls: ['/myStyle.css', '/myOtherStyle.css'], |
| 459 | + }) class TestCmp {} |
| 460 | + `, |
| 461 | + }, |
| 462 | + ]); |
| 463 | + const {reflectionHost, handler} = setup(program, options, host, { |
| 464 | + externalRuntimeStyles: true, |
| 465 | + }); |
| 466 | + const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration); |
| 467 | + const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp)); |
| 468 | + if (detected === undefined) { |
| 469 | + return fail('Failed to recognize @Component'); |
| 470 | + } |
| 471 | + const {analysis} = handler.analyze(TestCmp, detected.metadata); |
| 472 | + expect(analysis?.meta.encapsulation).toBe(ViewEncapsulation.Emulated); |
| 473 | + }); |
| 474 | + |
| 475 | + it('should populate externalStyles from template link element when externalRuntimeStyles is enabled', () => { |
| 476 | + const {program, options, host} = makeProgram([ |
| 477 | + { |
| 478 | + name: _('/node_modules/@angular/core/index.d.ts'), |
| 479 | + contents: 'export const Component: any;', |
| 480 | + }, |
| 481 | + { |
| 482 | + name: _('/myStyle.css'), |
| 483 | + contents: '<div>hello world</div>', |
| 484 | + }, |
| 485 | + { |
| 486 | + name: _('/entry.ts'), |
| 487 | + contents: ` |
| 488 | + import {Component} from '@angular/core'; |
| 489 | +
|
| 490 | + @Component({ |
| 491 | + template: '<link rel="stylesheet" href="myTemplateStyle.css" />', |
| 492 | + styles: ['a { color: red; }', 'b { color: blue; }'], |
| 493 | + }) class TestCmp {} |
| 494 | + `, |
| 495 | + }, |
| 496 | + ]); |
| 497 | + const {reflectionHost, handler} = setup(program, options, host, { |
| 498 | + externalRuntimeStyles: true, |
| 499 | + }); |
| 500 | + const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration); |
| 501 | + const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp)); |
| 502 | + if (detected === undefined) { |
| 503 | + return fail('Failed to recognize @Component'); |
| 504 | + } |
| 505 | + const {analysis} = handler.analyze(TestCmp, detected.metadata); |
| 506 | + expect(analysis?.resources.styles.size).toBe(2); |
| 507 | + expect(analysis?.meta.externalStyles).toEqual(['myTemplateStyle.css']); |
| 508 | + }); |
| 509 | + |
| 510 | + it('should populate externalStyles with resolve return values when externalRuntimeStyles is enabled', () => { |
| 511 | + const {program, options, host} = makeProgram([ |
| 512 | + { |
| 513 | + name: _('/node_modules/@angular/core/index.d.ts'), |
| 514 | + contents: 'export const Component: any;', |
| 515 | + }, |
| 516 | + { |
| 517 | + name: _('/myStyle.css'), |
| 518 | + contents: '<div>hello world</div>', |
| 519 | + }, |
| 520 | + { |
| 521 | + name: _('/entry.ts'), |
| 522 | + contents: ` |
| 523 | + import {Component} from '@angular/core'; |
| 524 | +
|
| 525 | + @Component({ |
| 526 | + template: '<link rel="stylesheet" href="myTemplateStyle.css" />', |
| 527 | + styleUrl: '/myStyle.css', |
| 528 | + styles: ['a { color: red; }', 'b { color: blue; }'], |
| 529 | + }) class TestCmp {} |
| 530 | + `, |
| 531 | + }, |
| 532 | + ]); |
| 533 | + const {reflectionHost, handler, resourceLoader} = setup(program, options, host, { |
| 534 | + externalRuntimeStyles: true, |
| 535 | + }); |
| 536 | + resourceLoader.resolve = (v) => 'abc/' + v; |
| 537 | + const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration); |
| 538 | + const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp)); |
| 539 | + if (detected === undefined) { |
| 540 | + return fail('Failed to recognize @Component'); |
| 541 | + } |
| 542 | + const {analysis} = handler.analyze(TestCmp, detected.metadata); |
| 543 | + expect(analysis?.resources.styles.size).toBe(2); |
| 544 | + expect(analysis?.meta.externalStyles).toEqual([ |
| 545 | + 'abc//myStyle.css', |
| 546 | + 'abc/myTemplateStyle.css', |
| 547 | + ]); |
| 548 | + }); |
| 549 | + |
362 | 550 | it('should replace inline style content with transformed content', async () => { |
363 | 551 | const {program, options, host} = makeProgram([ |
364 | 552 | { |
|
0 commit comments