Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bootstrap the architecture documentation #22751

Merged
merged 7 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/architecture/automated-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Automated Testing

## Why is Puppeteer the tool of choice for end-to-end tests?

There exists a rich ecosystem of tooling available for web-based end-to-end automated testing. Thus, it's a common question: "Why does Gutenberg use [Puppeteer](https://developers.google.com/web/tools/puppeteer/) instead of ([Cypress](https://cypress.io/), [Selenium](https://www.selenium.dev/), [Playwright](https://github.com/microsoft/playwright), etc)?". Given some historical unreliability of the build results associated with end-to-end tests, it's especially natural to weigh this question in considering whether our tools are providing more value than the effort required in maintaining them. While we should always be comfortable in reevaluating earlier decisions, there were and continue to be many reasons that Puppeteer is the best compromise of the options available for end-to-end testing.

These include:

- **Interoperability with existing testing framework**. Puppeteer is "just" a tool for controlling a Chrome browser, and makes no assumptions about how it's integrated into a testing environment. While this requires some additional effort in ensuring the test environment is available, it also allows for cohesion in how it integrates with an existing setup. Gutenberg is able to consistently use Jest for both unit testing and end-to-end testing. This is contrasted with other solutions like Cypress, which provide their own testing framework and assertion library as part of an all-in-one solution.
Copy link
Contributor Author

@youknowriad youknowriad Jun 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think now it supports Firefox by default too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I wasn't sure how cohesive I could mention that here, without it overcomplicating the messaging. And the support is still pretty early. Puppeteer still markets itself as "Headless Chrome Node.js API", and "a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium." The text largely reflects that.

Copy link
Member

@aduth aduth Jun 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I kinda alluded to Firefox ("not just Chrome") later in the document, "largely":

The fact that Puppeteer largely targets the Chrome browser is non-ideal in how it does not provide full browser coverage.

- **An expressive but predictable API**. Puppeteer strikes a nice balance between low-level access to browser behavior, while retaining an expressive API for issuing and awaiting responses to those commands using modern JavaScript [`async` and `await` syntax](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await). This is contrasted with other solutions, which either don't support or leverage native language async functionality, don't expose direct access to the browser, or leverage custom domain-specific language syntaxes for expressing browser commands and assertions. The fact that Puppeteer largely targets the Chrome browser is non-ideal in how it does not provide full browser coverage. On the other hand, the limited set of browser targets offers more consistent results and stronger guarantees about how code is evaluated in the browser environment.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that Cypress doesn't natively support the fetch API.

