Skip to content

Commit

Permalink
Merge pull request #160 from Shopify/move-records-to-web-foundation
Browse files Browse the repository at this point in the history
Add quilt decision records to web-foundation
  • Loading branch information
ismail-syed committed Sep 11, 2020
2 parents ccc42e2 + 36b17d6 commit 4fdd8dc
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 0 deletions.
109 changes: 109 additions & 0 deletions handbook/Decision records/00 - Use a Lerna monorepo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Decision to use a Lerna monorepo

## Date

March 26, 2018

## Contributors

* [Mallory Allen](https://github.com/TheMallen)
* [Tzvi Melamed](https://github.com/tzvipm)

## Summary

We implement our common non-tooling non-polaris libraries as a monorepo based on [Lerna](https://github.com/lerna/lerna). This improves developer efficiency, discoverability, code-sharing, and helps us build for the long-term.

## Problem space

The web foundation team's number of open source packages is set to grow exponentially as we pull libraries out of web. As of now we estimate [at least 19 new libraries](https://github.com/Shopify/web/projects/17) will need to be available to our React web projects.

In the past we've tried pulling common bits of functionality into some chunky overly-broadly-named repos, such as [@shopify/javascript-utilities](https://github.com/Shopify/javascript-utilities) and [@shopify/react-utilities](https://github.com/Shopify/react-utilities). We wanted to avoid problems with maintaining highly granular packages by essentially packing multiple simple packages into domain-based mega-packages. However, thanks to the dependency between the two, even at this level these ended up underdeveloped and poorly maintained. They fell victim to the same set of problems that most multi-repo dependency graphs must overcome:

* Testability - Packages with dependencies are hard to test together
* Developer Ergonomics - Packages with dependencies are hard to develop locally due to shortcomings of NPM / Yarn link
* Grokability - It's hard to conceptualize multi-repo changes or refactors with PRs across different repos
* Efficiency - It’s a major time sink to get reviews and tophats on several PRs to several repos in a row for one small change to a dependency
* Startup Cost - Having to clone more repos discourages contribution to packages
* Discoverability - It’s difficult to know what repos exist to suit your needs when they’re only findable via search engine
* Boilerplate - Developers must rewrite build and linting configuration, or at least include all the necessary libraries each time a new package is exposed

In addition, the overly broad and vague naming on these packages made it difficult to reason about what should and should not be in them.

Since we have at least 5 dependant projects, and need to provide a stable, fast-iterating backbone of utilities to all of them as soon as possible, we decided for this project we would change our model.

## Solution

Broadly speaking we evaluated:

* Housing our libraries in a monorepo. A single repository holding the code of multiple projects, each of which can be deployed as a separate npm package. The individual packages in the monorepo can be dependents of each other, or they can have no connection.
* using [lerna](https://github.com/lerna/lerna)
* using yarn workspaces with [wsrun](https://github.com/whoeverest/wsrun)
* using [oao](https://github.com/guigrpa/oao)
* Continuing to create additional github repositories for each new javascript package, while enhancing our workflows for creating and maintaining multiple interdependant packages.
* using [builder](http://formidable.com/open-source/builder/)
* using [yeoman](http://yeoman.io/)
* using [sewing-kit](https://github.com/Shopify/sewing-kit)
* using default configs copied between repos

To explain how we came to the specific answers that we did, it's best to divide it into two parts.

### monorepo

We feel that a monorepo is our best bet for building out all the necessary packages quickly in a maintainable, discoverable way.

Advantages of using a monorepo include:

* common and universally enforceable testing, linting between packages
* easy local development of features which need to change multiple packages
* easy deployment of multiple dependant packages simultaneously
* less time spent creating prs, pinging reviewers on multiple repos
* better discoverability, visibility for other libraries by developers who already use one part of the monorepo
* easier sharing of utility scripts and generators across packages
* significantly easier to start a new package
* less github repos on our organization

Problems with monorepo strategies include:

* having to maintain support for them in our deployment tooling (shipit) and build scripts
* all the problems with testing and locally developing a web of packages persist for packages outside the monorepo
* does not help keep other, dependant, projects up to date on libraries within the repo
* a large multiproject repo can be intimidating to contribute to for newcomers

Since several of these painpoints are not unique to monorepos, we felt that the benefits outweighed them.

### lerna

Lerna is a tool for managing monorepo style codebases in javascript, built by the folks at Babel.

We feel that lerna is the best solution for javascript monorepos we can choose at the moment. No other solution we found matched all of it's features:

* Fast thanks to under the hood usage of yarn workspaces
* Parallelization support for running commands against multiple packages
* Supports automatic publishing of only updated packages
* Supports independent versioning

On top of that we have a working shipit integration for it, which should help us to get ramped up quickly.

### More details

For more insight on our explorations see:

* [decision rough notes](https://paper.dropbox.com/doc/Decision-Explorations-Mono-or-Many-repo-LNkDLM55dNzR2WK4ROtZr#:a2=-193436694).
* [lerna tests repo](https://github.com/TheMallen/lerna-tests)
* [extraction](https://github.com/Shopify/extraction-test-import-remote) [test](https://github.com/Shopify/extraction-test-react-utilities) [repos](https://github.com/Shopify/extraction-test-browser)
* [simple yeoman generator for libraries](https://github.com/Shopify/generator-shopify-library)
* [yarn workspace / lerna monorepo comparison (see commits)](https://github.com/Shopify/shopify-utilities)

## Tripwires

### Build Times

Build times will likely be worse in a mono-repo than they are in multiple small repos. We expect this to be manageable and worth the tradeoffs. If build times exceed 5 minutes then we will need to re-evaluate this decision.

### Developer Flow

The monorepo approach requires some manual steps that devs probably may not have done before. We will try to be explicit in our guidance for devs, and to maintain easy package.json commands for common tasks, but if we see a lot of malformed version bumps and confusion we will need to re-evaluate.

### Shipit Integration

Shipit currently supports JS monorepos through lerna. This support is relatively experimental, and far from battle tested. If we find ourselves spending more than an 5 hours in a week debugging / fixing shipit issues, we should re-evaluate our choices.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# We use @shopify/react-testing for testing React codebases

## Date

(retroactive) April 1, 2020

## Contributors

- @themallen (Mathew Allen)

## Summary

We use and maintain [`@shopify/react-testing`](https://github.com/Shopify/quilt/blob/master/packages/react-testing/README.md) for testing React applications at Shopify. Usage of supplemental tools for visual regression testing and to aid in manual functional testing is also encouraged where needed.

## Problem space

There are a number of testing libraries available for React applications. The most popular of these focus on testing the behaviour of components in response to initial data and subsequent user interaction and the composition of components together. Testing of the actual visual output of a component is usually not included in the scope of these libraries, or is encouraged to be done via snapshots of the DOM (which is highly [problematic](https://github.com/Shopify/web-foundation/blob/main/handbook/Decision%20records/03%20-%20We%20do%20not%20use%20Jest%20snapshot%20tests.md)).

### Prior Art #1 - Enzyme

[Enzyme](https://enzymejs.github.io/enzyme/) is a very popular testing library that offers many features such as JQuery style traversal of the React component tree, shallow rendering, and a variety of tools for simulating interactions with an application.

Unfortunately, Enzyme has a number of downsides for our usecase.

- It has frequently taken a long time to support new React features (specifically, it took quite a while for it to properly support hooks)
- It has a very large API surface area, much of which works against Shopify’s [testing conventions](https://github.com/Shopify/web-foundation/blob/main/Best%20practices/react/Testing.md). For example, Enzyme provides APIs like `setState` which encourage reaching in to implementation details of your components
- Enzyme is unlikely to add features we use or need in a testing library, such as automatic unmounting and a built-in version `trigger()`
- Enzyme tends to continue to support a large backlog of React versions, which makes contribution more difficult

### Prior Art #2 - Testing-Library

[testing-library](https://testing-library.com/docs/react-testing-library/intro) is a more recently popular testing library with an ethos of testing by mimicking how a user would use the application by sticking close to the DOM.

While this premise of writing tests that mirror user actions is compelling, basing all tests off the raw DOM being produced has a number of problems.

- Relying exclusively on DOM output can actually lead to testing **more** implementation details rather than less. Users generally do not interact with things based on constructs like test-ids ([see our previous decision log about test-ids](https://github.com/Shopify/web-foundation/blob/main/handbook/Decision%20records/04%20-%20We%20do%20not%20use%20test%20IDs.md)), or even by actual HTML attributes. The DOM itself is not a public API from a user's perspective.
- Tests that rely on fine-grain knowledge of the DOM structure have a tendency to false-positive _and_ false-negative. It is extremely difficult to judge how a change in DOM structure which fails a test actually reflects a user's experience of the feature.
- Tests which ignore component boundaries can easily rely on the implementation details of components potentially maintained by totally different teams. A test of a feature should not necessarily care about the implementation details of a button from a shared component library, just that the component/feature under test does what is expected.
- For an ecosystem like React, the DOM is not the only intended output.

## Solution

We use and maintain [`@shopify/react-testing`](https://github.com/Shopify/quilt/blob/main/packages/react-testing/README.md) as our test library of choice for unit testing component behaviour and composition. We supplement this with [visual regression testing](https://percy.io/), manual functional testing, and usage of tools like [storybook](https://storybook.js.org/) to aid in UI development where needed.

`@shopify/react-testing` was originally built by @lemonmade as a direct response to some difficulties we were experiencing with Enzyme, and has since gone on to become the dominant way we test components and features in React applications at Shopify. Since then we have also released `@shopify/preact-testing` for our Preact applications. The library is closer to `Enzyme` than `testing-library` but has a number of significantly different choices which make it an ideal middleground:

- A small API focused on testing the API of components directly and avoiding options which break that such as `setState`
- A total avoidance of "shallow" rendering
- A streamlined method for interacting with components avoiding event simulation in favour of `trigger`ing callbacks directly
- Tight integration with React's `act` system for testing complex asynchronous user flows
- Tracking major versions of React and aggressive deprecation of support for old versions allowing it to be updated and maintained quickly
- Built-in facilities for customizing the `mount` function to reuse setup when testing complex applications
- Smart cleanup behaviour allowing test suites to scale without memory leakage
- Terse and literate custom matchers for the [`Jest`](https://www.npmjs.com/package/jest) test runner

These features add up to make the library a well-positioned solution for behavioural and compositional testing of individual components and large-grain feature flows. This variety of test can go very far in keeping developers shipping confidently, but in some cases may need to be supplemented via other tools. Dedicated [visual regression testing tools](https://percy.io/) and [UI component development tools](https://storybook.js.org/) can be helpful in this regard.

0 comments on commit 4fdd8dc

Please sign in to comment.