apostrophe 3.30.0 → 3.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.eslintrc +3 -0
  2. package/CHANGELOG.md +26 -0
  3. package/modules/@apostrophecms/area/index.js +6 -0
  4. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +18 -2
  5. package/modules/@apostrophecms/asset/index.js +65 -7
  6. package/modules/@apostrophecms/asset/lib/webpack/utils.js +242 -28
  7. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +26 -16
  8. package/modules/@apostrophecms/i18n/i18n/en.json +19 -3
  9. package/modules/@apostrophecms/i18n/i18n/es.json +3 -0
  10. package/modules/@apostrophecms/i18n/i18n/fr.json +3 -0
  11. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +4 -1
  12. package/modules/@apostrophecms/i18n/i18n/sk.json +3 -0
  13. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +41 -20
  14. package/modules/@apostrophecms/image-widget/index.js +4 -1
  15. package/modules/@apostrophecms/image-widget/public/placeholder.jpg +0 -0
  16. package/modules/@apostrophecms/image-widget/ui/src/index.scss +3 -0
  17. package/modules/@apostrophecms/image-widget/views/widget.html +35 -27
  18. package/modules/@apostrophecms/login/index.js +123 -26
  19. package/modules/@apostrophecms/login/ui/apos/components/AposForgotPasswordForm.vue +124 -0
  20. package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +339 -0
  21. package/modules/@apostrophecms/login/ui/apos/components/AposResetPasswordForm.vue +163 -0
  22. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +135 -293
  23. package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +65 -14
  24. package/modules/@apostrophecms/login/ui/apos/mixins/AposLoginFormMixin.js +45 -0
  25. package/modules/@apostrophecms/login/views/passwordResetEmail.html +9 -0
  26. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +17 -6
  27. package/modules/@apostrophecms/modal/ui/apos/components/AposModalBody.vue +4 -1
  28. package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +7 -1
  29. package/modules/@apostrophecms/piece-type/index.js +1 -1
  30. package/modules/@apostrophecms/rich-text-widget/index.js +4 -1
  31. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +75 -12
  32. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +11 -5
  33. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +6 -2
  34. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Link.js +7 -4
  35. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +1 -1
  36. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +4 -1
  37. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +4 -0
  38. package/modules/@apostrophecms/template/index.js +11 -12
  39. package/modules/@apostrophecms/template/lib/bundlesLoader.js +20 -5
  40. package/modules/@apostrophecms/ui/ui/apos/mixins/AposAdvisoryLockMixin.js +2 -1
  41. package/modules/@apostrophecms/video-widget/index.js +4 -1
  42. package/modules/@apostrophecms/video-widget/views/widget.html +24 -16
  43. package/modules/@apostrophecms/widget-type/index.js +44 -8
  44. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidget.vue +5 -2
  45. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +11 -0
  46. package/package.json +6 -5
  47. package/test/assets.js +338 -25
  48. package/test/extra_node_modules/@company/bundle/index.js +8 -0
  49. package/test/extra_node_modules/@company/bundle/ui/src/company.js +3 -0
  50. package/test/extra_node_modules/@company/bundle/ui/src/company.scss +3 -0
  51. package/test/login.js +427 -12
  52. package/test/modules/@company/bundle/index.js +10 -0
  53. package/test/modules/@company/bundle/ui/src/company.js +3 -0
  54. package/test/modules/@company/bundle/ui/src/company.scss +3 -0
  55. package/test/modules/bundle-edge/index.js +10 -0
  56. package/test/modules/bundle-edge/ui/src/edge.js +3 -0
  57. package/test/modules/bundle-edge/ui/src/edge.scss +3 -0
  58. package/test/modules/bundle-page/index.js +2 -1
  59. package/test/modules/bundle-page/ui/src/extra.js +3 -1
  60. package/test/modules/bundle-page/ui/src/extra.scss +3 -0
  61. package/test/modules/bundle-page/ui/src/main.js +3 -0
  62. package/test/modules/bundle-page/ui/src/main.scss +3 -0
  63. package/test/modules/bundle-page-type/index.js +12 -0
  64. package/test/modules/bundle-page-type/ui/src/another.js +3 -0
  65. package/test/modules/bundle-page-type/ui/src/index.js +3 -0
  66. package/test/modules/bundle-page-type/ui/src/index.scss +3 -0
  67. package/test/modules/bundle-page-type/ui/src/main.js +3 -0
  68. package/test/modules/bundle-page-type/ui/src/main.scss +3 -0
  69. package/test/modules/bundle-widget/ui/src/extra2.js +3 -1
  70. package/test/modules/placeholder-page/index.js +21 -0
  71. package/test/modules/placeholder-page/views/page.html +3 -0
  72. package/test/modules/placeholder-widget/index.js +36 -0
  73. package/test/modules/placeholder-widget/views/widget.html +5 -0
  74. package/test/widgets.js +453 -114
  75. package/test-lib/test.js +9 -1
