diff --git a/docs/source/_static/plone-home-page.png b/docs/source/_static/plone-home-page.png new file mode 100644 index 0000000000..b2321e0eaf Binary files /dev/null and b/docs/source/_static/plone-home-page.png differ diff --git a/docs/source/addons/best-practices.md b/docs/source/addons/best-practices.md deleted file mode 100644 index 23545d628a..0000000000 --- a/docs/source/addons/best-practices.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -myst: - html_meta: - "description": "Integrate your add-on with Volto's add-on framework" - "property=og:description": "Integrate your add-on with Volto's add-on framework" - "property=og:title": "Best practices for add-ons" - "keywords": "Volto, Plone, frontend, React, best practices, add-ons" ---- - -# Best practices for add-ons - -Although the add-on framework is relatively new in Volto's world, there are -quite a few generic add-ons that can be used in any Volto project. - -Based on the experience gained developing some of these add-ons, we -recommend that you follow (no need for strictness, of course) these rough -guidelines: - -## Integrate your add-on with Volto's add-on framework - -Just like Plone add-ons provide some features by default, Volto add-ons should -register some features by default. For example, if your add-on provides widgets, -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` {term}`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 -provide a palette of colors from which to choose. The widget should integrate -with a default `settings.colorWidgetPalette`, which would be a list of colors. - -And of course, also provide a widget factory so it can be used to create -multiple instances of that color widget with custom color palettes. - -### Provide additional configuration - -An add-on can ship with multiple {term}`Volto configuration loader`s. This makes it -possible to provide configuration methods for demo purposes, for example, or to -ship with a default "shallow" integration, then provide another separate -configuration loader for a deeper integration. - -## Avoid shadowing Volto files - -This rule is meant to be broken. If you find that you need to customize -a particular file from Volto and you have multiple projects, better to create -an add-on that holds that customized file, so that you have a single place to -maintain that "file fork", but otherwise it's a good idea to avoid shipping -generic add-ons with Volto customizations. Make sure to include this information -as a warning in your add-on description! - -See if your use case is generic enough, maybe Volto needs to be extended to -cover that use case, directly in core. - -## Minimal documentation - -Deadlines can be rough and documentation tends to be pushed as last priority, -but please add a minimal Readme with a couple of lines and, most importantly, -a screenshot. - -Ideally, the Readme should also include install instructions and details on any -possible settings. - - -(testing-the-add-on-label)= - -## Testing the add-on - -It is not easy, right now, to ship an add-on with a self-bootstraping and -testing framework. But you can create a separate minimal Volto project that can -hold the Cypress integration tests and trigger the CI tests. - -## Use appropriate npmjs tags - -If you're releasing your add-on to `npmjs.com`, please consider adding the -following tags, next to your add-on-specific tags: - -- `volto-addon` -- `volto` -- `plone` -- `react` - -## Include in `collective/awesome-volto` - -Even if you think your add-on is not generic or it's tricky to integrate, please -consider including your add-on in the -[`collective/awesome-volto`](https://github.com/collective/awesome-volto) add-ons -list. This provides visibility to your add-on but also further solidifies -Volto's position in our Plone community. diff --git a/docs/source/addons/index.md b/docs/source/addons/index.md deleted file mode 100644 index ed13a38d17..0000000000 --- a/docs/source/addons/index.md +++ /dev/null @@ -1,544 +0,0 @@ ---- -myst: - html_meta: - "description": "Volto add-ons extend the core functionality of the Plone CMS frontend." - "property=og:description": "Volto add-ons extend the core functionality of the Plone CMS frontend." - "property=og:title": "Volto add-ons" - "keywords": "Volto, add-on, extensions, frontend, Plone" ---- - -# Volto add-ons - -```{toctree} -:maxdepth: 1 - -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 -``` - -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", - - "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. -``` - -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. - -## 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. -``` - -You can use `yarn test src/addons/add-on-name` to run tests. - -## Code linting - -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`. diff --git a/docs/source/addons/theme.md b/docs/source/addons/theme.md deleted file mode 100644 index 99251fd370..0000000000 --- a/docs/source/addons/theme.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -myst: - html_meta: - "description": "Create a theme add-on" - "property=og:description": "Create a theme add-on" - "property=og:title": "Create a theme add-on" - "keywords": "Volto, Plone, Semantic UI, CSS, Volto theme" ---- - -# Create a Volto theme add-on - -We can create a Volto Add-on that acts as a Volto theme Add-on, so we can detach it from the project files. -The advantage is that you convert the project Volto theme in a pluggable one, so you can deploy the same theme in different projects. -You can even have themes depending on conditions that you could inject on build time. -This is the purpose of `volto.config.js`, the ability of declaring `add-ons` and the active `theme` programmatically. See {ref}`volto-config-js` for more information. -For convenience, it can also be set via a `THEME` environment variable. - -1. Add a `theme` key in your `volto.config.js` file in the root of your project: - -```js -module.exports = { - addons: [], - theme: 'volto-my-theme' -}; -``` - -or add a key in your `package.json` project: - -```json -"theme": "volto-my-theme" -``` - -or via a `THEME` variable: - -```shell -THEME='volto-my-theme' pnpm start -``` - -2. Create a directory `src/theme` in your add-on, then add this file `theme.config`, replacing `` with your add-on name: - -```less -/******************************* - Theme Selection -*******************************/ - -/* To override a theme for an individual element specify theme name below */ - -/* Global */ -@site : 'pastanaga'; -@reset : 'pastanaga'; - -/* Elements */ -@button : 'pastanaga'; -@container : 'pastanaga'; -@divider : 'pastanaga'; -@flag : 'pastanaga'; -@header : 'pastanaga'; -@icon : 'pastanaga'; -@image : 'pastanaga'; -@input : 'pastanaga'; -@label : 'pastanaga'; -@list : 'pastanaga'; -@loader : 'pastanaga'; -@placeholder : 'pastanaga'; -@rail : 'pastanaga'; -@reveal : 'pastanaga'; -@segment : 'pastanaga'; -@step : 'pastanaga'; - -/* Collections */ -@breadcrumb : 'pastanaga'; -@form : 'pastanaga'; -@grid : 'pastanaga'; -@menu : 'pastanaga'; -@message : 'pastanaga'; -@table : 'pastanaga'; - -/* Modules */ -@accordion : 'pastanaga'; -@checkbox : 'pastanaga'; -@dimmer : 'pastanaga'; -@dropdown : 'pastanaga'; -@embed : 'pastanaga'; -@modal : 'pastanaga'; -@nag : 'pastanaga'; -@popup : 'pastanaga'; -@progress : 'pastanaga'; -@rating : 'pastanaga'; -@search : 'pastanaga'; -@shape : 'pastanaga'; -@sidebar : 'pastanaga'; -@sticky : 'pastanaga'; -@tab : 'pastanaga'; -@transition : 'pastanaga'; - -/* Views */ -@ad : 'pastanaga'; -@card : 'pastanaga'; -@comment : 'pastanaga'; -@feed : 'pastanaga'; -@item : 'pastanaga'; -@statistic : 'pastanaga'; - -/* Extras */ -@main : 'pastanaga'; -@custom : 'pastanaga'; - -/******************************* - Folders -*******************************/ - -/* Path to theme packages */ -@themesFolder : '~volto-themes'; - -/* Path to site override folder */ -@siteFolder : "/theme"; - -/******************************* - Import Theme -*******************************/ - -@import (multiple) "~semantic-ui-less/theme.less"; -@fontPath : "~volto-themes/@{theme}/assets/fonts"; - -.loadAddonOverrides() { - @import (optional) "@{siteFolder}/@{addon}/@{addontype}s/@{addonelement}.overrides"; -} - -/* End Config */ -``` - -3. Declare the theme as an add-on by adding its name to the value for the `addons` key in either `volto.config.js` or `package.json` of your project. -4. After starting Volto, the theme should be active. - Now you can add overrides to the default theme in `src/theme`, same as you would in a project. -5. Now you can safely delete your project's `theme` folder, since the one in the add-on will take precedence and a project can only have one active theme at a time. - -## Using your own theming escape hatch - -Volto theming uses Semantic UI theming capabilities to define and extend a theme for your site. -However, while maintaining and playing well with the Semantic UI Volto base, using a traditional CSS approach can be done using the LESS preprocessor-based `extras` escape hatch. - -At the same time, one can either discard or complement the extras escape hatch and add your own, by customizing the `theme.js` module in Volto. - -```js -import 'semantic-ui-less/semantic.less'; -import '@plone/volto/../theme/themes/pastanaga/extras/extras.less'; - -// You can add more entry points for theming -import '@kitconcept/volto-light-theme/theme/main.scss'; -``` - -Customizing it is a special use case in Volto: add a `./@root/theme.js` file structure in your `customizations` folder in your add-on or project. - -You may want to do this to create a complete new theming experience adapted to your way of doing things that do not match the current Volto theming experience. -For example, if you want to use another preprocessor in the theme, like SCSS. -Maybe because your client forces you to have another entirely base of pre-made components based on another library other than Semantic UI: -See {ref}`volto-custom-theming-strategy` for an example of a custom theme escape hatch. - -While building your own escape hatch for theming, you can use the preprocessor of your choice (in the example, SCSS) while maintaining the "base" Volto theme, but customizing it using the resultant CSS. - -You can see an example of such a theme in: https://github.com/kitconcept/volto-light-theme - -## Modify a custom theme from another add-on - -Sometimes you have a custom theme that you want to reuse through all your projects, but with some differences, maintaining the base. -Usually, the only option would be to use an add-on that adds more CSS to the base theme, using imports that will load after the theme. -However, there is a problem with this approach. -You cannot use existing theme variables, including breakpoints, on these new styles. -Similarly, it gets somewhat detached from the normal flow of the loaded theme. -The same applies for add-ons, as they are detached from the current theme. -One could use a Semantic UI approach for making this work, but it's Semantic UI bound. - -```{warning} -This is only possible when using your own escape hatch, and works only with SCSS-based themes, and not with Semantic UI themes, since it enables a couple of entry points that only support SCSS files. -For an example of how it could be used, see: https://github.com/kitconcept/volto-light-theme -``` - -If your custom escape hatch defines a custom theme using SCSS, you can take advantage of this feature. -Although not limited to this, it would be possible to extend this feature to add more entry points, using another preprocessor or theming approach. - -This feature enables two entry points: variables and main. -From your add-on code, you can extend an existing theme by creating a file corresponding to each entry point: - -* `./src/theme/_variables.scss` -* `./src/theme/_main.scss` - -### Variables (`addonsThemeCustomizationsVariables`) - -Use this entry point file to modify the original variables of the current loaded theme by adding the entry point before the theme variable definitions. -In the theme, it should be imported as shown below: - -```scss hl_lines="2" -@import 'addonsThemeCustomizationsVariables'; -@import 'variables'; -@import 'typography'; -@import 'utils'; -@import 'layout'; -``` - -```{warning} -Following SCSS best practices, your theme variables should be "overridable" using the `!default` flag. -This assigns a value to a variable _only_ if that variable isn't defined or its value is [`null`](https://sass-lang.com/documentation/values/null). -Otherwise, the existing value will be used. -For more information, see https://sass-lang.com/documentation/variables#default-values -``` - -Volto will not only load your add-on entry point files, but it will also detect all the add-ons that have these entry point files and import them grouped under a single file. -It will also automatically add an `addonsThemeCustomizationsVariables` alias that can be referenced from the theme as shown above. - -### Main (`addonsThemeCustomizationsMain`) - -This entry point is intended to add your own style definitions, complementing those in the theme. -You should add it after all the CSS of your theme: - -```scss hl_lines="6" -@import 'blocks/search'; -@import 'blocks/listing'; - -@import 'temp'; - -@import 'addonsThemeCustomizationsMain'; - -/* No CSS beyond this point */ -``` - -Volto will also detect all the add-ons that have these entry point files, and import them grouped under a single file, and will automatically add an `addonsThemeCustomizationsMain` alias that can be referenced from the theme as shown above. - -```{note} -It will only work in combination with the theme declaration in `volto.config.js` or in `package.json`. -``` diff --git a/docs/source/conceptual-guides/add-ons.md b/docs/source/conceptual-guides/add-ons.md new file mode 100644 index 0000000000..da1d986f5d --- /dev/null +++ b/docs/source/conceptual-guides/add-ons.md @@ -0,0 +1,234 @@ +--- +myst: + html_meta: + "description": "Volto add-ons extend the core functionality of the Plone CMS user interface." + "property=og:description": "Volto add-ons extend the core functionality of the Plone CMS user interface." + "property=og:title": "Volto add-ons" + "keywords": "Volto, add-on, extensions, user interface, frontend, Plone" +--- + +# Volto add-on concepts + +This guide describes Volto add-on concepts. + + +## What is a Volto add-on? + +Volto add-ons are just CommonJS or ESM packages. +Their main purpose is to encapsulate logic, configuration, components, customizations, and even themes in a reusable way. + +Suppose you want to have more control and flexibility beyond the plain Volto project when building a site. +You can build a Volto {term}`add-on` and make it available as a generic JavaScript package. +Then you can reuse and include it in any Volto project. + +An add-on can configure or provide any of the following aspects of Volto. + +- Provide additional views and blocks. +- Override or extend Volto's built-in views, blocks, and settings. +- Shadow or 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, loading custom Razzle and webpack plugins. +- Provide even a custom theme. + + +## Volto registry + +Volto has a built-in extensible and pluggable system to enhance the Plone CMS user interface. +It helps developers extend Volto in a pluggable way through {term}`add-on`s. +This system is implemented through Volto's registry. + +For Volto 17 and earlier, the registry was integrated into Volto core. + +From Volto 18 onward, the Volto registry is in its own package [`@plone/registry`](https://plone-registry.readthedocs.io/). + + +## Add-on configuration pipeline + +A Volto app's configuration is determined through a pipeline starting with Volto's default configuration, then each of your app's add-ons' configuration. +In Volto 17 and earlier, you can also use project configuration at the end of the pipeline after any add-ons. + +```{deprecated} Volto 18.0.0 +The project configuration approach is deprecated and will be removed in Volto 19. +``` + +Add-ons are applied in the order they are declared in the `addons` key of {file}`package.json` or programmatically via a provided configuration file. +Add-ons can override configuration coming from other add-ons, providing a hierarchy of configuration stacks. + +Add-ons can be chained, where each one can configure the app in some way. +If needed, each add-on in the chain can override or extend the previous configuration that other add-ons set. +Thus, the order in which you register add-ons matters. + +Add-ons can define shadowed components. +"Component shadowing" is a technique for overriding modules of other packages at build time. +This technique builds upon the `resolve.aliases` facilities of bundlers, so modules can be replaced when the app is being built. + +Volto will automatically provide aliases for your package. +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. +These include {doc}`JavaScript language features <../contributing/language-features>` with transpilation by Babel, whole-process customization via {file}`razzle.extend.js`, and integration with Volto's {term}`configuration registry`. +``` + + +### Use cases + +In practice with the configuration pipeline, for example, you can create a "policy" core add-on for your project, and use another add-on for your project's theme. +This way the project itself renders as a simple boilerplate, which you can extend or rebuild at any time. + +You can also reuse add-ons across projects, and adjust them using other add-ons, depending on the other projects' requirements. + + +% TODO: Should this section be moved to a how-to guide? +## Add-on configuration + +The default export of your add-on's main {file}`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. +An {file}`index.js` file should contain the following code. + +```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 {file}`package.json` file of your add-on should contain the following code. + +```json +{ + "main": "src/index.js", +} +``` + +In effect, Volto does the equivalent of the following pseudocode: + +```js +import installMyVoltoAddon from 'my-volto-addon' + +// ... in the configuration registry setup step: +const configRegistry = installMyVoltoAddon(defaultRegistry); +``` + +The Volto add-on needs to export a default function that receives the Volto configuration registry. +Then it is free to change the registry. +Finally, it must return that registry. + +Volto will execute all the add-on configuration functions in a chain to compute the final configuration registry. + +```{note} +An add-on's default configuration method will always be loaded. +``` + +```{seealso} +See [@kitconcept/volto-button-block](https://github.com/kitconcept/volto-button-block) as an example. +``` + + +### Provide optional add-on configurations + +You can export additional configuration functions from your add-on's main {file}`index.js`. + +```js +import applyConfig, {loadOptionalBlocks,overrideSomeDefaultBlock} from './config'; + +export { loadOptionalBlocks, overrideSomeDefaultBlock }; +export default applyConfig; +``` + +```{seealso} +{doc}`../development/add-ons/load-add-on-configuration` +``` + + +% TODO: Should this section be moved to a how-to guide? +### Define your add-ons programmatically + +The `addons` key in the {file}`package.json` file alone might not be flexible enough in complex scenarios. +You can programmatically load your add-ons outside your {file}`package.json` file using a {file}`volto.config.js` file with the following content. + +```js +module.exports = { + addons: ['@eeacms/volto-accordion-block'] +} +``` + +This creates an "escape hatch", where you can use logic and environment conditions to define the add-ons to load in the current project, as in the next example. +The add-ons that you define here will be added to the existing ones in {file}`package.json`. + +```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, +}; +``` + +```{important} +You must add the `addons` key with the value of your add-on package's name wherever you configure it. +In Plone terminology, it is like including a Python egg in the `zcml` section of `zc.buildout`. +``` + +```{seealso} +{doc}`../configuration/volto-config-js` +``` + + +## Add-on dependencies + +Add-ons can depend on any other JavaScript package, including other Volto add-ons. +To do this, specify the name of your Volto add-on dependency in your `dependencies` key of your {file}`package.json` file. +Then create a new `addons` key in the {file}`package.json` file of your add-on, where you specify the extra Volto add-on dependency. +By doing this, the add-ons can "chain load" one another. + +```json +{ + "name": "volto-slate", + + "addons": ["@eeacms/volto-object-widget"] +} +``` + + +## Publish an add-on + +Volto add-ons should not be transpiled. +They should be released as "source" packages. + +Their primary entry point (the `main` key of their {file}`package.json`) must point to a module that exports a default function, which acts as a default configuration loader for that package. + +You can publish an add-on to an npm registry or to a remote repository host such as GitHub or GitLab, like any other package. +If you publish your add-on to the [npm Registry](https://www.npmjs.com/) or make your repository public, as a bonus, you will benefit from collaborating on open source software. + + +% Where does this go? +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. +[Eric Brehault](https://github.com/ebrehault) ported this amazing Python tool. diff --git a/docs/source/conceptual-guides/index.md b/docs/source/conceptual-guides/index.md new file mode 100644 index 0000000000..a81d8cd024 --- /dev/null +++ b/docs/source/conceptual-guides/index.md @@ -0,0 +1,19 @@ +--- +myst: + html_meta: + "description": "Volto conceptual guides" + "property=og:description": "Volto conceptual guides" + "property=og:title": "Conceptual guides" + "keywords": "Volto, user interface, frontend, Plone, conceptual guides" +--- + +# Conceptual guides + +This section of the documentation contains conceptual guides for various aspects of Volto. + +```{toctree} +:hidden: +:maxdepth: 2 + +add-ons +``` diff --git a/docs/source/configuration/how-to.md b/docs/source/configuration/how-to.md index a1023c1837..b8f8da6147 100644 --- a/docs/source/configuration/how-to.md +++ b/docs/source/configuration/how-to.md @@ -35,7 +35,8 @@ Both use the same method, using a function as the default export. This function 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. -See the {doc}`../addons/index` section for extended information on how to work with add-ons. +See the {doc}`../conceptual-guides/add-ons` and {doc}`../development/add-ons/index` sections for extended information on how to work with add-ons. + ## Extending configuration in a project diff --git a/docs/source/contributing/developing-core.md b/docs/source/contributing/developing-core.md index 0e961feb81..cad81c3685 100644 --- a/docs/source/contributing/developing-core.md +++ b/docs/source/contributing/developing-core.md @@ -165,7 +165,7 @@ cd volto Install the frontend dependencies. ```shell -pnpm install +make install ``` diff --git a/docs/source/contributing/language-features.md b/docs/source/contributing/language-features.md index 258582172a..7102420229 100644 --- a/docs/source/contributing/language-features.md +++ b/docs/source/contributing/language-features.md @@ -1,73 +1,64 @@ --- myst: html_meta: - "description": "Volto is developed using Babel to transpile modern JavaScript to JavaScript that browsers are able to understand and execute." - "property=og:description": "Volto is developed using Babel to transpile modern JavaScript to JavaScript that browsers are able to understand and execute." - "property=og:title": "Language features and conventions" - "keywords": "Volto, Plone, frontend, React, Babel, translations, language, internationalization, i18n, localization, transpilation" + "description": "Volto uses several tools and follows conventions that provide features and browser support for the JavaScript language." + "property=og:description": "Volto uses several tools and follows conventions that provide features and browser support for the JavaScript language." + "property=og:title": "JavaScript language features and browser support" + "keywords": "Volto, Plone, frontend, React, Babel, JavaScript, transpilation" --- +% Mixture of conceptual guide and how-to guide +# JavaScript language features and browser support -# Language features and conventions +Volto uses several tools and follows conventions that provide features and browser support for the JavaScript language. + +% Conceptual guide ## Babel -Volto is developed using Babel to transpile modern JavaScript to JavaScript that -browsers are able to understand and execute. +Babel transpiles {term}`ECMAScript` code, including React and JSX, into a backwards compatible version of JavaScript in current and older browsers or environments. -Ecma International's TC39 (https://tc39.es/) is a group of JavaScript developers, -implementers, academics, and more, collaborating with the community to maintain and -evolve the definition of JavaScript. They stablished a process -(https://tc39.es/process-document/) where the proposals are discussed, developed, and -eventually approved (or dropped). The process has five stages (0 to 4) where reaching -the stage 4 means the proposal is accepted and it becomes part of the JavaScript -specification. +Babel provides features and syntax that you can use in code when you develop on Volto. +These features derive from the proposals that the {term}`TC39` produces. -Babel enables a series of features and syntax that the developer can use in code to -develop Volto on. These features are the proposals the TC39 is working on in the -different stages of evolution. +Volto uses `babel-razzle-preset`, which in turns uses `@babel/preset-env`, which together enable the use of all [TC39 finished proposals](https://github.com/tc39/proposals/blob/HEAD/finished-proposals.md#finished-proposals). -Volto uses `babel-razzle-preset` which in turns uses `@babel/preset-env` which enables -the use of all TC39 proposals currently in TC39's stage 4 -(https://github.com/tc39/proposals/blob/HEAD/finished-proposals.md#finished-proposals). -### Browser compatibility +% How-to guide +## Browser compatibility -Babel preset-env uses `browserlist` which gives the ability to micromanage the -transformations needed by the current project depending of the browser support you are -currently targeting. +`@babel/preset-env` uses `browserslist`, which you can use to manage the transformations needed to target specific browser support in your project. +This reduces the size of bundles, as Babel will apply only the required transforms that your target environment needs. -By doing this, it enables the bundles to be smaller, as the resulting code does not need to -support old browsers (thus, transform your code to ES5 compatible code) as Babel will -apply only the required transforms that your target environments need. For more -information: https://babeljs.io/docs/babel-preset-env#browserslist-integration +```{seealso} +https://babeljs.io/docs/babel-preset-env#browserslist-integration +``` -Volto project generators use this browserlist by default (you can find it in your local `package.json`): +Volto project generators use `browserslist` queries by default, which is in your local {file}`package.json`. +You can adjust this file according to the environments you want to target. ```json - "browserslist": [ - ">1%", - "last 4 versions", - "Firefox ESR", - "not ie 11", - "not dead" - ], +"browserslist": [ + ">1%", + "last 4 versions", + "Firefox ESR", + "not ie 11", + "not dead" +], +``` + +```{seealso} +For usage and syntax, see the `browserslist` documentation of [Queries](https://github.com/browserslist/browserslist#queries). ``` -which you can adjust depending on the environments you are targeting in your local -`package.json` file. You can find more information about how the queries in `broserlist` -works in: https://github.com/browserslist/browserslist#queries -### Support to deprecated browsers +% How-to guide +## Support of deprecated browsers ```{warning} -Volto does not support deprecated browsers from its vendor (eg. IE11). +Volto does not support deprecated browsers, such as Internet Explorer 11. ``` -If you still need to support deprecated browsers, you should use `browserslist` in your -project to enable the required transforms for the target deprecated environments you -have to support. +If you still need to support deprecated browsers, you should use `browserslist` in your project to enable the required transforms for the target deprecated environments you must support. -However, Volto (or its dependencies) might not be compatible with old browsers anyways, -and you might need to provide some other workarounds to make the build work (and the -deprecated browser not crash). You can refer to {doc}`this (outdated) -document <../development/ie11compat>` in order to get some hints on how to do it. +However, Volto or its dependencies might not be compatible with old browsers. +You might need to create some workarounds to make the build work, and the deprecated browser not crash. diff --git a/docs/source/development/add-ons/best-practices.md b/docs/source/development/add-ons/best-practices.md new file mode 100644 index 0000000000..c023301634 --- /dev/null +++ b/docs/source/development/add-ons/best-practices.md @@ -0,0 +1,82 @@ +--- +myst: + html_meta: + "description": "Best practices for developing Volto add-ons" + "property=og:description": "Best practices for developing Volto add-ons" + "property=og:title": "Best practices for add-ons" + "keywords": "Volto, Plone, frontend, React, best, practices, add-ons" +--- + +# Best practices for add-ons + +This document describes the best practices when you develop your add-on. + + +## Integrate your add-on with Volto's add-on framework + +Just like Plone add-ons provide some features by default, Volto add-ons should register some features by default. +For example, if your add-on provides widgets, then register the most basic configuration of that widget with a name that it uses. + +For more complicated cases, see if you can structure your code to use the `settings` {term}`configuration registry`, or stash your configuration in your block registration. + +Let's say you're building a color picker widget. +You might want to provide a palette of colors from which to choose. +Your widget should integrate with a default `settings.colorWidgetPalette`, which would be a list of colors. + +You should also provide a widget factory, so it can be used to create multiple instances of that color widget with custom color palettes. + + +## Provide additional configuration + +An add-on can ship with multiple {term}`Volto configuration loader`s. +This makes it possible to provide multiple configuration methods for specific demonstration purposes. +Alternatively you could ship your add-on with a default "shallow" integration, then provide another separate configuration loader for a deeper integration. + + +## Avoid shadowing core Volto files + +This rule is meant to be broken. +If you need to customize a specific file in Volto core and you have multiple projects, it may be better to create an add-on that holds that customized file. +Doing so creates a single place to maintain that "file fork". +Otherwise, it's best to avoid shipping generic add-ons with Volto core customizations. + +If you customize core Volto files, you should warn consumers of your add-on in its description. + +If your use case is generic enough, [file a feature request](https://github.com/plone/volto/issues/new?assignees=&labels=04+type%3A+enhancement&projects=&template=feature_request.md&title=) to discuss with the Volto Team whether your customization should be included directly in core. + + +## Documentation + +"If it ain't documented, it's broken." + +At least create a README with a brief description and a screenshot or video of what your add-on does. + +Ideally, the README should include requirements or compatability with various versions of Volto and Plone, and installation and configuration instructions. + + +## Test the add-on + +```{versionadded} Volto 18.0.0-alpha.43 +``` + +Cookieplone provides a self-bootstrapping and testing framework in Volto 18. +See {doc}`test-add-ons-18`. + +Previously in Volto 17 and early alpha versions of Volto 18, it was not easy to ship an add-on with a self-bootstrapping and testing framework. +However, for these older versions of Volto you can create a separate minimal Volto project that can hold the Cypress integration tests and trigger the CI tests. + + +## Use appropriate npm Registry tags + +If you release your add-on to the [npm Registry](https://www.npmjs.com/), consider adding the following tags, next to your add-on-specific tags. + +- `volto-addon` +- `volto` +- `plone` +- `react` + + +## Add to `collective/awesome-volto` + +Consider adding your add-on to the [`collective/awesome-volto`](https://github.com/collective/awesome-volto) add-ons list. +This list provides visibility to your add-on, as well as further solidifies Volto's position in the Plone community. diff --git a/docs/source/development/add-ons/create-an-add-on-17.md b/docs/source/development/add-ons/create-an-add-on-17.md new file mode 100644 index 0000000000..6034a5ef57 --- /dev/null +++ b/docs/source/development/add-ons/create-an-add-on-17.md @@ -0,0 +1,20 @@ +--- +myst: + html_meta: + "description": "How to create an add-on for Volto 17" + "property=og:description": "How to create an add-on for Volto 17" + "property=og:title": "Create an add-on for Volto 17" + "keywords": "add-on, Volto, create" +--- + +# Create an add-on for Volto 17 + +Volto add-on packages are just CommonJS packages. +The only requirement is that they point the `main` key of their {file}`package.json` to a module that exports as a default function, acting as a {term}`Volto configuration loader`. + +You can use Plone's Yeoman-based generator [`generator-volto`](https://github.com/plone/generator-volto) to create a Volto add-on. + +```shell +npm install -g @plone/generator-volto +yo @plone/volto:addon [] [options] +``` diff --git a/docs/source/development/add-ons/create-an-add-on-18.md b/docs/source/development/add-ons/create-an-add-on-18.md new file mode 100644 index 0000000000..15ffca9d6a --- /dev/null +++ b/docs/source/development/add-ons/create-an-add-on-18.md @@ -0,0 +1,200 @@ +--- +myst: + html_meta: + "description": "How to create an add-on for Volto 18" + "property=og:description": "How to create an add-on for Volto 18" + "property=og:title": "Create an add-on for Volto 18" + "keywords": "add-on, Volto, create, development" +--- + +# Create an add-on for Volto 18 + +This chapter describes how you can create an add-on using Volto 18 or later for the Plone user interface, 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 Volto version 18.0.0-alpha.43 and above. +``` + +## System requirements + +Follow the section {ref}`plone:create-project-cookieplone-system-requirements` to set up your system. + + +## Generate the add-on project + +To develop an add-on for only the frontend, then run the following command to generate your add-on project using the `frontend_addon` Cookieplone template. +To develop add-ons for each the frontend and backend that work together, then instead use the Cookieplone template `project` in the command. +See {doc}`plone:install/create-project-cookieplone` for details of the latter scenario. +The following output assumes the former scenario. + +```shell +pipx run cookieplone frontend_addon +``` + +```console +❯ pipx run cookieplone frontend_addon +⚠️ cookieplone is already on your PATH and installed at + /Users//.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//.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//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 creates a folder with the name of the add-on, in this example, `volto-addon`. + +Change your current working directory to {file}`volto-addon`. + +```shell +cd volto-addon +``` + +To install the add-on setup, use the following command. + +```shell +make install +``` + + +## Start Plone backend Docker container + +In the currently open shell 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 with 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/development/add-ons/extend-eslint-add-on.md b/docs/source/development/add-ons/extend-eslint-add-on.md new file mode 100644 index 0000000000..6017463332 --- /dev/null +++ b/docs/source/development/add-ons/extend-eslint-add-on.md @@ -0,0 +1,43 @@ +--- +myst: + html_meta: + "description": "Extend ESLint configuration from an add-on" + "property=og:description": "Extend ESLint configuration from an add-on" + "property=og:title": "Extend ESLint configuration from an add-on" + "keywords": "Volto, add-on, extensions, frontend, Plone, configuration, ESLint, lint" +--- + +# Extend ESLint configuration from an add-on + +```{versionadded} Volto 16.4.0 +``` + +Starting with Volto v16.4.0, you can customize the ESLint configuration from an add-on. +You should provide a {file}`eslint.extend.js` file in your add-on's root folder, which exports a `modify(defaultConfig)` function. +For example, to host some code outside the regular {file}`src/` folder of your add-on, you need to add the following {file}`eslint.extend.js` file: + +```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 allows the add-on `@plone-collective/some-volto-add-on` to host some code outside its normal {file}`src/` folder. +If you put that code in the {file}`extra` folder, that code would be available under the `@plone-collective/extra` name. + +```{note} +This takes care only of the ESLint integration. +For proper language support, you'll still need to configure it in the {file}`razzle.extend.js` file of your add-on. +``` diff --git a/docs/source/development/add-ons/extend-webpack-add-on.md b/docs/source/development/add-ons/extend-webpack-add-on.md new file mode 100644 index 0000000000..c7fbd69aed --- /dev/null +++ b/docs/source/development/add-ons/extend-webpack-add-on.md @@ -0,0 +1,55 @@ +--- +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" + "keywords": "Volto, Plone, webpack, add-on, razzle.extend.js, Razzle" +--- + +# Extend webpack setup from an add-on + +```{deprecated} Volto 18 +The project configuration approach as described in this document is deprecated in Volto 18 and will be removed in Volto 19. +You should instead follow the add-on approach as described in {doc}`../../conceptual-guides/add-ons`. +``` + +Just like you can extend Razzle's configuration from the project, you can do the same with an add-on. +You should provide a {file}`razzle.extend.js` file in your add-on root folder. +The following code example manages two things. + +- Add a new webpack plugin, [`webpack-bundle-analyzer`](https://www.npmjs.com/package/webpack-bundle-analyzer). +- Reconfigure the `theme.config` alias, to enable a custom Semantic UI theme inside the add-on. + + ```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, + }; + ``` + +```{seealso} +[`volto-searchlib`'s {file}`razzle.extend.js`](https://github.com/eea/volto-searchlib/blob/d84fec8eec1def0088d8025eaf5d7197074b95a7/razzle.extend.js) file for an example of how to include additional paths for the Babel configuration, and how to add additional webpack name aliases. +``` diff --git a/docs/source/addons/i18n.md b/docs/source/development/add-ons/i18n.md similarity index 61% rename from docs/source/addons/i18n.md rename to docs/source/development/add-ons/i18n.md index 9df832da70..ab8d838f8f 100644 --- a/docs/source/addons/i18n.md +++ b/docs/source/development/add-ons/i18n.md @@ -4,17 +4,19 @@ myst: "description": "Internationalize your add-on and override translations" "property=og:description": "Internationalize your add-on and override translations" "property=og:title": "Add-on Internationalization" - "keywords": "Internationalization, i18n, add-on" + "keywords": "Volto, internationalization, i18n, add-on" --- -# Add-on Internationalization +# Add-on internationalization + +The {term}`internationalization` (i18n) workflow in and add-on is similar to core Volto. +You develop your add-on, then add the translations to your code. -The internationalization workflow is the same as in main Volto: you develop your add-on, then add the translations to your code. See {ref}`create-i18n-strings` for how to mark strings and phrases as translatable. -Your add-on has a `locales` folder with a `.pot` file. +Your add-on has a {file}`locales` folder with a `.pot` file. -1. Create the following structure in your add-ons `locales` folder for every language you want to support. +1. Create the following structure in your add-ons {file}`locales` folder for every language you want to support. As an example for the language Italian: ```text @@ -24,14 +26,14 @@ Your add-on has a `locales` folder with a `.pot` file. ``` 1. Run `pnpm i18n` in the context of your add-on. -1. Go to each `.po` file in your `locales` folder, and write the translations for each translation literal. +1. Go to each `.po` file in your {file}`locales` folder, and write the translations for each translation literal. In the context of your project, run `pnpm i18n` to merge the add-on translations with the ones of your project. ## Override translations -If you have multiple add-ons installed in your project, the translations are loaded in the order your add-ons are listed in `package.json`. +If you have multiple add-ons installed in your project, the translations are loaded in the order your add-ons are listed in {file}`package.json`. If two add-ons provide different translations for the same message, then the last defined add-on wins. When running `pnpm i18n` in the context of your project, the project's own locales are processed last and can override translations from any add-on. diff --git a/docs/source/development/add-ons/index.md b/docs/source/development/add-ons/index.md new file mode 100644 index 0000000000..97296c4a12 --- /dev/null +++ b/docs/source/development/add-ons/index.md @@ -0,0 +1,30 @@ +--- +myst: + html_meta: + "description": "How to develop Volto add-ons" + "property=og:description": "How to develop Volto add-ons" + "property=og:title": "Develop Volto add-ons" + "keywords": "Volto, Plone, CMS, add-on" +--- + +# Develop Volto add-ons + +```{toctree} +:maxdepth: 1 + +install-an-add-on +install-an-add-on-dev-18 +install-an-add-on-dev-17 +load-add-on-configuration +create-an-add-on-18 +create-an-add-on-17 +test-add-ons-18 +test-add-ons-17 +extend-webpack-add-on +extend-eslint-add-on +troubleshoot-transpilation +i18n +best-practices +theme +public-folder +``` diff --git a/docs/source/development/add-ons/install-an-add-on-dev-17.md b/docs/source/development/add-ons/install-an-add-on-dev-17.md new file mode 100644 index 0000000000..8c938c6391 --- /dev/null +++ b/docs/source/development/add-ons/install-an-add-on-dev-17.md @@ -0,0 +1,124 @@ +--- +myst: + html_meta: + "description": "How to install an add-on in development mode in Volto 17 in your Plone project" + "property=og:description": "How to install an add-on in development mode in Volto 17 in your Plone project" + "property=og:title": "Install an add-on in development mode in Volto 17" + "keywords": "Volto, Plone, add-on, stable, development, mode" +--- + +# Install an add-on in development mode in Volto 17 + +Use [`mrs-developer`](https://www.npmjs.com/package/mrs-developer) to manage the development cycle of Volto add-ons. +This tool pulls the remote code and configures the current project, making the add-on available for the build. +By doing this, you can develop both the project and the add-on product as if they were both part of the current codebase. + +`mrs-developer` is included and installed by default when you generate a project with the generator. +Use the following command to install the configuration of `mrs.developer.json` in your project. + +```shell +make install +``` + + +## Configure `mrs-developer` + +{file}`mrs.developer.json` is the configuration file that instructs `mrs-developer` from where it should pull the packages. +The generator includes an empty one for you. +Edit {file}`mrs.developer.json` and add the following code. + +```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 add-on appears in `src/addons/`. + +```{note} +The `package` property is optional. +Use it only if your package has a namespace. + +`src` is required if the content of your add-on is located in the `src` directory. +Since that is the convention for all Volto add-on packages, you must always include it. +``` + +```{seealso} +See [`mrs-developer` configuration options](https://www.npmjs.com/package/mrs-developer). +``` + + +## Resolve import paths + +Your project uses a file to configure import paths, either {file}`tsconfig.json` or {file}`jsconfig.json` at the Volto project root. +`mrs-developer` automatically manages the content of this file for you. +If you choose not to use `mrs-developer`, you'll have to manually add configuration to this file. + +```json +{ + "compilerOptions": { + "paths": { + "acme-volto-foo-addon": [ + "addons/acme-volto-foo-addon/src" + ] + }, + "baseUrl": "src" + } +} +``` + + +```{warning} +Both values for `paths` and `baseUrl` must match your project's layout. +``` + +```{tip} +You should use the `src` path inside your package and point the `main` key in {file}`package.json` to the {file}`index.js` file in {file}`src/index.js`. +``` + + +## Add-on development lifecycle + +If you want to "disable" using the development version of an add-on, or keep a more stable version of `mrs.developer.json` in your source code repository, you can set its status by adding a `develop` key to {file}`mrs.developer.json` as shown. + +```json +{ + "acme-volto-foo-addon": { + "package": "@acme/volto-foo-addon", + "url": "git@github.com:acme/my-volto-addon.git", + "path": "src", + "develop": true + } +} +``` + +Whenever you change a value in your {file}`mrs.developer.json`, you must run `make install` again. + + +## Add-on dependencies, yarn workspaces + +If your add-on needs to bring in additional JavaScript package dependencies, you'll have to set your add-on package as a "Yarn workspace". +You should add a `workspaces` key to the {file}`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 clean up the `src/addons` folder whenever you toggle the development status of an add-on, as the existence of the add-on folder under `src/addons` will still influence yarn. +To do so, run `make install` again to remove the no longer required package. diff --git a/docs/source/development/add-ons/install-an-add-on-dev-18.md b/docs/source/development/add-ons/install-an-add-on-dev-18.md new file mode 100644 index 0000000000..7828759fc7 --- /dev/null +++ b/docs/source/development/add-ons/install-an-add-on-dev-18.md @@ -0,0 +1,143 @@ +--- +myst: + html_meta: + "description": "How to install an add-on in development mode in Volto 18 in your Plone project" + "property=og:description": "How to install an add-on in development mode in Volto 18 in your Plone project" + "property=og:title": "Install an add-on in development mode in Volto 18" + "keywords": "Volto, Plone, add-on, development, mode" +--- + +# Install an add-on in development mode in Volto 18 + +Use [`mrs-developer`](https://www.npmjs.com/package/mrs-developer) to manage the development cycle of Volto add-ons. +This tool pulls the remote code and configures the current project, making the add-on available for the build. +By doing this, you can develop both the project and the add-on product as if they were both part of the current codebase. + +`mrs-developer` is included and installed by default when you generate a project with Cookieplone. +Use the following command to install the configuration of `mrs.developer.json` in your project. + +```shell +make install +``` + +Next, you need to add the add-on to the `addons` key of your Plone project's {file}`package.json`. + +```json +{ + "name": "my-volto-project", + "addons": [ + "name-of-add-on" + ] +} +``` + +```{seealso} +Alternatively, you can use {file}`volto.config.js` to declare add-ons in your Plone project. +See {doc}`../../configuration/volto-config-js`. +``` + +## Configure `mrs-developer` + +{file}`mrs.developer.json` is the configuration file that instructs `mrs-developer` from where it should pull the packages. +Cookieplone includes an empty one for you. +Edit {file}`mrs.developer.json` and add the following code. + +```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 add-on appears in `packages/acme-volto-foo-addon/`. + +```{note} +The `package` property is optional. +Use it only if your package has a namespace. + +`src` is required if the content of your add-on is located in the `src` directory. +Since that is the convention for all Volto add-on packages, you must always include it. +``` + +```{seealso} +See [`mrs-developer` configuration options](https://www.npmjs.com/package/mrs-developer). +``` + + +## Resolve import paths + +```{versionadded} Volto 18.0.0-alpha.43 +``` + +The Cookieplone setup uses `pnpm` to resolve import paths. +You have nothing to do here. + + +## Add-on development lifecycle + +If you want to "disable" using the development version of an add-on, or keep a more stable version of `mrs.developer.json` in your source code repository, you can set its status by adding a `develop` key to {file}`mrs.developer.json` as shown. + +```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 + } +} +``` + +Whenever you change a value in your {file}`mrs.developer.json`, you must run `make install` again. + + +## Add-on dependencies + +If your add-on needs to bring in additional JavaScript package dependencies, you'll have to declare them as normal package dependencies. + + +## `pnpm` workspaces + +You need to configure your add-ons using a `pnpm` workspace. +You can configure them using the file {file}`pnpm-workspace.yaml` and declare all your development add-ons in there. + +```yaml +packages: + - 'core/packages/*' + - 'packages/*' +``` + +If the add-on you are developing was created using {term}`Cookieplone`, then you have to add the following to {file}`pnpm-workspace.yaml` detect them. + +```yaml +packages: + - 'core/packages' + - 'packages/my-policy-addon' + - 'packages/**/packages/*' +``` + +Note the nesting of `packages` since a {term}`Cookieplone` generated add-on will have a `packages` folder in itself. +You can explicitly declare the add-ons, too. + +```yaml +packages: + - 'core/packages' + - 'packages/my-policy-addon' + - 'packages/my-development-mode-addon/packages/my-development-mode-addon' + - 'packages/**/packages/*' +``` + +```{important} +Run `make install` after any change in {file}`pnpm-workspace.yaml` to update the setup. +``` diff --git a/docs/source/development/add-ons/install-an-add-on.md b/docs/source/development/add-ons/install-an-add-on.md new file mode 100644 index 0000000000..8dfc9df327 --- /dev/null +++ b/docs/source/development/add-ons/install-an-add-on.md @@ -0,0 +1,69 @@ +--- +myst: + html_meta: + "description": "How to install an add-on in Volto" + "property=og:description": "How to install an add-on in Volto" + "property=og:title": "Install an add-on in Volto" + "keywords": "add-on, Volto, install" +--- + +# Install an add-on in Volto + +This document describes how to install an add-on in Volto. + +You can install an add-on just like any other JavaScript package from the [npm Registry](https://www.npmjs.com/). + +`````{tab-set} +:sync-group: install-add-on + +````{tab-item} Volto 18 +:sync: volto-18 +```shell +pnpm --filter add +``` +```` + +````{tab-item} Volto 17 +:sync: volto-17 +```shell +yarn add +``` +```` +````` + +If the add-on is not published on the npm Registry, [you can install it directly from GitHub](https://pnpm.io/cli/add#install-from-git-repository). + + +`````{tab-set} +:sync-group: install-add-on + +````{tab-item} Volto 18 +:sync: volto-18 +```shell +pnpm add collective/volto-dropdownmenu +``` +```` + +````{tab-item} Volto 17 +:sync: volto-17 +```shell +yarn add collective/volto-dropdownmenu +``` +```` +````` + +Next, you need to add the add-on to the `addons` key of your Plone project's {file}`package.json`. + +```json +{ + "name": "my-volto-project", + "addons": [ + "name-of-add-on" + ] +} +``` + +```{seealso} +Alternatively, you can use {file}`volto.config.js` to declare add-ons in your Plone project. +See {doc}`../../configuration/volto-config-js`. +``` diff --git a/docs/source/development/add-ons/load-add-on-configuration.md b/docs/source/development/add-ons/load-add-on-configuration.md new file mode 100644 index 0000000000..33f0a21c9b --- /dev/null +++ b/docs/source/development/add-ons/load-add-on-configuration.md @@ -0,0 +1,65 @@ +--- +myst: + html_meta: + "description": "Load configuration from add-ons" + "property=og:description": "Load configuration from add-ons" + "property=og:title": "Load configuration from add-ons" + "keywords": "Volto, add-on, extensions, frontend, Plone, configuration" +--- + +# Load 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 configuration methods, making it possible to selectively choose which specific add-on functionality you want to load. + +Some add-ons might allow the Volto project to selectively load some of their configuration, so they may offer additional configuration functions. +You can load them by overloading the add-on name in the `addons` {file}`package.json` key, as shown. + +```{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 {file}`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 can edit your project's {file}`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, +} +``` + +Volto provides a helper method `applyConfig` to do the same. + +```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 method returns the configuration object, avoiding errors when developing add-ons. diff --git a/docs/source/addons/public-folder.md b/docs/source/development/add-ons/public-folder.md similarity index 70% rename from docs/source/addons/public-folder.md rename to docs/source/development/add-ons/public-folder.md index 188a9e155a..aec4ab220c 100644 --- a/docs/source/addons/public-folder.md +++ b/docs/source/development/add-ons/public-folder.md @@ -1,10 +1,10 @@ --- myst: html_meta: - "description": "How to add static served files from your add-on to your build" - "property=og:description": "How to add static served files to the build from an add-on" + "description": "How to add static files from your add-on to your build" + "property=og:description": "How to add static files from your add-on to your build" "property=og:title": "Add static files from your add-on to your build" - "keywords": "Volto, Plone, Semantic UI, CSS, Volto theme, add-on, static, assets, files, build" + "keywords": "Volto, Plone, Semantic UI, CSS, theme, add-on, static, assets, files, build" --- # Add static files from your add-on to your build @@ -22,8 +22,7 @@ It is useful to define static files such as the following: ## Procedure to include static files -Create a folder named `public` at the root of your add-on, and add the static files to it. +Create a folder named {file}`public` at the root of your add-on, and add the static files to it. The build process will copy the files, taking into account all add-ons' defined order. The build process copies first the static files defined by Volto, then the static files from add-ons as defined by their configuration order. The last defined file overwrites any previously defined files. - diff --git a/docs/source/development/add-ons/test-add-ons-17.md b/docs/source/development/add-ons/test-add-ons-17.md new file mode 100644 index 0000000000..047c508869 --- /dev/null +++ b/docs/source/development/add-ons/test-add-ons-17.md @@ -0,0 +1,104 @@ +--- +myst: + html_meta: + "description": "How to test add-ons in Volto 17" + "property=og:description": "How to test add-ons in Volto 17" + "property=og:title": "Test add-ons in Volto 17" + "keywords": "Volto, Plone, testing, CI, add-ons" +--- + +# Test add-ons in Volto 17 + +Volto uses {term}`Jest` for unit tests. +You must configure {file}`package.json` to let Jest know about your aliases and make them available to it to resolve them. + +```{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. + + +## Override Jest configuration + +In {term}`CI` or for testing add-ons, it's useful to modify Jest's {file}`package.json` configuration file. +You can use a {file}`jest.config.js` file, or point the test runner to a file of your choice, using the `RAZZLE_JEST_CONFIG` environment variable. + +```shell +RAZZLE_JEST_CONFIG=my-custom-jest-config.js yarn test +``` + +Both configurations are merged in a way that the keys of the configuration provided override the initial {file}`package.json` configuration, either in Volto or in your projects. + + +## Test add-ons in isolation + +Testing an add-on in isolation, as you would 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 to bootstrap a dockerized environment where you can run any test to your add-on. + + +### Set up the environment + +Run the following command once. + +```shell +make dev +``` + + +### Build the containers manually + +Run the following commands. + +```shell +make build-backend +make build-addon +``` + + +### Unit tests + +Run the following command. + +```shell +make test +``` + + +### Acceptance tests + +Use {term}`Cypress` to run acceptance tests. +Run the following command once. + +```shell +make install-acceptance +``` + +To start the servers, run the following command. + +```shell +make start-test-acceptance-server +``` + +You run the frontend in development mode, so you can develop while writing tests. +Run the following command to run Cypress tests afterward. + +```shell +make test-acceptance +``` + +When finished, shut down the backend server. + +```shell +make stop-test-acceptance-server +``` diff --git a/docs/source/development/add-ons/test-add-ons-18.md b/docs/source/development/add-ons/test-add-ons-18.md new file mode 100644 index 0000000000..cb55a72ad3 --- /dev/null +++ b/docs/source/development/add-ons/test-add-ons-18.md @@ -0,0 +1,63 @@ +--- +myst: + html_meta: + "description": "Test add-ons in Volto 18" + "property=og:description": "Test add-ons in Volto 18" + "property=og:title": "Test add-ons in Volto 18" + "keywords": "Volto, Plone, testing, test, CI, add-ons" +--- + +# Test add-ons in Volto 18 + +```{warning} +This guide assumes that you've used {term}`Cookieplone` to create your add-on boilerplate. +``` + +Volto uses {term}`Jest` for unit tests. +You can create unit tests for testing your add-on. + +Run the following command. + +```shell +make test +``` + +## Override Jest configuration + +In {term}`CI` or for testing add-ons, it's useful to modify Jest's {file}`package.json` configuration file. +You can use the file {file}`jest.config.js` provided by the boilerplate. +The test command will load it and apply it. + +```{warning} +Do not modify the existing keys in there if you don't know what you are doing, since some of them are required for the tests to run properly in the Volto context. +``` + +Both configurations are merged in a way that the keys of the configuration provided override the initial {file}`package.json` configuration, either in Volto or in your projects. + +```{note} +For more background on testing add-ons in Volto 18, see {doc}`../../contributing/testing`, since the developer experience has been unified for both add-ons and Volto core. +``` + +### Acceptance tests + +Use {term}`Cypress` to run acceptance tests. + +To start the backend server, run the following command. +This will start a Docker container with a vanilla Plone backend. + +```shell +make acceptance-backend-start +``` + +To start the frontend acceptance server in development mode, run the following command. + +```shell +make acceptance-frontend-dev-start +``` + +You can run the frontend in development mode, so you can develop while writing tests. +Run the following command to run Cypress tests afterward. + +```shell +make acceptance-test +``` diff --git a/docs/source/development/add-ons/theme.md b/docs/source/development/add-ons/theme.md new file mode 100644 index 0000000000..914e3b1295 --- /dev/null +++ b/docs/source/development/add-ons/theme.md @@ -0,0 +1,248 @@ +--- +myst: + html_meta: + "description": "How to create a Volto theme add-on" + "property=og:description": "How to create a Volto theme add-on" + "property=og:title": "Create a Volto theme add-on" + "keywords": "Volto, Plone, Semantic UI, CSS, theme, add-on" +--- + +# Create a Volto theme add-on + +You can create a Volto theme add-on, keeping it separate from your project files. +By making your Volto theme add-on pluggable, you can deploy the same theme in different projects. +You can even create themes that depend on conditions that you inject at build time. + +The file {file}`volto.config.js` provides the ability to programmatically declare add-ons and the active theme. +See {ref}`volto-config-js` for more information. +For convenience, it can also be set via a `THEME` environment variable. + +1. In your {file}`volto.config.js` file at the root of your project, add a `theme` key with the value of your theme's name. + + ```js + module.exports = { + addons: [], + theme: 'volto-my-theme' + }; + ``` + + Alternatively, you can add a `theme` key in your {file}`package.json` project. + + ```json + "theme": "volto-my-theme" + ``` + + Or you can set the theme name through the `THEME` environment variable. + + ```shell + THEME='volto-my-theme' pnpm start + ``` + +2. Create a directory {file}`src/theme` in your add-on. + Inside that directory, create a new file {file}`theme.config`, adding the following content, but replacing `` with your add-on name. + + ```less + /******************************* + Theme Selection + *******************************/ + + /* To override a theme for an individual element specify theme name below */ + + /* Global */ + @site : 'pastanaga'; + @reset : 'pastanaga'; + + /* Elements */ + @button : 'pastanaga'; + @container : 'pastanaga'; + @divider : 'pastanaga'; + @flag : 'pastanaga'; + @header : 'pastanaga'; + @icon : 'pastanaga'; + @image : 'pastanaga'; + @input : 'pastanaga'; + @label : 'pastanaga'; + @list : 'pastanaga'; + @loader : 'pastanaga'; + @placeholder : 'pastanaga'; + @rail : 'pastanaga'; + @reveal : 'pastanaga'; + @segment : 'pastanaga'; + @step : 'pastanaga'; + + /* Collections */ + @breadcrumb : 'pastanaga'; + @form : 'pastanaga'; + @grid : 'pastanaga'; + @menu : 'pastanaga'; + @message : 'pastanaga'; + @table : 'pastanaga'; + + /* Modules */ + @accordion : 'pastanaga'; + @checkbox : 'pastanaga'; + @dimmer : 'pastanaga'; + @dropdown : 'pastanaga'; + @embed : 'pastanaga'; + @modal : 'pastanaga'; + @nag : 'pastanaga'; + @popup : 'pastanaga'; + @progress : 'pastanaga'; + @rating : 'pastanaga'; + @search : 'pastanaga'; + @shape : 'pastanaga'; + @sidebar : 'pastanaga'; + @sticky : 'pastanaga'; + @tab : 'pastanaga'; + @transition : 'pastanaga'; + + /* Views */ + @ad : 'pastanaga'; + @card : 'pastanaga'; + @comment : 'pastanaga'; + @feed : 'pastanaga'; + @item : 'pastanaga'; + @statistic : 'pastanaga'; + + /* Extras */ + @main : 'pastanaga'; + @custom : 'pastanaga'; + + /******************************* + Folders + *******************************/ + + /* Path to theme packages */ + @themesFolder : '~volto-themes'; + + /* Path to site override folder */ + @siteFolder : "/theme"; + + /******************************* + Import Theme + *******************************/ + + @import (multiple) "~semantic-ui-less/theme.less"; + @fontPath : "~volto-themes/@{theme}/assets/fonts"; + + .loadAddonOverrides() { + @import (optional) "@{siteFolder}/@{addon}/@{addontype}s/@{addonelement}.overrides"; + } + + /* End Config */ + ``` + +3. Declare the theme as an add-on by adding its name to the value for the `addons` key in either {file}`volto.config.js` or {file}`package.json` in your project. + +4. After starting Volto, the theme should be active. + Now you can add overrides to the default theme in {file}`src/theme`, the same as you would in a project. + +5. Finally, you can safely delete your project's original {file}`theme` folder, since the one in the add-on will take precedence, and a project can only have one active theme at a time. + + +## Using your own theming escape hatch + +Volto theming uses Semantic UI theming capabilities to define and extend a theme for your site. +However, while maintaining and playing well with the Semantic UI Volto base, you can use a traditional CSS approach using the LESS preprocessor-based `extras` escape hatch. + +At the same time, you can either discard or complement the `extras` escape hatch and add your own, by customizing the {file}`theme.js` module in Volto. + +```js +import 'semantic-ui-less/semantic.less'; +import '@plone/volto/../theme/themes/pastanaga/extras/extras.less'; + +// You can add more entry points for theming +import '@kitconcept/volto-light-theme/theme/main.scss'; +``` + +Customizing the base theme is a special use case in Volto. +To begin, add a {file}`./@root/theme.js` file structure in your {file}`customizations` folder in your add-on or project. + +You may want to do this to create a completely new theming experience adapted to your way of doing things that do not match the current Volto theming experience. +For example, if you want to use another preprocessor in the theme, such as SCSS. +Or perhaps your client requires the base consist entirely of pre-made components based on another library beside Semantic UI. +See {ref}`volto-custom-theming-strategy` for an example of a custom theme escape hatch. + +While building your own escape hatch for theming, you can use the preprocessor of your choice, while maintaining the "base" Volto theme, but customizing it using the resultant CSS. + +You can see an example of such a theme in [Volto Light Theme](https://github.com/kitconcept/volto-light-theme). + + +## Modify a custom theme from another add-on + +Sometimes you have a custom theme that you want to reuse through all your projects, but with some differences, maintaining the base. +Usually, the only option would be to use an add-on that adds more CSS to the base theme, using imports that will load after the theme. +However, there is a problem with this approach. +You cannot use existing theme variables, including breakpoints, on these new styles. +Similarly, it gets somewhat detached from the normal flow of the loaded theme. +The same applies for add-ons, as they are detached from the current theme. +You could use a Semantic UI approach for making this work, but then it's bound to Semantic UI. + +```{warning} +This is only possible when using your own escape hatch, and works only with SCSS-based themes, and not with Semantic UI themes, since it enables a couple of entry points that only support SCSS files. +For an example of how it could be used, see [Volto Light Theme](https://github.com/kitconcept/volto-light-theme). +``` + +If your custom escape hatch defines a custom theme using SCSS, you can take advantage of this feature. +Although not limited to this, it would be possible to extend this feature to add more entry points, using another preprocessor or theming approach. + +This feature enables two entry point files, {file}`_variables.scss` and {file}`_main.scss`. +From your add-on code, you can extend an existing theme by creating a file corresponding to each entry point: + +- {file}`./src/theme/_variables.scss` +- {file}`./src/theme/_main.scss` + + +### Variables + +You can use the entry point `addonsThemeCustomizationsVariables` to modify the original variables of the currently loaded theme by adding the entry point before the theme variable definitions. +In the theme, it should be imported as shown below. + +```{code-block} scss +:emphasize-lines: 2 + +@import 'addonsThemeCustomizationsVariables'; +@import 'variables'; +@import 'typography'; +@import 'utils'; +@import 'layout'; +``` + +````{warning} +Following SCSS best practices, your theme variables should be "overridable" using the `!default` flag. +This assigns a value to a variable _only_ if that variable isn't defined or its value is [`null`](https://sass-lang.com/documentation/values/null). +Otherwise, the existing value will be used. + +```{seealso} +https://sass-lang.com/documentation/variables#default-values +``` +```` + +Volto will not only load your add-on entry point files, but it will also detect all the add-ons that have these entry point files, and import them grouped under a single file. +It will also automatically add an `addonsThemeCustomizationsVariables` alias that you can reference from the theme as shown above. + + +### Main + +You can use the entry point `addonsThemeCustomizationsMain` to add your own style definitions, complementing those in the theme. +You should add it after all the CSS of your theme: + +```{code-block} scss +:emphasize-lines: 6 + +@import 'blocks/search'; +@import 'blocks/listing'; + +@import 'temp'; + +@import 'addonsThemeCustomizationsMain'; + +/* No CSS beyond this point */ +``` + +Volto will also detect all the add-ons that have these entry point files, and import them grouped under a single file. +It will also automatically add an `addonsThemeCustomizationsMain` alias that you can reference from the theme as shown above. + +```{note} +It will only work in combination with the theme declaration in {file}`volto.config.js` or in {file}`package.json`. +``` diff --git a/docs/source/development/add-ons/troubleshoot-transpilation.md b/docs/source/development/add-ons/troubleshoot-transpilation.md new file mode 100644 index 0000000000..2cf7e761b6 --- /dev/null +++ b/docs/source/development/add-ons/troubleshoot-transpilation.md @@ -0,0 +1,78 @@ +--- +myst: + html_meta: + "description": "Troubleshoot untranspiled add-on dependencies" + "property=og:description": "Troubleshoot untranspiled add-on dependencies" + "property=og:title": "Troubleshoot untranspiled add-on dependencies" + "keywords": "Volto, add-on, extensions, frontend, Plone, configuration, troubleshoot" +--- + +# Troubleshoot untranspiled add-on dependencies + +```{note} +In Volto 18 and later, Babel improved support for ES specifications, such as for the null coalescence operator. +However the following procedure can be useful in other scenarios. +``` + +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 such as 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 {file}`node_modules` are excluded from this process. +You need to include the add-on path in the list of modules to be transpiled. +To do so, customize the webpack configuration in the {file}`razzle.config.js` file in your add-on. +For example, suppose that you 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 you need some setup to get the webpack configuration from Volto's configuration. +Once you have that, you 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/development/i18n.md b/docs/source/development/i18n.md index f9f2f4dd03..7fb232a9b6 100644 --- a/docs/source/development/i18n.md +++ b/docs/source/development/i18n.md @@ -11,7 +11,7 @@ myst: {term}`Internationalization` (i18n) is the process of creating user interfaces which are suitable for different languages and cultural contexts. -This chapter describes the most common use cases for internationalization when developing your {doc}`../addons/index` or contributing to the Volto core itself. +This chapter describes the most common use cases for internationalization when developing your {doc}`../development/add-ons/index` or contributing to the Volto core itself. ## Process and file structure overview diff --git a/docs/source/development/index.md b/docs/source/development/index.md index a1e5952999..b0e1d64b1a 100644 --- a/docs/source/development/index.md +++ b/docs/source/development/index.md @@ -21,6 +21,7 @@ Or jump in to any topic listed below. overview creating-project +add-ons/index folder-structure environment-variables customizing-components diff --git a/docs/source/index.md b/docs/source/index.md index 2b47ebce16..e96dcc0e1d 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -71,6 +71,7 @@ tutorials/index contributing/index release-notes/index release-management-notes/index +conceptual-guides/index ``` % Only check change log entries in Volto documentation—not when it is included in the main Plone documentation—to ensure links work and do not redirect. diff --git a/docs/source/theming/index.md b/docs/source/theming/index.md index fe401772f3..e2577b2c31 100644 --- a/docs/source/theming/index.md +++ b/docs/source/theming/index.md @@ -9,6 +9,10 @@ myst: # Theming +This section of the documentation describes theming in Volto. + + +## Conceptual guides ```{toctree} :maxdepth: 1 @@ -17,7 +21,19 @@ about-semantic semanticui-theming theming-engine theming-strategy +``` + + +## How-to guides + +```{toctree} +:maxdepth: 1 + custom-styling using-third-party-themes theming-a-base-theme ``` + +```{seealso} +For how to create your theme as an add-on in Volto 18 and later, see {doc}`../development/add-ons/theme`. +``` diff --git a/packages/volto/news/6397.documentation b/packages/volto/news/6397.documentation new file mode 100644 index 0000000000..21135b8b4a --- /dev/null +++ b/packages/volto/news/6397.documentation @@ -0,0 +1 @@ +Overhaul and update of the add-ons section in documentation. @sneridagh