Skip to content

Commit c92fdad

Browse files
authored
fix(material/schematics): Create a schematic to add the base theme dimension (#27964)
As of v17, users need to include the new "base" theme dimension for the components they use. For users that are using the "theme" mixins now, the base dimension will be pulled in automatically. However, for users using the "color", "typography", and "density" mixins only, they will need to include the "base" mixin as well. This schematic works by scanning all of the app's Sass to determine which components had their "color", "typography", or "density" mixins included, but did *not* have their "theme" mixin included. It then locates all of the calls to "mat.core()" and inserts calls to the "base" mixins for the identified compoennts, immediately following. It makes sense to use "mat.core()" as a signal for when to insert them, because the "base" mixins should be used once-per-app, like "mat.core()". We can't guarantee that a "$theme" will be available at the insertion point, so we create a "$dummy-theme" to pass to the base mixins. We also insert a comment above the new mixins explaining why they were added and a TODO to clean up the "$dummy-theme". Once we have the documentation updated to cover the base dimension, we should follow-up by linking to it from the inserted comment.
1 parent 21a0c6a commit c92fdad

File tree

5 files changed

+777
-2
lines changed

5 files changed

+777
-2
lines changed

src/material/_index.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
@forward './core/style/elevation' show elevation, overridable-elevation, elevation-transition;
4848

4949
// Theme bundles
50-
@forward './core/theming/all-theme' show all-component-themes;
50+
@forward './core/theming/all-theme' show all-component-themes, all-component-bases;
5151
@forward './core/color/all-color' show all-component-colors;
5252
@forward './core/typography/all-typography' show all-component-typographies;
5353

src/material/schematics/ng-update/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import {
1414
} from '@angular/cdk/schematics';
1515

1616
import {materialUpgradeData} from './upgrade-data';
17+
import {ThemeBaseMigration} from './migrations/theme-base-v17';
1718

18-
const materialMigrations: NullableDevkitMigration[] = [];
19+
const materialMigrations: NullableDevkitMigration[] = [ThemeBaseMigration];
1920

2021
/** Entry point for the migration schematics with target of Angular Material v17 */
2122
export function updateToV17(): Rule {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {extname} from '@angular-devkit/core';
10+
import {SchematicContext} from '@angular-devkit/schematics';
11+
import {DevkitMigration, ResolvedResource, TargetVersion} from '@angular/cdk/schematics';
12+
import {addThemeBaseMixins, checkThemeBaseMixins} from './migration';
13+
14+
/** Adds an @include for theme base mixins that aren't already included by the app. */
15+
export class ThemeBaseMigration extends DevkitMigration<null> {
16+
/** Number of files that have been migrated. */
17+
static migratedFileCount = 0;
18+
19+
/** All base mixins that we have found an existing @include for. */
20+
static foundBaseMixins = new Set<string>();
21+
22+
/** All base mixins that appear to be missing an @include. */
23+
static missingBaseMixins = new Set<string>();
24+
25+
/** Whether to run this migration. */
26+
enabled = this.targetVersion === TargetVersion.V17;
27+
28+
/**
29+
* All Sass stylesheets visited. (We save a record, so we can go back through them in the
30+
* `postAnalysis` phase).
31+
*/
32+
visitedSassStylesheets: ResolvedResource[] = [];
33+
34+
/**
35+
* Visit each stylesheet, noting which base mixins are accounted for (because the user is calling
36+
* `mat.<component>-theme()`), and which ones are missing (because the user is calling one of the
37+
* theme-partial mixins: `mat.<component-color>()`, `mat.<component>-typography()`,
38+
* or `mat.<component>-density()`.
39+
*
40+
* We don't make any modifications at this point. Instead, the results of visiting each stylesheet
41+
* are aggregated into a static variable which is used to determine which mixins to add in
42+
* `postAnalysis` phase.
43+
*/
44+
override visitStylesheet(stylesheet: ResolvedResource): void {
45+
if (extname(stylesheet.filePath) === '.scss') {
46+
this.visitedSassStylesheets.push(stylesheet);
47+
48+
const content = stylesheet.content;
49+
const {found, missing} = checkThemeBaseMixins(content);
50+
for (const mixin of found) {
51+
ThemeBaseMigration.foundBaseMixins.add(mixin);
52+
ThemeBaseMigration.missingBaseMixins.delete(mixin);
53+
}
54+
for (const mixin of missing) {
55+
if (!ThemeBaseMigration.foundBaseMixins.has(mixin)) {
56+
ThemeBaseMigration.missingBaseMixins.add(mixin);
57+
}
58+
}
59+
}
60+
}
61+
62+
/**
63+
* Perform the necessary updates detected while visiting the stylesheets. The
64+
* `mat.<component>-base()` mixins behave similarly to `mat.core()`, in that they needed to be
65+
* included once globally. So we locate calls to `mat.core()` and add the missing mixins
66+
* identified by earlier at these locations.
67+
*/
68+
override postAnalysis() {
69+
// If we're not missing any mixins, there's nothing to migrate.
70+
if (ThemeBaseMigration.missingBaseMixins.size === 0) {
71+
return;
72+
}
73+
// If we have all-component-bases, we don't need any others and there is nothing to migrate.
74+
if (ThemeBaseMigration.foundBaseMixins.has('all-component-bases')) {
75+
return;
76+
}
77+
// If we're missing all-component-bases, we just need to add it, not the individual mixins.
78+
if (ThemeBaseMigration.missingBaseMixins.has('all-component-bases')) {
79+
ThemeBaseMigration.missingBaseMixins = new Set(['all-component-bases']);
80+
}
81+
for (const stylesheet of this.visitedSassStylesheets) {
82+
const content = stylesheet.content;
83+
const migratedContent = content
84+
? addThemeBaseMixins(content, ThemeBaseMigration.missingBaseMixins)
85+
: content;
86+
87+
if (migratedContent && migratedContent !== content) {
88+
this.fileSystem
89+
.edit(stylesheet.filePath)
90+
.remove(0, stylesheet.content.length)
91+
.insertLeft(0, migratedContent);
92+
ThemeBaseMigration.migratedFileCount++;
93+
}
94+
}
95+
if (ThemeBaseMigration.migratedFileCount === 0) {
96+
const mixinsText = [...ThemeBaseMigration.missingBaseMixins]
97+
.sort()
98+
.map(m => `mat.${m}($theme)`)
99+
.join('\n');
100+
this.failures.push({
101+
filePath: this.context.tree.root.path,
102+
message:
103+
`The following mixins could not be automatically added, please add them manually` +
104+
` if needed:\n${mixinsText}`,
105+
});
106+
}
107+
}
108+
109+
/** Logs out the number of migrated files at the end of the migration. */
110+
static override globalPostMigration(
111+
_tree: unknown,
112+
_targetVersion: TargetVersion,
113+
context: SchematicContext,
114+
): void {
115+
const fileCount = ThemeBaseMigration.migratedFileCount;
116+
const mixinCount = ThemeBaseMigration.missingBaseMixins.size;
117+
118+
if (fileCount > 0 && mixinCount > 0) {
119+
const fileCountText = fileCount === 1 ? '1 file' : `${fileCount} files`;
120+
const mixinCountText =
121+
mixinCount === 1 ? '1 theme base mixin' : `${mixinCount} theme base mixins`;
122+
context.logger.info(
123+
`Added ${mixinCountText} to ${fileCountText}.` +
124+
' Please search for, and address, any "TODO(v17)" comments.',
125+
);
126+
}
127+
128+
// Reset to avoid leaking between tests.
129+
ThemeBaseMigration.migratedFileCount = 0;
130+
ThemeBaseMigration.missingBaseMixins = new Set();
131+
ThemeBaseMigration.foundBaseMixins = new Set();
132+
}
133+
}

0 commit comments

Comments
 (0)