build-component.js 10 KB

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