In our previous article we talked a bit about the history of Magento’s architecture and where/how the new PWA Studio fits in. Today we’re going to take a quick tour of those tools to see what we have at our disposal. We’ll be touching on some technical details involving React — if you’ve never used this technology before you may want to review our primer.
If you want to use Magento’s PWA tools your path forward isn’t clear. The documentation that exists (as of this writing) is incomplete and often misleading. Also — the “PWA Studio” project is a loose collection of NPM packages. It’s not a single thing you can grab and install.
Also also — I am, at this point and at best, a PWA Studio hobbyist. I may have some details wrong and my hot takes are from an oven that’s still pre-heating. Corrections via Twitter or Email are encouraged and appreciated.
The Code
Magento’s PWA Studio code is hosted in a single GitHub repository. This monorepo is managed with the lerna project. A monorepo is when the source code for multiple public packages is hosted in a single repository. The packages that make up PWA Studio are
- @magento/create-pwa
- …
- @magento/pwa-buildpack
- @magento/babel-preset-peregrine
- …
- @magento/upward-js
- @magento/upward-spec
- …
- @magento/peregrine
- @magento/venia-concept
- @magento/venia-ui
- …
- graphql-cli-validate-magento-pwa-queries
- @magento/pagebuilder
You can see the source code for these packages in the repo.
Getting Started
PWA Studio gives you tools to build a React application that will read data from a running Magento 2 instance via an UPWARD web server. This means if you want to use PWA Studio you need to have an Magento 2 instance running somewhere.
In December of 2020, I’d recommend you start with the latest version of Magento 2.4. Older versions of Magento are tied to older versions of PWA Studio (here’s a version matrix), and older versions of PWA Studio have fewer and buggier features. Also, Magento’s PWA Studio uses the relatively new GraphQL APIs provided by Magento 2, and early versions of Magento’s GraphQL implementation were buggy as well.
If you are going to try and use an older version — it’s important to remember that individual PWA Studio packages don’t use the same PWA Studio version number. For example, the @magento/pwa-buildpack package that shipped with PWA Studio 8 is actually version 7.x of the package.
There’s a few Magento docs that recommend you use the @magento/create-pwa package to get started with PWA Studio. This package implements a simple command line program that collects a few input variables from you, and then uses them to run the following command
$ buildpack create-project \
# the folder where you want buildpack ot put your scafolding project
/path/to/a/folder \
# the PWA template we'll base your project on (see below)
--template "venia-concept"\
# The name of your project -- placed in the generated package.json
--name "your-project"\
# The project's author -- placed in the generated package.json
--author "Alan Storm <astorm@alanstorm.com>"\
# The url of the Magento system that will be the backend
# for thi PWA Studio applicaton -- placed in the generated .env
--backend-url "https://master-7rqtwti-mfwmkrjfqvbjk.us-4.magentosite.cloud/"\
# the braintree payment token for PWA Checkout, placed in .env
--braintree-token "sandbox_8yrzsvtm_s2bg8fs563crhqzk"\
--npm-client "npm"\
--no-install
The @magento/create-pwa package seems like it’s intended to demo PWA Studio, and not be a part of a developer’s normal workflow. By default, it hooks your PWA Studio scaffolding application up to a demo instance of Magento 2 hosted in a Magento’s cloud product. You can also instruct the command to point at your own Magento instance.
PWA Buildpack
As a developer the first, and most important package you want is @magento/pwa-buildpack. This package provides all the tooling you need for a PWA development enviornment, including the buildpack
command line script mentioned above.
Let’s install @magento/pwa-buildpack as a global npm package
$ npm install -g @magento/pwa-buildpack
If you’re not familiar with global NPM packages (-g
), a properly installed version of Node.js includes a global node_modules
folder. When you use the -g
flag this tells NPM to install a package in your global node_modules
folder. This central location’s node_modules/.bin
folder should (via your Node Installer) already be in your unix $PATH
, which means any bin
binaries provided by the package will be available for you to run. This include the above buildpack
command. Give it a try
$ buildpack --version
7.0.0
$ which buildpack
/path/to/.nvm/versions/node/v14.2.0/bin/buildpack
Whether you use @magento/create-pwa or invoke the buildpack
command directly, this invocation
$ buildpack create-project /path/to/a/folder \
--template "venia-concept"\
--name "your-project"\
--author "Alan Storm <astorm@alanstorm.com>"\
--backend-url "https://master-7rqtwti-mfwmkrjfqvbjk.us-4.magentosite.cloud/"\
--braintree-token "sandbox_8yrzsvtm_s2bg8fs563crhqzk"\
--npm-client "npm"\
--no-install
will create a new scaffolding project for you. This project will contain a package.json
file with everything you need to run a PWA Studio React application, as well as some extra scaffolding and support files. It will also contain an .env
configuration file that’s already pointing at the Magento URL you provided above.
If you used the --no-install
option after running the above command, you’ll need to manually run npm install
.
$ cd /path/to/a/folder
$ npm install
Template Projects
The create-project
sub-command works by copying a template project. Above that’s the venia-concept
template configured via --template
. The template project is the code stored in another NPM package — @magento/venia-concept (souce code here).
In theory the --template
option should allow you to choose other templates — however as of this writing this option is hard coded to only accept the value venia-concept
, and this value is later mapped to the @magento/venia-concept package. This is a strange choice that we will not dwell on.
The create-project
sub-command copies this template’s source to the directory you indicated and makes some adjustments to it based on your parameters. This should leave you with a ready to run system.
After you run the command, you’ll have a bunch of files in a folder that look like this
$ ls -a /path/to/a/folder
.editorconfig
.env
.eslintignore
.eslintrc.js
.gitignore
.graphqlconfig
README.md
babel.config.js
deployVeniaSampleData.sh
local-intercept.js
package.json
prettier.config.js
src
template.html
upward.yml
webpack.config.js
Sample Data
If you’re interested in demoing the Venia project and you’re not using Magento’s demo cloud instance (i.e. the --backend-url
flag was set to point at your own Magento development system) — you’ll want to run the deployVeniaSampleData.sh
shell script.
This shell script will setup the sample data package in your Magento store. Remember, PWA Studio uses your Magento 2 system as a datasource. This @magento/venia-concept sample project expects certain specific data to be there. If this is your first time using the project you’ll want to install this data.
$ chmox +x ./deployVeniaSampleData.sh # the shell script doesn't ship
# with execute permission.
$ ./deployVeniaSampleData.sh
Please specify absolute path to your Magento 2 instance
Magento root folder:
This command will
- Ask you for the root folder of your Magento 2 project
- Add a number of sample data composer packages to your project’s
composer.json
file - Update your composer dependencies to install those packages .
At the time of this writing, you’ll need to manually run
$ ./bin/magento setup:resource
from your Magento instance to get the sample data into your system. Just in case it’s not obvious — Don’t do this to your production system.
Starting the Node.js Server(s)
Once you have your Magento system ready, you’ll want to start the Node.js web server. You can do this with either of the following
$ npm run watch
$ npm run build ; npm run start
Both the start
and watch
commands ultimately run the buildpack serve .
command.
The watch
command runs a development server that will automatically restart when it detects changes — similar to the nodemon package.
When you run this server, it acts as both the web server that will serve your React application’s JS, CSS, and HTML files AND as the “UPWARD” proxy server we mentioned earlier.
If you start a service using npm run watch
you should be able to browse to the following URL
http://localhost:10000/
and see your PWA application
If you look at your browser’s debugger network tab, you can see API calls being made into this same server
This server then proxies these requests on to your Magento server. If you’re curious, UPWARD is just an express middleware.
Where’s React?
So far we’ve seen that this scaffolding project provides us with the UPWARD web server and the sort of build enviornment that a javascript developer would expect in 2020. Next up, let’s take a look at what PWA Studio does with React.
To start, this scaffolding project serves a React application to end users. If we consider our previous simple architecture diagrams, this means Node.js is both the static web server that serves the React application AND the UPWARD server.
This scaffolding project also provides a React bootstrap file. This bootstrap file creates a React application that has access to a base URL for UPWARD’s GraphQL endpoint, an instantiated Apollo Link object that points at that base URL, and a redux-store object that is aware of the React components defined in the @magento/peregrine package.
React Bootstrap File
The src/index.js
file is the React application’s main entry point — we can see the call to ReactDOM.render
here.
// File: src/index.js
ReactDOM.render(
<Adapter apiBase={apiBase} apollo={{ link: apolloLink }} store={store}>
<AppContextProvider>
<App />
</AppContextProvider>
</Adapter>,
document.getElementById('root')
);
We’re rendering an initial root component and passing in a few variables via props.
We have apiBase
, which is a plain URL rendered as a string.
// File: src/index.js
const apiBase = new URL('/graphql', location.origin).toString();
and apolloLink
, an object created from a class in the @apollo/client
package.
// File: src/index.js
import { ApolloLink } from '@apollo/client';
/* ... */
const apolloLink = ApolloLink.from([
new MutationQueueLink(),
new RetryLink({
delay: {
initial: 300,
max: Infinity,
jitter: true
},
attempts: {
max: 5,
retryIf: error => error && navigator.onLine
}
}),
authLink,
errorLink,
// An apollo-link-http Link
Adapter.apolloLink(apiBase)
]);
and finally the redux-store object
// File: src/index.js
import store from './store';
// File: src/store.js
import { combineReducers, createStore } from 'redux';
import { enhancer, reducers } from '@magento/peregrine';
// This is the connective layer between the Peregrine store and the
// venia-concept UI. You can add your own reducers/enhancers here and combine
// them with the Peregrine exports.
//
// example:
// const rootReducer = combineReducers({ ...reducers, ...myReducers });
// const rootEnhancer = composeEnhancers(enhancer, myEnhancer);
// export default createStore(rootReducer, rootEnhancer);
const rootReducer = combineReducers(reducers);
export default createStore(rootReducer, enhancer);
That’s “store” as in storage, not “store” as in your online retail experience. Redux is a complicated topic and hard to get into unless you’re already experienced with React. So far I’ve found this article to be the most approachable if you already know some React basics.
These three base React components (Adapter
, AppContextProvider
, and App
) all come from the @magento/venia-drivers
and @magento/venia-ui packages.
// File: src/index.js
import { Adapter } from '@magento/venia-drivers';
/* ... */
import App, { AppContextProvider } from '@magento/venia-ui/lib/components/App';
The @magento/venia-drivers
package isn’t an NPM distributed package. If you look at the scaffolding project’s package.json
file we’ll see the following
// File: package.json
"browser": {
"@magento/venia-drivers": "src/drivers"
},
This appears to be using the browser
attribute to somehow define a fake @magento/venia-drivers
package that points to the local src/drivers
module. If we take a look at that module
// File: src/drivers.js
export {
connect,
Link,
Redirect,
Route,
Switch,
withRouter,
useHistory,
useLocation,
useRouteMatch,
useParams
} from '@magento/venia-ui/lib/drivers';
export { default as resourceUrl } from '@magento/venia-ui/lib/util/makeUrl';
export { default as Adapter } from '@magento/venia-ui/lib/drivers/adapter';
We see our Adapter
ultimately comes from the @magento/venia-ui package as well.
All this means that our React components are spread across a few different packages. We have some that are sort-of-local to this scaffolding project (the fake @magento/venia-drivers
package), which means they’re part of the @magento/venia-concept buildpack template package. Then we have some that are a part of the @magento/venia-ui package, and finally a number of them that are part of the @magento/peregrine package.
What Button do I Push?
As to how we’re intended to use all this — that’s still unclear to me. Lots of people have been happy to tell me what PWA Studio isn’t, but not that many have been able to say what it is. Thinking it through — the @magento/peregrine package seems safe as a plain old dependency — Magento’s docs define it as
a collection of functions that act as the brains of your visual components. They provide pieces of logic for UI components, such as the ones provided by the Venia library. Use or remix these functions and components to create a unique Magento PWA storefront.
What’s trickier is any package with Venia in the name — Magento goes out of its way to call the Venia storefront a “proof-of-concept” — implying that they’e showing you what your own buildpack template project should look like, but that it’s not intended to be used as-is. However — there don’t seem to be any other buildpack templates available.
Also, there’s the @magento/venia-ui package. Is this a part of the proof-of-concept as the Venia name implies? If so, then why do other packages like @magento/peregrine use it as a dependency?
This isn’t my first — or even third — rodeo. I can see how the project ended up here — but right now PWA Studio seems half real and half vaporware. As a non-affiliated developer it’s hard to know what we’re meant to do with all this — which is probably an answer in and of itself.
Wrap Up
If you’re looking at all this the same way you might look at a traditional open source application — it’s a bit confusing. A serious React team/engineer might look at PWA Studio and say “why should I bother with all this half baked code when I can just start from scratch and make API calls into Magento myself?”.
This straw-engineer would not be wrong — but remember that the goal of PWA Studio isn’t to provide the world with a free and open source solution for Progressive Web Applications. It’s to change and modernize the Magento development ecosystem such that the tooling Adobe’s biggest customers expect to be there (React, GraphQL, etc.) is there.
In that sense PWA Studio has succeeded — the effort forced Adobe’s internal engineering departments to prioritize a GraphQL API for Magento 2 and the mere existence of PWA Studio sets the stage for a larger PWA ecosystem to exist. Even when a team looks look at PWA Studio and says “forget this, let’s build our own PWA” that’s still a “win” in Adobe’s column.
All that said — when, five years ago, Magento insiders said “don’t worry, PWA will obsolete all that Knockout/UI Components stuff” I expected there’d be more here. Software can be a complicated profession.