Skip to content

Commit

Permalink
New configuration registry (#2196)
Browse files Browse the repository at this point in the history
* Introduce the config registry, update code to use it here and there

* Renaming

* More refactor for the components folder - down to 140

* locales lazy load in components

* Main entry points refactor, maintaining 'the bootstrap'

* Missing Ladable upstream changes

* WIP - fix and refactor tests

* All tests passing now

* More adapting tests

* Remove circular dep plugin from production build

* Remove the explicit bootstraping for now

* Allow loading a default config loader method from @package/config (#2198)

* Add applyConfig to the generator, add missing addonRoutes, addonReducers to the registry

* Change to bootstrap the inner Volto config instead of the top level one

* Pull from the new config registry

* Add upgrade docs

* Update changelog

* Migrate last ~/config that made all tests fail since last sync with master!

* Typo

* Back to bootstrap using ~/config instead of @plone/volto/config

* MAke the pure way default

* Finish documentation

* Fix Filewidget test

* Improve docs for Volto's configuration

* Removal of the old references to the old configuration

* Update docs/source/addons/index.md

Co-authored-by: Piero Nicolli <pnicolli@users.noreply.github.com>

* Update CHANGELOG.md

Co-authored-by: Piero Nicolli <pnicolli@users.noreply.github.com>

* Last touches to the documentation, add mocked notice to mocks, add peerDependencies recommendation

Co-authored-by: Tiberiu Ichim <tiberiuichim@users.noreply.github.com>
Co-authored-by: Piero Nicolli <pnicolli@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 17, 2021
1 parent d21d59f commit 7c69271
Show file tree
Hide file tree
Showing 141 changed files with 1,650 additions and 2,599 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,9 @@

### Breaking

- Introduction of the new Volto Configuration Registry @sneridagh @tiberiuichim
For more information about this breaking change: https://docs.voltocms.com/upgrade-guide/#upgrading-to-volto-12xx

### Feature

### Bugfix
Expand Down
151 changes: 24 additions & 127 deletions __tests__/create-addons-loader.test.js
Expand Up @@ -11,6 +11,7 @@ This file is autogenerated. Don't change it directly.
Instead, change the "addons" setting in your package.json file.
*/
const projectConfigLoader = require('@package/config');
const safeWrapper = (func) => (config) => {
const res = func(config);
Expand All @@ -20,177 +21,73 @@ const safeWrapper = (func) => (config) => {
return res;
}
const projectConfig = (config) => {
return typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config;
}
const load = (config) => {
const addonLoaders = [];
if(!addonLoaders.every((el) => typeof el === "function")) {
throw new TypeError(
'Each addon has to provide a function applying its configuration to the projects configuration.',
);
}
return addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config);
return projectConfig(addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config));
};
export default load;
`);
});

test('one addon creates loader', () => {
const code = getLoader.getAddonsLoaderCode(['volto-addon1']);
expect(code).toBe(`/*
This file is autogenerated. Don't change it directly.
Instead, change the "addons" setting in your package.json file.
*/
import voltoAddon1 from 'volto-addon1';
const safeWrapper = (func) => (config) => {
const res = func(config);
if (typeof res === 'undefined') {
throw new Error("Configuration function doesn't return config");
}
return res;
}
const load = (config) => {
const addonLoaders = [voltoAddon1];
if(!addonLoaders.every((el) => typeof el === "function")) {
throw new TypeError(
'Each addon has to provide a function applying its configuration to the projects configuration.',
expect(code.indexOf("import voltoAddon1 from 'volto-addon1';") > 0).toBe(
true,
);
}
return addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config);
};
export default load;
`);
});

test('two addons create loaders', () => {
const code = getLoader.getAddonsLoaderCode([
'volto-addon1',
'volto-addon2',
]);
expect(code).toBe(`/*
This file is autogenerated. Don't change it directly.
Instead, change the "addons" setting in your package.json file.
*/
expect(
code.indexOf(`
import voltoAddon1 from 'volto-addon1';
import voltoAddon2 from 'volto-addon2';
const safeWrapper = (func) => (config) => {
const res = func(config);
if (typeof res === 'undefined') {
throw new Error("Configuration function doesn't return config");
}
return res;
}
const load = (config) => {
const addonLoaders = [voltoAddon1, voltoAddon2];
if(!addonLoaders.every((el) => typeof el === "function")) {
throw new TypeError(
'Each addon has to provide a function applying its configuration to the projects configuration.',
);
}
return addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config);
};
export default load;
`);
import voltoAddon2 from 'volto-addon2';`) > 0,
).toBe(true);
});

test('one addons plus one extra creates loader', () => {
const code = getLoader.getAddonsLoaderCode(['volto-addon1:loadExtra1']);
expect(code).toBe(`/*
This file is autogenerated. Don't change it directly.
Instead, change the "addons" setting in your package.json file.
*/
expect(
code.indexOf(`
import voltoAddon1, { loadExtra1 as loadExtra10 } from 'volto-addon1';
const safeWrapper = (func) => (config) => {
const res = func(config);
if (typeof res === 'undefined') {
throw new Error("Configuration function doesn't return config");
}
return res;
}
const load = (config) => {
const addonLoaders = [voltoAddon1, loadExtra10];
if(!addonLoaders.every((el) => typeof el === "function")) {
throw new TypeError(
'Each addon has to provide a function applying its configuration to the projects configuration.',
);
}
return addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config);
};
export default load;
`);
`) > 0,
).toBe(true);
});

test('one addons plus two extras creates loader', () => {
const code = getLoader.getAddonsLoaderCode([
'volto-addon1:loadExtra1,loadExtra2',
]);
expect(code).toBe(`/*
This file is autogenerated. Don't change it directly.
Instead, change the "addons" setting in your package.json file.
*/
expect(
code.indexOf(`
import voltoAddon1, { loadExtra1 as loadExtra10, loadExtra2 as loadExtra21 } from 'volto-addon1';
const safeWrapper = (func) => (config) => {
const res = func(config);
if (typeof res === 'undefined') {
throw new Error("Configuration function doesn't return config");
}
return res;
}
const load = (config) => {
const addonLoaders = [voltoAddon1, loadExtra10, loadExtra21];
if(!addonLoaders.every((el) => typeof el === "function")) {
throw new TypeError(
'Each addon has to provide a function applying its configuration to the projects configuration.',
);
}
return addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config);
};
export default load;
`);
`) > 0,
).toBe(true);
});

test('two addons plus extras creates loader', () => {
const code = getLoader.getAddonsLoaderCode([
'volto-addon1:loadExtra1,loadExtra2',
'volto-addon2:loadExtra3,loadExtra4',
]);
expect(code).toBe(`/*
This file is autogenerated. Don't change it directly.
Instead, change the "addons" setting in your package.json file.
*/
expect(
code.indexOf(`
import voltoAddon1, { loadExtra1 as loadExtra10, loadExtra2 as loadExtra21 } from 'volto-addon1';
import voltoAddon2, { loadExtra3 as loadExtra32, loadExtra4 as loadExtra43 } from 'volto-addon2';
const safeWrapper = (func) => (config) => {
const res = func(config);
if (typeof res === 'undefined') {
throw new Error("Configuration function doesn't return config");
}
return res;
}
const load = (config) => {
const addonLoaders = [voltoAddon1, loadExtra10, loadExtra21, voltoAddon2, loadExtra32, loadExtra43];
if(!addonLoaders.every((el) => typeof el === "function")) {
throw new TypeError(
'Each addon has to provide a function applying its configuration to the projects configuration.',
);
}
return addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config);
};
export default load;
`);
`) > 0,
).toBe(true);
});
});

Expand Down
7 changes: 6 additions & 1 deletion create-addons-loader.js
Expand Up @@ -27,6 +27,7 @@ This file is autogenerated. Don't change it directly.
Instead, change the "addons" setting in your package.json file.
*/
const projectConfigLoader = require('@package/config');
`;
let configsToLoad = [],
counter = 0;
Expand Down Expand Up @@ -67,14 +68,18 @@ const safeWrapper = (func) => (config) => {
return res;
}
const projectConfig = (config) => {
return typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config;
}
const load = (config) => {
const addonLoaders = [${configsToLoad.join(', ')}];
if(!addonLoaders.every((el) => typeof el === "function")) {
throw new TypeError(
'Each addon has to provide a function applying its configuration to the projects configuration.',
);
}
return addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config);
return projectConfig(addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config));
};
export default load;
`;
Expand Down
2 changes: 1 addition & 1 deletion docs/source/addons/best-practices.md
Expand Up @@ -15,7 +15,7 @@ register the most basic configuration of that widget with a name that can be
used.

On more complicated cases, see if you can structure your code to use the
`settings` registry of `~/config`, or stash your configuration in your block
`settings` configuration registry, or stash your configuration in your block
registration, for example.

As an example: let's say we're building a Color Picker widget and we want to
Expand Down
3 changes: 1 addition & 2 deletions docs/source/addons/index.md
Expand Up @@ -45,7 +45,7 @@ section.
### Loading addon configuration

As a convenience, an addon can export configuration functions that can mutate,
in-place, the overall ``~/config`` registry. An addon can export multiple
in-place, the overall Volto configuration registry. An addon can export multiple
configurations methods, making it possible to selectively choose which specific
addon functionality you want to load.

Expand Down Expand Up @@ -364,4 +364,3 @@ If you have generated your Volto project recently (after the summer of 2020),
you don't have to do anything to have automatic integration with ESLint,
otherwise make sure to upgrade your project's `.eslintrc` to the `.eslintrc.js`
version, according to the [Upgrade Guide](/upgrade-guide).

92 changes: 53 additions & 39 deletions docs/source/configuration/how-to.md
@@ -1,50 +1,53 @@
# The configuration object
# The configuration registry

Volto has a central configuration object used to parameterize Volto. It lives in Volto itself but it can be customized in a per project basis.
Volto has a centralized configuration registry used to parameterize Volto. It has the
form of a singleton that can be called and queried from anywhere in your code like this:

You can find it in Volto in the `src/config` module.
```js
import config from '@plone/volto/registry';
```

When you create your project via the generator, you can find it in `src/config.js`.
then access any of its internal configuration to retrieve the configuration you require
like:

```js
import {
settings as defaultSettings,
views as defaultViews,
widgets as defaultWidgets,
blocks as defaultBlocks,
addonRoutes as defaultAddonRoutes,
addonReducers as defaultAddonReducers,
} from '@plone/volto/config';

export const settings = {
...defaultSettings,
};

export const views = {
...defaultViews,
};

export const widgets = {
...defaultWidgets,
};

export const blocks = {
...defaultBlocks,
};

export const addonRoutes = [...defaultAddonRoutes];
export const addonReducers = { ...defaultAddonReducers };
const absoluteUrl = `${config.settings.apiPath}/${content.url}`
```

It gets the default config from Volto and leave it available to you to customize it in your project.
Both add-ons and projects can extend Volto's configuration registry. First the add-ons
configuration is applied, in the order they are defined in `package.json`, then finally
the project configuration is applied. Visualized like a pipe would be:

> Default Volto configuration -> Add-on 1 -> Add-on 2 -> ... -> Add-on n -> Project
Both use the same method, using a function as the default export. This function takes a
`config` and should return the `config` once you've ended your modifications. For
add-ons, it must be provided in the main `index.js` module of the add-on. For project's
it must be provided in the `src/config.js` module of the project.

Reading the source code for the `~/config` registry is an absolute key in
understanding Volto and what can be configured.
See the [Add-ons](/addons) section for extended information on how to work with add-ons.

As you can see from the snippet above, in your Volto project you'll have
a `src/config.js` file. This file is referenced throughout the codebase as
`~/config`. You can see that, in its default version, all it does is import
Volto's default configuration objects and export them further.
## Extending configuration in a project

You must provide a function as default export in your `src/config.js`:

```js
export default function applyConfig(config) {
config.settings = {
...config.settings,
isMultilingual: true,
supportedLanguages: ['en', 'de'],
defaultLanguage: 'de',
navDepth: 3,
};

return config;
}
```

you have all Volto's default configuration and the already applied from your project's
add-ons configuration in `config` argument. Next, perform all the required modifications
to the config and finally, return the config object.

By reading Volto's
[src/config/index.js](https://github.com/plone/volto/tree/master/src/config/index.js),
Expand All @@ -58,7 +61,7 @@ a Volto project.

## settings

The `settings` object of the `~/config` is a big registry of miscellaneous
The `settings` object of the configruration registry is a big registry of miscellaneous
settings. See the [Settings reference](/configuration/settings-reference) for
a bit more details.

Expand All @@ -83,6 +86,17 @@ to render the content. There are 4 types of views:
- and the error views, to be used for regular error pages (Forbidden, Not
Found, etc).

## blocks

The `blocks` registry holds the information of all the registered blocks in Volto. There are 4 configurations available:

- blocksConfig
- requiredBlocks
- groupBlocksOrder
- initialBlocks

See [Blocks](/blocks/settings) for more information.

## addonReducers

In the `addonReducers` you can register and potentially override (by name) any
Expand Down

0 comments on commit 7c69271

Please sign in to comment.