Edit in GitHubLog an issue

Extension development

Extensions provide new storefront functionality, extend existing components, or replace different storefront parts.


PWA Studio merges third-party code into the final application bundle to build web functionality on top of a base storefront. The extensibility framework provided by the pwa-buildpack package lets you create these third-party extensions for PWA Studio storefronts, such as Venia.

Extensions can change the behavior of existing components, add new features, or even provide translations. Language packs are a specific extension type which provide translation data for the internationalization feature.

Project manifest file#

PWA Studio extensions are Node packages, which means it requires a package.json file. The package.json file is the project manifest. It contains metadata about the project, such as the name, entry point, and dependencies.

You can manually create this file, but we recommend using the CLI command yarn init or npm init in your project directory. Running either command launches an interactive questionnaire to help you fill in your project metadata.

Example manifest file#

The following is an example package.json file for an extension called my-extension. It contains both an intercept and declare file under the src/targets directory.

Copied to your clipboard
2 "name": "my-extension",
3 "version": "1.0.0",
4 "description": "An example extension package",
5 "main": "src/myList.js",
6 "license": "MIT",
7 "peerDependencies": {
8 "react": "^17.0.1"
9 },
10 "pwa-studio": {
11 "targets": {
12 "intercept": "src/targets/my-intercept",
13 "declare": "src/targets/my-declare"
14 }
15 }

Intercept and declare files#

Extensions use intercept and declare files to interact with the extensibility framework. You can create these files anywhere in your project. The pwa-studio.targets.intercept and pwa-studio.targets.declare values in the package.json file point to the locations for these files.

For more information about these files, see the extensibility framework topic.

Create an extension's API#

Storefront developers can use Targetables to change the behavior of your extensions, but Targets are the formal API for modules and extensions. They are also the only way other third-party extensions can intercept and use your extension's API.

Declare a Target#

Extensions declare their own Targets for interception through the declare file. Declare files export a function that receives a TargetProvider object. The TargetProvider object has a declare() function that accepts a dictionary object of named Targets. The TargetProvider also provides a utility collection called types, which holds all the legal constructors for Targets.

Example for declaring a target#

The following is an example of code in a declare file that exposes a myListContent target:

Copied to your clipboard
1// src/targets/my-declare.js
3module.exports = (targets) => {
4 targets.declare({
5 myListContent: new targets.types.SyncWaterfall(["myListContent"]),
6 });

The type for this Target is SyncWaterfall. These Target types run their interceptors synchronously and in subscription order. After that, they pass the return value as an argument to the next interceptor.

For more information on different Target types, see the documentation for Hook types in the Tapable library.

Define the API#

The purpose of an extension's API is to provide functions that perform specific and predictable code transformations to files within the extension. Use the tools provided by the extensibility framework to define the extension's API in the project's intercept file.

Example for defining the API#

The following example defines the myListContent target API from the previous example:

Copied to your clipboard
3// Get the Targetables manager
4const { Targetables } = require("@magento/pwa-buildpack");
6module.exports = (targets) => {
7 // Create a Targetables factory bound to the TargetProvider (targets)
8 const targetables = Targetables.using(targets);
10 // Tell the build process to use an esModules loader for this extension
11 targetables.setSpecialFeatures("esModules");
13 // Create a TargetableModule instance representing the myList.js file
14 // And provide it a TargetablePublisher to define the API
15 targetables.module("my-extension/src/myList.js", {
16 // Provide a publish() function that accepts the extension's TargetProvider
17 // and an instance of this TargetableModule
18 publish(myTargets, self) {
19 // Define the Target's API
20 const myListContentAPI = {
21 // Define an `addContent()` function for the API
22 addContent(content) {
23 // Use the `insertBeforeSource()` function to make source code changes
24 self.insertBeforeSource(
25 "]; // List content data",
26 `\n\t\t"${content}",`
27 );
28 },
29 };
30 // Connect the API to the `myListContent` target
31 myTargets.myListContent.call(myListContentAPI);
32 },
33 });

For more information on the Targetables API used in this example, see the following reference pages:

The API the myListContent target publishes contains an addContent() function that makes modifications to the src/myList.js file. The content for src/myList.js is as follows:

Copied to your clipboard
1import React from "react";
3const MyList = () => {
4 const listContentData = []; // List content data
6 const renderedContent = listContentData.map((content) => {
7 return <li key={content}>{content}</li>;
8 });
10 return <ul>{renderedContent}</ul>;
13export default MyList;

Access an extension's API#

Using the MyList component in your storefront with no modifications renders an empty list. To add content, the storefront project or a third party extension must intercept and tap into the myListContent target to access the API.

The following shows how a storefront or third part extension can access and use that API in their intercept file:

Copied to your clipboard
1// intercept.js
3const { Targetables } = require("@magento/pwa-buildpack");
5function localIntercept(targets) {
6 const targetables = Targetables.using(targets);
8 targets.of("my-extension").myListContent.tap((api) => {
9 api.addContent("Hello");
10 api.addContent("World");
11 });
14module.exports = localIntercept;

Now, when the MyList component renders, it contains the two list entries added through the API.

Project dependencies#

If your extension needs third-party libraries, you can add them as dependencies. PWA Studio extensions are Node packages, so most of their dependencies should be peer dependencies. Storefront developers should make sure their project has the dependencies an extension requires. This safeguards against duplicate copies of the same library in the final application bundle.

Install and test locally#

To install and test your extension on a local storefront project, add the extension as a local dependency or list it as a build dependency.

Adding as a local dependency#

The package.json file lets you specify a local path instead of a version for a dependency. This tells the package manager to install that package from that local path instead of searching online. A local dependency in your storefront project's package.json file looks like the following:

Copied to your clipboard
2 "dependencies": {
3 "my-extension": "file:../relative/path/to/my-extension"
4 }

Use the yarn or npm command to add this entry to the package.json file:

Copied to your clipboard
yarn add file:../relative/path/to/my-extension
Copied to your clipboard
npm install -S ../relative/path/to/my-extension

Adding as a build dependency#

Buildpack provides an alternate way of installing a local extensions by linking it to Yarn's or NPM's global package set and listing it in the BUILDBUS_DEPS_ADDITIONAL environment variable.

Use the yarn link or npm link command in your extension project to symlink it to the global package set.

In your storefront project, run yarn link <package-name> or npm link <package-name> to link the two packages together. This lets Node and Webpack resolve your extension from the storefront project without adding it as an entry in the dependency array.

Edit your storefront project's .env file and add your extension's name to the comma-separated value for BUILDBUS_DEPS_ADDITIONAL. This tells the build process that it should check these packages for intercept and declare files.

Next steps

  • Privacy
  • Terms of Use
  • Do not sell my personal information
  • AdChoices
Copyright © 2022 Adobe. All rights reserved.