build-component.js 13 KB

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