- **Surfacing bugs, not obscuring them**. Many alternative solutions offer options to automatically await settled network requests or asynchronous appearance of elements on the page. While this can serve as a convenience in accounting for unpredictable delays, it can also unknowingly cause oversight of legitimate user-facing issues. For example, if an element will only appear on the page after some network request or computation has completed, it may be easy to overlook that these delays can cause unpredictable and frustrating behavior for users ([example](https://github.com/WordPress/gutenberg/pull/11287)). Given that developers often test on high-end hardware and stable network connections, consideration of resiliency on low-end hardware or spotty network availability is not always on the forefront of one's considerations. Puppeteer forces us to acknowledge these delays with explicit `waitFor*` expressions, putting us in much greater alignment with the real-world experience of an end-user.
- **Debugging**. It's important that in that case that a test fails, there should be straight-forward means to diagnose and resolve the issue. While its offerings are rather simplistic relative to the competition, Puppeteer does expose options to run tests as "headful" (with the browser visible) and with delayed actions. Combined with the fact that it interoperates well with native language / runtime features (e.g. debugger statements or breakpoints), this provides developers with sufficient debugging access.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could mention that Cypress didn't support keyboard interactions properly, not sure if it's still the case though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind just saw the link to the PR which describes these.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd initially taken an angle of describing the history of end-to-end testing in Gutenberg, and more specifically "Why we don't use Cypress" and describing all its faults, including what you describe as some of the browser emulation issues. I eventually reoriented it to a more positive "What requirements we want from our testing tool" direction.

But yes, the linked pull request can help illustrate some of those more specific examples.

For more context, refer to the following resources:

- [Testing Overview: End-to-End Testing](https://github.com/WordPress/gutenberg/blob/master/docs/contributors/testing-overview.md#end-to-end-testing)
- [Testing: Experiment with Puppeteer for E2E testing](https://github.com/WordPress/gutenberg/pull/5618)
- In early iterations, the contributing team opted to use Cypress for end-to-end testing. This pull request outlines problems with the approach, and proposed the initial transition to Puppeteer.
- [JavaScript Chat Summary: January 28, 2020](https://make.wordpress.org/core/2020/02/04/javascript-chat-summary-january-28-2020/)
- Playwright is a new offering created by many of the original contributors to Puppeteer. It offers increased browser coverage and improved reliability of tests. While still early in development at the time of this writing, there has been some interest in evaluating it for future use as an end-to-end testing solution.
9 changes: 9 additions & 0 deletions docs/architecture/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Architecture

This living document serves as a high-level documentation to explain the conceptual choices and tool choices in the Gutenberg repository.

- [Modularity and WordPress Packages](docs/architecture/modularity.md).
- [Block Editor Performance](docs/architecture/performance.md).
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
- What are the decision decisions behind the Data Module?
- [Why is Puppeteer the tool of choice for end-to-end tests?](/docs/architecture/automated-testing.md)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In dd33523, I added a document addressing this question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks these are great.

- What's the difference between the different editor packages? and what's the purpose of each package?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hoping that by starting this, other folks can chime-in and document other decisions made on the repo.

85 changes: 85 additions & 0 deletions docs/architecture/modularity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Modularity

The WordPress block editor is based around the idea that you can combine independent blocks together to write your post or build your page. Blocks can also use and interact with each other. This makes it very modular and flexible.

But the Block Editor does not embrace modularity for its behavior and output only, the Gutenberg repository is also built from the ground up as several reusable and independent modules or packages, that, combined together lead to the application and interface we all now. These modules are known as [WordPress packages](https://www.npmjs.com/org/wordpress) and are published and updated regularly on the npm package repository.

These packages are used to power the Block Editor but they can be used to power any page in the WordPress Admin or outside.

## Why?

Using a modular architecture has several benefits for all the actors involved:

* Each package is an independent unit and has a well defined public API that is used to interact with other packages and third-party code. This makes it easier for **Core Contributors** to reason about the codebase. They can focus on a single package at a time, understand it and make updates while knowing exactly how these changes could impact all the other parts relying on the given package.
* A module approach is also beneficial to the **end-user**. It allows to selectively load scripts on different WordPress Admin pages while keeping the bundle size contained. For instance, if we use the components package to power our plugin's settings page, there's no need to load the block-editor package on that page.
* This architecture also allows **third-party developers** to reuse these packages inside and outside the WordPress context by using these packages as npm or WordPress script dependencies.

## Types of packages

Almost everything in the Gutenberg repository is built into a package. We can split these packages into two different types:

### Production packages

These are the packages that ship in WordPress itself as JavaScript scripts. These constitute the actual production code that runs on your browsers. As an example, there's a `components` package serving as a reusable set of React components used to prototype and build interfaces quickly. There's also an `api-fetch` package that can be used to call WordPress Rest APIs.

Third-party developers can use these production packages in two different ways:

* If you're building a JavaScript application, website, page that runs outside of the context of WordPress, you can consume these packages like any other JavaScript package in the npm registry.

```
npm install @wordpress/components
```

```js
import { Button } from '@wordpress/components';

function MyApp() {
return (
<Button>Nice looking button</Button>
);
}
```

* If you're building a plugin that runs on WordPress, you'd probably prefer consuming the package that ships with WordPress itself. This allows multiple plugins to reuse the same packages and avoid code duplication. In WordPress, these packages are available as WordPress scripts with a handle following this format `wp-package-name` (e.g. `wp-components`). Once you add the script to your own WordPress plugin scripts dependencies, the package will be available on the `wp` global variable.

```php
// myplugin.php
// Exemple of script registration dependending on the "components" and "element packages.
wp_register_script( 'myscript', 'pathtomyscript.js', array ('wp-components', "wp-element" ) );
```

```js
// Using the package in your scripts
const { Button } = wp.components;

function MyApp() {
return (
<Button>Nice looking button</Button>
);
}
```

Script dependencies definition can be a tedious task for developers. Mistakes and oversight can happen easily. If you want to learn how you can automate this task. Check the [@wordpress/scripts](https://developer.wordpress.org/block-editor/packages/packages-scripts/#build) and [@wordpress/dependency-extraction-webpack-plugin](https://developer.wordpress.org/block-editor/packages/packages-dependency-extraction-webpack-plugin/) documentation.

#### Packages with stylesheets

Some production packages provide stylesheets to function properly.

* If you're using the package as an npm dependency, the stylesheets will be available on the `build-style` folder of the package. Make sure to load this style file on your application.
* If you're working in the context of WordPress, you'll have to enqueue these stylesheets or add them to your stylesheets dependencies. The stylesheet handles are the same as the script handles.

In the context of existing WordPress pages, if you omit to define the scripts or styles dependencies properly, your plugin might still work properly if these scripts and styles are already loaded there by WordPress or by other plugins, but it's highly recommended to define all your dependencies exhaustively if you want to avoid potential breakage in future versions.

#### Packages with data stores

Some WordPress production packages define data stores to handle their state. These stores can also be used by third-party plugins and themes to retrieve data and to manipulate it. The name of these data stores is also normalized following this format `core/package-name` (E.g. the `@wordpress/block-editor` package defines and uses the `core/block-editor` package).

If you're using one of these stores to access and manipulate WordPress data in your plugins, don't forget to add the corresponding WordPress script to your own script dependencies for your plugin to work properly. (For instance, if you're retrieving data from the `core/block-editor` store, you should add the `wp-block-editor` package to your script dependencies like shown above).

### Development packages

These are packages used in development mode to help developers with daily tasks to develop, build and ship JavaScript applications, WordPress plugins and themes. They include tools for linting your codebase, building it, testing it...

## Going further

- [Package Reference](/docs/designers-developers/developers/packages.md)
30 changes: 30 additions & 0 deletions docs/architecture/performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Performance

Performance is a key feature for editor applications and the Block editor is not an exception.

## Metrics

To ensure the block editor stays performant across releases and development, we monitor some key metrics using [performance testing](/docs/contributors/testing-overview.md#performance-testing).

**Loading Time:** The time it takes to load an editor page.
**Typing Time:** The time it takes for the browser to respond while typing on the editor.
**Block Selection Time:** The time it takes for the browser to respond after a user selects block. (Inserting a block is also equivalent to selecting a block. Monitoring the selection is sufficient to cover both metrics).

## Key Performance Decisions and Solutions

**Data Module Async Mode**

The Data Module of the WordPress Packages and the Block Editor is based on Redux. It means the state is kept globally and whenever a change happens, the components (UI) relying on that state may update.

As the number of rendered components grows (for example on long posts), the performance suffers because of the global state acting as an event dispatcher to all components. This is a common pitfall in Redux applications and the issue is solved on Gutenberg using the Data Modules Async Mode.

The Async mode is the idea that you can decide whether to refresh/rerender a part of the React component tree synchronously or asynchronously.

Rendering asynchronously in this context means that if a change is triggered in the global state, the subscribers (components) are not called synchronously, instead, we wait for the browser to be idle and perform the updates to React Tree.

Based on the idea that **when editing a given block, it is very rare that an update to that block affects other parts of the content**, the block editor canvas only renders the selected block is synchronous mode, all the remaining blocks are rendered asynchronously. This ensures that the editor stays responsive as the post grows.

## Going further

- [Journey towards a performant editor](https://riad.blog/2020/02/14/a-journey-towards-a-performant-web-editor/)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's ok to have a link to my blog like that but I felt that that post doesn't make sense as is in the docs but that it's still good to learn more about the perf work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My two cents is that it's okay to have for now as a V1 :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An external link policy was discussed in a recent docs meeting and the basic summary: ideally we enhance official docs, but if valuable external resource exists it is ok to link.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy and paste then? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine copy/pasting like I did for the "modularity" doc, this blog post link here just don't fit in the docs IMO


18 changes: 18 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,24 @@
"markdown_source": "../docs/designers-developers/developers/tutorials/format-api/3-apply-format.md",
"parent": "format-api"
},
{
"title": "Architecture",
"slug": "index",
"markdown_source": "../docs/architecture/index.md",
"parent": null
},
{
"title": "Modularity",
"slug": "modularity",
"markdown_source": "../docs/architecture/modularity.md",
"parent": "index"
},
{
"title": "Performance",
"slug": "performance",
"markdown_source": "../docs/architecture/performance.md",
"parent": "index"
},
{
"title": "Component Reference",
"slug": "components",
Expand Down
4 changes: 4 additions & 0 deletions docs/toc.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@
{ "docs/designers-developers/developers/tutorials/format-api/3-apply-format.md": [] }
] }
] },
{ "docs/architecture/index.md": [
{ "docs/architecture/modularity.md": [] },
{ "docs/architecture/performance.md": [] }
] },
{ "packages/components/README.md": "{{components}}" },
{ "docs/designers-developers/developers/data/README.md": [
{ "docs/designers-developers/developers/data/data-core.md": []},
Expand Down