-
Notifications
You must be signed in to change notification settings - Fork 683
Project coding standards and conventions
- File naming and directory structure
- React components
- Root components
- Client state management
- Middleware
- Application drivers
-
JavaScript files must use the
.js
file extensionThis project does not use TypeScript or ECMAScript modules, so
.ts
and.mjs
extensions are not used. The.jsx
extension is unnecessary because all files are processed for JSX. -
GraphQL files must use the
.graphql
extensionThe build process parses and analyzes GraphQL files based on this extension.
-
Filenames must be in camel case
Example:
camelCasedFilename.js
This is done for consistency and readability. Even files defining a component, such as the Button component, must have lower case file names.
-
React component folders must use proper case
Example:
ProperCaseDirectory
Components must always be directories and never single files. The ProperCase indicates that the directory is a component.
-
Names for
.css
and.js
files for the same component must matchExample:
checkbox.css
andcheckbox.js
This is done for consistency, readability, and future extensibility.
-
Use a
container.js
file to wrap simple, presentational components with a Higher-Order ComponentPresentational components are meant to be simple and portable. They require only props as arguments and no other external connections.
Wrapping these components with an HOC allows them to connect to the application's router, Redux store, and network/cache clients.
-
Do not use underscores and hyphens in names
Data from the GraphQL API is
snake_case
and all other property and variable names arecamelCase
. This helps to visually distinguish between internal variable names and data objects from the API.
-
Each React component is contained in a single folder
React components must be independent and interchangeable. Their tests, sub-components, stylesheets, and utilities should be self-contained. They should not be reliant on other components without explicitly naming them in import strings.
-
A component's public API is exposed through its
index.js
fileThe
index.js
file is a common standard in a React project. Naming a fileindex.js
allows a component to import another component using its directory name.// src/components/Button/index.js export { default } from './button';
// src/components/Form/form.js import Button from 'src/components/Button'
In the example provided, the Button component exports its components in it's
index.js
file and theform.js
module imports that button by only referring to the directory.Text editors can become confusing to navigate when files with the same name are open. Therefore, the
index.js
file should be a short (one or two line) "pass-through" file which exports a named sibling file.A component typically exports only a
default
export, which is the component constructor itself. -
Components not exported through
index.js
are considered private APIIf a component author exports another public file, such as a sub-component or a utility, the
index
.js file is the place to include the export.// src/components/Button/index.js export { default } from './button'; export { default as MultiButton } from './multiButton';
This guarantees that Webpack can bundle the minimum amount of code possible.
-
Sub-components are defined in files in a component's root directory or created in their own folders
Use sub-components to define complex components. A sub-component defined inside a parent component's folder is considered a private class.
Not all sub-components need to be put in separate directories nor made public.
Don't import a sub-component directly from a component folder.
// src/components/Form/form.js import InnerButton from 'src/components/Button/innerButton'; // Don't do this!
-
Create component tests in the
__tests__
folderThe Jest testing framework, which is the recommended PWA Studio test framework integrated with Buildpack, searches for tests by default in folders called
__tests__
. It will find these folders in any subfolder.Keep tests for a component inside that component's folder to preserve isolation.
-
Create storybook tests in the
__stories__
folderThe Storybook framework, which is the recommended self-documenting component playground for PWA Studio, searches for story files by default in folders called
__stories__
. It will find these folders in any subfolder.Keep stories for a component inside that component's folder to preserve isolation.
-
Root components are defined in the
RootComponents
directoryThe
RootComponents
directory is specifically named in Webpack configuration.The Buildpack
MagentoRootComponentsPlugin
searches thesrc/RootComponents
folder for files containing comment directives to identify themselves as root components. -
Root components are defined with comments that identify them as entry points for a page type
A RootComponent should be used to render the overall UI for an entity page (i.e. a product page with a routable URL). RootComponents are designed to handle particular entities, so they must declare this compatibility in their comment directives to help the
MagentoRootComponentsPlugin
use them when necessary. -
Root components are associated with server routes
A RootComponent renders a "page", or an overall UI state, in the PWA. These large state changes should correspond with navigations between routes that are linkable.
This may be managed by the server's SEO configuration.
-
Use Redux for global state and as a client side store for optimized data
Storing data using Redux is done for expediency because it comes with well-understood patterns for adding new functionality and state management, making asynchronous requests, and and storing the results of those requests.
Note: The PWA Studio project will eventually remove Redux and replace it with a more consistent GraphQL state management system so that server-side state and client-side state can be handled in the same way.
-
Redux actions and reducers are members of a set of "store slices"
Store slices, which are named sections of store functionality, are a way of encapsulating independent business logic in client state management.
For example, cart-related state changes are in the cart slice.
Slices are implemented by dividing actions and reducers into subfolders and files named after that slice.
Slices can depend on each others' state through actions which can dispatch actions from other slices. The app slice handles any state values not associated with a particular slice.
-
Redux actions are defined in the
actions
directoryActions are objects with a specific purpose in Redux, and Action creator functions have a particular signature, which should be grouped together.
However, actions are properties of the app, not of individual components, so the
actions
directory should live alongside thecomponents
directory. -
Redux actions must be placed in subdirectories in the
actions
directoryAction subdirectories are named after store slices, such as
cart
orcategory
. Redux actions must belong to a store slice, a named section of store functionality that implements all logic for that functionality.These subdirectories also have a standard structure.
-
Follow Flux Standard Action standards when naming and defining actions
The Flux Standard Actions specification helps keep action definitions standard.
Actions should be plain JavaScript objects that can be serialized into JSON. This allows them to be used as test fixtures, and sent across window boundaries via
postMessage
.They must always have
type
,payload
, anderror
properties, and optionally ameta
property. This allows polymorphic treatment of action objects by reducers and other functions which need to operate on them.
-
Use the redux-actions library to create and handle actions
The redux-actions library helps authors maintain a list of action type strings and handlers. Thse can be reused throughout the project.
-
The prefix for an action type must be unique and in all caps
Action types in Redux must be in ALL_CAPS, for easier visual identification and consistency. When configuring a "prefix" in a call to redux-actions'
createActions
, make sure it's in all caps. -
Use the
createActions()
function to create an actions object from a list of action typesThe
createActions()
method allows the definition of multiple namespaced actions without the additional boilerplate of manually implementing their factory functions. -
Action files are separated into synchronous (
actions.js
) and asynchronous (asyncActions.js
) actionsSynchronous actions are always handled fully synchronously by the store slice. They perform no side effects on dispatch. They rarely need custom implementations because the functions created by redux-actions are sufficient in almost all cases.
Asynchronous actions perform side effect. They usually by make network calls and dispatch their results as payloads. These actions require custom implementations.
-
Asynchronous actions can import actions from different domain slices
The approved way for store slices to interact with each other is through actions importing other actions.
For example, the checkout store slice handles the state management of a checkout flow. When that flow completes, the cart slice must reset itself.
Therefore, the
src/actions/checkout/asyncActions.js
file must importsrc/actions/cart/
to dispatch the appropriate action. -
Action names are public API
There is no way to keep actions private.
Action type names are visible throughout the entire application, as a principle of Redux. Reducers have access to these actions, and any component can use the
connect()
binding of react-redux to acquire a handle on those action creators. -
Redux reducers are defined in the
reducers
directoryReducers work together with actions.
Actions are dispatched to the Redux store to indicate a potential state change. Reducers perform that state change by taking the action along with the old state and returning a new state.
They must always be named after their store slice, e.g.
src/reducers/cart.js
.Reducers must be synchronous by default, so instead of two separate files, they can be implemented in one.
-
State objects should never be mutated
Always return new state objects.
Redux works best when all state objects are immutable. This allows a component to determine a change in state by checking simple reference identity. This prevents it from receiving a false negative.
If a reducer reuses an object and modifies its properties, a component trying to compare an old state with a new state object may get a false positive.
-
Redux middlewares and store enhancers are located in the
middleware
directoryRedux enhancers and middlewares add custom global functionality to the Redux store.
Our middlewares include:
-
redux-thunk
- a utility for dispatching asynchronous actions and results -
redux-log
- used for logging state information to the console in dev mode - A "backstop reducer" - used for handling unexpected errors with a fallback UI
It may become necessary to modify or add Redux middleware when building a project If it becomes necessary, put the middleware here.
-
-
Application drivers are located in the
drivers
directoryDrivers are code units that connect React components to the "outside world", the state of the application, and network. This concept goes beyond the props sent to the component and its internal state.
Drivers include API clients, router components for interacting with a router, and higher-order components that connect components to the Redux store.
All of these code units should be centralized in
src/drivers/index.js
.Often they will be pass-through exports of the underlying libraries, like
react-router
andreact-redux
. Putting them in one place helps a downstream user override one or more of these drivers for custom behavior and testability using a minimum amount of configuration. -
Application drivers should include an Adapter component which connects a React application to all drivers
The current driver utilities rely on ancestor components to work.
For example, a Link element requires an ancestor
react-router
component, a Query element requires an ancestorapollo
component, and a Redux connection require a Redux store registered at the top level.If possible, create a single component which adds all those dependencies and then render its children.
- Sync calls:
- Check the calendar
- Recordings - https://goo.gl/2uWUhX
- Slack: #pwa Join #pwa
- Contributing
- Product