theming.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright Google LLC All Rights Reserved.
  5. *
  6. * Use of this source code is governed by an MIT-style license that can be
  7. * found in the LICENSE file at https://angular.dev/license
  8. */
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. exports.addThemeToAppStyles = addThemeToAppStyles;
  11. exports.addTypographyClass = addTypographyClass;
  12. const core_1 = require("@angular-devkit/core");
  13. const schematics_1 = require("@angular-devkit/schematics");
  14. const schematics_2 = require("@angular/cdk/schematics");
  15. const change_1 = require("@schematics/angular/utility/change");
  16. const workspace_1 = require("@schematics/angular/utility/workspace");
  17. const path_1 = require("path");
  18. const create_custom_theme_1 = require("./create-custom-theme");
  19. /** Path segment that can be found in paths that refer to a prebuilt theme. */
  20. const prebuiltThemePathSegment = '@angular/material/prebuilt-themes';
  21. /** Default file name of the custom theme that can be generated. */
  22. const defaultCustomThemeFilename = 'custom-theme.scss';
  23. /** Add pre-built styles to the main project style file. */
  24. function addThemeToAppStyles(options) {
  25. return (host, context) => {
  26. const themeName = options.theme || 'azure-blue';
  27. return themeName === 'custom'
  28. ? insertCustomTheme(options.project, host, context.logger)
  29. : insertPrebuiltTheme(options.project, themeName, context.logger);
  30. };
  31. }
  32. /** Adds the global typography class to the body element. */
  33. function addTypographyClass(options) {
  34. return async (host) => {
  35. const workspace = await (0, workspace_1.getWorkspace)(host);
  36. const project = (0, schematics_2.getProjectFromWorkspace)(workspace, options.project);
  37. const projectIndexFiles = (0, schematics_2.getProjectIndexFiles)(project);
  38. if (!projectIndexFiles.length) {
  39. throw new schematics_1.SchematicsException('No project index HTML file could be found.');
  40. }
  41. if (options.typography) {
  42. projectIndexFiles.forEach(path => (0, schematics_2.addBodyClass)(host, path, 'mat-typography'));
  43. }
  44. };
  45. }
  46. /**
  47. * Insert a custom theme to project style file. If no valid style file could be found, a new
  48. * Scss file for the custom theme will be created.
  49. */
  50. async function insertCustomTheme(projectName, host, logger) {
  51. const workspace = await (0, workspace_1.getWorkspace)(host);
  52. const project = (0, schematics_2.getProjectFromWorkspace)(workspace, projectName);
  53. const stylesPath = (0, schematics_2.getProjectStyleFile)(project, 'scss');
  54. const themeContent = (0, create_custom_theme_1.createCustomTheme)(projectName);
  55. if (!stylesPath) {
  56. if (!project.sourceRoot) {
  57. throw new schematics_1.SchematicsException(`Could not find source root for project: "${projectName}". ` +
  58. `Please make sure that the "sourceRoot" property is set in the workspace config.`);
  59. }
  60. // Normalize the path through the devkit utilities because we want to avoid having
  61. // unnecessary path segments and windows backslash delimiters.
  62. const customThemePath = (0, core_1.normalize)((0, path_1.join)(project.sourceRoot, defaultCustomThemeFilename));
  63. if (host.exists(customThemePath)) {
  64. logger.warn(`Cannot create a custom Angular Material theme because
  65. ${customThemePath} already exists. Skipping custom theme generation.`);
  66. return (0, schematics_1.noop)();
  67. }
  68. host.create(customThemePath, themeContent);
  69. return addThemeStyleToTarget(projectName, 'build', customThemePath, logger);
  70. }
  71. const insertion = new change_1.InsertChange(stylesPath, 0, themeContent);
  72. const recorder = host.beginUpdate(stylesPath);
  73. recorder.insertLeft(insertion.pos, insertion.toAdd);
  74. host.commitUpdate(recorder);
  75. return (0, schematics_1.noop)();
  76. }
  77. /** Insert a pre-built theme into the angular.json file. */
  78. function insertPrebuiltTheme(project, theme, logger) {
  79. const themePath = `@angular/material/prebuilt-themes/${theme}.css`;
  80. return (0, schematics_1.chain)([
  81. addThemeStyleToTarget(project, 'build', themePath, logger),
  82. addThemeStyleToTarget(project, 'test', themePath, logger),
  83. ]);
  84. }
  85. /** Adds a theming style entry to the given project target options. */
  86. function addThemeStyleToTarget(projectName, targetName, assetPath, logger) {
  87. return (0, workspace_1.updateWorkspace)(workspace => {
  88. const project = (0, schematics_2.getProjectFromWorkspace)(workspace, projectName);
  89. // Do not update the builder options in case the target does not use the default CLI builder.
  90. if (!validateDefaultTargetBuilder(project, targetName, logger)) {
  91. return;
  92. }
  93. const targetOptions = (0, schematics_2.getProjectTargetOptions)(project, targetName);
  94. const styles = targetOptions['styles'];
  95. if (!styles) {
  96. targetOptions['styles'] = [assetPath];
  97. }
  98. else {
  99. const existingStyles = styles.map(s => (typeof s === 'string' ? s : s.input));
  100. for (let [index, stylePath] of existingStyles.entries()) {
  101. // If the given asset is already specified in the styles, we don't need to do anything.
  102. if (stylePath === assetPath) {
  103. return;
  104. }
  105. // In case a prebuilt theme is already set up, we can safely replace the theme with the new
  106. // theme file. If a custom theme is set up, we are not able to safely replace the custom
  107. // theme because these files can contain custom styles, while prebuilt themes are
  108. // always packaged and considered replaceable.
  109. if (stylePath.includes(defaultCustomThemeFilename)) {
  110. logger.error(`Could not add the selected theme to the CLI project ` +
  111. `configuration because there is already a custom theme file referenced.`);
  112. logger.info(`Please manually add the following style file to your configuration:`);
  113. logger.info(` ${assetPath}`);
  114. return;
  115. }
  116. else if (stylePath.includes(prebuiltThemePathSegment)) {
  117. styles.splice(index, 1);
  118. }
  119. }
  120. styles.unshift(assetPath);
  121. }
  122. });
  123. }
  124. /**
  125. * Validates that the specified project target is configured with the default builders which are
  126. * provided by the Angular CLI. If the configured builder does not match the default builder,
  127. * this function can either throw or just show a warning.
  128. */
  129. function validateDefaultTargetBuilder(project, targetName, logger) {
  130. const targets = targetName === 'test' ? (0, schematics_2.getProjectTestTargets)(project) : (0, schematics_2.getProjectBuildTargets)(project);
  131. const isDefaultBuilder = targets.length > 0;
  132. // Because the build setup for the Angular CLI can be customized by developers, we can't know
  133. // where to put the theme file in the workspace configuration if custom builders are being
  134. // used. In case the builder has been changed for the "build" target, we throw an error and
  135. // exit because setting up a theme is a primary goal of `ng-add`. Otherwise if just the "test"
  136. // builder has been changed, we warn because a theme is not mandatory for running tests
  137. // with Material. See: https://github.com/angular/components/issues/14176
  138. if (!isDefaultBuilder && targetName === 'build') {
  139. throw new schematics_1.SchematicsException(`Your project is not using the default builders for ` +
  140. `"${targetName}". The Angular Material schematics cannot add a theme to the workspace ` +
  141. `configuration if the builder has been changed.`);
  142. }
  143. else if (!isDefaultBuilder) {
  144. // for non-build targets we gracefully report the error without actually aborting the
  145. // setup schematic. This is because a theme is not mandatory for running tests.
  146. logger.warn(`Your project is not using the default builders for "${targetName}". This ` +
  147. `means that we cannot add the configured theme to the "${targetName}" target.`);
  148. }
  149. return isDefaultBuilder;
  150. }
  151. //# sourceMappingURL=theming.js.map