builder/lib/tasks/bundlers/generateLibraryPreload.js

  1. const log = require("@ui5/logger").getLogger("builder:tasks:bundlers:generateLibraryPreload");
  2. const moduleBundler = require("../../processors/bundlers/moduleBundler");
  3. const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized;
  4. const {negateFilters} = require("../../lbt/resources/ResourceFilterList");
  5. function getDefaultLibraryPreloadFilters(namespace, excludes) {
  6. const filters = [
  7. `${namespace}/`,
  8. `${namespace}/**/manifest.json`,
  9. `!${namespace}/*-preload.js`, // exclude all bundles
  10. `!${namespace}/designtime/`,
  11. `!${namespace}/**/*.designtime.js`,
  12. `!${namespace}/**/*.support.js`
  13. ];
  14. if (Array.isArray(excludes)) {
  15. const allFilterExcludes = negateFilters(excludes);
  16. // Add configured excludes at the end of filter list
  17. allFilterExcludes.forEach((filterExclude) => {
  18. // Allow all excludes (!) and limit re-includes (+) to the library namespace
  19. if (filterExclude.startsWith("!") || filterExclude.startsWith(`+${namespace}/`)) {
  20. filters.push(filterExclude);
  21. } else {
  22. log.warn(`Configured preload exclude contains invalid re-include: !${filterExclude.substr(1)}. ` +
  23. `Re-includes must start with the library's namespace ${namespace}`);
  24. }
  25. });
  26. }
  27. return filters;
  28. }
  29. function getBundleDefinition(namespace, excludes) {
  30. // TODO: move to config of actual core project
  31. if (namespace === "sap/ui/core") {
  32. return {
  33. name: `${namespace}/library-preload.js`,
  34. sections: [
  35. {
  36. // exclude the content of sap-ui-core by declaring it as 'provided'
  37. mode: "provided",
  38. filters: [
  39. "ui5loader-autoconfig.js",
  40. "sap/ui/core/Core.js"
  41. ],
  42. resolve: true
  43. },
  44. {
  45. mode: "preload",
  46. filters: [
  47. // Note: Don't pass configured preload excludes for sap.ui.core
  48. // as they are already hardcoded below.
  49. // In future the sap/ui/core/library-preload should be configured
  50. // as a custom bundle in the ui5.yaml.
  51. ...getDefaultLibraryPreloadFilters(namespace),
  52. `!${namespace}/cldr/`,
  53. "*.js",
  54. "sap/base/",
  55. "sap/ui/base/",
  56. "sap/ui/dom/",
  57. "sap/ui/events/",
  58. "sap/ui/model/",
  59. "sap/ui/security/",
  60. "sap/ui/util/",
  61. "sap/ui/Global.js",
  62. // include only thirdparty that is very likely to be used
  63. "sap/ui/thirdparty/crossroads.js",
  64. "sap/ui/thirdparty/caja-html-sanitizer.js",
  65. "sap/ui/thirdparty/hasher.js",
  66. "sap/ui/thirdparty/signals.js",
  67. "sap/ui/thirdparty/jquery-mobile-custom.js",
  68. "sap/ui/thirdparty/jqueryui/jquery-ui-core.js",
  69. "sap/ui/thirdparty/jqueryui/jquery-ui-position.js",
  70. // other excludes (not required for productive scenarios)
  71. "!sap-ui-*.js",
  72. "!sap/ui/core/support/",
  73. "!sap/ui/core/plugin/DeclarativeSupport.js",
  74. "!sap/ui/core/plugin/LessSupport.js"
  75. ],
  76. resolve: false,
  77. resolveConditional: false,
  78. renderer: true
  79. }
  80. ]
  81. };
  82. }
  83. return {
  84. name: `${namespace}/library-preload.js`,
  85. sections: [
  86. {
  87. mode: "preload",
  88. filters: getDefaultLibraryPreloadFilters(namespace, excludes),
  89. resolve: false,
  90. resolveConditional: false,
  91. renderer: true
  92. }
  93. ]
  94. };
  95. }
  96. function getDesigntimeBundleDefinition(namespace) {
  97. return {
  98. name: `${namespace}/designtime/library-preload.designtime.js`,
  99. sections: [
  100. {
  101. mode: "preload",
  102. filters: [
  103. `${namespace}/**/*.designtime.js`,
  104. `${namespace}/designtime/`,
  105. `!${namespace}/**/*-preload.designtime.js`,
  106. `!${namespace}/designtime/**/*.properties`,
  107. `!${namespace}/designtime/**/*.svg`,
  108. `!${namespace}/designtime/**/*.xml`
  109. ],
  110. resolve: false,
  111. resolveConditional: false,
  112. renderer: false
  113. }
  114. ]
  115. };
  116. }
  117. function getSupportFilesBundleDefinition(namespace) {
  118. return {
  119. name: `${namespace}/library-preload.support.js`,
  120. sections: [
  121. {
  122. mode: "preload",
  123. filters: [
  124. `${namespace}/**/*.support.js`,
  125. `!${namespace}/**/*-preload.support.js`
  126. ],
  127. resolve: false,
  128. resolveConditional: false,
  129. renderer: false
  130. }
  131. ]
  132. };
  133. }
  134. function createLibraryBundles(libraryNamespace, resources, excludes) {
  135. return Promise.all([
  136. moduleBundler({
  137. options: {
  138. bundleDefinition: getBundleDefinition(libraryNamespace, excludes),
  139. bundleOptions: {
  140. optimize: true,
  141. usePredefineCalls: true,
  142. ignoreMissingModules: true
  143. }
  144. },
  145. resources
  146. }),
  147. moduleBundler({
  148. options: {
  149. bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace),
  150. bundleOptions: {
  151. optimize: true,
  152. usePredefineCalls: true,
  153. ignoreMissingModules: true,
  154. skipIfEmpty: true
  155. }
  156. },
  157. resources
  158. }),
  159. moduleBundler({
  160. options: {
  161. bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace),
  162. bundleOptions: {
  163. optimize: false,
  164. usePredefineCalls: true,
  165. ignoreMissingModules: true,
  166. skipIfEmpty: true
  167. }
  168. },
  169. resources
  170. })
  171. ]);
  172. }
  173. function getModuleBundlerOptions(config) {
  174. const moduleBundlerOptions = {};
  175. // required in sap-ui-core-nojQuery.js and sap-ui-core-nojQuery-dbg.js
  176. const providedSection = {
  177. mode: "provided",
  178. filters: [
  179. "jquery-ui-core.js",
  180. "jquery-ui-datepicker.js",
  181. "jquery-ui-position.js",
  182. "sap/ui/thirdparty/jquery.js",
  183. "sap/ui/thirdparty/jquery/*",
  184. "sap/ui/thirdparty/jqueryui/*"
  185. ]
  186. };
  187. moduleBundlerOptions.bundleOptions = {
  188. optimize: config.preload,
  189. decorateBootstrapModule: config.preload,
  190. addTryCatchRestartWrapper: config.preload,
  191. usePredefineCalls: config.preload
  192. };
  193. moduleBundlerOptions.bundleDefinition = getSapUiCoreBunDef(config.name, config.filters, config.preload);
  194. if (config.provided) {
  195. moduleBundlerOptions.bundleDefinition.sections.unshift(providedSection);
  196. }
  197. return moduleBundlerOptions;
  198. }
  199. function getSapUiCoreBunDef(name, filters, preload) {
  200. const bundleDefinition = {
  201. name,
  202. sections: []
  203. };
  204. // add raw section
  205. bundleDefinition.sections.push({
  206. // include all 'raw' modules that are needed for the UI5 loader
  207. mode: "raw",
  208. filters,
  209. resolve: true, // dependencies for raw modules are taken from shims in .library files
  210. sort: true, // topological sort on raw modules is mandatory
  211. declareModules: false
  212. });
  213. if (preload) {
  214. // add preload section
  215. bundleDefinition.sections.push({
  216. mode: "preload",
  217. filters: [
  218. "sap/ui/core/Core.js"
  219. ],
  220. resolve: true
  221. });
  222. }
  223. // add require section
  224. bundleDefinition.sections.push({
  225. mode: "require",
  226. filters: [
  227. "sap/ui/core/Core.js"
  228. ]
  229. });
  230. return bundleDefinition;
  231. }
  232. /**
  233. * Task for library bundling.
  234. *
  235. * @public
  236. * @alias module:@ui5/builder.tasks.generateLibraryPreload
  237. * @param {object} parameters Parameters
  238. * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
  239. * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
  240. * @param {module:@ui5/builder.tasks.TaskUtil|object} [parameters.taskUtil] TaskUtil
  241. * @param {string[]} [parameters.options.excludes=[]] List of modules declared as glob patterns (resource name patterns)
  242. * that should be excluded from the library-preload.js bundle.
  243. * A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisk,
  244. * denote an arbitrary number of characters or folder names.
  245. * Re-includes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later
  246. * inclusion overrides an earlier exclusion, and vice versa.
  247. * @param {object} parameters.options Options
  248. * @param {string} parameters.options.projectName Project name
  249. * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
  250. */
  251. module.exports = function({workspace, dependencies, taskUtil, options: {projectName, excludes = []}}) {
  252. const combo = new ReaderCollectionPrioritized({
  253. name: `libraryBundler - prioritize workspace over dependencies: ${projectName}`,
  254. readers: [workspace, dependencies]
  255. });
  256. return combo.byGlob("/**/*.{js,json,xml,html,properties,library}").then((resources) => {
  257. // Find all libraries and create a library-preload.js bundle
  258. let p = Promise.resolve();
  259. // Create core bundles for older versions (<1.97.0) which don't define bundle configuration in the ui5.yaml
  260. // See: https://github.com/SAP/openui5/commit/ff127fd2d009162ea43ad312dec99d759ebc23a0
  261. if (projectName === "sap.ui.core") {
  262. const coreProject = resources[0]._project;
  263. const coreSpecVersion = coreProject && coreProject.specVersion;
  264. // Instead of checking the sap.ui.core library version, the specVersion is checked against all versions
  265. // that have been defined for sap.ui.core before the bundle configuration has been introduced.
  266. // This is mainly to have an easier check without version parsing or using semver.
  267. // If no project/specVersion is available, the bundles should also be created to not break potential
  268. // existing use cases without a properly formed/formatted project tree.
  269. if (!coreSpecVersion || ["0.1", "1.1", "2.0"].includes(coreSpecVersion)) {
  270. const isEvo = resources.find((resource) => {
  271. return resource.getPath() === "/resources/ui5loader.js";
  272. });
  273. let filters;
  274. if (isEvo) {
  275. filters = ["ui5loader-autoconfig.js"];
  276. } else {
  277. filters = ["jquery.sap.global.js"];
  278. }
  279. p = Promise.all([
  280. moduleBundler({
  281. options: getModuleBundlerOptions({name: "sap-ui-core.js", filters, preload: true}),
  282. resources
  283. }),
  284. moduleBundler({
  285. options: getModuleBundlerOptions({name: "sap-ui-core-dbg.js", filters, preload: false}),
  286. resources
  287. }),
  288. moduleBundler({
  289. options: getModuleBundlerOptions({
  290. name: "sap-ui-core-nojQuery.js", filters, preload: true, provided: true
  291. }),
  292. resources
  293. }),
  294. moduleBundler({
  295. options: getModuleBundlerOptions({
  296. name: "sap-ui-core-nojQuery-dbg.js", filters, preload: false, provided: true
  297. }),
  298. resources
  299. }),
  300. ]).then((results) => {
  301. const bundles = Array.prototype.concat.apply([], results);
  302. return Promise.all(bundles.map((bundle) => {
  303. if (taskUtil) {
  304. taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle);
  305. }
  306. return workspace.write(bundle);
  307. }));
  308. });
  309. }
  310. }
  311. return p.then(() => {
  312. return workspace.byGlob("/resources/**/.library").then((libraryIndicatorResources) => {
  313. if (libraryIndicatorResources.length > 0) {
  314. return libraryIndicatorResources;
  315. } else {
  316. // Fallback to "library.js" as library indicator
  317. log.verbose(
  318. `Could not find a ".library" file for project ${projectName}, falling back to "library.js".`);
  319. return workspace.byGlob("/resources/**/library.js");
  320. }
  321. }).then((libraryIndicatorResources) => {
  322. if (libraryIndicatorResources.length < 1) {
  323. // No library found - nothing to do
  324. log.verbose(
  325. `Could not find a ".library" or "library.js" file for project ${projectName}. ` +
  326. `Skipping library preload bundling.`);
  327. return;
  328. }
  329. return Promise.all(libraryIndicatorResources.map((libraryIndicatorResource) => {
  330. // Determine library namespace from library indicator file path
  331. // ending with either ".library" or "library.js" (see fallback logic above)
  332. // e.g. /resources/sap/foo/.library => sap/foo
  333. // /resources/sap/bar/library.js => sap/bar
  334. const libraryNamespacePattern = /^\/resources\/(.*)\/(?:\.library|library\.js)$/;
  335. const libraryIndicatorPath = libraryIndicatorResource.getPath();
  336. const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern);
  337. if (libraryNamespaceMatch && libraryNamespaceMatch[1]) {
  338. const libraryNamespace = libraryNamespaceMatch[1];
  339. return createLibraryBundles(libraryNamespace, resources, excludes)
  340. .then((results) => {
  341. const bundles = Array.prototype.concat.apply([], results);
  342. return Promise.all(bundles.map((bundle) => {
  343. if (bundle) {
  344. if (taskUtil) {
  345. taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle);
  346. }
  347. return workspace.write(bundle);
  348. }
  349. }));
  350. });
  351. } else {
  352. log.verbose(
  353. `Could not determine library namespace from file "${libraryIndicatorPath}" ` +
  354. `for project ${projectName}. Skipping library preload bundling.`);
  355. return Promise.resolve();
  356. }
  357. }));
  358. });
  359. });
  360. });
  361. };