--- url: /5.x/intro.md --- # Intro *** A blazing fast headless frontend for your Magento 2 webshop! Easy customisations from Laravel Blade templates with renderless Vue components. Catalog data comes from the Magento database through Laravel Eloquent for speed, everything else is using GraphQL. For filters and searching we're using InstantSearch and indexation is handled with Laravel Scout. --- --- url: /5.x/installation.md description: >- The Rapidez requirements and install instructions including info on CORS in Magento/Elasticsearch/OpenSearch, Multistore Nginx configuration, Magento 2 demo shop in Docker and the Rapidez checkout standalone. --- # Installation *** \[\[toc]] ## Requirements * [Laravel requirements](https://laravel.com/docs/12.x/deployment#server-requirements) * PHP >= 8.2 * MySQL >= 8.0.28 * Elasticsearch Basic >= 8.11, or OpenSearch ^2 * Magento >= 2.4.8 ([or use a demo shop](#demo-magento-2-webshop)) ::: tip Elasticsearch Basic There are multiple Elasticsearch versions and licenses, see the [subscriptions page](https://www.elastic.co/subscriptions). Rapidez requires at least the basic version which is free but not always installed by default. Make sure you use the "full" or "non-OSS" version otherwise you'll get "no handler for type flattened" errors while [indexing](indexer.md). ::: ## Install Rapidez 1. **Start a fresh project** ``` composer create-project rapidez/rapidez:^5.0 yourproject ``` 2. **Add the Magento 2 and search credentials** When you have a Magento 2 installation running, add the URL and database credentials to the `.env`. You may also need to set your ElasticSearch/OpenSearch URL and credentials. Have a look at the [configuration docs](configuration.md) for all options. If you don't have a Magento installation running yet, we'll set one up for you with Docker from the install command. This will also set up an ElasticSearch container for you. 3. **Run the install command** ``` php artisan rapidez:install ``` 4. **Explore** Visit Rapidez within your browser! πŸš€ Plenty of options there: * [Laravel Herd](https://herd.laravel.com/) * [Laravel Valet](https://laravel.com/docs/12.x/valet) * `php artisan serve` *** ::: details Use in an existing Laravel project The `rapidez/rapidez` repository contains a fresh Laravel installation with the Rapidez Core and some other packages installed to give you a good start. If you have an existing project or want to start from scratch, you can install the Rapidez packages yourself. Just have a look at the `composer.json` ::: ::: tip Magento module Rapidez has a Companion Magento module available called [Rapidez\_Compadre](https://github.com/rapidez/magento2-compadre) This is in no way required but adds additional functionality that we found lacking in Magento. Current functionality can be found [here](https://github.com/rapidez/magento2-compadre#current-functionality) ::: ::: warning Product image speed The first time product images will load slowly as they are resized on the fly. Have a look at the [Image Resizer](https://github.com/rapidez/image-resizer) package to read how this is working. ::: ## CORS ### Magento Rapidez is making AJAX requests to the Magento API which requires CORS to be opened. * You have to restrict this to your domain within your webserver configuration or with, for example, this [Magento 2 CORS module](https://github.com/graycoreio/magento2-cors). When using the above module the following will probably not be necessary. * Using Laravel Valet you can use [this](https://gist.github.com/poul-kg/b669a76fc27afcc31012aa0b0e34f738) and for Valet+, see [this](https://github.com/weprovide/valet-plus/issues/493). * With the Docker Magento installation, it's already opened [with a patch](https://github.com/michielgerritsen/magento2-extension-integration-test/blob/master/magento/patches/cors.patch). ### Elasticsearch If you're using your own Elasticsearch installation, you have to open CORS in `elasticsearch.yml` and restart Elasticsearch. An example can be found in the project: [`elasticsearch.yml`](https://github.com/rapidez/rapidez/blob/master/elasticsearch.yml). That configuration is used when you're using Elasticsearch from the [Docker Compose config](https://github.com/rapidez/rapidez/blob/master/docker-compose.yml). ### OpenSearch If you're using your own OpenSearch installation you will have to open CORS in `opensearch.yml` see: [Secure OpenSearch](deployment.md#secure-opensearch). ## Multistore When you have set up multiple stores in Magento, Rapidez needs to know which store to show. Rapidez listens to the `MAGE_RUN_CODE` like Magento does. You will have to set that variable from your webserver. With Nginx, you could use a map, for example: ```nginx map $http_host $MAGE_RUN_CODE { default default_store_code; second-store.com second_store_code; third-store.com third_store_code; } ``` And pass that to PHP-FPM: ```nginx fastcgi_param MAGE_RUN_CODE $MAGE_RUN_CODE; ``` ## Demo Magento 2 webshop If you do not have a Magento 2 installation yet, you want to test Rapidez, or like to develop with a fresh Magento 2 installation, you can use a Magento 2 and Elasticsearch installation in a Docker container. ::: tip Make sure Docker can use at least 4GB of memory! ::: If you have no webserver running on your laptop (or simply want to use a self-contained version of the project) consider using `.env.example.traefik` as your `.env`. This will use [Traefik](https://traefik.io/traefik) to make available without any configuration necessary. Otherwise opt for `.env.example.docker` for your `.env`. This will give you the reins, exposing the websites on the ports defined in the `.env.example.docker` (by default, magento: , rapidez: , elasticsearch: ) ```bash docker-compose up -d docker exec rapidez_magento magerun2 indexer:reindex ``` Edit the `.env` ```dotenv MAGENTO_URL=http://localhost:1234 DB_PORT=3307 DB_DATABASE=magento DB_USERNAME=magento DB_PASSWORD=password ``` ## Standalone Checkout Are you not ready to fully commit to Rapidez just yet? We support running it as a standalone checkout as well. More information can be found in the [Standalone Checkout Blog](https://rapidez.io/blog/standalone-checkout). You will need to install [rapidez/magento2-standalone-checkout](https://github.com/rapidez/magento2-standalone-checkout) within Magento: ```bash composer install rapidez/magento2-standalone-checkout bin/magento module:enable Rapidez_StandaloneCheckout ``` Then you can activate it by setting your Rapidez base url under: Stores > Configuration > Rapidez > Standalone Checkout > Rapidez Url ### Credential storage Rapidez temporarily needs to store the customers' cart mask and customer token, we save this in cache. This is usually not a problem, however if the cache gets cleared often this can lead to annoyances if the customer needs to press the checkout button multiple times. You can select a different cache store to work around this with the following ENV variable: `STANDALONE_CHECKOUT_CACHE_STORE`. We suggest copying the [Redis store in config/cache.php](https://github.com/laravel/framework/blob/79b44b168da164191950aab79ba1689f0087ccda/config/cache.php#L74) to something like `redis-persistent` and setting the connection to `default` instead of `cache`. ### Nginx Since you're using standalone checkout you might not want to expose the other urls. To do so you can use this nginx rule: ```nginx # Redirect non-standalone-checkout urls to Magento. location ~* ^\/(?!(api|healthcheck|checkout.*|mollie.+|paynl.*|msp-return)(\/|$)) { return 308 $scheme://$request_uri; } ``` --- --- url: /5.x/configuration.md description: >- How to configure Rapidez, where the configuration can be found and how the configuration works with the Magento config. --- # Configuration *** You can publish all of the Rapidez config files with: ``` php artisan vendor:publish --provider="Rapidez\Core\RapidezServiceProvider" --tag="config" ``` ::: tip Note It's usually a good idea to only copy over the config files that you need to change. This can help with future updates that may end up changing things in these config files. ::: After that, you'll find all configuration options in `config/rapidez/*.php` with comments explaining the options. Most of them use the `env()` function, so it's possible to have different configurations per environment in the `.env`. ::: tip For more info on how the configuration works, read the [Laravel configuration docs](https://laravel.com/docs/12.x/configuration). ::: *** \[\[toc]] ## Multistore When you need a different configuration for a specific store, you can create a store specific configuration file: `/config/rapidez/stores/{store_code}/{config_file}` with only the changes you need compared with the default. For example, when store code `de` needs different checkout steps; create `/config/rapidez/stores/de/frontend.php` with: ```php [ 'onestep' ], ]; ``` ## Magento configuration Some Magento configuration options involve the frontend. As the Magento frontend is not used, most of them don't do anything. Rapidez listens to some of those configurations, they are listed here: [`config/rapidez/magento-defaults.php`](https://github.com/rapidez/core/blob/master/config/rapidez/magento-defaults.php) with their defaults as not all of those can be queried from Magento. If you need to access a Magento configuration, you can use the [`@config` Blade Directive](theming.html#config) or the Rapidez facade `Rapidez::config()`, which accepts the same parameters as the directive. For more advanced usage (like defining which scope), you can use: ```php \Rapidez\Core\Models\Config::getValue($path, ConfigScopes::SCOPE_STORE, $scopeId, $options) ``` ## Base URL Rapidez will become the frontend, Magento will only be used for the backend. Because of this, they'll need two different URLs. We suggest setting the `web/secure/base_url` differently per scope. The store scope should point to your Rapidez url, and the website scope should point to your Magento url: * **Store View:** `https://webshop.com` * **Website:** `https://magento.webshop.com` If you have set this correctly, you can set `GET_MAGENTO_URL_FROM_DATABASE=true` in the Rapidez `.env`, and it will automatically apply the URLs in Rapidez itself. ## Forgot password email You have to change the URL in the forgot password email to the Rapidez URL: ```diff - {{var this.getUrl($store,'customer/account/createPassword/',[_query:[token:$customer.rp_token],_nosid:1])}} + https://your-rapidez-url.com/resetpassword?token={{var customer.rp_token}} ``` ### Alternatively If you [set the store view "Base URL" to that of your Rapidez installation](configuration.html#base-url), you can keep the `getUrl()` to dynamically determine the URL. ```diff - {{var this.getUrl($store,'customer/account/createPassword/',[_query:[token:$customer.rp_token],_nosid:1])}} + {{var this.getUrl($store,'resetpassword',[_query:[token:$customer.rp_token],_nosid:1])}} ``` ## Customer Token Lifetime By default, the customer token lifetime is set to 1 hour in Magento, so a customer needs to log in again when the token expires in Rapidez. It's recommended to raise the expiration to, for example, 24 hours. Check these settings in the store configuration: * Services > OAuth > Access Token Expiration * Services > Magento Web API > JWT Authentication > Customer JWT Expires In ## Customer Data Lifetime When using the Standalone Checkout the default Customer Data Lifetime of 1 hour may result in the customer and/or their cart being removed in Magento. It's recommended to raise the lifetime to, for example, 3 hours. Check this setting in the store configuration: * Customers > Customer configuration > Online Customers Options > Customer Data Lifetime ## Enable Guest Checkout Login You can find this setting in the store configuration: * Sales > Checkout > Checkout Options > Enable Guest Checkout Login But keep in mind as also mentioned there: > Enabling this setting will allow unauthenticated users to query if an e-mail address is already associated with a customer account. This can be used to enhance the checkout workflow for guests that do not realize they already have an account but comes at the cost of exposing information to unauthenticated users. ## Robots.txt Rapidez handles the `robots.txt` route through Laravel, and can be edited by overwriting the `resources/views/robots.blade.php` view. Because this is a Laravel Blade file, you can use blade logic to do anything you want. You can also manage part of the content through Magento. Rapidez includes the Magento robots.txt instructions by default. ::: warning 404 response Depending on your webserver configuration, this route could end up returning a 404 response. For example, [Laravel Forge](https://forge.laravel.com/) and [Laravel Valet](https://laravel.com/docs/12.x/valet) do include a line causing this as the file could not be found: ``` location = /robots.txt { access_log off; log_not_found off; } ``` The trick is to remove this line or extend it, depending on if you want the other log configurations: ``` location = /robots.txt { access_log off; log_not_found off; try_files $uri $uri/ /index.php?$query_string; } ``` ::: ::: tip Make sure you double check what's in Magento's robots.txt instructions on your site. There may be a bunch of `disallow` routes that are no longer necessary! ::: ## Elasticsearch To communicate with Elasticsearch, Rapidez is using the [rapidez/laravel-scout-elasticsearch](https://github.com/rapidez/laravel-scout-elasticsearch) package. If you need to change the Elasticsearch credentials, you can do so with these `.env` configurations: ```dotenv ELASTICSEARCH_HOST=http://localhost:9200 ELASTICSEARCH_USER= ELASTICSEARCH_PASSWORD= ELASTICSEARCH_SSL_VERIFICATION=true ``` You will also need to set a prefix for your indices: ```dotenv SCOUT_PREFIX= ``` ## OpenSearch For OpenSearch the same applies as [Elasticsearch](#elasticsearch) but you need to add this in your `.env`: ```dotenv SCOUT_SEARCH_BACKEND=opensearch ``` ## Early hints Rapidez comes with [Early Hints](https://github.com/justbetter/laravel-http3earlyhints) built in. You should enable your early hints in [Cloudflare](https://developers.cloudflare.com/cache/advanced-configuration/early-hints/#enable-early-hints) or your CDN for a boost. Anything loaded with `@vite()` will properly get loaded with early hints, it will also discover other elements on your page which could benefit from early hints. Like your `preload` and `preconnect` links, eager-loaded images, blocking scripts, and blocking styles. You can disable it with: ```dotenv EARLY_HINTS_ENABLED=false ``` ::: tip You should [preload your custom fonts](https://web.dev/articles/codelab-preload-web-fonts) if they're used during the initial render. ::: --- --- url: /5.x/upgrading.md description: >- Upgrade info from Rapidez v4 to Rapidez v5 including all changes like the removal of the Magento flat tables dependency, the Vue 3 and Tailwind 4 upgrade, new features like customer group pricing, tier pricing, product media videos and cache tags with step-by-step info on how to upgrade. There is also a prompt to automate the upgrade. --- # Upgrading *** \[\[toc]] ## Rapidez v5 In this release, we removed the [flat tables dependency](https://github.com/rapidez/core/pull/943)! πŸš€ And we also updated to [Vue 3](https://github.com/rapidez/core/pull/951) and [Tailwind 4](https://github.com/rapidez/core/pull/1211) 🀘🏻 We also added support for: * [Customer group pricing](https://github.com/rapidez/core/pull/1074) * [Tier pricing](https://github.com/rapidez/core/pull/1205) * [Product media videos](https://github.com/rapidez/core/pull/1128) * [Cache tags](https://github.com/rapidez/core/pull/1103) Other changes included in this release: * [Drop support for Laravel 11](https://github.com/rapidez/core/pull/1096) Some of these are quite substantially breaking changes. On this page we'll mention the most important things that you will need to do to upgrade. You should review [all template/config changes](https://github.com/rapidez/core/compare/4.x..master) and check the [full changelog](https://github.com/rapidez/core/releases). :::tip For any major update, you should be aware that all overwritten Blade, Vue, and PHP files will need to be checked. Checking the above diff is the quickest way to tell where you need to make changes. ::: ## Composer dependencies Check all your dependencies one by one to see if they're compatible and what has changed in changelogs / release notes. To get a nice overview, run the following command: ```bash composer outdated ``` ## Vue 3 ### Dependencies 1. **Remove** ```bash yarn remove @vitejs/plugin-vue2 vue-clickaway vue2-teleport vue-template-compiler ``` 2. **Install** ```bash yarn add -D @vitejs/plugin-vue vue3-click-away ``` And update your `vite.config.js` ```diff import path from 'path' import { defineConfig } from 'vite' import laravel from 'laravel-vite-plugin' - import vue from '@vitejs/plugin-vue2' + import vue from '@vitejs/plugin-vue' import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ ... alias: { '@': path.resolve(__dirname, './resources/js'), Vendor: path.resolve(__dirname, './vendor'), - vue: path.resolve(__dirname, './node_modules/vue/dist/vue.esm.js'), + vue: 'vue/dist/vue.esm-bundler.js', }, }, }) ``` 3. **Upgrade the other dependencies** ```bash yarn add -D @vueuse/core @vueuse/integrations cross-env instantsearch.js laravel-vite-plugin vite vue ``` 4. **Upgrade your scripts and templates** This will require some work but we've got a list of things to check. Please read the [Vue 3 migration guide](https://v3-migration.vuejs.org/). Some recurring themes we've run into: * [Dropped support for global $emit and $on](https://v3-migration.vuejs.org/breaking-changes/events-api#event-bus), For this we've [built a system on regular events](https://github.com/rapidez/core/blob/cecc136e5ef76dc4925186a332f875d3ffe658fe/resources/js/polyfills/emit.js#L18). You should replace `$root.$on`/`window.app.$on` with `window.$on`. * Check for any `v-if`s in combination with `v-for`, see: [If used on the same element, v-if will have higher precedence than v-for](https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html) * Rename all `slot-scope` to `v-slot` and move them to the component in your template * The `price`, `truncate` and `url` filters no longer work, replace `@{{ final_price | price }}` with `@{{ price(final_price) }}` as well as `$options.filters.price(final_price)` with `price(final_price)` * Any custom async components must be wrapped by `defineAsyncComponent`: `() => import('...')` to `defineAsyncComponent(() => import('...'))` * `window.app` no longer contains the custom variables. Replace `window.app.<...>` e.g. `cart` with `window.app.config.globalProperties.<...>` or if you’re within vue templates directly do `<...>` * When inside js functions all computed (refs) must be retrieved with `.value`. Examples are: `cart.value`, `user.value`, `token.value`, `mask.value`, `loading.value` * All calls to `Vue.set` calls should be removed, and replaced by setting the variable directly. 5. **Build** ```bash yarn build ``` :::tip We recommend to double check all frontend dependencies with `yarn outdated`. ::: ## Tailwind v4 1. **Migrate to Vite** If you are using Vite, we recommend migrating from the PostCSS plugin. Add this to your `vite.config.js`: ```diff + import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ ... plugins: [ + tailwindcss(), ] }) ``` Then you need to install `@tailwindcss/vite`: ```bash yarn add -D @tailwindcss/vite ``` 2. **Update to Tailwind CSS 4** Tailwind CSS has a built-in upgrade tool that can be found in the [Tailwind CSS docs](https://tailwindcss.com/docs/upgrade-guide#using-the-upgrade-tool). ```bash npx @tailwindcss/upgrade ``` It is still necessary to review the changes that are made when the upgrade is done. Check if all [deprecated utilities](https://tailwindcss.com/docs/upgrade-guide#removed-deprecated-utilities) are changed. ```diff - class="bg-black bg-opacity-50 flex-shrink-0" + class="bg-black/50 shrink-0" ``` Also check the [renamed utilities](https://tailwindcss.com/docs/upgrade-guide#renamed-utilities) ```diff - class="ring outline-none" + class="ring-3 outline-hidden" ``` 3. **Custom classes** Since Rapidez v3, we have made changes to use [default colors](https://docs.rapidez.io/3.x/theming.html#colors), so we can use classes like `bg`, `text`, or `border`. In Rapidez v5, we removed the colors from the `rapidez/core` and added them inside the [blade components](https://github.com/rapidez/blade-components/blob/master/resources/css/components/rapidez.css). 4. **Build** ```bash yarn build ``` :::tip We recommend to double check all changes that are made after the update. ::: ## Flat tables This update gets rid of the dependency on the Magento flat tables. This means that they can be disabled entirely (both the product and category tables) in your magento configuration, which should speed up the indexing process in Magento substantially. However, this also means that any and all custom queries, scopes, relationships, custom attributes, and model overwrites may need to be upgraded. ### Scopes Any custom scopes added to product or category models that affect the SQL query directly (e.g. with a join) will need to be checked. In some cases they will need to be completely refactored (For example: see [this package update](https://github.com/rapidez/amasty-automatic-related-products/compare/4.0.0...5.0.0)). ### Relationships Relationships that relate to `entity_id` (or also `sku` on the product model) will be unaffected. ### Attribute changes The base product and category models now query the `catalog_product_entity` and `catalog_category_entity` tables. All of the attributes get added by using relations, which means you won't have direct access to attribute data in queries anymore. We have created a `whereAttribute` helper for this. For example: ```diff -Product::where('color', 'red')->get(); +Product::whereAttribute('color', 'red')->get(); ``` The way you retrieve attribute values from a product or category model has also changed somewhat. Where previously you would retrieve values directly from the object, you should now use one of the `->label()`, `->value()`, and `->raw()` functions. For product specifications, updating this may look something like: ```diff -
{{ $product->climate }}
+
{{ $product->label('climate') }}
``` Note that the `->value()` function will be used by default, which will return the actual value as given in the database. This means that text attributes and numeric attributes will usually not need to be updated. For example, `$product->name` doesn't need to be changed. This change should only be relevant for attributes where the displayed value comes from the `eav_attribute_option_value` table. ### Overwrites If you've overwritten any model classes that relate to products/categories or the `ProductController` class: these files have changed significantly. You should migrate these carefully by looking at the full diff of these models. Frontend overwrites will also need to be checked individually. You should look at the diff linked at the start of this page. ## Other changes ### Variables on the product model These specific variables have been removed and should be replaced like so: | Variable | Replacement | Extra info | |---|---|---| | `->min_sale_qty` | `->stock->min_sale_qty` | | | `->max_sale_qty` | `->stock->max_sale_qty` | | | `->qty_increments` | `->stock->qty_increments` | | | `->in_stock` | `->stock->is_in_stock` | | | `->upsell_ids` | `->upsells()->pluck('linked_product_id')` | Depending on the situation, using `->upsells->pluck('linked_product_id')` may be more fitting. | | `->relation_ids` | `->relationProducts()->pluck('linked_product_id')` | Same as above | | `->grouped` | `->children` | These returned the same list already, this is just for simplicity. | | `->images` | `->media` | This is no longer an array of image urls, but with an 'image' key. | | `->reviews_count` | `->reviewSummary->reviews_count` | | | `->reviews_score` | `->reviewSummary->reviews_score` | | Also check these values in javascript syntax (instead of `->` checking `.`). ### Translation strings A few translations strings have been changed and added: | Old | New | |---|---| | Firstname | First name | | Middlename | Middle name | | Lastname | Last name | | Housenumber | House number | | Added | |---| | Show more | Translation files, as well as usage of these translations, should be changed accordingly. ## Using AI? ::: details Using AI? This prompt will give you a head start! Do still check everything manually. This is a large prompt, we suggest using tools like [Chief](https://chiefloop.com/) to turn it into separate tasks so AI has a smaller chance of making mistakes. ````markdown # Rapidez v5 Upgrade Prompt You are an expert Laravel, Magento, Vue, and Tailwind developer tasked with upgrading a Rapidez project to v5. Use this as a strict migration checklist and implementation guide. Favor concrete code changes over high-level advice. ## Goal Upgrade a Rapidez project to v5, including: - Vue 2 to Vue 3 migration - Tailwind CSS v4 migration - Flat table removal compatibility updates - Product model API changes - Translation string updates ## Important Context - Rapidez v5 removes dependency on Magento flat tables - Rapidez v5 uses Vue 3 - Rapidez v5 uses Tailwind CSS 4 - All overwritten Blade, Vue, and PHP files must be reviewed - Review template/config diff: https://github.com/rapidez/core/compare/4.x..master - Review full releases/changelog (for 5.x versions): https://github.com/rapidez/core/releases ## 1. Dependency Upgrade Sequence ### 1.1 Composer dependencies Run and evaluate all incompatible packages: ```bash composer outdated ``` ### 1.2 Frontend dependencies for Vue 3 Remove Vue 2 packages: ```bash yarn remove @vitejs/plugin-vue2 vue-clickaway vue2-teleport vue-template-compiler ``` Install Vue 3 packages: ```bash yarn add -D @vitejs/plugin-vue vue3-click-away ``` Upgrade related frontend dependencies: ```bash yarn add -D @vueuse/core @vueuse/integrations cross-env instantsearch.js laravel-vite-plugin vite vue ``` ## 2. Vite Configuration Changes Update `vite.config.js` for Vue 3 and Tailwind v4. ```diff import path from 'path' import { defineConfig } from 'vite' import laravel from 'laravel-vite-plugin' - import vue from '@vitejs/plugin-vue2' + import vue from '@vitejs/plugin-vue' + import tailwindcss from '@tailwindcss/vite' import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ plugins: [ + tailwindcss(), laravel({ input: [...], refresh: true, }), vue(), ], resolve: { alias: { '@': path.resolve(__dirname, './resources/js'), Vendor: path.resolve(__dirname, './vendor'), - vue: path.resolve(__dirname, './node_modules/vue/dist/vue.esm.js'), + vue: 'vue/dist/vue.esm-bundler.js', }, }, }) ``` Install Tailwind Vite plugin: ```bash yarn add -D @tailwindcss/vite ``` ## 3. Vue 2 to Vue 3 Migration Rules Apply these across all Blade and Vue customizations. ### 3.1 Slots: `slot-scope` to `v-slot` ```vue
``` If a `