build-component.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  10. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  11. return new (P || (P = Promise))(function (resolve, reject) {
  12. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  13. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  14. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  15. step((generator = generator.apply(thisArg, _arguments || [])).next());
  16. });
  17. };
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. exports.buildComponent = buildComponent;
  20. const core_1 = require("@angular-devkit/core");
  21. const schematics_1 = require("@angular-devkit/schematics");
  22. const schema_1 = require("@schematics/angular/component/schema");
  23. const change_1 = require("@schematics/angular/utility/change");
  24. const workspace_1 = require("@schematics/angular/utility/workspace");
  25. const find_module_1 = require("@schematics/angular/utility/find-module");
  26. const parse_name_1 = require("@schematics/angular/utility/parse-name");
  27. const validation_1 = require("@schematics/angular/utility/validation");
  28. const workspace_models_1 = require("@schematics/angular/utility/workspace-models");
  29. const ast_utils_1 = require("@schematics/angular/utility/ast-utils");
  30. const fs_1 = require("fs");
  31. const path_1 = require("path");
  32. const ts = require("typescript");
  33. const get_project_1 = require("./get-project");
  34. const schematic_options_1 = require("./schematic-options");
  35. /**
  36. * Build a default project path for generating.
  37. * @param project The project to build the path for.
  38. */
  39. function buildDefaultPath(project) {
  40. const root = project.sourceRoot ? `/${project.sourceRoot}/` : `/${project.root}/src/`;
  41. const projectDirName = project.extensions['projectType'] === workspace_models_1.ProjectType.Application ? 'app' : 'lib';
  42. return `${root}${projectDirName}`;
  43. }
  44. /**
  45. * List of style extensions which are CSS compatible. All supported CLI style extensions can be
  46. * found here: angular/angular-cli/main/packages/schematics/angular/ng-new/schema.json#L118-L122
  47. */
  48. const supportedCssExtensions = ['css', 'scss', 'less'];
  49. function readIntoSourceFile(host, modulePath) {
  50. const text = host.read(modulePath);
  51. if (text === null) {
  52. throw new schematics_1.SchematicsException(`File ${modulePath} does not exist.`);
  53. }
  54. return ts.createSourceFile(modulePath, text.toString('utf-8'), ts.ScriptTarget.Latest, true);
  55. }
  56. function addDeclarationToNgModule(options) {
  57. return (host) => {
  58. if (options.skipImport || options.standalone || !options.module) {
  59. return host;
  60. }
  61. const modulePath = options.module;
  62. let source = readIntoSourceFile(host, modulePath);
  63. const componentPath = `/${options.path}/` +
  64. (options.flat ? '' : core_1.strings.dasherize(options.name) + '/') +
  65. core_1.strings.dasherize(options.name) +
  66. '.component';
  67. const relativePath = (0, find_module_1.buildRelativePath)(modulePath, componentPath);
  68. const classifiedName = core_1.strings.classify(`${options.name}Component`);
  69. const declarationChanges = (0, ast_utils_1.addDeclarationToModule)(source, modulePath, classifiedName, relativePath);
  70. const declarationRecorder = host.beginUpdate(modulePath);
  71. for (const change of declarationChanges) {
  72. if (change instanceof change_1.InsertChange) {
  73. declarationRecorder.insertLeft(change.pos, change.toAdd);
  74. }
  75. }
  76. host.commitUpdate(declarationRecorder);
  77. if (options.export) {
  78. // Need to refresh the AST because we overwrote the file in the host.
  79. source = readIntoSourceFile(host, modulePath);
  80. const exportRecorder = host.beginUpdate(modulePath);
  81. const exportChanges = (0, ast_utils_1.addExportToModule)(source, modulePath, core_1.strings.classify(`${options.name}Component`), relativePath);
  82. for (const change of exportChanges) {
  83. if (change instanceof change_1.InsertChange) {
  84. exportRecorder.insertLeft(change.pos, change.toAdd);
  85. }
  86. }
  87. host.commitUpdate(exportRecorder);
  88. }
  89. return host;
  90. };
  91. }
  92. function buildSelector(options, projectPrefix) {
  93. let selector = core_1.strings.dasherize(options.name);
  94. if (options.prefix) {
  95. selector = `${options.prefix}-${selector}`;
  96. }
  97. else if (options.prefix === undefined && projectPrefix) {
  98. selector = `${projectPrefix}-${selector}`;
  99. }
  100. return selector;
  101. }
  102. /**
  103. * Indents the text content with the amount of specified spaces. The spaces will be added after
  104. * every line-break. This utility function can be used inside of EJS templates to properly
  105. * include the additional files.
  106. */
  107. function indentTextContent(text, numSpaces) {
  108. // In the Material project there should be only LF line-endings, but the schematic files
  109. // are not being linted and therefore there can be also CRLF or just CR line-endings.
  110. return text.replace(/(\r\n|\r|\n)/g, `$1${' '.repeat(numSpaces)}`);
  111. }
  112. /**
  113. * Rule that copies and interpolates the files that belong to this schematic context. Additionally
  114. * a list of file paths can be passed to this rule in order to expose them inside the EJS
  115. * template context.
  116. *
  117. * This allows inlining the external template or stylesheet files in EJS without having
  118. * to manually duplicate the file content.
  119. */
  120. function buildComponent(options, additionalFiles = {}) {
  121. return (host, ctx) => __awaiter(this, void 0, void 0, function* () {
  122. const context = ctx;
  123. const workspace = yield (0, workspace_1.getWorkspace)(host);
  124. const project = (0, get_project_1.getProjectFromWorkspace)(workspace, options.project);
  125. const defaultComponentOptions = (0, schematic_options_1.getDefaultComponentOptions)(project);
  126. // TODO(devversion): Remove if we drop support for older CLI versions.
  127. // This handles an unreported breaking change from the @angular-devkit/schematics. Previously
  128. // the description path resolved to the factory file, but starting from 6.2.0, it resolves
  129. // to the factory directory.
  130. const schematicPath = (0, fs_1.statSync)(context.schematic.description.path).isDirectory()
  131. ? context.schematic.description.path
  132. : (0, path_1.dirname)(context.schematic.description.path);
  133. const schematicFilesUrl = './files';
  134. const schematicFilesPath = (0, path_1.resolve)(schematicPath, schematicFilesUrl);
  135. // Add the default component option values to the options if an option is not explicitly
  136. // specified but a default component option is available.
  137. Object.keys(options)
  138. .filter(key => options[key] == null &&
  139. defaultComponentOptions[key])
  140. .forEach(key => (options[key] = defaultComponentOptions[key]));
  141. if (options.path === undefined) {
  142. options.path = buildDefaultPath(project);
  143. }
  144. options.standalone = yield (0, schematic_options_1.isStandaloneSchematic)(host, options);
  145. if (!options.standalone) {
  146. options.module = (0, find_module_1.findModuleFromOptions)(host, options);
  147. }
  148. const parsedPath = (0, parse_name_1.parseName)(options.path, options.name);
  149. options.name = parsedPath.name;
  150. options.path = parsedPath.path;
  151. options.selector = options.selector || buildSelector(options, project.prefix);
  152. (0, validation_1.validateHtmlSelector)(options.selector);
  153. // In case the specified style extension is not part of the supported CSS supersets,
  154. // we generate the stylesheets with the "css" extension. This ensures that we don't
  155. // accidentally generate invalid stylesheets (e.g. drag-drop-comp.styl) which will
  156. // break the Angular CLI project. See: https://github.com/angular/components/issues/15164
  157. if (!supportedCssExtensions.includes(options.style)) {
  158. options.style = schema_1.Style.Css;
  159. }
  160. // Object that will be used as context for the EJS templates.
  161. const baseTemplateContext = Object.assign(Object.assign(Object.assign({}, core_1.strings), { 'if-flat': (s) => (options.flat ? '' : s) }), options);
  162. // Key-value object that includes the specified additional files with their loaded content.
  163. // The resolved contents can be used inside EJS templates.
  164. const resolvedFiles = {};
  165. for (let key in additionalFiles) {
  166. if (additionalFiles[key]) {
  167. const fileContent = (0, fs_1.readFileSync)((0, path_1.join)(schematicFilesPath, additionalFiles[key]), 'utf-8');
  168. // Interpolate the additional files with the base EJS template context.
  169. resolvedFiles[key] = (0, core_1.template)(fileContent)(baseTemplateContext);
  170. }
  171. }
  172. const templateSource = (0, schematics_1.apply)((0, schematics_1.url)(schematicFilesUrl), [
  173. options.skipTests ? (0, schematics_1.filter)(path => !path.endsWith('.spec.ts.template')) : (0, schematics_1.noop)(),
  174. options.inlineStyle ? (0, schematics_1.filter)(path => !path.endsWith('.__style__.template')) : (0, schematics_1.noop)(),
  175. options.inlineTemplate ? (0, schematics_1.filter)(path => !path.endsWith('.html.template')) : (0, schematics_1.noop)(),
  176. // Treat the template options as any, because the type definition for the template options
  177. // is made unnecessarily explicit. Every type of object can be used in the EJS template.
  178. (0, schematics_1.applyTemplates)(Object.assign({ indentTextContent, resolvedFiles }, baseTemplateContext)),
  179. // TODO(devversion): figure out why we cannot just remove the first parameter
  180. // See for example: angular-cli#schematics/angular/component/index.ts#L160
  181. (0, schematics_1.move)(null, parsedPath.path),
  182. ]);
  183. return () => (0, schematics_1.chain)([
  184. (0, schematics_1.branchAndMerge)((0, schematics_1.chain)([addDeclarationToNgModule(options), (0, schematics_1.mergeWith)(templateSource)])),
  185. ])(host, context);
  186. });
  187. }
  188. //# sourceMappingURL=build-component.js.map