package/.eslintrc CHANGED
@@ -3,6 +3,9 @@
3
3
  "apostrophe",
4
4
  "plugin:vue/vue3-recommended"
5
5
  ],
6
+ "env": {
7
+ "mocha": true
8
+ },
6
9
  "globals": {
7
10
  "apos": true
8
11
  },
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.32.0 (2022-11-09)
4
+
5
+ ### Adds
6
+
7
+ * Adds Reset Password feature to the login page. Note that the feature must be enabled and email delivery must be properly configured. See the [documentation](https://v3.docs.apostrophecms.org/reference/modules/login.html) for more details.
8
+ * Allow project-level developer to override bundling decisions by configuring the `@apostrophecms/asset` module. Check the [module documentation](https://v3.docs.apostrophecms.org/reference/modules/asset.html#options) for more information.
9
+
10
+ ### Fixes
11
+
12
+ * Query builders for regular select fields have always accepted null to mean "do not filter on this property." Now this also works for dynamic select fields.
13
+ * The i18n UI state management now doesn't allow actions while it's busy.
14
+ * Fixed various localization bugs in the text of the "Update" dropdown menu.
15
+ * The `singleton: true` option for piece types now automatically implies `showCreate: false`.
16
+ * Remove browser console warnings by handling Tiptap Editor's breaking changes and duplicated plugins.
17
+ * The editor modal now allocates more space to area fields when possible, resolving common concerns about editing large widgets inside the modal.
18
+
19
+ ## 3.31.0 (2022-10-27)
20
+
21
+ ### Adds
22
+
23
+ * Adds `placeholder: true` and `initialModal: false` features to improve the user experience of adding widgets to the page. Checkout the [Widget Placeholders documentation](https://v3.docs.apostrophecms.org/guide/areas-and-widgets.html#adding-placeholder-content-to-widgets) for more detail.
24
+
25
+ ### Fixes
26
+
27
+ * When another user is editing the document, the other user's name is now displayed correctly.
28
+
3
29
  ## 3.30.0 (2022-10-12)
4
30
 
5
31
  ### Adds
@@ -575,6 +575,8 @@ module.exports = {
575
575
  const widgetEditors = {};
576
576
  const widgetManagers = {};
577
577
  const widgetIsContextual = {};
578
+ const widgetHasPlaceholder = {};
579
+ const widgetHasInitialModal = {};
578
580
  const contextualWidgetDefaultData = {};
579
581
 
580
582
  _.each(self.widgetManagers, function (manager, name) {
@@ -584,6 +586,8 @@ module.exports = {
584
586
  widgetEditors[name] = (browserData && browserData.components && browserData.components.widgetEditor) || 'AposWidgetEditor';
585
587
  widgetManagers[name] = manager.__meta.name;
586
588
  widgetIsContextual[name] = manager.options.contextual;
589
+ widgetHasPlaceholder[name] = manager.options.placeholder;
590
+ widgetHasInitialModal[name] = !manager.options.placeholder && manager.options.initialModal !== false;
587
591
  contextualWidgetDefaultData[name] = manager.options.defaultData;
588
592
  });
589
593
 
@@ -594,6 +598,8 @@ module.exports = {
594
598
  widgetEditors
595
599
  },
596
600
  widgetIsContextual,
601
+ widgetHasPlaceholder,
602
+ widgetHasInitialModal,
597
603
  contextualWidgetDefaultData,
598
604
  widgetManagers,
599
605
  action: self.action
@@ -404,6 +404,8 @@ export default {
404
404
  }
405
405
  },
406
406
  async update(widget) {
407
+ widget.aposPlaceholder = false;
408
+
407
409
  if (this.docId === window.apos.adminBar.contextId) {
408
410
  apos.bus.$emit('context-edited', {
409
411
  [`@${widget._id}`]: widget
@@ -434,9 +436,17 @@ export default {
434
436
  } else if (this.widgetIsContextual(name)) {
435
437
  return this.insert({
436
438
  widget: {
437
- _id: cuid(),
438
439
  type: name,
439
- ...this.contextualWidgetDefaultData(name)
440
+ ...this.contextualWidgetDefaultData(name),
441
+ aposPlaceholder: this.widgetHasPlaceholder(name)
442
+ },
443
+ index
444
+ });
445
+ } else if (!this.widgetHasInitialModal(name)) {
446
+ return this.insert({
447
+ widget: {
448
+ type: name,
449
+ aposPlaceholder: this.widgetHasPlaceholder(name)
440
450
  },
441
451
  index
442
452
  });
@@ -502,6 +512,12 @@ export default {
502
512
  widgetIsContextual(type) {
503
513
  return this.moduleOptions.widgetIsContextual[type];
504
514
  },
515
+ widgetHasPlaceholder(type) {
516
+ return this.moduleOptions.widgetHasPlaceholder[type];
517
+ },
518
+ widgetHasInitialModal(type) {
519
+ return this.moduleOptions.widgetHasInitialModal[type];
520
+ },
505
521
  widgetEditorComponent(type) {
506
522
  return this.moduleOptions.components.widgetEditors[type];
507
523
  },
@@ -17,7 +17,8 @@ const {
17
17
  fillExtraBundles,
18
18
  getBundlesNames,
19
19
  writeBundlesImportFiles,
20
- findNodeModulesSymlinks
20
+ findNodeModulesSymlinks,
21
+ transformRebundledFor
21
22
  } = require('./lib/webpack/utils');
22
23
 
23
24
  module.exports = {
@@ -38,7 +39,10 @@ module.exports = {
38
39
  watch: true,
39
40
  // Miliseconds to wait between asset sources changes before
40
41
  // performing a build.
41
- watchDebounceMs: 1000
42
+ watchDebounceMs: 1000,
43
+ // Object containing instructions for remapping existing bundles.
44
+ // See the modulre reference documentation for more information.
45
+ rebundleModules: undefined
42
46
  },
43
47
 
44
48
  async init(self) {
@@ -51,16 +55,21 @@ module.exports = {
51
55
  self.initUploadfs();
52
56
 
53
57
  const {
54
- extensions = {}, extensionOptions = {}, verifiedBundles = {}
58
+ extensions = {},
59
+ extensionOptions = {},
60
+ verifiedBundles = {},
61
+ rebundleModules = {}
55
62
  } = await getWebpackExtensions({
56
63
  getMetadata: self.apos.synth.getMetadata,
57
- modulesToInstantiate: self.apos.modulesToBeInstantiated()
64
+ modulesToInstantiate: self.apos.modulesToBeInstantiated(),
65
+ rebundleModulesConfig: self.options.rebundleModules
58
66
  });
59
67
 
60
68
  self.extraBundles = fillExtraBundles(verifiedBundles);
61
69
  self.webpackExtensions = extensions;
62
70
  self.webpackExtensionOptions = extensionOptions;
63
71
  self.verifiedBundles = verifiedBundles;
72
+ self.rebundleModules = rebundleModules;
64
73
  self.buildWatcherEnable = process.env.APOS_ASSET_WATCH !== '0' && self.options.watch !== false;
65
74
  self.buildWatcherDebounceMs = parseInt(self.options.watchDebounceMs || 1000, 10);
66
75
  self.buildWatcher = null;
@@ -312,15 +321,27 @@ module.exports = {
312
321
  }
313
322
 
314
323
  if (options.index) {
324
+ // Gather modules with non-main, catch-all bundles
325
+ const ignoreModules = self.rebundleModules
326
+ .filter(entry => !entry.main && !entry.source)
327
+ .reduce((acc, entry) => ({
328
+ ...acc,
329
+ [entry.name]: true
330
+ }), {});
331
+
315
332
  indexJsImports = getImports(source, 'index.js', {
316
333
  invokeApps: true,
317
334
  enumerateImports: true,
318
335
  importSuffix: 'App',
319
- requireDefaultExport: true
336
+ requireDefaultExport: true,
337
+ mainModuleBundles: getMainModuleBundleFiles('js'),
338
+ ignoreModules
320
339
  });
321
340
  indexSassImports = getImports(source, 'index.scss', {
322
341
  importSuffix: 'Stylesheet',
323
- enumerateImports: true
342
+ enumerateImports: true,
343
+ mainModuleBundles: getMainModuleBundleFiles('scss'),
344
+ ignoreModules
324
345
  });
325
346
  }
326
347
 
@@ -351,7 +372,7 @@ module.exports = {
351
372
  name,
352
373
  buildDir,
353
374
  mainBundleName: outputFilename.replace('.js', ''),
354
- verifiedBundles: self.verifiedBundles,
375
+ verifiedBundles: getVerifiedBundlesInEffect(),
355
376
  getImportFileOutput,
356
377
  writeImportFile
357
378
  });
@@ -437,6 +458,32 @@ module.exports = {
437
458
  }));
438
459
  }
439
460
 
461
+ function getMainModuleBundleFiles(ext) {
462
+ return Object.values(self.verifiedBundles)
463
+ .reduce((acc, entry) => {
464
+ if (!entry.main) {
465
+ return acc;
466
+ };
467
+ return [
468
+ ...acc,
469
+ ...(entry[ext] || [])
470
+ ];
471
+ }, []);
472
+ }
473
+
474
+ function getVerifiedBundlesInEffect() {
475
+ return Object.entries(self.verifiedBundles)
476
+ .reduce((acc, [ key, entry ]) => {
477
+ if (entry.main) {
478
+ return acc;
479
+ };
480
+ return {
481
+ ...acc,
482
+ [key]: entry
483
+ };
484
+ }, {});
485
+ }
486
+
440
487
  function writeImportFile ({
441
488
  importFile,
442
489
  prologue,
@@ -625,6 +672,9 @@ module.exports = {
625
672
  for (const name of modulesToInstantiate) {
626
673
  const metadata = self.apos.synth.getMetadata(name);
627
674
  for (const entry of metadata.__meta.chain) {
675
+ if (options.ignoreModules?.[entry.name]) {
676
+ seen[entry.dirname] = true;
677
+ }
628
678
  if (seen[entry.dirname]) {
629
679
  continue;
630
680
  }
@@ -652,6 +702,10 @@ module.exports = {
652
702
  components.reverse();
653
703
  }
654
704
 
705
+ if (options.mainModuleBundles) {
706
+ components.push(...options.mainModuleBundles);
707
+ }
708
+
655
709
  return getImportFileOutput(components, options);
656
710
  }
657
711
 
@@ -883,6 +937,10 @@ module.exports = {
883
937
  },
884
938
  methods(self) {
885
939
  return {
940
+ // Register the library function as method to be used by core modules.
941
+ // Open the implementation for more dev comments.
942
+ transformRebundledFor,
943
+
886
944
  async initUploadfs() {
887
945
  if (self.options.uploadfs) {
888
946
  self.uploadfs = await self.apos.modules['@apostrophecms/uploadfs'].getInstance(self.options.uploadfs);
@@ -47,16 +47,25 @@ module.exports = {
47
47
  }
48
48
  },
49
49
 
50
+ // Export for testing
51
+ formatRebundleConfig,
52
+ verifyRebundleConfig,
53
+
54
+ transformRebundledFor,
55
+
50
56
  async getWebpackExtensions ({
51
- getMetadata, modulesToInstantiate
57
+ getMetadata, modulesToInstantiate, rebundleModulesConfig = {}
52
58
  }) {
53
59
  const modulesMeta = modulesToInstantiate
54
60
  .map((name) => getMetadata(name));
55
61
 
62
+ const rebundleModules = formatRebundleConfig(rebundleModulesConfig);
63
+
56
64
  const {
57
65
  extensions, extensionOptions, foundBundles
58
66
  } = getModulesWebpackConfigs(
59
- modulesMeta
67
+ modulesMeta,
68
+ rebundleModules
60
69
  );
61
70
 
62
71
  const verifiedBundles = await verifyBundlesEntryPoints(foundBundles);
@@ -64,26 +73,36 @@ module.exports = {
64
73
  return {
65
74
  extensions,
66
75
  extensionOptions,
67
- verifiedBundles
76
+ verifiedBundles,
77
+ rebundleModules
68
78
  };
69
79
  },
70
80
 
71
81
  fillExtraBundles (verifiedBundles = {}) {
72
- return Object.entries(verifiedBundles).reduce((acc, [ bundleName, { js, scss } ]) => {
73
- return {
74
- js: [
75
- ...acc.js,
76
- ...(js.length && !acc.js.includes(bundleName)) ? [ bundleName ] : []
77
- ],
78
- css: [
79
- ...acc.css,
80
- ...(scss.length && !acc.css.includes(bundleName)) ? [ bundleName ] : []
81
- ]
82
- };
83
- }, {
84
- js: [],
85
- css: []
86
- });
82
+
83
+ const res = Object.entries(verifiedBundles).reduce(
84
+ (acc, [ bundleName, entry ]) => {
85
+ const {
86
+ js, scss, main
87
+ } = entry;
88
+ if (main) {
89
+ return acc;
90
+ }
91
+ return {
92
+ js: [
93
+ ...acc.js,
94
+ ...(js.length && !acc.js.includes(bundleName)) ? [ bundleName ] : []
95
+ ],
96
+ css: [
97
+ ...acc.css,
98
+ ...(scss.length && !acc.css.includes(bundleName)) ? [ bundleName ] : []
99
+ ]
100
+ };
101
+ }, {
102
+ js: [],
103
+ css: []
104
+ });
105
+ return res;
87
106
  },
88
107
 
89
108
  getBundlesNames (bundles, es5 = false) {
@@ -177,13 +196,17 @@ async function findSymlinks(where, sub = '') {
177
196
  return result;
178
197
  }
179
198
 
180
- function getModulesWebpackConfigs (modulesMeta) {
199
+ function getModulesWebpackConfigs (modulesMeta, rebundleModules) {
181
200
  const {
182
201
  extensions, extensionOptions, bundles
183
202
  } = modulesMeta.reduce((modulesAcc, meta) => {
184
203
  const { webpack, __meta } = meta;
185
204
 
186
- const configs = formatConfigs(__meta.chain, webpack);
205
+ const configs = formatConfigs(
206
+ __meta.chain,
207
+ webpack,
208
+ rebundleModules
209
+ );
187
210
 
188
211
  if (!configs.length) {
189
212
  return modulesAcc;
@@ -235,15 +258,57 @@ function getModulesWebpackConfigs (modulesMeta) {
235
258
  };
236
259
 
237
260
  async function verifyBundlesEntryPoints (bundles) {
238
- const checkPathsPromises = bundles.map(async ({ bundleName, modulePath }) => {
261
+ const checkPathsPromises = bundles.map(async ({
262
+ bundleName, modulePath, bundleRemapping
263
+ }) => {
239
264
  const jsPath = `${modulePath}/ui/src/${bundleName}.js`;
240
265
  const scssPath = `${modulePath}/ui/src/${bundleName}.scss`;
266
+ const jsIndexPath = `${modulePath}/ui/src/index.js`;
267
+ const scssIndexPath = `${modulePath}/ui/src/index.scss`;
268
+ let main = false;
269
+ let withIndex;
241
270
 
242
271
  const jsFileExists = await fs.pathExists(jsPath);
243
272
  const scssFileExists = await fs.pathExists(scssPath);
244
273
 
274
+ for (const remapping of bundleRemapping) {
275
+ main = remapping.main;
276
+ // - catch all to "main" or new bundle name,
277
+ // already verified it's unique for the given module
278
+ if (!remapping.source) {
279
+ // Bundle name for "main" "doesn't matter - it will be ignored and never built,
280
+ // we want to achieve a free from colision name. What matters is main = true.
281
+ // Target is 'main' by convention: `main.bundleName`
282
+ bundleName = `${remapping.target}.${bundleName}`;
283
+ if (!remapping.main) {
284
+ // move "ui/src/index.*" to the bundle,
285
+ // verify existence later, better performance
286
+ withIndex = {
287
+ jsPath: jsIndexPath,
288
+ scssPath: scssIndexPath
289
+ };
290
+ bundleName = remapping.target;
291
+ }
292
+
293
+ break;
294
+ }
295
+ // - not a catch-all statement from this point on
296
+ // - "main" for a concrete bunbdle
297
+ if (remapping.main && remapping.source === bundleName) {
298
+ bundleName = `${remapping.target}.${bundleName}`;
299
+ break;
300
+ }
301
+ // - not "main", concrete bundle remapping
302
+ if (remapping.source === bundleName) {
303
+ bundleName = remapping.target;
304
+ break;
305
+ }
306
+ }
307
+
245
308
  return {
246
309
  bundleName,
310
+ main,
311
+ withIndex,
247
312
  ...jsFileExists && { jsPath },
248
313
  ...scssFileExists && { scssPath }
249
314
  };
@@ -251,8 +316,39 @@ async function verifyBundlesEntryPoints (bundles) {
251
316
 
252
317
  const bundlesPaths = await Promise.all(checkPathsPromises);
253
318
 
254
- const packedFilesByBundle = bundlesPaths.reduce((acc, {
255
- bundleName, jsPath, scssPath
319
+ // Verify and squash withIndex data
320
+ const seen = {};
321
+ const bundlesPathsWithIndex = [];
322
+ for (const entry of bundlesPaths) {
323
+ if (!entry.withIndex) {
324
+ bundlesPathsWithIndex.push(entry);
325
+ continue;
326
+ }
327
+ if (seen[entry.bundleName]) {
328
+ delete entry.withIndex;
329
+ bundlesPathsWithIndex.push(entry);
330
+ continue;
331
+ }
332
+ seen[entry.bundleName] = true;
333
+ const { jsPath, scssPath } = entry.withIndex;
334
+ const jsFileExists = await fs.pathExists(jsPath);
335
+ const scssFileExists = await fs.pathExists(scssPath);
336
+ if (!jsFileExists && !scssFileExists) {
337
+ delete entry.withIndex;
338
+ bundlesPathsWithIndex.push(entry);
339
+ continue;
340
+ }
341
+ bundlesPathsWithIndex.push({
342
+ ...entry,
343
+ withIndex: {
344
+ ...jsFileExists && { jsPath },
345
+ ...scssFileExists && { scssPath }
346
+ }
347
+ });
348
+ }
349
+
350
+ const packedFilesByBundle = bundlesPathsWithIndex.reduce((acc, {
351
+ bundleName, jsPath, scssPath, main, withIndex
256
352
  }) => {
257
353
  if (!jsPath && !scssPath) {
258
354
  return acc;
@@ -261,11 +357,18 @@ async function verifyBundlesEntryPoints (bundles) {
261
357
  return {
262
358
  ...acc,
263
359
  [bundleName]: {
360
+ main,
361
+ // Boolean indicating if the "main" index.js is included
362
+ // caused by a remapping. It's not yet used anywhere but
363
+ // it's an useful information that we keep.
364
+ withIndex: !!withIndex || acc[bundleName]?.withIndex,
264
365
  js: [
366
+ ...withIndex?.jsPath ? [ withIndex?.jsPath ] : [],
265
367
  ...acc[bundleName] ? acc[bundleName].js : [],
266
368
  ...jsPath ? [ jsPath ] : []
267
369
  ],
268
370
  scss: [
371
+ ...withIndex?.scssPath ? [ withIndex?.scssPath ] : [],
269
372
  ...acc[bundleName] ? acc[bundleName].scss : [],
270
373
  ...scssPath ? [ scssPath ] : []
271
374
  ]
@@ -276,7 +379,105 @@ async function verifyBundlesEntryPoints (bundles) {
276
379
  return packedFilesByBundle;
277
380
  };
278
381
 
279
- function formatConfigs (chain, webpackConfigs) {
382
+ // Normalize config:
383
+ // - from { moduleName: 'main' }
384
+ // to [{ main: true, name: 'moduleName', target: 'main' }]
385
+ // - from { moduleName: bundleName }
386
+ // to [{ main: false, name: 'moduleName', target: bundleName }]
387
+ // - from { 'moduleName:sourceBundle': 'main' }
388
+ // to [{ main: true, name: 'moduleName', source: 'sourceBundle', target: 'main' }]
389
+ // - from { 'moduleName:sourceBundle': targetBundle }
390
+ // to [{ main: false, name: 'moduleName', source: 'sourceBundle', target: targetBundle }]
391
+ function formatRebundleConfig(mappingConfig = {}) {
392
+ const result = Object.keys(mappingConfig).reduce((transformed, key) => {
393
+ const main = mappingConfig[key] === 'main';
394
+ const [ moduleName, sourceBundle ] = key.split(':');
395
+ return [
396
+ ...transformed,
397
+ {
398
+ name: moduleName,
399
+ source: sourceBundle,
400
+ target: mappingConfig[key],
401
+ main
402
+ }
403
+ ];
404
+ }, []);
405
+ // Better panic than sorry!
406
+ verifyRebundleConfig(result);
407
+ return result;
408
+ }
409
+
410
+ // This function is used to detect re-bundled and moved to the main build bundles.
411
+ // It returns filtered configuration containing the proper bundle names.
412
+ // See usage in `template/lib/bundlesLoader.js` and `widget-type/index.js`.
413
+ // Expected arguments:
414
+ // - moduleName: the module owning the bundleConfig
415
+ // - bundleConfig: the bundle configuration ({ bundles: {...} })
416
+ // - rebundleConfigs: the normalized output of `formatRebundleConfig(asset.options.rebundleModules)`
417
+ function transformRebundledFor(moduleName, bundleConfigs, rebundleConfigs) {
418
+ const rebundle = rebundleConfigs
419
+ .filter(entry => entry.name === moduleName);
420
+ let result = { ...bundleConfigs };
421
+
422
+ for (const entry of rebundle) {
423
+ // 1. CatchAll to "main", already bundled in the main build - skip.
424
+ if (!entry.source && entry.main) {
425
+ result = {};
426
+ break;
427
+ }
428
+ // 2. CatchAll to a new bundle name, preserve merged options.
429
+ if (!entry.source && !entry.main) {
430
+ const options = Object.values(bundleConfigs)
431
+ .reduce((all, opts) => ({
432
+ ...all,
433
+ ...opts
434
+ }), {});
435
+ result = { [entry.target]: options };
436
+ break;
437
+ }
438
+ // 3. Rename a single bundle.
439
+ if (entry.source) {
440
+ // 3.1. ... but it's sent to the main build
441
+ if (entry.main) {
442
+ delete result[entry.source];
443
+ continue;
444
+ }
445
+ // 3.2. rename it, preserve the options
446
+ if (bundleConfigs[entry.source]) {
447
+ result[entry.target] = { ...bundleConfigs[entry.source] };
448
+ delete result[entry.source];
449
+ }
450
+ }
451
+ }
452
+
453
+ return result;
454
+ }
455
+
456
+ // Expects formatted by formatRebundleConfig() `asset.options.rebundleModules`
457
+ function verifyRebundleConfig(config = []) {
458
+ const targeted = [];
459
+ const catchAllModules = {};
460
+ for (const entry of config) {
461
+ if (!entry.source) {
462
+ catchAllModules[entry.name] = `${entry.name}: ${entry.target}`;
463
+ } else {
464
+ targeted.push(entry);
465
+ }
466
+ }
467
+ for (const entry of targeted) {
468
+ if (catchAllModules[entry.name]) {
469
+ const conflicting = `${entry.name}${entry.source ? `:${entry.source}` : ''}: ${entry.target}`;
470
+ throw new Error(
471
+ 'Invalid apos.asset.options.rebundleModules: ' +
472
+ `"${catchAllModules[entry.name]}" conflicts with ` +
473
+ `"${conflicting}"`
474
+ );
475
+ }
476
+ }
477
+ }
478
+
479
+ // Gather and transform all available webpack configs
480
+ function formatConfigs (chain, webpackConfigs, rebundleModules) {
280
481
  return Object.entries(webpackConfigs)
281
482
  .map(([ name, config ], i) => {
282
483
 
@@ -285,16 +486,26 @@ function formatConfigs (chain, webpackConfigs) {
285
486
  }
286
487
 
287
488
  const {
288
- bundles = {}, extensions = {}, extensionOptions = {}
489
+ bundles = {},
490
+ extensions = {},
491
+ extensionOptions = {}
289
492
  } = config;
493
+ const bundleNames = Object.keys(bundles);
494
+
495
+ // Remapping only for the explicitly defined by the module bundle
496
+ // configurations
497
+ const bundleRemapping = rebundleModules
498
+ .filter(entry => entry.name === chain[i].name);
290
499
 
291
500
  return {
501
+ name: chain[i].name,
292
502
  extensions,
293
503
  extensionOptions,
294
504
  bundles: {
295
505
  [name]: {
296
- bundleNames: Object.keys(bundles),
297
- modulePath: chain[i].dirname
506
+ bundleNames,
507
+ modulePath: chain[i].dirname,
508
+ bundleRemapping
298
509
  }
299
510
  }
300
511
  };
@@ -303,11 +514,14 @@ function formatConfigs (chain, webpackConfigs) {
303
514
 
304
515
  function flattenBundles (bundles) {
305
516
  return Object.values(bundles)
306
- .reduce((acc, { bundleNames, modulePath }) => {
517
+ .reduce((acc, {
518
+ bundleNames, modulePath, bundleRemapping
519
+ }) => {
307
520
  return [
308
521
  ...acc,
309
522
  ...bundleNames.map((bundleName) => ({
310
523
  bundleName,
524
+ bundleRemapping,
311
525
  modulePath
312
526
  }))
313
527
  ];