diff --git a/CHANGELOG.md b/CHANGELOG.md index b08558b834..8fd1423edd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ * Extra bundle detection when using external build module works properly now. * Widget players are now properly invoked when they arrive later in the page load process. * Fix permission grid tooltip display. +* Fixes a bug that crashes external frontend applications. +* Fixes a false positive warning for module not in use for project level submodules (e.g. `widges/module.js`) and dot-folders (e.g. `.DS_Store`). ### Adds * It's possible now to target the HMR build when registering via `template.append` and `template.prepend`. Use `when: 'hmr:public'` or `when: 'hmr:apos'` that will be evaluated against the current asset `options.hmr` configuration. +* Adds asset module option `options.modulePreloadPolyfill` (default `true`) to allow disabling the polyfill preload for e.g. external front-ends. +* Adds `bundleMarkup` to the data sent to the external front-end, containing all markup for injecting Apostrophe UI in the front-end. ## 4.9.0 (2024-10-31) diff --git a/index.js b/index.js index 3e7b2f713c..2106f04c9f 100644 --- a/index.js +++ b/index.js @@ -699,11 +699,18 @@ async function apostrophe(options, telemetry, rootSpan) { } } async function testDir(name) { + if (name.startsWith('.')) { + return; + } // Projects that have different theme modules activated at different times // are a frequent source of false positives for this warning, so ignore // seemingly unused modules with "theme" in the name if (!validSteps.includes(name)) { try { + // It's a project level modules definition, skip it. + if (fs.existsSync(path.resolve(self.localModules, name, 'modules.js'))) { + return; + } const submodule = await self.root.import(path.resolve(self.localModules, name, 'index.js')); if (submodule && submodule.options && submodule.options.ignoreUnusedFolderWarning) { return; diff --git a/modules/@apostrophecms/asset/index.js b/modules/@apostrophecms/asset/index.js index fb6cc3a556..3482ba9c33 100644 --- a/modules/@apostrophecms/asset/index.js +++ b/modules/@apostrophecms/asset/index.js @@ -98,6 +98,10 @@ module.exports = { // Force the HMR WS port when it operates on the same process as Apostrophe. // Most of the time you won't need to change this. hmrPort: null, + // Let the external build module inject a pollyfill for the module preload, + // adding the `modulepreload` support for the browsers that don't support it. + // Can be disabled in e.g. external front-ends. + modulePreloadPolyfill: true, // Completely disable the asset runtime auto-build system. // When an external build module is registered, only manifest data // will be loaded and no build will be executed. @@ -477,6 +481,7 @@ module.exports = { // - `devServer`: if `false`, the dev server is disabled. Otherwise, it's a string // (enum) `public` or `apos`. Note that if `hmr` is disabled, the dev server will be always // `false`. + // - `modulePreloadPolyfill`: if `true`, a module preload polyfill is injected. // - `types`: optional array, if present it represents the only entrypoint types (entrypoint.type) // that should be built. // - `sourcemaps`: if `true`, the source maps are generated in production. @@ -494,15 +499,16 @@ module.exports = { isTask: !argv['check-apos-build'], hmr: self.hasHMR(), hmrPort: self.options.hmrPort, + modulePreloadPolyfill: self.options.modulePreloadPolyfill, sourcemaps: self.options.productionSourceMaps }; options.devServer = !options.isTask && self.hasDevServer() ? self.options.hmr : false; - // Skip all public and keep only the apos scenes. + // Skip prebundled UI and keep only the apos scenes. if (!self.options.publicBundle) { - options.types = [ 'apos', 'bundled' ]; + options.types = [ 'apos', 'index' ]; } return options; diff --git a/modules/@apostrophecms/asset/lib/build/external-module-api.js b/modules/@apostrophecms/asset/lib/build/external-module-api.js index 56cf9b5302..9f92638d48 100644 --- a/modules/@apostrophecms/asset/lib/build/external-module-api.js +++ b/modules/@apostrophecms/asset/lib/build/external-module-api.js @@ -69,13 +69,8 @@ module.exports = (self) => { // Returns an array of objects with the following properties: // - `name`: the entrypoint name. It's usually the relative to `ui` folder // name(`src`, `apos`, `public`) or an extra bundle name. + // - `label`: the human-readable label for the entrypoint, used to print CLI messages. // - `type`: (enum) the entrypoint type. It can be `index`, `apos`, `custom` (e.g. extra bundles) or - // `bundled` (e.g. `ui/public`). Every type has associated manager that provides handling for the entrypoint. - // - `useMeta`: if `true`, the entrypoint will be created based on the source metadata (see - // `computeSourceMeta()` method). - // - `bundle`: if `true`, the entrypoint should be bundled by the build module. - // - `index`: if `true`, the entrypoint processes only `{name}/index.{js,scss}` module files. - // - `apos`: if `true`, the entrypoint processes components, icons and apps. // - `ignoreSources`: an array of sources that shouldn't be processed when creating the entrypoint. // - `sources`: an object with `js` and `scss` arrays of extra sources to be included in the entrypoint. // These sources are not affected by the `ignoreSources` configuration. @@ -84,10 +79,15 @@ module.exports = (self) => { // - `prologue`: a string with the prologue to be added to the entrypoint. // - `condition`: the JS `module` or `nomodule` condition. Undefined for no specific condition. // - `outputs`: an array of output extensions for the entrypoint (currently not fully utilized) + // - `inputs`: an array of input extensions for the entrypoint (currently not fully utilized) // - `scenes`: an array of scenes to be in the final post-bundle step. The scenes are instructions // for the Apostrophe core to combine the builds and release them. Currently supported scenes are // `apos` and `public` and custom scene names equal to extra bundle (only those who should be // loaded separately in the browser). + // + // Additonal properties added after entrypoints are processed by the core and the external build module: + // - `manifest`: object, see the manifest section of `configureBuildModule()` docs for more information. + // - `bundles`: a `Set` containing the bundle names that this entrypoint is part of (both css and js). getBuildEntrypoints(types, recompute = false) { if (!self.hasBuildModule()) { return self.builds; diff --git a/modules/@apostrophecms/asset/lib/build/internals.js b/modules/@apostrophecms/asset/lib/build/internals.js index 8e8539945e..9bf354e57d 100644 --- a/modules/@apostrophecms/asset/lib/build/internals.js +++ b/modules/@apostrophecms/asset/lib/build/internals.js @@ -88,7 +88,7 @@ module.exports = (self) => { enhancedConfig.ignoreSources.push(...bundleConfig.scss); } // 2.3. Add the extra bundle configuration so that - // it only processes the configured `sources` (`useMeta: false`). + // it only processes the configured `sources` if (!bundleConfig.main) { entrypoints.push({ name: bundleName, diff --git a/modules/@apostrophecms/schema/lib/addFieldTypes.js b/modules/@apostrophecms/schema/lib/addFieldTypes.js index c8530f70a5..3741962f2c 100644 --- a/modules/@apostrophecms/schema/lib/addFieldTypes.js +++ b/modules/@apostrophecms/schema/lib/addFieldTypes.js @@ -861,7 +861,9 @@ module.exports = (self) => { self.addFieldType({ name: 'object', - async convert(req, field, data, destination, { fetchRelationships = true, ancestors = {}, doc = {} } = {}) { + async convert(req, field, data, destination, { + fetchRelationships = true, ancestors = {}, doc = {} + } = {}) { data = data[field.name]; const schema = field.schema; const errors = []; diff --git a/modules/@apostrophecms/template/index.js b/modules/@apostrophecms/template/index.js index 2c749a1cb9..9a3c1c3e88 100644 --- a/modules/@apostrophecms/template/index.js +++ b/modules/@apostrophecms/template/index.js @@ -1054,6 +1054,24 @@ module.exports = { data.template = template; // For simple cases (not piece pages and the like) data.module = moduleName; + + // Provide the `apos` scene bundles to the exsternal front-end + if (self.apos.asset.hasBuildModule()) { + const modulePreload = new Set(); + data.bundleMarkup = { + js: self.apos.asset.getBundlePageMarkup({ + scene: 'apos', + output: 'js', + modulePreload + }), + css: self.apos.asset.getBundlePageMarkup({ + scene: 'apos', + output: 'css' + }) + }; + data.bundleMarkup.js.push(...Array.from(modulePreload)); + } + return data; },