diff --git a/docs/source/addons/addon-configuration-pipeline.md b/docs/source/addons/addon-configuration-pipeline.md new file mode 100644 index 0000000000..59ced77c4a --- /dev/null +++ b/docs/source/addons/addon-configuration-pipeline.md @@ -0,0 +1,68 @@ +--- +myst: + html_meta: + "description": "Add-on and project configuration pipeline" + "property=og:description": "Add-on and project configuration pipeline" + "property=og:title": "Add-on and project configuration pipeline" + "keywords": "Volto, Plone, Volto add-ons, Volto Project" +--- +%Explanation +# Add-on and project configuration pipeline + +Volto build its configuration out of Volto's configuration defaults, add-ons configuration, and project configuration. +This is called the configuration pipeline. + +![Volto configuration pipeline](./configuration-pipeline.png) + +It's applied in this particular order. +Configurations can be overriden at a later stage. + +## All-in with add-on approach + +You can take full advantage of the configuration pipeline and do not use the project configuration at all. +You can create a "policy" core product for your project, and use another for your project's theme. +This way, the project itself renders as a simple boilerplate, expendable or rebuild-able at any time. +You can also reuse add-ons across projects, and adjust them using another add-ons, depending on the other projects requirements. + +```{deprecated} Volto 18.0.0 +The project configuration approach is deprecated and will be removed in Volto 19. +``` + +## Define your add-ons programatically + +Having the `addons` key in `package.json` is not flexibile enough in complex scenarios. +You can load your add-ons programatically, outside `package.json` using `volto.config.js` like this: + +```js +module.exports = { + addons: ['@eeacms/volto-accordion-block'] +} +``` + +This is an "escape hatch" where you can use logic and environment conditions to define the add-ons to be loaded in the current project. Take a look: + + +```js +let addons = []; +if (process.env.MY_SPECIAL_ENV_VAR) { // Does not have to be RAZZLE_ + addons = ['volto-my-awesome-special-add-on']; +} + +if (process.env.MARKER_FOR_MY_SECRET_PROJECT) { // Does not have to be RAZZLE_ + addons = [ + '@kitconcept/volto-heading-block', + '@kitconcept/volto-slider-block', + 'volto-my-secret-project-add-on', + ]; +} + +module.exports = { + addons: addons, +}; +``` + +They are added to the existing ones in `package.json`. + +```{seealso} +{doc}`../configuration/volto-config-js` +``` diff --git a/docs/source/addons/configuration-pipeline.png b/docs/source/addons/configuration-pipeline.png new file mode 100644 index 0000000000..7f5f1e953b Binary files /dev/null and b/docs/source/addons/configuration-pipeline.png differ diff --git a/docs/source/addons/how-an-add-on-works.md b/docs/source/addons/how-an-add-on-works.md new file mode 100644 index 0000000000..ddd47ea9ef --- /dev/null +++ b/docs/source/addons/how-an-add-on-works.md @@ -0,0 +1,118 @@ +--- +myst: + html_meta: + "description": "How does a Volto add-on work?" + "property=og:description": "How does a Volto add-on works?" + "property=og:title": "How does a Volto add-on works?" + "keywords": "Volto, Plone, Volto add-ons, JavaScript, JavaScript dependencies" +--- +% Explanation +# How does a Volto add-on work? + +Volto add-on packages are just CommonJS/ESM packages. +Their main purpose is encapsulate logic, configuration and customizations in a reusable way. +The only requirement is that they point the `main` key of their `package.json` to a module that exports, as a default function that acts as a Volto configuration loader. + +Similarly to how you develop a Plone backend Python add-on, you can control all aspects of Volto from a Volto add-on. + +This gives you the ability to move all your project configuration, components, customizations and even theme files to an add-on. +This has the advantage to render the project configuration empty and expendable, so you could at any point not only reuse the add-on(s) outside the current project, but also have the project as simply boilerplate that could be replaced at any point (for example, a Volto version upgrade). + +An add-on can be published in an npm registry, just as any other package. +However, Volto add-ons should not be transpiled. +They should be released as "source" packages. + +See [@kitconcept/volto-button-block](https://github.com/kitconcept/volto-button-block) as an example. + +### Add-on configuration + +The default export of your add-on main `index.js` file should be a function with +the signature `config => config`. +That is, it should take the `global` configuration object and return it, possibly mutated or changed. +So your main `index.js` will look like: + +```js +export default function applyConfig(config) { + config.blocks.blocksConfig.faq_viewer = { + id: 'faq_viewer', + title: 'FAQ Viewer', + edit: FAQBlockEdit, + view: FAQBlockView, + icon: chartIcon, + group: 'common', + restricted: false, + mostUsed: true, + sidebarTab: 1, + security: { + addPermission: [], + view: [], + }, + }; + return config; +} +``` + +And the `package.json` file of your add-on: + +```json +{ + "main": "src/index.js", +} +``` + +In effect, Volto does the equivalent of: + +``` +import installMyVoltoAddon from 'my-volto-addon' + +// ... in the configuration registry setup step: +const configRegistry = installMyVoltoAddon(defaultRegistry); +``` + +So the Volto add-on needs to export a default function that receives the Volto configuration registry, is free to change the registry as it sees fit, then it needs to return that registry. + +Volto will chain-execute all the add-on configuration functions to compute the final configuration registry. + +```{info} +An add-on's default configuration method will always be loaded. +``` + +### Providing optional add-on configurations + +You can export additional configuration functions from your add-on's main +`index.js`. + +```js +import applyConfig, {loadOptionalBlocks,overrideSomeDefaultBlock} from './config'; + +export { loadOptionalBlocks, overrideSomeDefaultBlock }; +export default applyConfig; +``` + +```{seealso} +{doc}`./how-to-load-addon-configuration` +``` + +## Customizations + +Add-on packages can include customization folders, just like the Volto projects. +The customizations are resolved in the order: add-ons (as sorted in the `addons` key of your project's `package.json`) then the customizations in the Volto project, last one wins. + +```{tip} +See the {ref}`advanced-customization-scenarios-label` section on how to enhance this pattern and how to include customizations inside add-ons. +``` + +## Add-on dependencies + +Add-ons can depend on any other JavaScript package, but they can also depend on other Volto add-ons. +To do this, specify the name of your Volto add-on dependency in your `dependencies` key of `package.json` and create a new `addons` key in the `package.json` of your add-on, where you specify the extra Volto add-on dependency. + +By doing this, the add-ons can "chain-load" one another, so you don't have to keep track of intermediary dependencies. + +```json +{ + "name": "volto-slate", + + "addons": ["@eeacms/volto-object-widget"] +} +``` diff --git a/docs/source/addons/how-to-create-an-addon-prerelease.md b/docs/source/addons/how-to-create-an-addon-prerelease.md new file mode 100644 index 0000000000..2c8334b540 --- /dev/null +++ b/docs/source/addons/how-to-create-an-addon-prerelease.md @@ -0,0 +1,184 @@ +--- +myst: + html_meta: + "description": "How to create a frontend add-on (development or pre-release)" + "property=og:description": "How to create a frontend add-on (development or pre-release)" + "property=og:title": "How to create a frontend add-on (development or pre-release)" + "keywords": "add-on, Volto, create" +--- + +# How to create a frontend add-on (development or pre-release) + +This chapter describes how you can create a Volto add-on using the latest **development release** version of Plone with **Volto 18 or later** for the frontend, while having full control over its development and deployment. + +```{versionadded} Volto 18.0.0-alpha.43 +{term}`Cookieplone` is now the method to create a Plone add-on with unstable versions of Volto, version 18.0.0-alpha.43 and above. +``` + +Follow the steps required to install [Cookieplone]({doc}`plone:install/create-project-cookieplone.md`). + +```shell +pipx run cookieplone frontend_addon +``` + +```console +❯ pipx run cookieplone frontend_addon +⚠️ cookieplone is already on your PATH and installed at /Users/sneridagh/.local/bin/cookieplone. Downloading and running anyway. +╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── cookieplone ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ .xxxxxxxxxxxxxx. │ +│ ;xxxxxxxxxxxxxxxxxxxxxx; │ +│ ;xxxxxxxxxxxxxxxxxxxxxxxxxxxx; │ +│ xxxxxxxxxx xxxxxxxxxx │ +│ xxxxxxxx. .xxxxxxxx │ +│ xxxxxxx xxxxxxx: xxxxxxx │ +│ :xxxxxx xxxxxxxxxx xxxxxx: │ +│ :xxxxx+ xxxxxxxxxxx +xxxxx: │ +│ .xxxxx. :xxxxxxxxxx .xxxxx. │ +│ xxxxx+ ;xxxxxxxx +xxxxx │ +│ xxxxx +xx. xxxxx. │ +│ xxxxx: .xxxxxxxx :xxxxx │ +│ xxxxx .xxxxxxxxxx xxxxx │ +│ xxxxx xxxxxxxxxxx xxxxx │ +│ xxxxx .xxxxxxxxxx xxxxx │ +│ xxxxx: .xxxxxxxx :xxxxx │ +│ .xxxxx ;xx. ... xxxxx. │ +│ xxxxx+ :xxxxxxxx +xxxxx │ +│ .xxxxx. :xxxxxxxxxx .xxxxx. │ +│ :xxxxx+ xxxxxxxxxxx ;xxxxx: │ +│ :xxxxxx xxxxxxxxxx xxxxxx: │ +│ xxxxxxx xxxxxxx; xxxxxxx │ +│ xxxxxxxx. .xxxxxxxx │ +│ xxxxxxxxxx xxxxxxxxxx │ +│ ;xxxxxxxxxxxxxxxxxxxxxxxxxxxx+ │ +│ ;xxxxxxxxxxxxxxxxxxxxxx; │ +│ .xxxxxxxxxxxxxx. │ +│ │ +╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +You've downloaded /Users/sneridagh/.cookiecutters/cookieplone-templates before. Is it okay to delete and re-download it? [y/n] (y): +╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Volto Addon Generator ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ Creating a new Volto Addon │ +│ │ +│ Sanity check results: │ +│ │ +│ │ +│ - Node: ✓ │ +│ - git: ✓ │ +│ │ +╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + [1/8] Add-on Title (Volto Add-on): + [2/8] Add-on (Short name of the addon) (volto-addon): + [3/8] A short description of your addon (A new add-on for Volto): + [4/8] Author (Plone Community): + [5/8] Author E-mail (collective@plone.org): + [6/8] GitHub Username or Organization (collective): + [7/8] Package name on NPM (volto-addon): + [8/8] Volto version (18.0.0-alpha.46): +╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Volto Add-on generation ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ Summary: │ +│ │ +│ - Volto version: 18.0.0-alpha.46 │ +│ - Output folder: /Users/sneridagh/Development/plone/volto-addon │ +│ │ +│ │ +╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 🎉 New addon was generated 🎉 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ volto-addon │ +│ │ +│ Now, enter the generated directory and finish the install: │ +│ │ +│ cd volto-addon │ +│ make install │ +│ │ +│ start coding, and push to your organization. │ +│ │ +│ Sorry for the convenience, │ +│ The Plone Community. │ +│ │ +│ https://plone.org/ +``` + +Cookieplone has created a folder with the name of the add-on. +Change your current working directory to {file}`volto-addon`. + +```shell +cd volto-addon +``` + +To install the frontend setup, use the following command. + +```shell +make install +``` + +### Start Plone backend Docker container + +In the currently open session, issue the following command. + +```shell +make backend-docker-start +``` + +```console +❯ make backend-docker-start +==> Start Docker-based Plone Backend +======================================================================================= +Creating Plone volto SITE: Plone +Aditional profiles: +THIS IS NOT MEANT TO BE USED IN PRODUCTION +Read about it: https://6.docs.plone.org/install/containers/images/backend.html +======================================================================================= +Ignoring index for /app/var/filestorage/Data.fs +INFO:Plone Site Creation:Creating a new Plone site @ Plone +INFO:Plone Site Creation: - Using the voltolighttheme distribution and answers from /app/scripts/default.json +INFO:Plone Site Creation: - Stopping site creation, as there is already a site with id Plone at the instance. Set DELETE_EXISTING=1 to delete the existing site before creating a new one. +Using default configuration +2024-10-11 16:12:47 INFO [chameleon.config:39][MainThread] directory cache: /app/var/cache. +2024-10-11 16:12:48 INFO [plone.restapi.patches:16][MainThread] PATCH: Disabled ZPublisher.HTTPRequest.ZopeFieldStorage.VALUE_LIMIT. This enables file uploads larger than 1MB. +2024-10-11 16:12:49 INFO [plone.volto:23][MainThread] Aliasing collective.folderish classes to plone.volto classes. +2024-10-11 16:12:50 INFO [Zope:42][MainThread] Ready to handle requests +Starting server in PID 1. +2024-10-11 16:12:50 INFO [waitress:486][MainThread] Serving on http://0.0.0.0:8080 +``` + +This will start a clean Plone server for development purposes so you can start developing your add-on. + +### Start Plone development frontend + +Create a second shell session in a new window. +Change your current working directory to {file}`volto-addon`. +Start the Plone development frontend with the following command. + +```shell +make start +``` + +```console +webpack 5.90.1 compiled successfully in 11004 ms +sswp> Handling Hot Module Reloading +Using volto.config.js in: //frontend/volto.config.js +✅ Server-side HMR Enabled! +Volto is running in SEAMLESS mode +Proxying API requests from http://localhost:3000/++api++ to http://localhost:8080/Plone +🎭 Volto started at 0.0.0.0:3000 🚀 +``` + +Note that the Plone frontend uses an internal proxy server to connect with the Plone backend. +Open a browser at the following URL to visit your Plone site. + +http://localhost:3000 + +You will see a page similar to the following. + +```{image} /_static/plone-home-page.png +:alt: Plone home page +:class: figure +``` + +Your newly created add-on will be installed by default in a vanilla core Volto. +You can start developing it in the add-on package located in: {file}`packages/volto-addon`. + +You can stop the site with {kbd}`ctrl-c`. diff --git a/docs/source/addons/how-to-create-an-addon-stable.md b/docs/source/addons/how-to-create-an-addon-stable.md new file mode 100644 index 0000000000..e79ae37285 --- /dev/null +++ b/docs/source/addons/how-to-create-an-addon-stable.md @@ -0,0 +1,21 @@ +--- +myst: + html_meta: + "description": "How to create a frontend add-on" + "property=og:description": "How to create a frontend add-on" + "property=og:title": "How to create a frontend add-on" + "keywords": "add-on, Volto, create" +--- + +# How to create a frontend add-on (stable release) + +Volto add-on packages are just CommonJS packages. +The only requirement is that they point the `main` key of their `package.json` to a module that exports, as a default function that acts as a {term}`Volto configuration loader`. + +Although you could simply use `npm init` to generate an add-on initial code, we now have a nice +[Yeoman-based generator](https://github.com/plone/generator-volto) that you can use: + +```shell +npm install -g @plone/generator-volto +yo @plone/volto:addon [] [options] +``` diff --git a/docs/source/addons/how-to-extend-eslint-addon.md b/docs/source/addons/how-to-extend-eslint-addon.md new file mode 100644 index 0000000000..a98804fec3 --- /dev/null +++ b/docs/source/addons/how-to-extend-eslint-addon.md @@ -0,0 +1,41 @@ +--- +myst: + html_meta: + "description": "Extend ESlint configuration from add-ons" + "property=og:description": "Extend ESlint configuration from add-ons" + "property=og:title": "Extend ESlint configuration from add-ons" + "keywords": "Volto, add-on, extensions, frontend, Plone, configuration, ESlint, lint" +--- + +# Extending Eslint configuration from an add-on + +Starting with Volto v16.4.0, you can also customize the Eslint configuration from an add-on. +You should provide a `eslint.extend.js` file in your add-on root folder, which exports a `modify(defaultConfig)` function. +For example, to host some code outside the regular `src/` folder of your add-on, this `eslint.extend.js` file is needed: + +```js +const path = require('path'); + +module.exports = { + modify(defaultConfig) { + const aliasMap = defaultConfig.settings['import/resolver'].alias.map; + const addonPath = aliasMap.find( + ([name]) => name === '@plone-collective/some-volto-add-on', + )[1]; + + const extraPath = path.resolve(`${addonPath}/../extra`); + aliasMap.push(['@plone-collective/extra', extraPath]); + + return defaultConfig; + }, +}; +``` + +This would allow the `@plone-collective/some-volto-add-on` to host some code +outside of its normal `src/` folder, let's say in the `extra` folder, and that +code would be available under the `@plone-collective/extra` name. + +```{note} +This is taking care only of the Eslint integration. +For proper language support, you'll still need to do it in the `razzle.extend.js` of your add-on. +``` diff --git a/docs/source/addons/how-to-extend-webpack-addon.md b/docs/source/addons/how-to-extend-webpack-addon.md new file mode 100644 index 0000000000..b43c8f9095 --- /dev/null +++ b/docs/source/addons/how-to-extend-webpack-addon.md @@ -0,0 +1,50 @@ +--- +myst: + html_meta: + "description": "Extend Webpack setup from an add-on with `razzle.extend.js`" + "property=og:description": "Extend Webpack setup from an add-on with `razzle.extend.js`" + "property=og:title": "Extend Webpack setup from an add-on with `razzle.extend.js`" + "keywords": "Volto, Plone, Webpack, Volto add-on" +--- + +# Extend Webpack setup from an add-on with `razzle.extend.js` + +Just like you can extend Razzle's configuration from the project, you can do so +with an addon, as well. You should provide a `razzle.extend.js` file in your +addon root folder. Here's an example of such file, where we achieve two things: + +- we add a new webpack plugin, the + [bundle-analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer) +- we reconfigure the `theme.config` alias, to enable a custom Semantic theme inside the addon: + +```js +const analyzerPlugin = { + name: 'bundle-analyzer', + options: { + analyzerHost: '0.0.0.0', + analyzerMode: 'static', + generateStatsFile: true, + statsFilename: 'stats.json', + reportFilename: 'reports.html', + openAnalyzer: false, + }, +}; + +const plugins = (defaultPlugins) => { + return defaultPlugins.concat([analyzerPlugin]); +}; +const modify = (config, { target, dev }, webpack) => { + const themeConfigPath = `${__dirname}/theme/theme.config`; + config.resolve.alias['../../theme.config$'] = themeConfigPath; + + return config; +}; + +module.exports = { + plugins, + modify, +}; +``` + +Check +[volto-searchlib razzle.extend.js](https://github.com/eea/volto-searchlib/blob/d84fec8eec1def0088d8025eaf5d7197074b95a7/razzle.extend.js) file for an example on how to include additional paths to the Babel configuration and how to add additional webpack name aliases. diff --git a/docs/source/addons/how-to-install-an-addon-dev-prerelease.md b/docs/source/addons/how-to-install-an-addon-dev-prerelease.md new file mode 100644 index 0000000000..36f840fcd8 --- /dev/null +++ b/docs/source/addons/how-to-install-an-addon-dev-prerelease.md @@ -0,0 +1,131 @@ +--- +myst: + html_meta: + "description": "How to install a frontend add-on in development mode in your Plone project" + "property=og:description": "How to install a frontend add-on in development mode in your Plone project" + "property=og:title": "How to install a frontend add-on in development mode" + "keywords": "Volto, Plone, Volto add-on, unreleased, development" +--- + +# How to install a frontend add-on in development mode (development or pre-release) + +We use `mrs-developer` tool to manage the development cycle of Volto add-ons. +This tool help us to pull the remote code and configure the current project to have the add-on(s) available for the build. + +[Eric Brehault](https://github.com/ebrehault) ported this amazing Python tool, +which provides a way to pull a package from git and set it up as a dependency +for the current project codebase. + +To facilitate addon development lifecycle we recommend using +[mrs-developer](https://www.npmjs.com/package/mrs-developer). + +By doing this, you can develop both the project and the add-on product as if +they were both part of the current codebase. Once the add-on development is +done, you can publish the package to an npm repository. + +`mrs-developer` is included and installed by default when you generate a project with Cookieplone. +There is a `Makefile` command that installs the configuration of `mrs.developer.json` in your project. + +```shell +make install +``` + +## mrs.developer.json + +This is the configuration file that instructs `mrs-developer` from where it has +to pull the packages. +The generator includes an empty one for you, edit `mrs.developer.json` and add: + +```json +{ + "acme-volto-foo-addon": { + "output": "packages", + "package": "@acme/volto-foo-addon", + "url": "git@github.com:acme/my-volto-addon.git", + "path": "src" + } +} +``` + +Then run: + +```bash +make install +``` + +Now the addon is found in `packages/acme-volto-foo-addon`. + +```{note} +`package` property is optional, set it up only if your package has a scope (namespace). +`src` is required if the content of your addon is located in the `src` +directory (but, as that is the convention recommended for all Volto add-on +packages, you will always include it) +``` + +If you want to know more about `mrs-developer` config options, please refer to +[its npm page](https://www.npmjs.com/package/mrs-developer). + +## tsconfig.json / jsconfig.json + +Cookieplone setup does not require that `mrs-developer` tweaks the base `tsconfig.json/jsconfig.json`, since `pnpm` takes care of the resolution "the right way". +This is different from the `yarn` setup, that required it. + +### Addon development lifecycle + +If you want to "disable" using the development version of an addon, or keep +a more stable version of `mrs.developer.json` in your source code repository, +you can set its developing status by adding a `develop` key: + +```json +{ + "acme-volto-foo-addon": { + "output": "packages", + "package": "@acme/volto-foo-addon", + "url": "git@github.com:acme/my-volto-addon.git", + "path": "src", + "develop": true + } +} +``` + +You can toggle that key to `false` and run `make install` again. + +### Addon dependencies + +If your addon needs to bring in additional JavaScript package dependencies, +you'll have to declare them as normal package dependencies. + +### `pnpm` workspaces + +Your add-on needs to be detected by your setup as a workspace. +You can configure them using `pnpm-workspace.yaml` file and declare every development add-on in there. + +```yaml +packages: + - 'core/packages/*' + - 'packages/*' +``` + +If the add-on you are developing uses the `pnpm` setup as well (recommended), then you have to add: + +```yaml +packages: + - 'core/packages' + - 'packages/my-policy-addon' + - 'packages/**/packages/*' +``` + +for detecting them. +Declaring the add-ons explicitly works too: + +```yaml +packages: + - 'core/packages' + - 'packages/my-policy-addon' + - 'packages/my-development-mode-addon' + - 'packages/**/packages/*' +``` + +```{warning} +Don't forget to run `make install` after any change in this files to update the setup. +``` diff --git a/docs/source/addons/how-to-install-an-addon-dev-stable.md b/docs/source/addons/how-to-install-an-addon-dev-stable.md new file mode 100644 index 0000000000..f2a4fd975a --- /dev/null +++ b/docs/source/addons/how-to-install-an-addon-dev-stable.md @@ -0,0 +1,142 @@ +--- +myst: + html_meta: + "description": "How to install a frontend add-on in development mode in your Plone project" + "property=og:description": "How to install a frontend add-on in development mode in your Plone project" + "property=og:title": "How to install a frontend add-on in development mode" + "keywords": "Volto, Plone, Volto add-on, unreleased, development" +--- + +# How to install a frontend add-on in development mode (stable release) + +We use `mrs-developer` tool to manage the development cycle of Volto add-ons. +This tool help us to pull the remote code and configure the current project to have the add-on(s) available for the build. + +## Add `mrs-developer` dependency and related script + +[Eric Brehault](https://github.com/ebrehault) ported this amazing Python tool, +which provides a way to pull a package from git and set it up as a dependency +for the current project codebase. + +To facilitate addon development lifecycle we recommend using +[mrs-developer](https://www.npmjs.com/package/mrs-developer). + +By doing this, you can develop both the project and the add-on product as if +they were both part of the current codebase. Once the add-on development is +done, you can publish the package to an npm repository. + +`mrs-developer` is included and installed by default when you generate a project with the generator. +There is a `Makefile` command that installs the configuration of `mrs.developer.json` in your project. + +```shell +make install +``` + +## mrs.developer.json + +This is the configuration file that instructs `mrs-developer` from where it has +to pull the packages. +The generator includes an empty one for you, edit `mrs.developer.json` and add: + +```json +{ + "acme-volto-foo-addon": { + "package": "@acme/volto-foo-addon", + "url": "git@github.com:acme/my-volto-addon.git", + "path": "src" + } +} +``` + +Then run: + +```bash +make install +``` + +Now the addon is found in `src/addons/`. + +```{note} +`package` property is optional, set it up only if your package has a scope (namespace). +`src` is required if the content of your addon is located in the `src` +directory (but, as that is the convention recommended for all Volto add-on +packages, you will always include it) +``` + +If you want to know more about `mrs-developer` config options, please refer to +[its npm page](https://www.npmjs.com/package/mrs-developer). + +## tsconfig.json / jsconfig.json + +`mrs-developer` automatically manages the contents of this file for you, but if you choose not +to use mrs-developer, you'll have to add something like this to your +`tsconfig.json` or `jsconfig.json` file in the Volto project root: + +```json +{ + "compilerOptions": { + "paths": { + "acme-volto-foo-addon": [ + "addons/acme-volto-foo-addon/src" + ] + }, + "baseUrl": "src" + } +} +``` + +This is required so the project "knows" about your package in development and the imports to work correctly. + +```{warning} +Please note that both `paths` and `baseUrl` are required to match your +project layout. +``` + +```{tip} +You should use the `src` path inside your package and point the `main` key +in `package.json` to the `index.js` file in `src/index.js`. +``` + +### Addon development lifecycle + +If you want to "disable" using the development version of an addon, or keep +a more stable version of `mrs.developer.json` in your source code repository, +you can set its developing status by adding a `develop` key: + +```json +{ + "acme-volto-foo-addon": { + "package": "@acme/volto-foo-addon", + "url": "git@github.com:acme/my-volto-addon.git", + "path": "src", + "develop": true + } +} +``` + +You can toggle that key to `false` and run `make install` again. + +### Addon dependencies, yarn workspaces + +If your addon needs to bring in additional JavaScript package dependencies, +you'll have to set your addon package as a "Yarn workspace". You do this by +adding a `workspaces` key to the the `package.json` of your Volto project: + +```json +... +"workspaces": ["src/addons/my-volto-addon"], +... +``` + +It is common practice to use a star glob pattern for the workspaces: + +```json +... +"workspaces": ["src/addons/*"], +... +``` + +If you do this, make sure to always cleanup the `src/addons` folder whenever +you toggle the development status of an addon, as the existence of the addon +folder under the `src/addons` will still influence yarn. +Running `make install` again will do the trick and remove the not required anymore package. diff --git a/docs/source/addons/how-to-install-an-addon-prerelease.md b/docs/source/addons/how-to-install-an-addon-prerelease.md new file mode 100644 index 0000000000..a708383cfa --- /dev/null +++ b/docs/source/addons/how-to-install-an-addon-prerelease.md @@ -0,0 +1,51 @@ +--- +myst: + html_meta: + "description": "How to install a frontend add-on in your Volto project" + "property=og:description": "How to install a frontend add-on in your Volto project" + "property=og:title": "How to install a frontend add-on" + "keywords": "add-on, Volto, install" +--- + +# How to install an add-on in your Volto project (development or pre-release) + +You can install a Volto add-on just like any other JS package: + +```shell +pnpm --filter add +``` + +If the add-on is not published on the npm registry, [you can retrieve it directly from Github](https://pnpm.io/cli/add#install-from-git-repository): + +```shell +pnpm add collective/volto-dropdownmenu +``` + +Next, you'll need to add the add-on (identified by its JS package name) to the +`addons` key of your Volto project's `package.json`. + +```js +{ + "name": "my-nice-volto-project", + + "addons": [ + "acme-volto-foo-add-on", + "@plone/some-add-on", + "collective-another-volto-add-on" + ], + +} +``` + +```{warning} +Adding the add-on package to the `addons` key is mandatory! It allows Volto +to treat that package properly and provide it with BabelJS language +features. In Plone terminology, it is like including a Python egg to the +`zcml` section of `zc.buildout`. +``` + +```{seealso} +Alternatively, you can use `volto.config.js` to declare add-ons in your Plone project: +{doc}`../configuration/volto-config-js` +``` + diff --git a/docs/source/addons/how-to-install-an-addon-stable.md b/docs/source/addons/how-to-install-an-addon-stable.md new file mode 100644 index 0000000000..42c997faf1 --- /dev/null +++ b/docs/source/addons/how-to-install-an-addon-stable.md @@ -0,0 +1,51 @@ +--- +myst: + html_meta: + "description": "How to install a frontend add-on in your Plone project" + "property=og:description": "How to install a frontend add-on in your Plone project" + "property=og:title": "How to install a frontend add-on" + "keywords": "add-on, Volto, install" +--- + +# Configuring Volto to use a released add-on (stable release) + +You can install a Volto add-on just like any other JS package: + +```shell +yarn add name-of-add-on +``` + +If the add-on is not published on npm, you can retrieve it directly from Github: + +```shell +yarn add collective/volto-dropdownmenu +``` + +Next, you'll need to add the add-on (identified by its JS package name) to the +`addons` key of your Volto project's `package.json`. + +```js +{ + "name": "my-nice-volto-project", + + "addons": [ + "acme-volto-foo-add-on", + "@plone/some-add-on", + "collective-another-volto-add-on" + ], + +} +``` + +```{warning} +Adding the add-on package to the `addons` key is mandatory! It allows Volto +to treat that package properly and provide it with BabelJS language +features. In Plone terminology, it is like including a Python egg to the +`zcml` section of `zc.buildout`. +``` + +```{seealso} +Alternatively, you can use `volto.config.js` to declare add-ons in your Plone project: +{doc}`../configuration/volto-config-js` +``` + diff --git a/docs/source/addons/how-to-load-addon-configuration.md b/docs/source/addons/how-to-load-addon-configuration.md new file mode 100644 index 0000000000..a89decd0f2 --- /dev/null +++ b/docs/source/addons/how-to-load-addon-configuration.md @@ -0,0 +1,70 @@ +--- +myst: + html_meta: + "description": "Loading configuration from add-ons" + "property=og:description": "Loading configuration from add-ons" + "property=og:title": "Loading configuration from add-ons" + "keywords": "Volto, add-on, extensions, frontend, Plone, configuration" +--- + +# Loading configuration from add-ons + +As a convenience, an add-on can export configuration functions that can mutate, +in-place, the overall Volto {term}`configuration registry`. +An add-on can export multiple configurations methods, making it possible to selectively choose which specific add-on functionality you want to load. + +Some add-ons might choose to allow the Volto project to selectively load some of +their configuration, so they may offer additional configuration functions, +which you can load by overloading the add-on name in the `addons` package.json +key, like so: + +```{code-block} json +:emphasize-lines: 4 + +{ + "name": "my-nice-volto-project", + "addons": [ + "acme-volto-foo-add-on:loadOptionalBlocks,overrideSomeDefaultBlock", + "volto-ga" + ], +} +``` + +```{note} +The additional comma-separated names should be exported from the add-on +package's `index.js`. The main configuration function should be exported as +the default. An add-on's default configuration method will always be loaded. +``` + +If for some reason, you want to manually load the add-on, you could always do, +in your project's `config.js` module: + +```js +import loadExampleAddon, { enableOptionalBlocks } from 'volto-example-add-on'; +import * as voltoConfig from '@plone/volto/config'; + +const config = enableOptionalBlocks(loadExampleAddon(voltoConfig)); + +export blocks = { + ...config.blocks, +} +``` + +As this is a common operation, Volto provides a helper method for this: + +```js +import { applyConfig } from '@plone/volto/helpers'; +import * as voltoConfig from '@plone/volto/config'; + +const config = applyConfig([ + enableOptionalBlocks, + loadExampleAddon +], voltoConfig); + +export blocks = { + ...config.blocks, +} +``` + +The `applyConfig` helper ensures that each configuration methods returns the +config object, avoiding odd and hard to track errors when developing add-ons. diff --git a/docs/source/addons/how-to-testing-addons-prerelease.md b/docs/source/addons/how-to-testing-addons-prerelease.md new file mode 100644 index 0000000000..720464443b --- /dev/null +++ b/docs/source/addons/how-to-testing-addons-prerelease.md @@ -0,0 +1,13 @@ +--- +myst: + html_meta: + "description": "Testing add-ons (development or pre-release)" + "property=og:description": "Testing add-ons (development or pre-release)" + "property=og:title": "Testing add-ons (development or pre-release)" + "keywords": "Volto, Plone, Testing, CI, Add-ons" +--- + +# Testing add-ons (development or pre-release) + +% I have doubts if include this in a section on its own in "Development". +% In that section we would depict all the development process as a whole. diff --git a/docs/source/addons/how-to-testing-addons.md b/docs/source/addons/how-to-testing-addons.md new file mode 100644 index 0000000000..19373ea9c3 --- /dev/null +++ b/docs/source/addons/how-to-testing-addons.md @@ -0,0 +1,105 @@ +--- +myst: + html_meta: + "description": "Testing add-ons" + "property=og:description": "Testing add-ons" + "property=og:title": "Testing add-ons" + "keywords": "Volto, Plone, Testing, CI, Add-ons" +--- + +# Testing add-ons (stable release) + +We should let jest know about our aliases and make them available to it to resolve them, so in `package.json`: + +```{code-block} json +:emphasize-lines: 6 + + "jest": { + "moduleNameMapper": { + "@plone/volto/(.*)$": "/node_modules/@plone/volto/src/$1", + "@package/(.*)$": "/src/$1", + "@plone/some-volto-addon/(.*)$": "/src/addons/@plone/some-volto-addon/src/$1", + "my-volto-addon/(.*)$": "/src/addons/my-volto-addon/src/$1", + "~/(.*)$": "/src/$1" + }, +``` + +You can use `yarn test src/addons/addon-name` to run tests. + +## Jest configuration override + +In CI or for testing addons, it's interesting to provide an alternate Jest configuration +or slightly modify it. +Volto provide a way to do it using a `jest.config.js` file or pointing the test runner to a file of your choice, using `RAZZLE_JEST_CONFIG` environment variable. + +```shell +RAZZLE_JEST_CONFIG=my-custom-jest-config.js yarn test +``` + +```{note} +Both configurations are merged in a way that the keys of the config provided override the initial (`package.json`) default config, either in Volto or in your projects. +``` + +This is specially useful in CI while developing add-ons, so you can pass a specific configuration that deals with the add-on config properly. + +## Testing add-ons in isolation + +Testing an add-on in isolation as you would do when you develop a Plone Python backend add-on can be a bit challenging, since an add-on needs a working project in order to bootstrap itself. +The latest generator has the boilerplate needed in order to bootstrap a dockerized environment where you can run any test to your add-on. + +### Setup the environment + +Run once + +```shell +make dev +``` + +### Build the containers manually + +Run + +```shell +make build-backend +make build-addon +``` + +### Unit tests + +Run + +```shell +make test +``` + +### Acceptance tests + +Run once + +```shell +make install-acceptance +``` + +For starting the servers + +Run + +```shell +make start-test-acceptance-server +``` + +The frontend is run in dev mode, so development while writing tests is possible. + +Run + +```shell +make test-acceptance +``` + +To run Cypress tests afterwards. + +When finished, don't forget to shutdown the backend server. + +```shell +make stop-test-acceptance-server +``` diff --git a/docs/source/addons/how-to-troubleshoot-transpilation.md b/docs/source/addons/how-to-troubleshoot-transpilation.md new file mode 100644 index 0000000000..6e0404cf58 --- /dev/null +++ b/docs/source/addons/how-to-troubleshoot-transpilation.md @@ -0,0 +1,82 @@ +--- +myst: + html_meta: + "description": "Problems with untranspiled add-on dependencies" + "property=og:description": "Problems with untranspiled add-on dependencies" + "property=og:title": "Problems with untranspiled add-on dependencies" + "keywords": "Volto, add-on, extensions, frontend, Plone, configuration" +--- + +# Problems with untranspiled add-on dependencies + +```{note} +From Volto 18, the Babel support for ES specifications like the null coalescence operator has improved. +However, this procedure can be useful in other scenarios. +We are keeping it as reference. +``` + +When using external add-ons in your project, sometimes you will run into add-ons +that are not securely transpiled or haven't been transpiled at all. In that case +you might see an error like the following: + +```console +Module parse failed: Unexpected token (10:41) in @react-leaflet/core/esm/path.js +... +const options = props.pathOptions ?? {}; +... +``` + +Babel automatically transpiles the code in your add-on, but `node_modules` are +excluded from this process, so we need to include the add-on path in the list of +modules to be transpiled. This can be accomplished by customizing the webpack +configuration in the `razzle.config.js` file in your add-on. For example, +suppose that we want to use react-leaflet, which has a known transpilation +issue: + +```js +const path = require('path'); +const makeLoaderFinder = require('razzle-dev-utils/makeLoaderFinder'); + +const babelLoaderFinder = makeLoaderFinder('babel-loader'); + +const jsConfig = require('./jsconfig').compilerOptions; + +const pathsConfig = jsConfig.paths; +let voltoPath = './node_modules/@plone/volto'; +Object.keys(pathsConfig).forEach((pkg) => { + if (pkg === '@plone/volto') { + voltoPath = `./${jsConfig.baseUrl}/${pathsConfig[pkg][0]}`; + } +}); + +const { modifyWebpackConfig, plugins } = require(`${voltoPath}/razzle.config`); + +const customModifyWebpackConfig = ({ env, webpackConfig, webpackObject, options }) => { + const config = modifyWebpackConfig({ + env, + webpackConfig, + webpackObject, + options, + }); + const babelLoader = config.module.rules.find(babelLoaderFinder); + const { include } = babelLoader; + const corePath = path.join( + path.dirname(require.resolve('@react-leaflet/core')), + '..', + ); + const esmPath = path.join( + path.dirname(require.resolve('react-leaflet')), + '..', + ); + + include.push(corePath); + include.push(esmPath); + return config; +}; + +module.exports = { modifyWebpackConfig: customModifyWebpackConfig, plugins }; +``` + +First we need some setup to get the webpack configuration from Volto's configuration. +Once we have that, we need to resolve the path to the desired add-ons and push it +into the Babel loader include list. After this, the add-ons will load correctly. diff --git a/docs/source/addons/index.md b/docs/source/addons/index.md index ed13a38d17..c8de7c3695 100644 --- a/docs/source/addons/index.md +++ b/docs/source/addons/index.md @@ -7,538 +7,77 @@ myst: "keywords": "Volto, add-on, extensions, frontend, Plone" --- +%TODO: This is Explanation # Volto add-ons ```{toctree} :maxdepth: 1 +how-an-add-on-works +addon-configuration-pipeline +how-to-install-an-addon-stable +how-to-install-an-addon-prerelease +how-to-install-an-addon-dev-stable +how-to-install-an-addon-dev-prerelease +how-to-load-addon-configuration +how-to-create-an-addon-stable +how-to-create-an-addon-prerelease +how-to-testing-addons +how-to-extend-webpack-addon +how-to-extend-eslint-addon +how-to-troubleshoot-transpilation i18n best-practices theme public-folder ``` -There are several advanced scenarios where we might want to have more control -and flexibility beyond using the plain Volto project to build a site. - -We can build Volto {term}`add-on` products and make them available as generic -JavaScript packages that can be included in any Volto project. By doing so we -can provide code and component reutilization across projects and, of course, -benefit from open source collaboration. - -```{note} -By declaring a JavaScript package as a Volto add-on, Volto provides -several integration features: language features (so they can be transpiled -by Babel), whole-process customization via razzle.extend.js and -integration with Volto's {term}`configuration registry`. -``` - -The add-on can be published to an npm registry or directly installed from github -by Yarn. By using [mrs-develop](https://github.com/collective/mrs-developer), -it's possible to have a workflow similar to zc.buildout's mr.developer, where -you can "checkout" an add-on for development. - -An add-on can be almost anything that a Volto project can be. They can: - -- provide additional views and blocks -- override or extend Volto's builtin views, blocks, settings -- shadow (customize) Volto's (or another add-on's) modules -- register custom routes -- provide custom {term}`Redux` actions and reducers -- register custom Express middleware for Volto's server process -- tweak Volto's Webpack configuration, load custom Razzle and Webpack plugins -- even provide a custom theme, just like a regular Volto project does. - -## Configuring a Volto project to use an add-on - -You can install a Volto add-on just like any other JS package: - -```shell -yarn add name-of-add-on +```{include} ./what-is-an-addon.md ``` -If the add-on is not published on npm, you can retrieve it directly from Github: - -```shell -yarn add collective/volto-dropdownmenu -``` - -Next, you'll need to add the add-on (identified by its JS package name) to the -`addons` key of your Volto project's `package.json`. More details in the next -section. - -### Loading add-on configuration - -As a convenience, an add-on can export configuration functions that can mutate, -in-place, the overall Volto {term}`configuration registry`. An add-on can export multiple -configurations methods, making it possible to selectively choose which specific -add-on functionality you want to load. - -In your Volto project's ``package.json`` you can allow the add-on to alter the -global configuration by adding, in the `addons` key, a list of Volto add-on -package names, like: - -```js -{ - "name": "my-nice-volto-project", +## Configuring Volto to use a frontend add-on - "addons": [ - "acme-volto-foo-add-on", - "@plone/some-add-on", - "collective-another-volto-add-on" - ], +Volto can be configured to use multiple add-ons. +You have to install the code and then enable the add-on in the configuration. +You can install the code by installing a released add-on package in the setup, or use a development add-on package. +A development add-on package can be either local to the Plone project or as a repo checkout. -} -``` +### Installing a released add-on -```{warning} -Adding the add-on package to the `addons` key is mandatory! It allows Volto -to treat that package properly and provide it with BabelJS language -features. In Plone terminology, it is like including a Python egg to the -`zcml` section of zc.buildout. -``` +This is the most common use case, you want to install a Volto add-on that has been released as a in npm registry (or alike). +These are the instructions for the stable release. -Some add-ons might choose to allow the Volto project to selectively load some of -their configuration, so they may offer additional configuration functions, -which you can load by overloading the add-on name in the `addons` package.json -key, like so: +{doc}`./how-to-install-an-addon-stable` -```{code-block} json -:emphasize-lines: 4 +### Installing a released add-on (development or pre-release) -{ - "name": "my-nice-volto-project", - "addons": [ - "acme-volto-foo-add-on:loadOptionalBlocks,overrideSomeDefaultBlock", - "volto-ga" - ], -} -``` +These are the instructions for the development or pre-release. -```{note} -The additional comma-separated names should be exported from the add-on -package's ``index.js``. The main configuration function should be exported as -the default. An add-on's default configuration method will always be loaded. -``` +{doc}`./how-to-install-an-addon-prerelease` -If for some reason, you want to manually load the add-on, you could always do, -in your project's ``config.js`` module: +### Installing an add-on in development mode -```js -import loadExampleAddon, { enableOptionalBlocks } from 'volto-example-add-on'; -import * as voltoConfig from '@plone/volto/config'; +It is also usual that you develop an add-on at the same time that you are developing a project. +These are the instructions for the stable release. -const config = enableOptionalBlocks(loadExampleAddon(voltoConfig)); +{doc}`./how-to-install-an-addon-dev-stable` -export blocks = { - ...config.blocks, -} -``` +### Installing an add-on in development mode (development or pre-release) -As this is a common operation, Volto provides a helper method for this: +{doc}`./how-to-install-an-addon-dev-prerelease` -```js -import { applyConfig } from '@plone/volto/helpers'; -import * as voltoConfig from '@plone/volto/config'; - -const config = applyConfig([ - enableOptionalBlocks, - loadExampleAddon -], voltoConfig); +### Loading add-on configuration -export blocks = { - ...config.blocks, -} -``` +Add-ons can provide configuration: -The `applyConfig` helper ensures that each configuration methods returns the -config object, avoiding odd and hard to track errors when developing add-ons. +{doc}`./how-to-load-addon-configuration` ## Creating add-ons -Volto add-on packages are just CommonJS packages. The only requirement is that -they point the `main` key of their `package.json` to a module that exports, as -a default function that acts as a {term}`Volto configuration loader`. - -Although you could simply use `npm init` to generate an add-on initial code, -we now have a nice -[Yeoman-based generator](https://github.com/plone/generator-volto) that you can use: - -```shell -npm install -g @plone/generator-volto -yo @plone/volto:addon [] [options] -``` - -Volto will automatically provide aliases for your (unreleased) package, so that -once you've released it, you don't need to change import paths, since you can -use the final ones from the very beginning. This means that you can use imports -such as `import { Something } from '@plone/my-volto-add-on'` without any extra -configuration. - -### Use mrs-developer to manage the development cycle - -#### Add mrs-developer dependency and related script - -[Eric Brehault](https://github.com/ebrehault) ported this amazing Python tool, -which provides a way to pull a package from git and set it up as a dependency -for the current project codebase. - -To facilitate add-on development lifecycle we recommend using -[mrs-developer](https://www.npmjs.com/package/mrs-developer). - -By doing this, you can develop both the project and the add-on product as if -they were both part of the current codebase. Once the add-on development is -done, you can publish the package to an npm repository. - -```shell -yarn add mrs-developer -``` - -Then, in `package.json`: - -```{code-block} json -:emphasize-lines: 2 -"scripts": { - "develop": "missdev --config=jsconfig.json --output=addons", -} -``` - -We can configure `mrs-developer` to use any directory that you want. Here we -are telling it to create the directory `src/addons` and put the packages -managed by `mrs-developer` inside. - -#### mrs.developer.json - -This is the configuration file that instructs `mrs-developer` from where it has -to pull the packages. So, create `mrs.developer.json` and add: - -```json -{ - "acme-volto-foo-add-on": { - "package": "@acme/volto-foo-add-on", - "url": "git@github.com:acme/my-volto-add-on.git", - "path": "src" - } -} -``` - -Then run: - -```shell -make develop -``` - -Now the add-on is found in `src/addons/`. - -```{note} -`package` property is optional, set it up only if your package has a scope. -`src` is required if the content of your add-on is located in the `src` -directory (but, as that is the convention recommended for all Volto add-on -packages, you will always include it) -``` - -If you want to know more about `mrs-developer` config options, please refer to -[its npm page](https://www.npmjs.com/package/mrs-developer). - -#### tsconfig.json / jsconfig.json - -`mrs-developer` automatically creates this file for you, but if you choose not -to use mrs-developer, you'll have to add something like this to your -`tsconfig.json` or `jsconfig.json` file in the Volto project root: - -```json -{ - "compilerOptions": { - "paths": { - "acme-volto-foo-add-on": [ - "addons/acme-volto-foo-add-on/src" - ] - }, - "baseUrl": "src" - } -} -``` - -```{warning} -Please note that both `paths` and `baseUrl` are required to match your -project layout. -``` - -```{tip} -You should use the `src` path inside your package and point the `main` key -in `package.json` to the `index.js` file in `src/index.js`. -``` - -### Customizations - -add-on packages can include customization folders, just like the Volto projects. -The customizations are resolved in the order: add-ons (as sorted in the `addons` -key of your project's `package.json`) then the customizations in the Volto -project, last one wins. - -```{tip} -See the {ref}`advanced-customization-scenarios-label` -section on how to enhance this pattern and how to include customizations -inside add-ons. -``` - -### Providing add-on configuration - -The default export of your add-on main `index.js` file should be a function with -the signature ``config => config``. -That is, it should take the ``global`` configuration object and return it, -possibly mutated or changed. So your main `index.js` will look like: - -```js -export default function applyConfig(config) { - config.blocks.blocksConfig.faq_viewer = { - id: 'faq_viewer', - title: 'FAQ Viewer', - edit: FAQBlockEdit, - view: FAQBlockView, - icon: chartIcon, - group: 'common', - restricted: false, - mostUsed: true, - sidebarTab: 1, - }; - return config; -} -``` - -And the `package.json` file of your add-on: - -```json -{ - "main": "src/index.js", -} -``` - -```{warning} -An add-on's default configuration method will always be loaded. -``` - -#### Multiple add-on configurations - -You can export additional configuration functions from your add-on's main -`index.js`. - -```js -import applyConfig, {loadOptionalBlocks,overrideSomeDefaultBlock} from './config'; - -export { loadOptionalBlocks, overrideSomeDefaultBlock }; -export default applyConfig; -``` - -## Add third-party dependencies to your add-on - -If you're developing the add-on and you wish to add an external dependency, you'll have to switch your project to be a [Yarn Workspaces root](https://yarnpkg.com/features/workspaces). - -So you'll need to add, in your Volto project's `package.json`: - -```json -"private": true, -"workspaces": [], -``` - -Then populate the `workspaces` key with the path to your development add-ons: - -```json -"workspaces": [ - "src/addons/my-volto-add-on" -] -``` -You'll have to manage the add-on dependencies via the workspace root (your Volto -project). For example, to add a new dependency: - -```shell -yarn workspace @plone/my-volto-add-on add some-third-party-package -``` - -You can run `yarn workspaces info` to see a list of workspaces defined. - -In case you want to add new dependencies to the Volto project, now you'll have -to run the `yarn add` command with the `-W` switch: - -```shell -yarn add -W some-dependency -``` - -## Extending Razzle from an add-on - -Just like you can extend Razzle's configuration from the project, you can do so -with an add-on, as well. You should provide a `razzle.extend.js` file in your -add-on root folder. An example of such file where the theme.config alias is -changed, to enable a custom Semantic theme inside the add-on: - - -```js -const plugins = (defaultPlugins) => { - return defaultPlugins; -}; -const modify = (config, { target, dev }, webpack) => { - const themeConfigPath = `${__dirname}/theme/theme.config`; - config.resolve.alias['../../theme.config$'] = themeConfigPath; - - return config; -}; - -module.exports = { - plugins, - modify, -}; -``` - -## Extending Eslint configuration from an add-on - -Starting with Volto v16.4.0, you can also customize the Eslint configuration -from an add-on. You should provide a `eslint.extend.js` file in your -add-on root folder, which exports a `modify(defaultConfig)` function. For -example, to host some code outside the regular `src/` folder of your add-on, -this `eslint.extend.js` file is needed: - -```js -const path = require('path'); - -module.exports = { - modify(defaultConfig) { - const aliasMap = defaultConfig.settings['import/resolver'].alias.map; - const addonPath = aliasMap.find( - ([name]) => name === '@plone-collective/some-volto-add-on', - )[1]; - - const extraPath = path.resolve(`${addonPath}/../extra`); - aliasMap.push(['@plone-collective/extra', extraPath]); - - return defaultConfig; - }, -}; -``` - -This would allow the `@plone-collective/some-volto-add-on` to host some code -outside of its normal `src/` folder, let's say in the `extra` folder, and that -code would be available under the `@plone-collective/extra` name. Note: this is -taking care only of the Eslint integration. For proper language support, you'll -still need to do it in the `razzle.extend.js` of your add-on. - -## add-on dependencies - -Sometimes your add-on depends on another add-on. You can declare add-on dependency -in your add-on's `addons` key, just like you do in your project. By doing so, -that other add-on's configuration loader is executed first, so you can depend on -the configuration being already applied. Another benefit is that you'll have -to declare only the "top level" add-on in your project, the dependencies will be -discovered and automatically treated as Volto add-ons. For example, `volto-slate` -depends on `volto-object-widget`'s configuration being already applied, so -`volto-slate` can declare in its `package.json`: - -```json -{ - "name": "volto-slate", - - "addons": ["@eeacms/volto-object-widget"] -} -``` - -And of course, the dependency add-on can depend, on its turn, on other add-ons -which will be loaded as well. Circular dependencies should be avoided. - -## Problems with untranspiled add-on dependencies - -When using external add-ons in your project, sometimes you will run into add-ons -that are not securely transpiled or haven't been transpiled at all. In that case -you might see an error like the following: - -```console -Module parse failed: Unexpected token (10:41) in @react-leaflet/core/esm/path.js -... -const options = props.pathOptions ?? {}; -... -``` - -Babel automatically transpiles the code in your add-on, but `node_modules` are -excluded from this process, so we need to include the add-on path in the list of -modules to be transpiled. This can be accomplished by customizing the webpack -configuration in the `razzle.config.js` file in your add-on. For example, -suppose that we want to use react-leaflet, which has a known transpilation -issue: - -```js -const path = require('path'); -const makeLoaderFinder = require('razzle-dev-utils/makeLoaderFinder'); - -const babelLoaderFinder = makeLoaderFinder('babel-loader'); - -const jsConfig = require('./jsconfig').compilerOptions; - -const pathsConfig = jsConfig.paths; -let voltoPath = './node_modules/@plone/volto'; -Object.keys(pathsConfig).forEach((pkg) => { - if (pkg === '@plone/volto') { - voltoPath = `./${jsConfig.baseUrl}/${pathsConfig[pkg][0]}`; - } -}); - -const { modifyWebpackConfig, plugins } = require(`${voltoPath}/razzle.config`); - -const customModifyWebpackConfig = ({ env, webpackConfig, webpackObject, options }) => { - const config = modifyWebpackConfig({ - env, - webpackConfig, - webpackObject, - options, - }); - const babelLoader = config.module.rules.find(babelLoaderFinder); - const { include } = babelLoader; - const corePath = path.join( - path.dirname(require.resolve('@react-leaflet/core')), - '..', - ); - const esmPath = path.join( - path.dirname(require.resolve('react-leaflet')), - '..', - ); - - include.push(corePath); - include.push(esmPath); - return config; -}; - -module.exports = { modifyWebpackConfig: customModifyWebpackConfig, plugins }; -``` - -First we need some setup to get the webpack configuration from Volto's configuration. -Once we have that, we need to resolve the path to the desired add-ons and push it -into the Babel loader include list. After this, the add-ons will load correctly. - -## Testing add-ons - -We should let jest know about our aliases and make them available to it to -resolve them, so in `package.json`: - -```{code-block} json -:emphasize-lines: 6 - -"jest": { - "moduleNameMapper": { - "@plone/volto/(.*)$": "/node_modules/@plone/volto/src/$1", - "@package/(.*)$": "/src/$1", - "@plone/some-volto-add-on/(.*)$": "/src/addons/@plone/some-volto-add-on/src/$1", - "my-volto-add-on/(.*)$": "/src/addons/my-volto-add-on/src/$1", - "~/(.*)$": "/src/$1" - }, -``` - -```{tip} -We're in the process of moving the default scaffolding generators to -provide a `jest.config.js` file in Volto, making this step unneeded. -``` +In case you want to create your own add-on, here you can find the instructions: -You can use `yarn test src/addons/add-on-name` to run tests. +{doc}`./how-to-create-an-addon-stable` -## Code linting +### Creating add-ons (development or pre-release) -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 {doc}`../upgrade-guide/index`. +{doc}`./how-to-create-an-addon-prerelease` diff --git a/docs/source/addons/public-folder.md b/docs/source/addons/public-folder.md index 188a9e155a..09409dd167 100644 --- a/docs/source/addons/public-folder.md +++ b/docs/source/addons/public-folder.md @@ -7,7 +7,7 @@ myst: "keywords": "Volto, Plone, Semantic UI, CSS, Volto theme, add-on, static, assets, files, build" --- -# Add static files from your add-on to your build +# How to add static files from your add-on to your build In the Volto build process, you can add static files to your build, then serve them along with the compiled files. Static files are not transformed or compiled by the build process. diff --git a/docs/source/addons/what-is-an-addon.md b/docs/source/addons/what-is-an-addon.md new file mode 100644 index 0000000000..5c97cca7a1 --- /dev/null +++ b/docs/source/addons/what-is-an-addon.md @@ -0,0 +1,45 @@ +--- +myst: + html_meta: + "description": "What is a Volto add-on" + "property=og:description": "What is a Volto add-on" + "property=og:title": "What is a Volto add-on" + "keywords": "add-on, Volto, create" +--- +%Explanation +# What is a Volto add-on + +There are several advanced scenarios where we might want to have more control and flexibility beyond using the plain Volto project to build a site. + +We can build Volto {term}`add-on` products and make them available as generic JavaScript packages that can be included in any Volto project. +By doing so we can provide code and component reutilization across projects and, of course, benefit from open source collaboration. + +Volto add-on packages are just CommonJS/ESM packages. +The only requirement is that they point the `main` key of their `package.json` to a module that exports, as a default function that acts as a {term}`Volto configuration loader`. + +Volto will automatically provide aliases for your (unreleased) package, so that +once you've released it, you don't need to change import paths, since you can +use the final ones from the very beginning. This means that you can use imports +such as `import { Something } from '@plone/my-volto-add-on'` without any extra +configuration. + +```{note} +By declaring a JavaScript package as a Volto add-on, Volto provides +several integration features: language features (so they can be transpiled +by Babel), whole-process customization via razzle.extend.js and +integration with Volto's {term}`configuration registry`. +``` + +The add-on can be published to an npm registry or directly installed from github by the package manager. +By using [mrs-developer](https://github.com/collective/mrs-developer), it's possible to have a workflow similar to `zc.buildout`'s `mr.developer`, where you can "checkout" an add-on for development. + +An add-on can be almost anything that a Volto project can be. They can: + +- provide additional views and blocks +- override or extend Volto's builtin views, blocks, settings +- shadow (customize) Volto's (or another add-on's) modules +- register custom routes +- provide custom {term}`Redux` actions and reducers +- register custom Express middleware for Volto's server process +- tweak Volto's Webpack configuration, load custom Razzle and Webpack plugins +- even provide a custom theme, just like a regular Volto project does.