theming.js 7.4 KB

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