builder/lib/tasks/bundlers/generateComponentPreload.js

  1. const path = require("path");
  2. const moduleBundler = require("../../processors/bundlers/moduleBundler");
  3. const log = require("@ui5/logger").getLogger("builder:tasks:bundlers:generateComponentPreload");
  4. const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized;
  5. const {negateFilters} = require("../../lbt/resources/ResourceFilterList");
  6. /**
  7. * Task to for application bundling.
  8. *
  9. * @public
  10. * @alias module:@ui5/builder.tasks.generateComponentPreload
  11. * @param {object} parameters Parameters
  12. * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
  13. * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
  14. * @param {module:@ui5/builder.tasks.TaskUtil|object} [parameters.taskUtil] TaskUtil
  15. * @param {object} parameters.options Options
  16. * @param {string} parameters.options.projectName Project name
  17. * @param {string[]} [parameters.options.excludes=[]] List of modules declared as glob patterns (resource name patterns)
  18. * that should be excluded.
  19. * A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisk,
  20. * denote an arbitrary number of characters or folder names.
  21. * Re-includes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later
  22. * inclusion overrides an earlier exclusion, and vice versa.
  23. * @param {string[]} [parameters.options.paths] Array of paths (or glob patterns) for component files
  24. * @param {string[]} [parameters.options.namespaces] Array of component namespaces
  25. * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
  26. */
  27. module.exports = function({
  28. workspace, dependencies, taskUtil, options: {projectName, paths, namespaces, excludes = []}
  29. }) {
  30. const combo = new ReaderCollectionPrioritized({
  31. name: `generateComponentPreload - prioritize workspace over dependencies: ${projectName}`,
  32. readers: [workspace, dependencies]
  33. });
  34. // TODO 3.0: Limit to workspace resources?
  35. return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library}")
  36. .then(async (resources) => {
  37. let allNamespaces = [];
  38. if (paths) {
  39. allNamespaces = await Promise.all(paths.map(async (componentPath) => {
  40. const globPath = "/resources/" + componentPath;
  41. log.verbose(`Globbing for Components directories with configured path ${globPath}...`);
  42. const components = await combo.byGlob(globPath); // TODO 3.0: Limit to workspace resources?
  43. return components.map((component) => {
  44. const compDir = path.dirname(component.getPath()).replace(/^\/resources\//i, "");
  45. log.verbose(`Found component namespace ${compDir}`);
  46. return compDir;
  47. });
  48. }));
  49. }
  50. if (namespaces) {
  51. allNamespaces.push(...namespaces);
  52. }
  53. allNamespaces = Array.prototype.concat.apply([], allNamespaces);
  54. // As this task is often called with a single namespace, also check
  55. // for bad calls like "namespaces: [undefined]"
  56. if (!allNamespaces || !allNamespaces.length || !allNamespaces[0]) {
  57. throw new Error("generateComponentPreload: No component namespace(s) " +
  58. `found for project: ${projectName}`);
  59. }
  60. const allFilterExcludes = negateFilters(excludes);
  61. const unusedFilterExcludes = new Set(allFilterExcludes);
  62. const bundleDefinitions = allNamespaces.map((namespace) => {
  63. const filters = [
  64. `${namespace}/`,
  65. `${namespace}/**/manifest.json`,
  66. `${namespace}/changes/changes-bundle.json`,
  67. `${namespace}/changes/flexibility-bundle.json`,
  68. `!${namespace}/test/`
  69. ];
  70. // Add configured excludes for namespace
  71. allFilterExcludes.forEach((filterExclude) => {
  72. // Allow all excludes (!) and limit re-includes (+) to the component namespace
  73. if (filterExclude.startsWith("!") || filterExclude.startsWith(`+${namespace}/`)) {
  74. filters.push(filterExclude);
  75. unusedFilterExcludes.delete(filterExclude);
  76. }
  77. });
  78. // Exclude other namespaces at the end of filter list to override potential re-includes
  79. // from "excludes" config
  80. allNamespaces.forEach((ns) => {
  81. if (ns !== namespace && ns.startsWith(`${namespace}/`)) {
  82. filters.push(`!${ns}/`);
  83. // Explicitly exclude manifest.json files of subcomponents since the general exclude above this
  84. // comment only applies to the configured default file types, which do not include ".json"
  85. filters.push(`!${ns}/**/manifest.json`);
  86. }
  87. });
  88. return {
  89. name: `${namespace}/Component-preload.js`,
  90. defaultFileTypes: [
  91. ".js",
  92. ".control.xml",
  93. ".fragment.html",
  94. ".fragment.json",
  95. ".fragment.xml",
  96. ".view.html",
  97. ".view.json",
  98. ".view.xml",
  99. ".properties"
  100. ],
  101. sections: [
  102. {
  103. mode: "preload",
  104. filters: filters,
  105. resolve: false,
  106. resolveConditional: false,
  107. renderer: false
  108. }
  109. ]
  110. };
  111. });
  112. if (unusedFilterExcludes.size > 0) {
  113. unusedFilterExcludes.forEach((filterExclude) => {
  114. log.warn(
  115. `Configured preload exclude contains invalid re-include: !${filterExclude.substr(1)}. ` +
  116. `Re-includes must start with a component namespace (${allNamespaces.join(" or ")})`
  117. );
  118. });
  119. }
  120. return Promise.all(bundleDefinitions.map((bundleDefinition) => {
  121. log.verbose(`Generating ${bundleDefinition.name}...`);
  122. return moduleBundler({
  123. resources,
  124. options: {
  125. bundleDefinition,
  126. bundleOptions: {
  127. ignoreMissingModules: true,
  128. optimize: true
  129. }
  130. }
  131. });
  132. }));
  133. })
  134. .then((processedResources) => {
  135. return Promise.all(processedResources.map((resource) => {
  136. if (taskUtil) {
  137. taskUtil.setTag(resource[0], taskUtil.STANDARD_TAGS.IsBundle);
  138. }
  139. return workspace.write(resource[0]);
  140. }));
  141. });
  142. };